diff --git a/spine-ts/spine-core/src/Skeleton.ts b/spine-ts/spine-core/src/Skeleton.ts index 277a21d4f3..012ea48017 100644 --- a/spine-ts/spine-core/src/Skeleton.ts +++ b/spine-ts/spine-core/src/Skeleton.ts @@ -28,6 +28,7 @@ *****************************************************************************/ import { Attachment } from "./attachments/Attachment.js"; +import { ClippingAttachment } from "./attachments/ClippingAttachment.js"; import { MeshAttachment } from "./attachments/MeshAttachment.js"; import { PathAttachment } from "./attachments/PathAttachment.js"; import { RegionAttachment } from "./attachments/RegionAttachment.js"; @@ -35,6 +36,7 @@ import { Bone } from "./Bone.js"; import { IkConstraint } from "./IkConstraint.js"; import { PathConstraint } from "./PathConstraint.js"; import { PhysicsConstraint } from "./PhysicsConstraint.js"; +import { SkeletonClipping } from "./SkeletonClipping.js"; import { SkeletonData } from "./SkeletonData.js"; import { Skin } from "./Skin.js"; import { Slot } from "./Slot.js"; @@ -46,6 +48,7 @@ import { Color, Utils, MathUtils, Vector2, NumberArrayLike } from "./Utils.js"; * * See [Instance objects](http://esotericsoftware.com/spine-runtime-architecture#Instance-objects) in the Spine Runtimes Guide. */ export class Skeleton { + private static quadTriangles = [0, 1, 2, 2, 3, 0]; static yDown = false; /** The skeleton's setup pose data. */ @@ -606,8 +609,9 @@ export class Skeleton { /** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. * @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB. * @param size An output value, the width and height of the AABB. - * @param temp Working memory to temporarily store attachments' computed world vertices. */ - getBounds (offset: Vector2, size: Vector2, temp: Array = new Array(2)) { + * @param temp Working memory to temporarily store attachments' computed world vertices. + * @param clipper {@link SkeletonClipping} to use. If null, no clipping is applied. */ + getBounds (offset: Vector2, size: Vector2, temp: Array = new Array(2), clipper: SkeletonClipping | null = null) { if (!offset) throw new Error("offset cannot be null."); if (!size) throw new Error("size cannot be null."); let drawOrder = this.drawOrder; @@ -617,18 +621,29 @@ export class Skeleton { if (!slot.bone.active) continue; let verticesLength = 0; let vertices: NumberArrayLike | null = null; + let triangles: NumberArrayLike | null = null; let attachment = slot.getAttachment(); if (attachment instanceof RegionAttachment) { verticesLength = 8; vertices = Utils.setArraySize(temp, verticesLength, 0); - (attachment).computeWorldVertices(slot, vertices, 0, 2); + attachment.computeWorldVertices(slot, vertices, 0, 2); + triangles = Skeleton.quadTriangles; } else if (attachment instanceof MeshAttachment) { let mesh = (attachment); verticesLength = mesh.worldVerticesLength; vertices = Utils.setArraySize(temp, verticesLength, 0); mesh.computeWorldVertices(slot, 0, verticesLength, vertices, 0, 2); + triangles = mesh.triangles; + } else if (attachment instanceof ClippingAttachment && clipper != null) { + clipper.clipStart(slot, attachment); + continue; } - if (vertices) { + if (vertices && triangles) { + if (clipper != null && clipper.isClipping()) { + clipper.clipTriangles(vertices, verticesLength, triangles, triangles.length); + vertices = clipper.clippedVertices; + verticesLength = clipper.clippedVertices.length; + } for (let ii = 0, nn = vertices.length; ii < nn; ii += 2) { let x = vertices[ii], y = vertices[ii + 1]; minX = Math.min(minX, x); @@ -637,7 +652,9 @@ export class Skeleton { maxY = Math.max(maxY, y); } } + if (clipper != null) clipper.clipEndWithSlot(slot); } + if (clipper != null) clipper.clipEnd(); offset.set(minX, minY); size.set(maxX - minX, maxY - minY); } diff --git a/spine-ts/spine-core/src/SkeletonClipping.ts b/spine-ts/spine-core/src/SkeletonClipping.ts index e3beec5aae..17a0eb8bbd 100644 --- a/spine-ts/spine-core/src/SkeletonClipping.ts +++ b/spine-ts/spine-core/src/SkeletonClipping.ts @@ -80,7 +80,90 @@ export class SkeletonClipping { return this.clipAttachment != null; } - clipTriangles (vertices: NumberArrayLike, verticesLength: number, triangles: NumberArrayLike, trianglesLength: number, uvs: NumberArrayLike, + clipTriangles(vertices: NumberArrayLike, verticesLength: number, triangles: NumberArrayLike, trianglesLength: number): void; + clipTriangles(vertices: NumberArrayLike, verticesLength: number, triangles: NumberArrayLike, trianglesLength: number, uvs: NumberArrayLike, + light: Color, dark: Color, twoColor: boolean): void; + clipTriangles(vertices: NumberArrayLike, verticesLength: number, triangles: NumberArrayLike, trianglesLength: number, uvs?: NumberArrayLike, + light?: Color, dark?: Color, twoColor?: boolean): void { + + if (uvs && light && dark && typeof twoColor === 'boolean') + this.clipTrianglesRender(vertices, verticesLength, triangles, trianglesLength, uvs, light, dark, twoColor); + else + this.clipTrianglesNoRender(vertices, verticesLength, triangles, trianglesLength); + } + private clipTrianglesNoRender (vertices: NumberArrayLike, verticesLength: number, triangles: NumberArrayLike, trianglesLength: number) { + + let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + let clippedTriangles = this.clippedTriangles; + let polygons = this.clippingPolygons!; + let polygonsCount = polygons.length; + let vertexSize = 2; + + let index = 0; + clippedVertices.length = 0; + clippedTriangles.length = 0; + outer: + for (let i = 0; i < trianglesLength; i += 3) { + let vertexOffset = triangles[i] << 1; + let x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + let x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + let x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + + for (let p = 0; p < polygonsCount; p++) { + let s = clippedVertices.length; + if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { + let clipOutputLength = clipOutput.length; + if (clipOutputLength == 0) continue; + + let clipOutputCount = clipOutputLength >> 1; + let clipOutputItems = this.clipOutput; + let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * vertexSize); + for (let ii = 0; ii < clipOutputLength; ii += 2) { + let x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[s + 1] = y; + s += 2; + } + + s = clippedTriangles.length; + let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); + clipOutputCount--; + for (let ii = 1; ii < clipOutputCount; ii++) { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = (index + ii); + clippedTrianglesItems[s + 2] = (index + ii + 1); + s += 3; + } + index += clipOutputCount + 1; + + } else { + let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * vertexSize); + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[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); + index += 3; + continue outer; + } + } + } + } + + private clipTrianglesRender (vertices: NumberArrayLike, verticesLength: number, triangles: NumberArrayLike, trianglesLength: number, uvs: NumberArrayLike, light: Color, dark: Color, twoColor: boolean) { let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; diff --git a/spine-ts/spine-webgl/src/SkeletonRenderer.ts b/spine-ts/spine-webgl/src/SkeletonRenderer.ts index 8692c266ac..e9f7df5f6d 100644 --- a/spine-ts/spine-webgl/src/SkeletonRenderer.ts +++ b/spine-ts/spine-webgl/src/SkeletonRenderer.ts @@ -205,4 +205,9 @@ export class SkeletonRenderer { } clipper.clipEnd(); } + + /** Returns the {@link SkeletonClipping} used by this renderer for use with e.g. {@link Skeleton.getBounds} **/ + public getSkeletonClipping (): SkeletonClipping { + return this.clipper; + } }