From 3fa97353f158de73e4a225d76886266650b55d46 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Tue, 12 Aug 2025 10:32:17 +0200 Subject: [PATCH] [ts] Add SkeletonRendererCore. --- spine-ts/spine-canvaskit/src/index.ts | 145 +++-------------- spine-ts/spine-core/src/SkeletonClipping.ts | 163 ++++++++++++++------ spine-ts/spine-core/src/index.ts | 25 +-- spine-ts/spine-pixi-v8/src/Spine.ts | 90 +++++------ 4 files changed, 191 insertions(+), 232 deletions(-) diff --git a/spine-ts/spine-canvaskit/src/index.ts b/spine-ts/spine-canvaskit/src/index.ts index c6104dd7d..34a82de43 100644 --- a/spine-ts/spine-canvaskit/src/index.ts +++ b/spine-ts/spine-canvaskit/src/index.ts @@ -34,20 +34,14 @@ import { AnimationStateData, AtlasAttachmentLoader, BlendMode, - ClippingAttachment, - MathUtils, - MeshAttachment, - type NumberArrayLike, Physics, - RegionAttachment, Skeleton, SkeletonBinary, - SkeletonClipping, type SkeletonData, SkeletonJson, + SkeletonRendererCore, Texture, TextureAtlas, - Utils, } from "@esotericsoftware/spine-core"; import type { Canvas, CanvasKit, Image, Paint, Shader } from "canvaskit-wasm"; @@ -223,11 +217,7 @@ export class SkeletonDrawable { * Renders a {@link Skeleton} or {@link SkeletonDrawable} to a CanvasKit {@link Canvas}. */ export class SkeletonRenderer { - private clipper = new SkeletonClipping(); - private static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; - private scratchPositions = Utils.newFloatArray(100); - private scratchUVs = Utils.newFloatArray(100); - private scratchColors = new Uint32Array(100 / 4); + private skeletonRenderer = new SkeletonRendererCore(); /** * Creates a new skeleton renderer. @@ -235,119 +225,34 @@ export class SkeletonRenderer { */ constructor (private ck: CanvasKit) { } - /** - * Renders a skeleton or skeleton drawable in its current pose to the canvas. - * @param canvas the canvas to render to. - * @param skeleton the skeleton or drawable to render. - */ render (canvas: Canvas, skeleton: Skeleton | SkeletonDrawable) { if (skeleton instanceof SkeletonDrawable) skeleton = skeleton.skeleton; - const clipper = this.clipper; - const drawOrder = skeleton.drawOrder; - const skeletonColor = skeleton.color; + let command = this.skeletonRenderer.render(skeleton); + while (command) { + const { positions, uvs, colors, indices } = command; + const ckImage = command.texture.getImage(); + const image = ckImage.image; + const width = image.width(); + const height = image.height(); - for (let i = 0, n = drawOrder.length; i < n; i++) { - const slot = drawOrder[i]; - if (!slot.bone.active) { - clipper.clipEnd(slot); - continue; + for (let i = 0; i < uvs.length; i += 2) { + uvs[i] = uvs[i] * width; + uvs[i + 1] = uvs[i + 1] * height; } - const pose = slot.applied; - const attachment = pose.attachment; - let positions = this.scratchPositions; - let triangles: Array; - let numVertices = 4; - - if (attachment instanceof RegionAttachment) { - attachment.computeWorldVertices(slot, positions, 0, 2); - triangles = SkeletonRenderer.QUAD_TRIANGLES; - } else if (attachment instanceof MeshAttachment) { - if (positions.length < attachment.worldVerticesLength) { - this.scratchPositions = Utils.newFloatArray(attachment.worldVerticesLength); - positions = this.scratchPositions; - } - numVertices = attachment.worldVerticesLength >> 1; - attachment.computeWorldVertices( - skeleton, - slot, - 0, - attachment.worldVerticesLength, - positions, - 0, - 2 - ); - triangles = attachment.triangles; - } else if (attachment instanceof ClippingAttachment) { - clipper.clipStart(skeleton, slot, attachment); - continue; - } else { - clipper.clipEnd(slot); - continue; - } - - const texture = attachment.region?.texture as CanvasKitTexture; - if (texture) { - let uvs = attachment.uvs; - let scaledUvs: NumberArrayLike; - let colors = this.scratchColors; - if (clipper.isClipping()) { - clipper.clipTrianglesUnpacked(positions, triangles, triangles.length, uvs); - if (clipper.clippedVertices.length <= 0) { - clipper.clipEnd(slot); - continue; - } - positions = clipper.clippedVertices; - uvs = scaledUvs = clipper.clippedUVs; - triangles = clipper.clippedTriangles; - numVertices = clipper.clippedVertices.length / 2; - colors = new Uint32Array(numVertices); - } else { - scaledUvs = this.scratchUVs; - if (this.scratchUVs.length < uvs.length) - scaledUvs = this.scratchUVs = Utils.newFloatArray(uvs.length); - if (colors.length < numVertices) - colors = this.scratchColors = new Uint32Array(numVertices); - } - - 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 attachmentColor = attachment.color; - const slotColor = pose.color; - - // using Uint32Array for colors allows to avoid canvaskit to allocate one each time - // but colors need to be in canvaskit format. - // See: https://github.com/google/skia/blob/bb8c36fdf7b915a8c096e35e2f08109e477fe1b8/modules/canvaskit/color.js#L163 - const finalColor = ( - MathUtils.clamp(skeletonColor.a * slotColor.a * attachmentColor.a * 255, 0, 255) << 24 | - MathUtils.clamp(skeletonColor.r * slotColor.r * attachmentColor.r * 255, 0, 255) << 16 | - MathUtils.clamp(skeletonColor.g * slotColor.g * attachmentColor.g * 255, 0, 255) << 8 | - MathUtils.clamp(skeletonColor.b * slotColor.b * attachmentColor.b * 255, 0, 255) << 0 - ) >>> 0; - for (let i = 0, n = numVertices; i < n; i++) colors[i] = finalColor; - - const vertices = this.ck.MakeVertices( - this.ck.VertexMode.Triangles, - positions, - scaledUvs, - colors, - triangles, - false - ); - const ckPaint = ckImage.paintPerBlendMode.get(slot.data.blendMode); - if (ckPaint) canvas.drawVertices(vertices, this.ck.BlendMode.Modulate, ckPaint); - vertices.delete(); - } - - clipper.clipEnd(slot); + const vertices = this.ck.MakeVertices( + this.ck.VertexMode.Triangles, + positions, + uvs, + colors, + indices as any as number[], + false + ); + const ckPaint = ckImage.paintPerBlendMode.get(command.blendMode); + if (ckPaint) canvas.drawVertices(vertices, this.ck.BlendMode.Modulate, ckPaint); + vertices.delete(); + command = command.next; } - clipper.clipEnd(); } + } diff --git a/spine-ts/spine-core/src/SkeletonClipping.ts b/spine-ts/spine-core/src/SkeletonClipping.ts index 55d64adc5..267d03179 100644 --- a/spine-ts/spine-core/src/SkeletonClipping.ts +++ b/spine-ts/spine-core/src/SkeletonClipping.ts @@ -43,6 +43,17 @@ export class SkeletonClipping { clippedUVs = new Array(); clippedTriangles = new Array(); + + _clippedVerticesTyped = new Float32Array(1024); + _clippedUVsTyped = new Float32Array(1024); + _clippedTrianglesTyped = new Uint32Array(1024); + clippedVerticesTyped = new Float32Array(0); + clippedUVsTyped = new Float32Array(0); + clippedTrianglesTyped = new Uint32Array(0); + clippedVerticesLength = 0; + clippedUVsLength = 0; + clippedTrianglesLength = 0; + private scratch = new Array(); private clipAttachment: ClippingAttachment | null = null; @@ -76,6 +87,9 @@ export class SkeletonClipping { this.clippedVertices.length = 0; this.clippedTriangles.length = 0; this.clippingPolygon.length = 0; + this.clippedVerticesLength = 0; + this.clippedUVsLength = 0; + this.clippedTrianglesLength = 0; } isClipping (): boolean { @@ -308,55 +322,82 @@ export class SkeletonClipping { return clipOutputItems != null; } - public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike, trianglesLength: number, uvs: NumberArrayLike) { - let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices, clippedUVs = this.clippedUVs; - let clippedTriangles = this.clippedTriangles; - let polygons = this.clippingPolygons!; - let polygonsCount = polygons.length; + public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike) { + const clipOutput = this.clipOutput; + let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped; + const polygons = this.clippingPolygons!; + const polygonsCount = polygons.length; let index = 0; - clippedVertices.length = 0; - clippedUVs.length = 0; - clippedTriangles.length = 0; + this.clippedVerticesLength = 0; + this.clippedUVsLength = 0; + this.clippedTrianglesLength = 0; + + this._clippedVerticesTyped; + this._clippedUVsTyped; + this._clippedTrianglesTyped; + + let clipped = false; + for (let i = 0; i < trianglesLength; i += 3) { let v = triangles[i] << 1; - let x1 = vertices[v], y1 = vertices[v + 1]; - let u1 = uvs[v], v1 = uvs[v + 1]; + const x1 = vertices[v], y1 = vertices[v + 1]; + const u1 = uvs[v], v1 = uvs[v + 1]; v = triangles[i + 1] << 1; - let x2 = vertices[v], y2 = vertices[v + 1]; - let u2 = uvs[v], v2 = uvs[v + 1]; + const x2 = vertices[v], y2 = vertices[v + 1]; + const u2 = uvs[v], v2 = uvs[v + 1]; v = triangles[i + 2] << 1; - let x3 = vertices[v], y3 = vertices[v + 1]; - let u3 = uvs[v], v3 = uvs[v + 1]; + const x3 = vertices[v], y3 = vertices[v + 1]; + const u3 = uvs[v], v3 = uvs[v + 1]; for (let p = 0; p < polygonsCount; p++) { - let s = clippedVertices.length; + let s = this.clippedVerticesLength; if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - let clipOutputLength = clipOutput.length; - if (clipOutputLength == 0) continue; - let d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; - let d = 1 / (d0 * d2 + d1 * (y1 - y3)); + const clipOutputLength = clipOutput.length; + if (clipOutputLength === 0) continue; + clipped = true; + const d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; + const d = 1 / (d0 * d2 + d1 * (y1 - y3)); let clipOutputCount = clipOutputLength >> 1; - let clipOutputItems = this.clipOutput; - let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * 2); - let clippedUVsItems = Utils.setArraySize(clippedUVs, s + clipOutputCount * 2); + const clipOutputItems = this.clipOutput; + + const newLength = s + clipOutputCount * 2; + 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)); + 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 x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + const x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; clippedVerticesItems[s] = x; clippedVerticesItems[s + 1] = y; - let c0 = x - x3, c1 = y - y3; - let a = (d0 * c0 + d1 * c1) * d; - let b = (d4 * c0 + d2 * c1) * d; - let c = 1 - a - b; + const c0 = x - x3, c1 = y - y3; + const a = (d0 * c0 + d1 * c1) * d; + const b = (d4 * c0 + d2 * c1) * d; + const c = 1 - a - b; clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; } - s = clippedTriangles.length; - let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); + s = this.clippedTrianglesLength; + const newLengthTriangles = s + 3 * (clipOutputCount - 2) + if (clippedTriangles.length < newLengthTriangles) { + this._clippedTrianglesTyped = new Uint32Array(newLengthTriangles * 2); + this._clippedTrianglesTyped.set(clippedTriangles.subarray(0, s)); + clippedTriangles = this._clippedTrianglesTyped; + } + this.clippedTrianglesLength = newLengthTriangles; + const clippedTrianglesItems = clippedTriangles; clipOutputCount--; for (let ii = 1; ii < clipOutputCount; ii++, s += 3) { clippedTrianglesItems[s] = index; @@ -366,32 +407,58 @@ export class SkeletonClipping { index += clipOutputCount + 1; } else { - let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * 2); - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; - let clippedUVSItems = Utils.setArraySize(clippedUVs, s + 3 * 2); - clippedUVSItems[s] = u1; - clippedUVSItems[s + 1] = v1; - clippedUVSItems[s + 2] = u2; - clippedUVSItems[s + 3] = v2; - clippedUVSItems[s + 4] = u3; - clippedUVSItems[s + 5] = v3; + let newLength = s + 3 * 2; + if (clippedVertices.length < newLength) { + this._clippedVerticesTyped = new Float32Array(newLength * 2); + this._clippedVerticesTyped.set(clippedVertices.subarray(0, s)); + clippedVertices = this._clippedVerticesTyped; + } + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + 2] = x2; + clippedVertices[s + 3] = y2; + clippedVertices[s + 4] = x3; + clippedVertices[s + 5] = y3; - s = clippedTriangles.length; - let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3); - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = (index + 1); - clippedTrianglesItems[s + 2] = (index + 2); + if (clippedUVs.length < newLength) { + this._clippedUVsTyped = new Float32Array(newLength * 2); + this._clippedUVsTyped.set(clippedUVs.subarray(0, s)); + 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; + + this.clippedVerticesLength = newLength; + this.clippedUVsLength = newLength; + + s = this.clippedTrianglesLength; + newLength = s + 3; + if (clippedTriangles.length < newLength) { + this._clippedTrianglesTyped = new Uint32Array(newLength * 2); + this._clippedTrianglesTyped.set(clippedTriangles.subarray(0, s)); + clippedTriangles = this._clippedTrianglesTyped; + } + + clippedTriangles[s] = index; + clippedTriangles[s + 1] = (index + 1); + clippedTriangles[s + 2] = (index + 2); index += 3; + + this.clippedTrianglesLength = newLength; break; } } } + + this.clippedVerticesTyped = this._clippedVerticesTyped.subarray(0, this.clippedVerticesLength) + this.clippedUVsTyped = this._clippedUVsTyped.subarray(0, this.clippedUVsLength) + this.clippedTrianglesTyped = this._clippedTrianglesTyped.subarray(0, this.clippedTrianglesLength) + return clipped; } /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping diff --git a/spine-ts/spine-core/src/index.ts b/spine-ts/spine-core/src/index.ts index a7f9a8752..454f2de5b 100644 --- a/spine-ts/spine-core/src/index.ts +++ b/spine-ts/spine-core/src/index.ts @@ -1,8 +1,18 @@ export * from './Animation.js'; export * from './AnimationState.js'; export * from './AnimationStateData.js'; -export * from './AtlasAttachmentLoader.js'; export * from './AssetManagerBase.js'; +export * from './AtlasAttachmentLoader.js'; +export * from './attachments/Attachment.js'; +export * from './attachments/AttachmentLoader.js'; +export * from './attachments/BoundingBoxAttachment.js'; +export * from './attachments/ClippingAttachment.js'; +export * from './attachments/HasTextureRegion.js'; +export * from './attachments/MeshAttachment.js'; +export * from './attachments/PathAttachment.js'; +export * from './attachments/PointAttachment.js'; +export * from './attachments/RegionAttachment.js'; +export * from './attachments/Sequence.js'; export * from './Bone.js'; export * from './BoneData.js'; export * from './BoneLocal.js'; @@ -25,12 +35,14 @@ export * from './Pose.js'; export * from './Posed.js'; export * from './PosedActive.js'; export * from './PosedData.js'; +export * from './polyfills.js'; export * from './Skeleton.js'; export * from './SkeletonBinary.js'; export * from './SkeletonBounds.js'; export * from './SkeletonClipping.js'; export * from './SkeletonData.js'; export * from './SkeletonJson.js'; +export * from './SkeletonRendererCore.js'; export * from './Skin.js'; export * from './Slider.js'; export * from './SliderData.js'; @@ -46,14 +58,3 @@ export * from './TransformConstraintPose.js'; export * from './Triangulator.js'; export * from './Update.js'; export * from './Utils.js'; -export * from './polyfills.js'; -export * from './attachments/Attachment.js'; -export * from './attachments/AttachmentLoader.js'; -export * from './attachments/BoundingBoxAttachment.js'; -export * from './attachments/ClippingAttachment.js'; -export * from './attachments/HasTextureRegion.js'; -export * from './attachments/MeshAttachment.js'; -export * from './attachments/PathAttachment.js'; -export * from './attachments/PointAttachment.js'; -export * from './attachments/RegionAttachment.js'; -export * from './attachments/Sequence.js'; diff --git a/spine-ts/spine-pixi-v8/src/Spine.ts b/spine-ts/spine-pixi-v8/src/Spine.ts index 0b1b8a3d3..1257cdb37 100644 --- a/spine-ts/spine-pixi-v8/src/Spine.ts +++ b/spine-ts/spine-pixi-v8/src/Spine.ts @@ -27,27 +27,12 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import { - Assets, - Bounds, - Cache, - Container, - ContainerOptions, - DestroyOptions, - fastCopy, - Graphics, - PointData, - Texture, - Ticker, - ViewContainer, -} from 'pixi.js'; -import { ISpineDebugRenderer } from './SpineDebugRenderer.js'; import { AnimationState, AnimationStateData, AtlasAttachmentLoader, - Attachment, - Bone, + type Attachment, + type Bone, ClippingAttachment, Color, MeshAttachment, @@ -61,11 +46,26 @@ import { SkeletonData, SkeletonJson, Skin, - Slot, + type Slot, type TextureAtlas, - TrackEntry, + type TrackEntry, Vector2, } from '@esotericsoftware/spine-core'; +import { + Assets, + type Bounds, + Cache, + Container, + type ContainerOptions, + type DestroyOptions, + fastCopy, + Graphics, + type PointData, + Texture, + Ticker, + ViewContainer, +} from 'pixi.js'; +import type { ISpineDebugRenderer } from './SpineDebugRenderer.js'; /** * Options to create a {@link Spine} using {@link Spine.from}. @@ -142,7 +142,7 @@ export class SetupPoseBoundsProvider implements SpineBoundsProvider { skeleton.setupPose(); skeleton.updateWorldTransform(Physics.update); 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 } : bounds; } @@ -180,7 +180,7 @@ export class SkinsAndAnimationBoundsProvider const clipper = this.clipping ? new SkeletonClipping() : undefined; const data = skeleton.data; if (this.skins.length > 0) { - let customSkin = new Skin("custom-skin"); + const customSkin = new Skin("custom-skin"); for (const skinName of this.skins) { const skin = data.findSkin(skinName); if (skin == null) continue; @@ -190,12 +190,12 @@ export class SkinsAndAnimationBoundsProvider } skeleton.setupPose(); - const animation = this.animation != null ? data.findAnimation(this.animation!) : null; + const animation = this.animation != null ? data.findAnimation(this.animation) : null; if (animation == null) { skeleton.updateWorldTransform(Physics.update); 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 } : bounds; } else { @@ -225,7 +225,7 @@ export class SkinsAndAnimationBoundsProvider width: maxX - minX, height: maxY - minY, }; - return bounds.width == Number.NEGATIVE_INFINITY + return bounds.width === Number.NEGATIVE_INFINITY ? { x: 0, y: 0, width: 0, height: 0 } : bounds; } @@ -414,10 +414,10 @@ export class Spine extends ViewContainer { /** If {@link Spine.autoUpdate} is `false`, this method allows to update the AnimationState and the Skeleton with the given delta. */ public update (dt: number): void { - this.internalUpdate(0, dt); + this.internalUpdate(undefined, dt); } - protected internalUpdate (_deltaFrame: any, deltaSeconds?: number): void { + protected internalUpdate (ticker?: Ticker, deltaSeconds?: number): void { // Because reasons, pixi uses deltaFrames at 60fps. // We ignore the default deltaFrames and use the deltaSeconds from pixi ticker. this._updateAndApplyState(deltaSeconds ?? Ticker.shared.deltaMS / 1000); @@ -568,18 +568,18 @@ export class Spine extends ViewContainer { const pose = slot.applied; const attachment = pose.attachment; if (attachment && attachment instanceof ClippingAttachment) { - const clip = (this.clippingSlotToPixiMasks[slot.data.name] ||= { slot, vertices: new Array() }); + const clip = (this.clippingSlotToPixiMasks[slot.data.name] ||= { slot, vertices: [] as number[] }); clip.maskComputed = false; this.currentClippingSlot = this.clippingSlotToPixiMasks[slot.data.name]; return; } // assign the currentClippingSlot mask to the slot object - let currentClippingSlot = this.currentClippingSlot; - let slotObject = this._slotsObject[slot.data.name]; + const currentClippingSlot = this.currentClippingSlot; + const slotObject = this._slotsObject[slot.data.name]; if (currentClippingSlot && slotObject) { - let slotClipping = currentClippingSlot.slot; - let clippingAttachment = slotClipping.pose.attachment as ClippingAttachment; + const slotClipping = currentClippingSlot.slot; + 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 let mask = currentClippingSlot.mask as Graphics; @@ -604,7 +604,7 @@ export class Spine extends ViewContainer { } // if current slot is the ending one of the currentClippingSlot mask, set currentClippingSlot to undefined - if (currentClippingSlot && (currentClippingSlot.slot.applied.attachment as ClippingAttachment).endSlot == slot.data) { + if (currentClippingSlot && (currentClippingSlot.slot.applied.attachment as ClippingAttachment).endSlot === slot.data) { this.currentClippingSlot = undefined; } @@ -711,10 +711,10 @@ export class Spine extends ViewContainer { cacheData.uvs, ); - const { clippedVertices, clippedUVs, clippedTriangles } = clipper; + const { clippedVerticesTyped, clippedUVsTyped, clippedTrianglesTyped } = clipper; - const verticesCount = clippedVertices.length / 2; - const indicesCount = clippedTriangles.length; + const verticesCount = clipper.clippedVerticesLength / 2; + const indicesCount = clipper.clippedTrianglesLength; if (!cacheData.clippedData) { cacheData.clippedData = { @@ -749,24 +749,10 @@ export class Spine extends ViewContainer { } const { vertices, uvs, indices } = clippedData; - - for (let i = 0; i < verticesCount; i++) { - vertices[i * 2] = clippedVertices[i * 2]; - vertices[(i * 2) + 1] = clippedVertices[(i * 2) + 1]; - - uvs[i * 2] = clippedUVs[(i * 2)]; - uvs[(i * 2) + 1] = clippedUVs[(i * 2) + 1]; - } - + vertices.set(clippedVerticesTyped); + uvs.set(clippedUVsTyped); + indices.set(clippedTrianglesTyped); clippedData.vertexCount = verticesCount; - - for (let i = 0; i < indicesCount; i++) { - if (indices[i] !== clippedTriangles[i]) { - this.spineAttachmentsDirty = true; - indices[i] = clippedTriangles[i]; - } - } - clippedData.indicesCount = indicesCount; }