diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/C3Matrix.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/C3Matrix.ts index 54a32bc0a..955832d8b 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/src/C3Matrix.ts +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/C3Matrix.ts @@ -38,9 +38,17 @@ export class C3Matrix { public tx = 0; public ty = 0; + public prevX = Infinity; + public prevY = Infinity; + public prevAngle = Infinity; + private tempPoint = new Vector2(); public update (x: number, y: number, angle: number) { + if (this.prevX === x && this.prevY === y && this.prevAngle === angle) return false; + this.prevX = x; + this.prevY = y; + this.prevAngle = angle; const cos = Math.cos(angle); const sin = Math.sin(angle); this.a = cos; @@ -49,6 +57,7 @@ export class C3Matrix { this.d = cos; this.tx = x; this.ty = y; + return true; } public gameToSkeleton (x: number, y: number) { diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts index 42b0ecfc7..599ed8f83 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts @@ -47,6 +47,14 @@ abstract class C3SkeletonRenderer< private tempArray = [] as number[]; private inv255 = 1 / 255; + private prevX = Infinity; + private prevY = Infinity; + private prevAngle = Infinity; + private prevRed = -1; + private prevGreen = -1; + private prevBlue = -1; + private prevAlpha = -1; + constructor ( protected renderer: Renderer, protected matrix: C3Matrix, @@ -54,45 +62,51 @@ abstract class C3SkeletonRenderer< super(); } - draw (skeleton: Skeleton, inColors: [number, number, number], opacity = 1, isPlaying = true, fromUpdate = true) { + draw (skeleton: Skeleton, inColors: [number, number, number], opacity = 1, requestRedraw = true) { const { matrix, inv255 } = this; + const { a, b, c, d, tx, ty, prevX, prevY, prevAngle } = matrix; - this.command = (isPlaying || !this.command) - ? this.render(skeleton, true, [...inColors, opacity]) - : this.command; + const requestRedrawForMatrix = this.prevX !== prevX || this.prevY !== prevY || this.prevAngle !== prevAngle; + this.prevX = prevX; + this.prevY = prevY; + this.prevAngle = prevAngle; + + const requestRedrawForColor = this.prevRed !== inColors[0] || this.prevGreen !== inColors[1] || this.prevBlue !== inColors[2] || this.prevAlpha !== opacity; + this.prevRed = inColors[0]; + this.prevGreen = inColors[1]; + this.prevBlue = inColors[2]; + this.prevAlpha = opacity; + + const newCommand = (requestRedraw || requestRedrawForColor || requestRedrawForMatrix || !this.command); + this.command = newCommand ? this.render(skeleton, true, [...inColors, opacity], 3) : this.command; let command = this.command; - const { a, b, c, d, tx, ty } = matrix; 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] = a * x + c * y + tx; - vertices[dstIndex + 1] = b * x + d * y + ty; - vertices[dstIndex + 2] = 0; + if (newCommand) { + for (let i = 0; i < numVertices; i++) { + const index = i * 3; + const x = positions[index]; + const y = positions[index + 1]; + positions[index] = a * x + c * y + tx; + positions[index + 1] = b * x + d * y + ty; - const color = colors[i]; - const colorDst = i * 4; - c3colors[colorDst] = (color >>> 16 & 0xFF) * inv255; - c3colors[colorDst + 1] = (color >>> 8 & 0xFF) * inv255; - c3colors[colorDst + 2] = (color & 0xFF) * inv255; - c3colors[colorDst + 3] = (color >>> 24) * inv255; + const color = colors[i]; + const colorDst = i * 4; + c3colors[colorDst] = (color >>> 16 & 0xFF) * inv255; + c3colors[colorDst + 1] = (color >>> 8 & 0xFF) * inv255; + c3colors[colorDst + 2] = (color & 0xFF) * inv255; + c3colors[colorDst + 3] = (color >>> 24) * inv255; + } } this.renderSkeleton( - vertices.subarray(0, numVertices * 3), + positions.subarray(0, numVertices * 3), uvs.subarray(0, numVertices * 2), indices.subarray(0, numIndices), c3colors.subarray(0, numVertices * 4), diff --git a/spine-ts/spine-construct3/src/c3runtime/instance.ts b/spine-ts/spine-construct3/src/c3runtime/instance.ts index 22389ddf1..c057e9d0e 100644 --- a/spine-ts/spine-construct3/src/c3runtime/instance.ts +++ b/spine-ts/spine-construct3/src/c3runtime/instance.ts @@ -41,6 +41,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { private assetLoader: AssetLoader; private skeletonRenderer?: C3RendererRuntime; private matrix: C3Matrix; + private requestRedraw = false; private verticesTemp = spine.Utils.newFloatArray(2 * 1024); @@ -116,14 +117,9 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { this.y + this.propOffsetY, this.angle + this.propOffsetAngle); - if (this.isPlaying) { - this.update(this.dt); - this.runtime.sdk.updateRender(); - } + if (this.isPlaying) this.update(this.dt); } - private fromUpdate = false; - private update (delta: number) { const { state, skeleton, animationSpeed, physicsMode, matrix } = this; @@ -140,7 +136,8 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { this.updateBoneFollowers(matrix); - this.fromUpdate = true; + this.runtime.sdk.updateRender(); + this.requestRedraw = true; } _draw (renderer: IRenderer) { @@ -153,8 +150,8 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { if (!skeleton) return; this.skeletonRenderer ||= new spine.C3RendererRuntime(renderer, this.matrix); - this.skeletonRenderer.draw(skeleton, this.colorRgb, this.opacity, this.isPlaying, this.fromUpdate); - this.fromUpdate = false; + this.skeletonRenderer.draw(skeleton, this.colorRgb, this.opacity, this.requestRedraw); + this.requestRedraw = false; if (this.propDebugSkeleton) this.skeletonRenderer.drawDebug(skeleton, this.x, this.y, this.getBoundingQuad(false)); this.renderDragHandles(); @@ -410,7 +407,6 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { this.skeleton.scaleY = this.propScaleY; this.update(0); - this.runtime.sdk.updateRender(); this.skeletonLoaded = true; this._trigger(C3.Plugins.EsotericSoftware_SpineConstruct3.Cnds.OnSkeletonLoaded); diff --git a/spine-ts/spine-core/src/SkeletonClipping.ts b/spine-ts/spine-core/src/SkeletonClipping.ts index 91b6dde71..271cc6668 100644 --- a/spine-ts/spine-core/src/SkeletonClipping.ts +++ b/spine-ts/spine-core/src/SkeletonClipping.ts @@ -324,7 +324,7 @@ export class SkeletonClipping { return clipOutputItems != null; } - public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike) { + public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike, stride = 2) { const clipOutput = this.clipOutput; let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped; // biome-ignore lint/style/noNonNullAssertion: clipStart define it @@ -343,17 +343,23 @@ export class SkeletonClipping { let clipped = false; for (let i = 0; i < trianglesLength; i += 3) { - let v = triangles[i] << 1; + let t = triangles[i]; + let v = t * stride; const x1 = vertices[v], y1 = vertices[v + 1]; - const u1 = uvs[v], v1 = uvs[v + 1]; + let uv = t << 1; + const u1 = uvs[uv], v1 = uvs[uv + 1]; - v = triangles[i + 1] << 1; + t = triangles[i + 1]; + v = t * stride; const x2 = vertices[v], y2 = vertices[v + 1]; - const u2 = uvs[v], v2 = uvs[v + 1]; + uv = t << 1; + const u2 = uvs[uv], v2 = uvs[uv + 1]; - v = triangles[i + 2] << 1; + t = triangles[i + 2]; + v = t * stride; const x3 = vertices[v], y3 = vertices[v + 1]; - const u3 = uvs[v], v3 = uvs[v + 1]; + uv = t << 1; + const u3 = uvs[uv], v3 = uvs[uv + 1]; for (let p = 0; p < polygonsCount; p++) { let s = this.clippedVerticesLength; @@ -367,20 +373,22 @@ export class SkeletonClipping { let clipOutputCount = clipOutputLength >> 1; const clipOutputItems = this.clipOutput; - const newLength = s + clipOutputCount * 2; + const newLength = s + clipOutputCount * stride; if (clippedVertices.length < newLength) { this._clippedVerticesTyped = new Float32Array(newLength * 2); this._clippedVerticesTyped.set(clippedVertices.subarray(0, s)); - this._clippedUVsTyped = new Float32Array(newLength * 2); - this._clippedUVsTyped.set(clippedUVs.subarray(0, s)); + this._clippedUVsTyped = new Float32Array((this.clippedUVsLength + clipOutputCount * 2) * 2); + this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength)); clippedVertices = this._clippedVerticesTyped; clippedUVs = this._clippedUVsTyped; } const clippedVerticesItems = clippedVertices; const clippedUVsItems = clippedUVs; this.clippedVerticesLength = newLength; - this.clippedUVsLength = newLength; - for (let ii = 0; ii < clipOutputLength; ii += 2, s += 2) { + + let uvIndex = this.clippedUVsLength; + this.clippedUVsLength = uvIndex + clipOutputCount * 2; + for (let ii = 0; ii < clipOutputLength; ii += 2, s += stride, uvIndex += 2) { const x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; clippedVerticesItems[s] = x; clippedVerticesItems[s + 1] = y; @@ -388,8 +396,8 @@ export class SkeletonClipping { const a = (d0 * c0 + d1 * c1) * d; const b = (d4 * c0 + d2 * c1) * d; const c = 1 - a - b; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + clippedUVsItems[uvIndex] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[uvIndex + 1] = v1 * a + v2 * b + v3 * c; } s = this.clippedTrianglesLength; @@ -411,7 +419,7 @@ export class SkeletonClipping { } else { - let newLength = s + 3 * 2; + let newLength = s + 3 * stride; if (clippedVertices.length < newLength) { this._clippedVerticesTyped = new Float32Array(newLength * 2); this._clippedVerticesTyped.set(clippedVertices.subarray(0, s)); @@ -419,25 +427,27 @@ export class SkeletonClipping { } clippedVertices[s] = x1; clippedVertices[s + 1] = y1; - clippedVertices[s + 2] = x2; - clippedVertices[s + 3] = y2; - clippedVertices[s + 4] = x3; - clippedVertices[s + 5] = y3; + clippedVertices[s + stride] = x2; + clippedVertices[s + stride + 1] = y2; + clippedVertices[s + stride * 2] = x3; + clippedVertices[s + stride * 2 + 1] = y3; - if (clippedUVs.length < newLength) { - this._clippedUVsTyped = new Float32Array(newLength * 2); - this._clippedUVsTyped.set(clippedUVs.subarray(0, s)); + let uvLength = this.clippedUVsLength + 3 * 2; + if (clippedUVs.length < uvLength) { + this._clippedUVsTyped = new Float32Array(uvLength * 2); + this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength)); clippedUVs = this._clippedUVsTyped; } - clippedUVs[s] = u1; - clippedUVs[s + 1] = v1; - clippedUVs[s + 2] = u2; - clippedUVs[s + 3] = v2; - clippedUVs[s + 4] = u3; - clippedUVs[s + 5] = v3; + let uvIndex = this.clippedUVsLength; + clippedUVs[uvIndex] = u1; + clippedUVs[uvIndex + 1] = v1; + clippedUVs[uvIndex + 2] = u2; + clippedUVs[uvIndex + 3] = v2; + clippedUVs[uvIndex + 4] = u3; + clippedUVs[uvIndex + 5] = v3; this.clippedVerticesLength = newLength; - this.clippedUVsLength = newLength; + this.clippedUVsLength = uvLength; s = this.clippedTrianglesLength; newLength = s + 3; diff --git a/spine-ts/spine-core/src/SkeletonRendererCore.ts b/spine-ts/spine-core/src/SkeletonRendererCore.ts index 06adc6299..d1142fae9 100644 --- a/spine-ts/spine-core/src/SkeletonRendererCore.ts +++ b/spine-ts/spine-core/src/SkeletonRendererCore.ts @@ -40,7 +40,7 @@ export class SkeletonRendererCore { private clipping = new SkeletonClipping(); private renderCommands: RenderCommand[] = []; - render (skeleton: Skeleton, pma = false, inColor?: [number, number, number, number]): RenderCommand | undefined { + render (skeleton: Skeleton, pma = false, inColor?: [number, number, number, number], stride = 2): RenderCommand | undefined { this.commandPool.reset(); this.renderCommands.length = 0; @@ -80,7 +80,7 @@ export class SkeletonRendererCore { continue; } - attachment.computeWorldVertices(slot, this.worldVertices, 0, 2); + attachment.computeWorldVertices(slot, this.worldVertices, 0, stride); vertices = this.worldVertices; verticesCount = 4; uvs = attachment.uvs as Float32Array; @@ -99,7 +99,7 @@ export class SkeletonRendererCore { if (this.worldVertices.length < attachment.worldVerticesLength) this.worldVertices = new Float32Array(attachment.worldVerticesLength); - attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, 2); + attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, stride); vertices = this.worldVertices; verticesCount = attachment.worldVerticesLength >> 1; uvs = attachment.uvs as Float32Array; @@ -163,19 +163,19 @@ export class SkeletonRendererCore { } if (clipper.isClipping()) { - clipper.clipTrianglesUnpacked(vertices, indices, indicesCount, uvs); + clipper.clipTrianglesUnpacked(vertices, indices, indicesCount, uvs, stride); vertices = clipper.clippedVerticesTyped; - verticesCount = clipper.clippedVerticesLength >> 1; + verticesCount = clipper.clippedVerticesLength / stride; uvs = clipper.clippedUVsTyped; indices = clipper.clippedTrianglesTyped; indicesCount = clipper.clippedTrianglesLength; } - const cmd = this.commandPool.getCommand(verticesCount, indicesCount); + const cmd = this.commandPool.getCommand(verticesCount, indicesCount, stride); cmd.blendMode = slot.data.blendMode; cmd.texture = texture; - cmd.positions.set(vertices.subarray(0, verticesCount << 1)); + cmd.positions.set(vertices.subarray(0, verticesCount * stride)); cmd.uvs.set(uvs.subarray(0, verticesCount << 1)); for (let j = 0; j < verticesCount; j++) { @@ -194,14 +194,14 @@ export class SkeletonRendererCore { } clipper.clipEnd(); - return this.batchCommands(); + return this.batchCommands(stride); } private batchSubCommands (commands: RenderCommand[], first: number, last: number, - numVertices: number, numIndices: number): RenderCommand { + numVertices: number, numIndices: number, stride: number): RenderCommand { const firstCmd = commands[first]; - const batched = this.commandPool.getCommand(numVertices, numIndices); + const batched = this.commandPool.getCommand(numVertices, numIndices, stride); batched.blendMode = firstCmd.blendMode; batched.texture = firstCmd.texture; @@ -216,7 +216,7 @@ export class SkeletonRendererCore { const cmd = commands[i]; batched.positions.set(cmd.positions, positionsOffset); - positionsOffset += cmd.numVertices << 1; + positionsOffset += cmd.numVertices * stride; batched.uvs.set(cmd.uvs, uvsOffset); uvsOffset += cmd.numVertices << 1; @@ -236,7 +236,7 @@ export class SkeletonRendererCore { return batched; } - private batchCommands (): RenderCommand | undefined { + private batchCommands (stride: number): RenderCommand | undefined { if (this.renderCommands.length === 0) return undefined; let root: RenderCommand | undefined; @@ -267,7 +267,7 @@ export class SkeletonRendererCore { numIndices += cmd.numIndices; } else { const batched = this.batchSubCommands(this.renderCommands, startIndex, i - 1, - numVertices, numIndices); + numVertices, numIndices, stride); if (!last) { root = last = batched; @@ -315,17 +315,17 @@ class CommandPool { private pool: RenderCommand[] = []; private inUse: RenderCommand[] = []; - getCommand (numVertices: number, numIndices: number): RenderCommand { + getCommand (numVertices: number, numIndices: number, stride: number): RenderCommand { let cmd: RenderCommand | undefined; for (const c of this.pool) { - if (c._positions.length >= numVertices << 1 && c._indices.length >= numIndices) { + if (c._positions.length >= numVertices * stride && c._indices.length >= numIndices) { cmd = c; break; } } if (!cmd) { - const _positions = new Float32Array(numVertices << 1); + const _positions = new Float32Array(numVertices * stride); const _uvs = new Float32Array(numVertices << 1); const _colors = new Uint32Array(numVertices); const _darkColors = new Uint32Array(numVertices); @@ -352,8 +352,8 @@ class CommandPool { cmd.numVertices = numVertices; cmd.numIndices = numIndices; - cmd.positions = cmd._positions.subarray(0, numVertices << 1); - cmd.uvs = cmd._uvs.subarray(0, numVertices * 2); + cmd.positions = cmd._positions.subarray(0, numVertices * stride); + cmd.uvs = cmd._uvs.subarray(0, numVertices << 1); cmd.colors = cmd._colors.subarray(0, numVertices); cmd.darkColors = cmd._darkColors.subarray(0, numVertices); cmd.indices = cmd._indices.subarray(0, numIndices);