diff --git a/spine-ts/spine-canvaskit/src/index.ts b/spine-ts/spine-canvaskit/src/index.ts index 3f7682faa..bf7ad3bd7 100644 --- a/spine-ts/spine-canvaskit/src/index.ts +++ b/spine-ts/spine-canvaskit/src/index.ts @@ -1,3 +1,32 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software or + * otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE + * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + export * from "@esotericsoftware/spine-core"; import { @@ -8,28 +37,20 @@ import { ClippingAttachment, Color, MeshAttachment, - NumberArrayLike, + type NumberArrayLike, Physics, RegionAttachment, Skeleton, SkeletonBinary, SkeletonClipping, - SkeletonData, + type SkeletonData, SkeletonJson, Texture, TextureAtlas, - TextureFilter, - TextureWrap, Utils, } from "@esotericsoftware/spine-core"; -import { - Canvas, - CanvasKit, - Image, - Paint, - Shader, - BlendMode as CanvasKitBlendMode, -} from "canvaskit-wasm"; + +import type { Canvas, CanvasKit, Image, Paint, Shader } from "canvaskit-wasm"; Skeleton.yDown = true; @@ -55,7 +76,7 @@ function toCkBlendMode (ck: CanvasKit, blendMode: BlendMode) { } } -function bufferToUtf8String (buffer: any) { +function bufferToUtf8String (buffer: ArrayBuffer | Buffer) { if (typeof Buffer !== "undefined") { return buffer.toString("utf-8"); } else if (typeof TextDecoder !== "undefined") { @@ -70,9 +91,9 @@ class CanvasKitTexture extends Texture { return this._image; } - setFilters (minFilter: TextureFilter, magFilter: TextureFilter): void { } + setFilters (): void { } - setWraps (uWrap: TextureWrap, vWrap: TextureWrap): void { } + setWraps (): void { } dispose (): void { const data: CanvasKitImage = this._image; @@ -89,7 +110,7 @@ class CanvasKitTexture extends Texture { static async fromFile ( ck: CanvasKit, path: string, - readFile: (path: string) => Promise + readFile: (path: string) => Promise ): Promise { const imgData = await readFile(path); if (!imgData) throw new Error(`Could not load image ${path}`); @@ -126,7 +147,7 @@ class CanvasKitTexture extends Texture { export async function loadTextureAtlas ( ck: CanvasKit, atlasFile: string, - readFile: (path: string) => Promise + readFile: (path: string) => Promise ): Promise { const atlas = new TextureAtlas(bufferToUtf8String(await readFile(atlasFile))); const slashIndex = atlasFile.lastIndexOf("/"); @@ -150,7 +171,7 @@ export async function loadTextureAtlas ( export async function loadSkeletonData ( skeletonFile: string, atlas: TextureAtlas, - readFile: (path: string) => Promise, + readFile: (path: string) => Promise, scale = 1 ): Promise { const attachmentLoader = new AtlasAttachmentLoader(atlas); @@ -158,12 +179,11 @@ export async function loadSkeletonData ( ? new SkeletonJson(attachmentLoader) : new SkeletonBinary(attachmentLoader); loader.scale = scale; - let data = await readFile(skeletonFile); - if (skeletonFile.endsWith(".json")) { - data = bufferToUtf8String(data); + const data = await readFile(skeletonFile); + if (loader instanceof SkeletonJson) { + return loader.readSkeletonData(bufferToUtf8String(data)) } - const skeletonData = loader.readSkeletonData(data); - return skeletonData; + return loader.readSkeletonData(data); } /** @@ -224,12 +244,12 @@ export class SkeletonRenderer { */ render (canvas: Canvas, skeleton: Skeleton | SkeletonDrawable) { if (skeleton instanceof SkeletonDrawable) skeleton = skeleton.skeleton; - let clipper = this.clipper; - let drawOrder = skeleton.drawOrder; - let skeletonColor = skeleton.color; + const clipper = this.clipper; + const drawOrder = skeleton.drawOrder; + const skeletonColor = skeleton.color; for (let i = 0, n = drawOrder.length; i < n; i++) { - let slot = drawOrder[i]; + const slot = drawOrder[i]; if (!slot.bone.active) { clipper.clipEnd(slot); continue; @@ -245,20 +265,19 @@ export class SkeletonRenderer { let attachmentColor: Color; let numVertices = 0; if (attachment instanceof RegionAttachment) { - let region = attachment as RegionAttachment; - positions = positions.length < 8 ? Utils.newFloatArray(8) : positions; + const region = attachment; numVertices = 4; region.computeWorldVertices(slot, positions, 0, 2); triangles = SkeletonRenderer.QUAD_TRIANGLES; uvs = region.uvs as Float32Array; - texture = region.region?.texture as CanvasKitTexture; + texture = region.region ?.texture as CanvasKitTexture; attachmentColor = region.color; } else if (attachment instanceof MeshAttachment) { - let mesh = attachment as MeshAttachment; - positions = - positions.length < mesh.worldVerticesLength - ? Utils.newFloatArray(mesh.worldVerticesLength) - : positions; + const mesh = attachment as MeshAttachment; + if (positions.length < mesh.worldVerticesLength) { + this.scratchPositions = Utils.newFloatArray(mesh.worldVerticesLength); + positions = this.scratchPositions; + } numVertices = mesh.worldVerticesLength >> 1; mesh.computeWorldVertices( skeleton, @@ -270,7 +289,7 @@ export class SkeletonRenderer { 2 ); triangles = mesh.triangles; - texture = mesh.region?.texture as CanvasKitTexture; + texture = mesh.region ?.texture as CanvasKitTexture; uvs = mesh.uvs as Float32Array; attachmentColor = mesh.color; } else if (attachment instanceof ClippingAttachment) { @@ -283,27 +302,47 @@ export class SkeletonRenderer { } if (texture) { + let scaledUvs: NumberArrayLike; if (clipper.isClipping()) { - clipper.clipTrianglesUnpacked( - positions, - triangles, - triangles.length, - uvs - ); + clipper.clipTrianglesUnpacked(positions, triangles, triangles.length, uvs); + if (clipper.clippedVertices.length <= 0) { + clipper.clipEnd(slot); + continue; + } positions = clipper.clippedVertices; uvs = clipper.clippedUVs; + scaledUvs = clipper.clippedUVs; triangles = clipper.clippedTriangles; + numVertices = clipper.clippedVertices.length / 2; + colors = Utils.newFloatArray(numVertices * 4); + } else { + scaledUvs = this.scratchUVs; + if (this.scratchUVs.length < uvs.length) { + this.scratchUVs = Utils.newFloatArray(uvs.length); + scaledUvs = this.scratchUVs; + } + if (colors.length / 4 < numVertices) { + this.scratchColors = Utils.newFloatArray(numVertices * 4); + colors = this.scratchColors; + } } - let slotColor = pose.color; - let finalColor = this.tempColor; + const ckImage = texture.getImage(); + const image = ckImage.image; + const width = image.width(); + const height = image.height(); + for (let i = 0; i < uvs.length; i += 2) { + scaledUvs[i] = uvs[i] * width; + scaledUvs[i + 1] = uvs[i + 1] * height; + } + + const slotColor = pose.color; + const finalColor = this.tempColor; finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r; finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g; finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b; finalColor.a = skeletonColor.a * slotColor.a * attachmentColor.a; - if (colors.length / 4 < numVertices) - colors = Utils.newFloatArray(numVertices * 4); for (let i = 0, n = numVertices * 4; i < n; i += 4) { colors[i] = finalColor.r; colors[i + 1] = finalColor.g; @@ -311,18 +350,6 @@ export class SkeletonRenderer { colors[i + 3] = finalColor.a; } - const scaledUvs = - this.scratchUVs.length < uvs.length - ? Utils.newFloatArray(uvs.length) - : this.scratchUVs; - const width = texture.getImage().image.width(); - const height = texture.getImage().image.height(); - for (let i = 0; i < uvs.length; i += 2) { - scaledUvs[i] = uvs[i] * width; - scaledUvs[i + 1] = uvs[i + 1] * height; - } - - const blendMode = slot.data.blendMode; const vertices = this.ck.MakeVertices( this.ck.VertexMode.Triangles, positions, @@ -331,11 +358,8 @@ export class SkeletonRenderer { triangles, false ); - canvas.drawVertices( - vertices, - this.ck.BlendMode.Modulate, - texture.getImage().paintPerBlendMode.get(blendMode)! - ); + const ckPaint = ckImage.paintPerBlendMode.get(slot.data.blendMode); + if (ckPaint) canvas.drawVertices(vertices, this.ck.BlendMode.Modulate, ckPaint); vertices.delete(); }