From 683574fb4112e5b16d856c29dba9d875c46c2c33 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Mon, 24 Nov 2025 17:20:49 +0100 Subject: [PATCH] Refactored rendering and matrix into specific class defined in c3 lib. --- spine-ts/spine-construct3/LICENSE | 26 ++ spine-ts/spine-construct3/README.md | 3 + .../spine-construct3-lib/README.md | 4 +- .../spine-construct3-lib/src/AssetLoader.ts | 6 +- .../spine-construct3-lib/src/C3Matrix.ts | 83 ++++++ .../src/C3SkeletonRenderer.ts | 239 ++++++++++++++++++ .../spine-construct3-lib/src/C3Texture.ts | 2 +- .../src/SpineBoundsProvider.ts | 29 +++ .../spine-construct3-lib/src/index.ts | 2 + .../src/c3runtime/instance.ts | 208 ++------------- spine-ts/spine-construct3/src/instance.ts | 78 +----- 11 files changed, 427 insertions(+), 253 deletions(-) create mode 100644 spine-ts/spine-construct3/LICENSE create mode 100644 spine-ts/spine-construct3/README.md create mode 100644 spine-ts/spine-construct3/spine-construct3-lib/src/C3Matrix.ts create mode 100644 spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts diff --git a/spine-ts/spine-construct3/LICENSE b/spine-ts/spine-construct3/LICENSE new file mode 100644 index 000000000..d03d3caed --- /dev/null +++ b/spine-ts/spine-construct3/LICENSE @@ -0,0 +1,26 @@ +Spine Runtimes License Agreement +Last updated April 5, 2025. Replaces all prior versions. + +Copyright (c) 2013-2025, 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. diff --git a/spine-ts/spine-construct3/README.md b/spine-ts/spine-construct3/README.md new file mode 100644 index 000000000..062b9593a --- /dev/null +++ b/spine-ts/spine-construct3/README.md @@ -0,0 +1,3 @@ +# spine-ts Construct3 + +Please see the top-level [README.md](../README.md) for more information. \ No newline at end of file diff --git a/spine-ts/spine-construct3/spine-construct3-lib/README.md b/spine-ts/spine-construct3/spine-construct3-lib/README.md index 577c4f38a..438eb9cd1 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/README.md +++ b/spine-ts/spine-construct3/spine-construct3-lib/README.md @@ -1,3 +1,3 @@ -# spine-ts THREE.JS +# spine-ts Construct3 Lib -Please see the top-level [README.md](../README.md) for more information. \ No newline at end of file +This is just an internal lib that contains spine-core and additional basic functions for C3. \ No newline at end of file diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/AssetLoader.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/AssetLoader.ts index 12f52abb0..4c970790a 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/src/AssetLoader.ts +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/AssetLoader.ts @@ -28,7 +28,7 @@ *****************************************************************************/ import { AtlasAttachmentLoader, SkeletonBinary, type SkeletonData, SkeletonJson, TextureAtlas, TextureAtlasPage } from "@esotericsoftware/spine-core"; -import { C3Texture, C3TextureEditor } from "./C3Texture"; +import { C3TextureEditor, C3TextureRuntime } from "./C3Texture"; interface CacheEntry { @@ -40,7 +40,7 @@ export class AssetLoader { private static CacheSkeleton = new Map>(); private static CacheAtlas = new Map>(); - private static CacheTexture = new Map>(); + private static CacheTexture = new Map>(); public async loadSkeletonEditor (sid: number, textureAtlas: TextureAtlas, scale = 1, instance: SDK.IWorldInstance) { const projectFile = instance.GetProject().GetProjectFileBySID(sid); @@ -167,7 +167,7 @@ export class AssetLoader { const image = await AssetLoader.createImageBitmapFromBlob(content, page.pma); if (!image) return null; - const spineTexture = new C3Texture(image, renderer, page); + const spineTexture = new C3TextureRuntime(image, renderer, page); this.addToCache(AssetLoader.CacheTexture, cacheKey, spineTexture); diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/C3Matrix.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/C3Matrix.ts new file mode 100644 index 000000000..54a32bc0a --- /dev/null +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/C3Matrix.ts @@ -0,0 +1,83 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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. + *****************************************************************************/ + +import { type Bone, Vector2 } from "@esotericsoftware/spine-core"; + +export class C3Matrix { + + public a = 0; + public b = 0; + public c = 0; + public d = 0; + public tx = 0; + public ty = 0; + + private tempPoint = new Vector2(); + + public update (x: number, y: number, angle: number) { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + this.a = cos; + this.b = sin; + this.c = -sin; + this.d = cos; + this.tx = x; + this.ty = y; + } + + public gameToSkeleton (x: number, y: number) { + const tx = x - this.tx; + const ty = y - this.ty; + const { a, b, c, d, tempPoint } = this; + const delta = a * d - b * c; + tempPoint.x = (d * tx - c * ty) / delta; + tempPoint.y = (a * ty - b * tx) / delta; + return tempPoint; + } + + public gameToBone (x: number, y: number, bone: Bone) { + const point = this.gameToSkeleton(x, y); + if (bone.parent) + return bone.parent.applied.worldToLocal(point); + return bone.applied.worldToLocal(point); + } + + public skeletonToGame = (skeletonX: number, skeletonY: number) => { + const { a, b, c, d, tempPoint } = this; + tempPoint.x = a * skeletonX + c * skeletonY + this.tx; + tempPoint.y = b * skeletonX + d * skeletonY + this.ty; + return tempPoint; + } + + public boneToGame (bone: Bone) { + const { applied } = bone; + return this.skeletonToGame(applied.worldX, applied.worldY); + } + +} diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts new file mode 100644 index 000000000..4110a1580 --- /dev/null +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts @@ -0,0 +1,239 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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. + *****************************************************************************/ + +import { type BlendMode, type Bone, MathUtils, type Skeleton, SkeletonRendererCore, Vector2 } from "@esotericsoftware/spine-core"; +import type { C3Matrix } from "./C3Matrix"; +import { BlendingModeSpineToC3, type C3TextureEditor, type C3TextureRuntime } from "./C3Texture"; + +type C3Renderer = IRenderer | SDK.Gfx.IWebGLRenderer; +type C3Texture = C3TextureRuntime | C3TextureEditor; +type C3Quad = DOMQuad | SDK.Quad; + +abstract class C3SkeletonRenderer< + Renderer extends C3Renderer, + Texture extends C3Texture, +> extends SkeletonRendererCore { + + private tempVertices = new Float32Array(4096); + private tempColors = new Float32Array(4096); + private tempPoint = new Vector2(); + private inv255 = 1 / 255; + + constructor ( + protected renderer: Renderer, + private skeleton: Skeleton, + protected matrix: C3Matrix, + ) { + super(); + } + + draw (opacity = 1) { + const { skeleton, matrix, inv255 } = this; + + let command = this.render(skeleton); + while (command) { + const { numVertices, positions, uvs, colors, indices, numIndices, blendMode } = command; + + const vertices = this.tempVertices.length < numVertices * 3 + ? (this.tempVertices = new Float32Array(numVertices * 3)) + : this.tempVertices; + + const c3colors = this.tempColors.length < numVertices * 4 + ? (this.tempColors = new Float32Array(numVertices * 4)) + : this.tempColors; + + for (let i = 0; i < numVertices; i++) { + const srcIndex = i * 2; + const { x, y } = matrix.skeletonToGame(positions[srcIndex], positions[srcIndex + 1]); + + const dstIndex = i * 3; + vertices[dstIndex] = x; + vertices[dstIndex + 1] = y; + vertices[dstIndex + 2] = 0; + + const color = colors[i]; + const colorDst = i * 4; + const alpha = (color >>> 24 & 0xFF) * inv255 * opacity; + const alphaInverse = inv255 * alpha; + c3colors[colorDst] = (color >>> 16 & 0xFF) * alphaInverse; + c3colors[colorDst + 1] = (color >>> 8 & 0xFF) * alphaInverse; + c3colors[colorDst + 2] = (color & 0xFF) * alphaInverse; + c3colors[colorDst + 3] = alpha; + } + + this.renderSkeleton( + vertices.subarray(0, numVertices * 3), + uvs.subarray(0, numVertices * 2), + indices.subarray(0, numIndices), + c3colors.subarray(0, numVertices * 4), + command.texture, + blendMode) + command = command.next; + } + } + + drawDebug (x: number, y: number, quad: C3Quad) { + const { skeleton, matrix } = this; + + const bones = skeleton.bones; + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + if (!bone.parent) continue; + const boneApplied = bone.applied; + const { x: x1, y: y1 } = matrix.skeletonToGame(boneApplied.worldX, boneApplied.worldY); + const x2 = bone.data.length * boneApplied.a + x1; + const y2 = bone.data.length * boneApplied.c + y1; + + this.setColor(1, 0, 0, 1); + this.setColorFillMode(); + + const t = this.tempPoint.set(y2 - y1, x1 - x2); + t.normalize(); + const width = 1 * 0.5; + const tx = t.x * width; + const ty = t.y * width; + this.poly([ + x1 + tx, y1 + ty, + x1 - tx, y1 - ty, + x2 + tx, y2 + ty, + x2 - tx, y2 - ty, + x2 + tx, y2 + ty, + x1 - tx, y1 - ty, + ]); + + this.setColor(0, 1, 0, 1); + this.poly(this.circle(x1, y1, 2)); + } + + this.renderGameObjectBounds(x, y, quad); + } + + protected abstract setColor (r: number, g: number, b: number, a: number): void; + protected abstract setColorFillMode (): void; + protected abstract poly (points: number[]): void; + + protected abstract renderSkeleton (vertices: Float32Array, uvs: Float32Array, indices: Uint16Array, colors: Float32Array, texture: Texture, blendMode: BlendMode): void; + public abstract renderGameObjectBounds (x: number, y: number, quad: DOMQuad | SDK.Quad): void; + + protected circle (x: number, y: number, radius: number) { + let segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0); + if (segments <= 0) throw new Error("segments must be > 0."); + const angle = 2 * MathUtils.PI / segments; + const cos = Math.cos(angle); + const sin = Math.sin(angle); + let cx = radius, cy = 0; + segments--; + const poly = []; + for (let i = 0; i < segments; i++) { + poly.push(x, y); + poly.push(x + cx, y + cy); + const temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + poly.push(x + cx, y + cy); + } + poly.push(x, y, x + cx, y + cy); + cx = radius; + cy = 0; + poly.push(x + cx, y + cy); + return poly; + } +} + +export class C3RendererRuntime extends C3SkeletonRenderer { + constructor (renderer: IRenderer, skeleton: Skeleton, matrix: C3Matrix) { + super(renderer, skeleton, matrix); + } + + protected setColor (r: number, g: number, b: number, a: number): void { + this.renderer.setColor([r, g, b, a]); + } + + protected setColorFillMode (): void { + this.renderer.setColorFillMode(); + } + + protected poly (points: number[]): void { + this.renderer.convexPoly(points); + } + + protected renderSkeleton (vertices: Float32Array, uvs: Float32Array, indices: Uint16Array, colors: Float32Array, texture: C3TextureRuntime, blendMode: BlendMode) { + this.renderer.setTexture(texture.texture); + this.renderer.setBlendMode(BlendingModeSpineToC3[blendMode]); + this.renderer.drawMesh(vertices, uvs, indices, colors); + }; + + public renderDragHandles (bone: Bone, radius: number) { + const boneApplied = bone.applied; + const { x: x1, y: y1 } = this.matrix.skeletonToGame(boneApplied.worldX, boneApplied.worldY); + this.renderer.setColorFillMode(); + this.renderer.setColor([1, 0, 0, .2]); + this.renderer.convexPoly(this.circle(x1, y1, radius)); + } + + public renderGameObjectBounds (x: number, y: number, quad: DOMQuad) { + const { renderer, matrix } = this; + renderer.setAlphaBlendMode(); + renderer.setColorFillMode(); + renderer.setColorRgba(0.25, 0, 0, 0.25); + renderer.lineQuad(quad); + renderer.line(x, y, matrix.tx, matrix.ty); + }; +} + +export class C3RendererEditor extends C3SkeletonRenderer { + protected setColor (r: number, g: number, b: number, a: number): void { + this.renderer.SetColorRgba(r, g, b, a); + } + + protected setColorFillMode (): void { + this.renderer.SetColorFillMode(); + } + + protected poly (points: number[]): void { + this.renderer.ConvexPoly(points); + } + + protected renderSkeleton (vertices: Float32Array, uvs: Float32Array, indices: Uint16Array, colors: Float32Array, texture: C3TextureEditor, blendMode: BlendMode) { + this.renderer.ResetColor(); + this.renderer.SetBlendMode(BlendingModeSpineToC3[blendMode]); + this.renderer.SetTextureFillMode(); + this.renderer.SetTexture(texture.texture); + this.renderer.DrawMesh(vertices, uvs, indices, colors); + }; + + public renderGameObjectBounds (x: number, y: number, quad: SDK.Quad): void { + const { renderer, matrix } = this; + renderer.SetAlphaBlend(); + renderer.SetColorFillMode(); + renderer.SetColorRgba(0.25, 0, 0, 0.25); + renderer.LineQuad(quad); + renderer.Line(x, y, matrix.tx, matrix.ty); + } +} \ No newline at end of file diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/C3Texture.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/C3Texture.ts index ac8b4d5e1..ea90e5dfe 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/src/C3Texture.ts +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/C3Texture.ts @@ -61,7 +61,7 @@ export class C3TextureEditor extends Texture { } } -export class C3Texture extends Texture { +export class C3TextureRuntime extends Texture { texture: ITexture; renderer?: IRenderer; diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/SpineBoundsProvider.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/SpineBoundsProvider.ts index 2511629d0..c5308931f 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/src/SpineBoundsProvider.ts +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/SpineBoundsProvider.ts @@ -1,3 +1,32 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, 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. + *****************************************************************************/ + import { AnimationState, Physics, Skeleton, SkeletonClipping, Skin } from "@esotericsoftware/spine-core"; interface Rectangle { diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/index.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/index.ts index a351846fc..6abb47381 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/src/index.ts +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/index.ts @@ -1,4 +1,6 @@ export * from "@esotericsoftware/spine-core"; export * from './AssetLoader.js'; +export * from './C3Matrix.js'; +export * from './C3SkeletonRenderer.js'; export * from './C3Texture.js'; export * from './SpineBoundsProvider.js'; diff --git a/spine-ts/spine-construct3/src/c3runtime/instance.ts b/spine-ts/spine-construct3/src/c3runtime/instance.ts index 22b429d64..227a6ce3e 100644 --- a/spine-ts/spine-construct3/src/c3runtime/instance.ts +++ b/spine-ts/spine-construct3/src/c3runtime/instance.ts @@ -1,4 +1,4 @@ -import type { AnimationState, AnimationStateListener, AssetLoader, Bone, Event, NumberArrayLike, RegionAttachment, Skeleton, SkeletonRendererCore, Skin, Slot, TextureAtlas, } from "@esotericsoftware/spine-construct3-lib"; +import type { AnimationState, AnimationStateListener, AssetLoader, Bone, C3Matrix, C3RendererRuntime, Event, NumberArrayLike, RegionAttachment, Skeleton, Skin, Slot, TextureAtlas, } from "@esotericsoftware/spine-construct3-lib"; const C3 = globalThis.C3; const spine = globalThis.spine; @@ -39,12 +39,8 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { public triggeredEventData?: Event; private assetLoader: AssetLoader; - private skeletonRenderer: SkeletonRendererCore; - - private a = 0; - private b = 0; - private c = 0; - private d = 0; + private skeletonRenderer?: C3RendererRuntime; + private matrix: C3Matrix; private tempVertices = new Float32Array(4096); private tempColors = new Float32Array(4096); @@ -86,7 +82,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { } this.assetLoader = new spine.AssetLoader(); - this.skeletonRenderer = new spine.SkeletonRendererCore(); + this.matrix = new spine.C3Matrix(); this._setTicking(true); } @@ -125,7 +121,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { } private update (delta: number) { - const { state, skeleton, animationSpeed, physicsMode } = this; + const { state, skeleton, animationSpeed, physicsMode, matrix } = this; if (!skeleton || !state) return; @@ -133,17 +129,13 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { state.update(adjustedDelta); skeleton.update(adjustedDelta); state.apply(skeleton); - - const cos = Math.cos(this.angle + this.propOffsetAngle); - const sin = Math.sin(this.angle + this.propOffsetAngle); - this.a = cos; - this.b = sin; - this.c = -sin; - this.d = cos; - + matrix.update( + this.x + this.propOffsetX, + this.y + this.propOffsetY, + this.angle + this.propOffsetAngle); skeleton.updateWorldTransform(physicsMode); - this.updateHandles(skeleton); + this.updateHandles(skeleton, matrix); this.updateBoneFollowers(); } @@ -156,134 +148,19 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { const { skeleton } = this; if (!skeleton) return; - this.renderSkeleton(renderer, skeleton); - this.renderDragHandles(renderer); - this.renderDebugSkeleton(renderer, skeleton); + this.skeletonRenderer ||= new spine.C3RendererRuntime(renderer, skeleton, this.matrix); + this.skeletonRenderer.draw(this.opacity); + if (this.propDebugSkeleton) this.skeletonRenderer.drawDebug(this.x, this.y, this.getBoundingQuad(false)); + this.renderDragHandles(); } - private renderSkeleton (renderer: IRenderer, skeleton: Skeleton) { - let command = this.skeletonRenderer.render(skeleton); - const opacity = this.opacity; - const inv255 = 1 / 255; - while (command) { - const { numVertices, positions, uvs, colors, indices, numIndices, blendMode } = command; - - const vertices = this.tempVertices.length < numVertices * 3 - ? (this.tempVertices = new Float32Array(numVertices * 3)) - : this.tempVertices; - - const c3colors = this.tempColors.length < numVertices * 4 - ? (this.tempColors = new Float32Array(numVertices * 4)) - : this.tempColors; - - for (let i = 0; i < numVertices; i++) { - const srcIndex = i * 2; - const { x, y } = this.skeletonToC3WorldCoordinates(positions[srcIndex], positions[srcIndex + 1]); - - const dstIndex = i * 3; - vertices[dstIndex] = x; - vertices[dstIndex + 1] = y; - vertices[dstIndex + 2] = 0; - - const color = colors[i]; - const colorDst = i * 4; - const alpha = (color >>> 24 & 0xFF) * inv255 * opacity; - const alphaInverse = inv255 * alpha; - c3colors[colorDst] = (color >>> 16 & 0xFF) * alphaInverse; - c3colors[colorDst + 1] = (color >>> 8 & 0xFF) * alphaInverse; - c3colors[colorDst + 2] = (color & 0xFF) * alphaInverse; - c3colors[colorDst + 3] = alpha; - } - - renderer.setTexture(command.texture.texture); - renderer.setBlendMode(spine.BlendingModeSpineToC3[blendMode]); - renderer.drawMesh( - vertices.subarray(0, numVertices * 3), - uvs.subarray(0, numVertices * 2), - indices.subarray(0, numIndices), - c3colors.subarray(0, numVertices * 4), - ); - - command = command.next; - } - } - - private renderDragHandles (renderer: IRenderer) { + private renderDragHandles () { for (const { bone, radius, debug } of this.dragHandles) { if (!debug) continue; - const boneApplied = bone.applied; - const { x: x1, y: y1 } = this.skeletonToC3WorldCoordinates(boneApplied.worldX, boneApplied.worldY); - renderer.setColorFillMode(); - renderer.setColor([1, 0, 0, .2]); - renderer.convexPoly(this.circle(x1, y1, radius)); + this.skeletonRenderer?.renderDragHandles(bone, radius); } } - private renderDebugSkeleton (renderer: IRenderer, skeleton: Skeleton) { - if (!this.propDebugSkeleton) return; - - const bones = skeleton.bones; - for (let i = 0, n = bones.length; i < n; i++) { - const bone = bones[i]; - if (!bone.parent) continue; - const boneApplied = bone.applied; - const { x: x1, y: y1 } = this.skeletonToC3WorldCoordinates(boneApplied.worldX, boneApplied.worldY); - const x2 = bone.data.length * boneApplied.a + x1; - const y2 = bone.data.length * boneApplied.c + y1; - - renderer.setColor([1, 0, 0, 1]); - renderer.setColorFillMode(); - - const t = this.tempPoint.set(y2 - y1, x1 - x2); - t.normalize(); - const width = 1 * 0.5; - const tx = t.x * width; - const ty = t.y * width; - renderer.convexPoly([ - x1 + tx, y1 + ty, - x1 - tx, y1 - ty, - x2 + tx, y2 + ty, - x2 - tx, y2 - ty, - x2 + tx, y2 + ty, - x1 - tx, y1 - ty, - ]); - - renderer.setColor([0, 1, 0, 1]); - renderer.convexPoly(this.circle(x1, y1, 2)); - } - - // debug bounds - renderer.setAlphaBlendMode(); - renderer.setColorFillMode(); - renderer.setColorRgba(0.25, 0, 0, 0.25); - renderer.lineQuad(this.getBoundingQuad(false)); - renderer.line(this.x, this.y, this.x + this.propOffsetX, this.y + this.propOffsetY); - } - - private circle (x: number, y: number, radius: number) { - let segments = Math.max(1, (6 * spine.MathUtils.cbrt(radius)) | 0); - if (segments <= 0) throw new Error("segments must be > 0."); - const angle = 2 * spine.MathUtils.PI / segments; - const cos = Math.cos(angle); - const sin = Math.sin(angle); - let cx = radius, cy = 0; - segments--; - const poly = []; - for (let i = 0; i < segments; i++) { - poly.push(x, y); - poly.push(x + cx, y + cy); - const temp = cx; - cx = cos * cx - sin * cy; - cy = sin * temp + cos * cy; - poly.push(x + cx, y + cy); - } - poly.push(x, y, x + cx, y + cy); - cx = radius; - cy = 0; - poly.push(x + cx, y + cy); - return poly; - } - /**********/ @@ -327,7 +204,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { } } - private updateHandles (skeleton: Skeleton) { + private updateHandles (skeleton: Skeleton, matrix: C3Matrix) { if (this.dragHandles.size === 0) return; // accessing mouse without having a mouse object will throw an error @@ -357,16 +234,16 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { if (handleObject.dragging) { if (bone.parent) { - const { x, y } = this.c3WorldCoordinatesToBone(mx - handleObject.offsetX, my - handleObject.offsetY, bone); + const { x, y } = matrix.gameToBone(mx - handleObject.offsetX, my - handleObject.offsetY, bone); boneApplied.x = x; boneApplied.y = y; } else { - const { x, y } = this.c3WorldCoordinatesToSkeleton(mx - handleObject.offsetX, my - handleObject.offsetY); + const { x, y } = matrix.gameToSkeleton(mx - handleObject.offsetX, my - handleObject.offsetY); boneApplied.x = x / skeleton.scaleX; boneApplied.y = -y / skeleton.scaleY * spine.Skeleton.yDir; } } else if (!this.prevLeftClickDown) { - const { x, y } = this.c3WorldCoordinatesToSkeleton(mx, my); + const { x, y } = matrix.gameToSkeleton(mx, my); const inside = handleObject.slot ? this.isInsideSlot(x, y, handleObject.slot, true) : this.inRadius(x, y, boneApplied.worldX, boneApplied.worldY, handleObject.radius); @@ -402,7 +279,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { if (skeletonCoordinate) return this.isPointInPolygon(vertices, hullLength, x, y); - const coords = this.c3WorldCoordinatesToSkeleton(x, y); + const coords = this.matrix.gameToSkeleton(x, y); return this.isPointInPolygon(vertices, hullLength, coords.x, coords.y); } @@ -806,7 +683,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { const instance = this.runtime.getInstanceByUid(follower.uid) as IWorldInstance; if (!instance) continue; - const { x, y } = this.boneToC3WorldCoordinates(bone); + const { x, y } = this.matrix.boneToGame(bone); const boneRotation = bone.applied.getWorldRotationX(); // Apply rotation to offset @@ -824,41 +701,6 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { /**********/ - /* - * Coordinates transformation - */ - - private c3WorldCoordinatesToSkeleton (x: number, y: number) { - const tx = x - (this.x + this.propOffsetX); - const ty = y - (this.y + this.propOffsetY); - const { a: ta, b: tb, c: tc, d: td, tempPoint } = this; - const delta = ta * td - tb * tc; - tempPoint.x = (td * tx - tc * ty) / delta; - tempPoint.y = (ta * ty - tb * tx) / delta; - return this.tempPoint; - } - - private c3WorldCoordinatesToBone (x: number, y: number, bone: Bone) { - const point = this.c3WorldCoordinatesToSkeleton(x, y); - if (bone.parent) - return bone.parent.applied.worldToLocal(point); - return bone.applied.worldToLocal(point); - } - - private skeletonToC3WorldCoordinates (skeletonX: number, skeletonY: number) { - const { a, b, c, d, tempPoint } = this; - tempPoint.x = a * skeletonX + c * skeletonY + this.x + this.propOffsetX; - tempPoint.y = b * skeletonX + d * skeletonY + this.y + this.propOffsetY; - return tempPoint; - } - - private boneToC3WorldCoordinates (bone: Bone) { - const { applied } = bone; - return this.skeletonToC3WorldCoordinates(applied.worldX, applied.worldY); - } - - /**********/ - /* * Bone */ @@ -947,7 +789,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { return 0; } - const point = this.boneToC3WorldCoordinates(bone); + const point = this.matrix.boneToGame(bone); return point.x; } @@ -964,7 +806,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { return 0; } - const point = this.boneToC3WorldCoordinates(bone); + const point = this.matrix.boneToGame(bone); return point.y; } @@ -972,7 +814,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { const bone = this.getBone(boneName); if (!bone) return; - const { x, y } = this.c3WorldCoordinatesToBone(c3X, c3Y, bone); + const { x, y } = this.matrix.gameToBone(c3X, c3Y, bone); bone.applied.x = x; bone.applied.y = y; } diff --git a/spine-ts/spine-construct3/src/instance.ts b/spine-ts/spine-construct3/src/instance.ts index 25392b8a5..e0be54d78 100644 --- a/spine-ts/spine-construct3/src/instance.ts +++ b/spine-ts/spine-construct3/src/instance.ts @@ -1,6 +1,6 @@ // / -import type { AnimationState, AssetLoader, Skeleton, SkeletonRendererCore, SpineBoundsProvider, TextureAtlas } from "@esotericsoftware/spine-construct3-lib"; +import type { AnimationState, AssetLoader, C3Matrix, C3RendererEditor, Skeleton, SpineBoundsProvider, TextureAtlas, } from "@esotericsoftware/spine-construct3-lib"; import type { SpineC3PluginType } from "./type"; const SDK = globalThis.SDK; @@ -25,7 +25,8 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { animation?: string; private assetLoader: AssetLoader; - private skeletonRenderer: SkeletonRendererCore; + private skeletonRenderer?: C3RendererEditor; + private matrix: C3Matrix; // position mode private positioningBounds = false; @@ -40,10 +41,6 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { height: 200, }; - // utils for drawing - private tempVertices = new Float32Array(4096); - private tempColors = new Float32Array(4096); - // errors private errorTextureAtlas?: string; private errorSkeleton?: string; @@ -56,7 +53,7 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { spine.Skeleton.yDown = true; this.assetLoader = new spine.AssetLoader(); - this.skeletonRenderer = new spine.SkeletonRendererCore(); + this.matrix = new spine.C3Matrix(); } Release () { @@ -118,64 +115,13 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { skeleton.scaleY = _inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number; } - const opacity = _inst.GetOpacity(); - const cos = Math.cos(offsetAngle); - const sin = Math.sin(offsetAngle); - const inv255 = 1 / 255; - this.update(0); - let command = this.skeletonRenderer.render(skeleton); - while (command) { - const { numVertices, positions, uvs, colors, indices, numIndices, blendMode } = command; - - const vertices = this.tempVertices.length < numVertices * 3 - ? (this.tempVertices = new Float32Array(numVertices * 3)) - : this.tempVertices; - - const c3colors = this.tempColors.length < numVertices * 4 - ? (this.tempColors = new Float32Array(numVertices * 4)) - : this.tempColors; - - for (let i = 0; i < numVertices; i++) { - const srcIndex = i * 2; - const dstIndex = i * 3; - const x = positions[srcIndex]; - const y = positions[srcIndex + 1]; - vertices[dstIndex] = x * cos - y * sin + offsetX; - vertices[dstIndex + 1] = x * sin + y * cos + offsetY; - vertices[dstIndex + 2] = 0; - - const color = colors[i]; - const colorDst = i * 4; - const alpha = (color >>> 24 & 0xFF) * inv255 * opacity; - const alphaInverse = inv255 * alpha; - c3colors[colorDst] = (color >>> 16 & 0xFF) * alphaInverse; - c3colors[colorDst + 1] = (color >>> 8 & 0xFF) * alphaInverse; - c3colors[colorDst + 2] = (color & 0xFF) * alphaInverse; - c3colors[colorDst + 3] = alpha; - } - - iRenderer.ResetColor(); - iRenderer.SetBlendMode(spine.BlendingModeSpineToC3[blendMode]); - iRenderer.SetTextureFillMode(); - iRenderer.SetTexture(command.texture.texture); - - iRenderer.DrawMesh( - vertices.subarray(0, numVertices * 3), - uvs.subarray(0, numVertices * 2), - indices.subarray(0, numIndices), - c3colors, - ); - - - command = command.next; - } - - iRenderer.SetAlphaBlend(); - iRenderer.SetColorFillMode(); - iRenderer.SetColorRgba(0.25, 0, 0, 0.25); - iRenderer.LineQuad(_inst.GetQuad()); - iRenderer.Line(rectX, rectY, offsetX, offsetY); + this.skeletonRenderer ||= new spine.C3RendererEditor(iRenderer, skeleton, this.matrix); + this.skeletonRenderer.draw(_inst.GetOpacity()); + const quad = _inst.GetQuad(); + if (_inst.GetPropertyValue(PLUGIN_CLASS.PROP_DEBUG_SKELETON) as boolean) + this.skeletonRenderer.drawDebug(rectX, rectY, quad); + this.skeletonRenderer.renderGameObjectBounds(rectX, rectY, quad); } else { iRenderer.SetAlphaBlend(); @@ -501,6 +447,10 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { state.update(delta); skeleton.update(delta); state.apply(skeleton); + this.matrix.update( + this._inst.GetX() + (this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X) as number), + this._inst.GetY() + (this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y) as number), + this._inst.GetAngle() + (this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE) as number)); skeleton.updateWorldTransform(spine.Physics.update); }