Skip to content

Commit

Permalink
[ts] Skeleton.getBounds() applies clipping, see #2515. Port of commits
Browse files Browse the repository at this point in the history
  • Loading branch information
davidetan committed May 3, 2024
1 parent b343543 commit f309722
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 5 deletions.
25 changes: 21 additions & 4 deletions spine-ts/spine-core/src/Skeleton.ts
Expand Up @@ -28,13 +28,15 @@
*****************************************************************************/

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";
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";
Expand All @@ -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. */
Expand Down Expand Up @@ -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<number> = new Array<number>(2)) {
* @param temp Working memory to temporarily store attachments' computed world vertices.
* @param clipper {@link SkeletonClipping} to use. If <code>null</code>, no clipping is applied. */
getBounds (offset: Vector2, size: Vector2, temp: Array<number> = new Array<number>(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;
Expand All @@ -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);
(<RegionAttachment>attachment).computeWorldVertices(slot, vertices, 0, 2);
attachment.computeWorldVertices(slot, vertices, 0, 2);
triangles = Skeleton.quadTriangles;
} else if (attachment instanceof MeshAttachment) {
let mesh = (<MeshAttachment>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);
Expand All @@ -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);
}
Expand Down
85 changes: 84 additions & 1 deletion spine-ts/spine-core/src/SkeletonClipping.ts
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions spine-ts/spine-webgl/src/SkeletonRenderer.ts
Expand Up @@ -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;
}
}

0 comments on commit f309722

Please sign in to comment.