[ts] Add SkeletonRendererCore.

This commit is contained in:
Davide Tantillo 2025-08-12 10:32:17 +02:00
parent 75421b2a41
commit 3fa97353f1
4 changed files with 191 additions and 232 deletions

View File

@ -34,20 +34,14 @@ import {
AnimationStateData, AnimationStateData,
AtlasAttachmentLoader, AtlasAttachmentLoader,
BlendMode, BlendMode,
ClippingAttachment,
MathUtils,
MeshAttachment,
type NumberArrayLike,
Physics, Physics,
RegionAttachment,
Skeleton, Skeleton,
SkeletonBinary, SkeletonBinary,
SkeletonClipping,
type SkeletonData, type SkeletonData,
SkeletonJson, SkeletonJson,
SkeletonRendererCore,
Texture, Texture,
TextureAtlas, TextureAtlas,
Utils,
} from "@esotericsoftware/spine-core"; } from "@esotericsoftware/spine-core";
import type { Canvas, CanvasKit, Image, Paint, Shader } from "canvaskit-wasm"; 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}. * Renders a {@link Skeleton} or {@link SkeletonDrawable} to a CanvasKit {@link Canvas}.
*/ */
export class SkeletonRenderer { export class SkeletonRenderer {
private clipper = new SkeletonClipping(); private skeletonRenderer = new SkeletonRendererCore();
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);
/** /**
* Creates a new skeleton renderer. * Creates a new skeleton renderer.
@ -235,119 +225,34 @@ export class SkeletonRenderer {
*/ */
constructor (private ck: CanvasKit) { } 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) { render (canvas: Canvas, skeleton: Skeleton | SkeletonDrawable) {
if (skeleton instanceof SkeletonDrawable) skeleton = skeleton.skeleton; if (skeleton instanceof SkeletonDrawable) skeleton = skeleton.skeleton;
const clipper = this.clipper; let command = this.skeletonRenderer.render(skeleton);
const drawOrder = skeleton.drawOrder; while (command) {
const skeletonColor = skeleton.color; 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++) { for (let i = 0; i < uvs.length; i += 2) {
const slot = drawOrder[i]; uvs[i] = uvs[i] * width;
if (!slot.bone.active) { uvs[i + 1] = uvs[i + 1] * height;
clipper.clipEnd(slot);
continue;
} }
const pose = slot.applied; const vertices = this.ck.MakeVertices(
const attachment = pose.attachment; this.ck.VertexMode.Triangles,
let positions = this.scratchPositions; positions,
let triangles: Array<number>; uvs,
let numVertices = 4; colors,
indices as any as number[],
if (attachment instanceof RegionAttachment) { false
attachment.computeWorldVertices(slot, positions, 0, 2); );
triangles = SkeletonRenderer.QUAD_TRIANGLES; const ckPaint = ckImage.paintPerBlendMode.get(command.blendMode);
} else if (attachment instanceof MeshAttachment) { if (ckPaint) canvas.drawVertices(vertices, this.ck.BlendMode.Modulate, ckPaint);
if (positions.length < attachment.worldVerticesLength) { vertices.delete();
this.scratchPositions = Utils.newFloatArray(attachment.worldVerticesLength); command = command.next;
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);
} }
clipper.clipEnd();
} }
} }

View File

@ -43,6 +43,17 @@ export class SkeletonClipping {
clippedUVs = new Array<number>(); clippedUVs = new Array<number>();
clippedTriangles = new Array<number>(); clippedTriangles = new Array<number>();
_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<number>(); private scratch = new Array<number>();
private clipAttachment: ClippingAttachment | null = null; private clipAttachment: ClippingAttachment | null = null;
@ -76,6 +87,9 @@ export class SkeletonClipping {
this.clippedVertices.length = 0; this.clippedVertices.length = 0;
this.clippedTriangles.length = 0; this.clippedTriangles.length = 0;
this.clippingPolygon.length = 0; this.clippingPolygon.length = 0;
this.clippedVerticesLength = 0;
this.clippedUVsLength = 0;
this.clippedTrianglesLength = 0;
} }
isClipping (): boolean { isClipping (): boolean {
@ -308,55 +322,82 @@ export class SkeletonClipping {
return clipOutputItems != null; return clipOutputItems != null;
} }
public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike, trianglesLength: number, uvs: NumberArrayLike) { public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike) {
let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices, clippedUVs = this.clippedUVs; const clipOutput = this.clipOutput;
let clippedTriangles = this.clippedTriangles; let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped;
let polygons = this.clippingPolygons!; const polygons = this.clippingPolygons!;
let polygonsCount = polygons.length; const polygonsCount = polygons.length;
let index = 0; let index = 0;
clippedVertices.length = 0; this.clippedVerticesLength = 0;
clippedUVs.length = 0; this.clippedUVsLength = 0;
clippedTriangles.length = 0; this.clippedTrianglesLength = 0;
this._clippedVerticesTyped;
this._clippedUVsTyped;
this._clippedTrianglesTyped;
let clipped = false;
for (let i = 0; i < trianglesLength; i += 3) { for (let i = 0; i < trianglesLength; i += 3) {
let v = triangles[i] << 1; let v = triangles[i] << 1;
let x1 = vertices[v], y1 = vertices[v + 1]; const x1 = vertices[v], y1 = vertices[v + 1];
let u1 = uvs[v], v1 = uvs[v + 1]; const u1 = uvs[v], v1 = uvs[v + 1];
v = triangles[i + 1] << 1; v = triangles[i + 1] << 1;
let x2 = vertices[v], y2 = vertices[v + 1]; const x2 = vertices[v], y2 = vertices[v + 1];
let u2 = uvs[v], v2 = uvs[v + 1]; const u2 = uvs[v], v2 = uvs[v + 1];
v = triangles[i + 2] << 1; v = triangles[i + 2] << 1;
let x3 = vertices[v], y3 = vertices[v + 1]; const x3 = vertices[v], y3 = vertices[v + 1];
let u3 = uvs[v], v3 = uvs[v + 1]; const u3 = uvs[v], v3 = uvs[v + 1];
for (let p = 0; p < polygonsCount; p++) { 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)) { if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
let clipOutputLength = clipOutput.length; const clipOutputLength = clipOutput.length;
if (clipOutputLength == 0) continue; if (clipOutputLength === 0) continue;
let d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; clipped = true;
let d = 1 / (d0 * d2 + d1 * (y1 - y3)); 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 clipOutputCount = clipOutputLength >> 1;
let clipOutputItems = this.clipOutput; const clipOutputItems = this.clipOutput;
let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * 2);
let clippedUVsItems = Utils.setArraySize(clippedUVs, s + clipOutputCount * 2); 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) { 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] = x;
clippedVerticesItems[s + 1] = y; clippedVerticesItems[s + 1] = y;
let c0 = x - x3, c1 = y - y3; const c0 = x - x3, c1 = y - y3;
let a = (d0 * c0 + d1 * c1) * d; const a = (d0 * c0 + d1 * c1) * d;
let b = (d4 * c0 + d2 * c1) * d; const b = (d4 * c0 + d2 * c1) * d;
let c = 1 - a - b; const c = 1 - a - b;
clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; clippedUVsItems[s] = u1 * a + u2 * b + u3 * c;
clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c;
} }
s = clippedTriangles.length; s = this.clippedTrianglesLength;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); 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--; clipOutputCount--;
for (let ii = 1; ii < clipOutputCount; ii++, s += 3) { for (let ii = 1; ii < clipOutputCount; ii++, s += 3) {
clippedTrianglesItems[s] = index; clippedTrianglesItems[s] = index;
@ -366,32 +407,58 @@ export class SkeletonClipping {
index += clipOutputCount + 1; index += clipOutputCount + 1;
} else { } 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); let newLength = s + 3 * 2;
clippedUVSItems[s] = u1; if (clippedVertices.length < newLength) {
clippedUVSItems[s + 1] = v1; this._clippedVerticesTyped = new Float32Array(newLength * 2);
clippedUVSItems[s + 2] = u2; this._clippedVerticesTyped.set(clippedVertices.subarray(0, s));
clippedUVSItems[s + 3] = v2; clippedVertices = this._clippedVerticesTyped;
clippedUVSItems[s + 4] = u3; }
clippedUVSItems[s + 5] = v3; 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; if (clippedUVs.length < newLength) {
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3); this._clippedUVsTyped = new Float32Array(newLength * 2);
clippedTrianglesItems[s] = index; this._clippedUVsTyped.set(clippedUVs.subarray(0, s));
clippedTrianglesItems[s + 1] = (index + 1); clippedUVs = this._clippedUVsTyped;
clippedTrianglesItems[s + 2] = (index + 2); }
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; index += 3;
this.clippedTrianglesLength = newLength;
break; 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 /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping

View File

@ -1,8 +1,18 @@
export * from './Animation.js'; export * from './Animation.js';
export * from './AnimationState.js'; export * from './AnimationState.js';
export * from './AnimationStateData.js'; export * from './AnimationStateData.js';
export * from './AtlasAttachmentLoader.js';
export * from './AssetManagerBase.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 './Bone.js';
export * from './BoneData.js'; export * from './BoneData.js';
export * from './BoneLocal.js'; export * from './BoneLocal.js';
@ -25,12 +35,14 @@ export * from './Pose.js';
export * from './Posed.js'; export * from './Posed.js';
export * from './PosedActive.js'; export * from './PosedActive.js';
export * from './PosedData.js'; export * from './PosedData.js';
export * from './polyfills.js';
export * from './Skeleton.js'; export * from './Skeleton.js';
export * from './SkeletonBinary.js'; export * from './SkeletonBinary.js';
export * from './SkeletonBounds.js'; export * from './SkeletonBounds.js';
export * from './SkeletonClipping.js'; export * from './SkeletonClipping.js';
export * from './SkeletonData.js'; export * from './SkeletonData.js';
export * from './SkeletonJson.js'; export * from './SkeletonJson.js';
export * from './SkeletonRendererCore.js';
export * from './Skin.js'; export * from './Skin.js';
export * from './Slider.js'; export * from './Slider.js';
export * from './SliderData.js'; export * from './SliderData.js';
@ -46,14 +58,3 @@ export * from './TransformConstraintPose.js';
export * from './Triangulator.js'; export * from './Triangulator.js';
export * from './Update.js'; export * from './Update.js';
export * from './Utils.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';

View File

@ -27,27 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * 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 { import {
AnimationState, AnimationState,
AnimationStateData, AnimationStateData,
AtlasAttachmentLoader, AtlasAttachmentLoader,
Attachment, type Attachment,
Bone, type Bone,
ClippingAttachment, ClippingAttachment,
Color, Color,
MeshAttachment, MeshAttachment,
@ -61,11 +46,26 @@ import {
SkeletonData, SkeletonData,
SkeletonJson, SkeletonJson,
Skin, Skin,
Slot, type Slot,
type TextureAtlas, type TextureAtlas,
TrackEntry, type TrackEntry,
Vector2, Vector2,
} from '@esotericsoftware/spine-core'; } 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}. * Options to create a {@link Spine} using {@link Spine.from}.
@ -142,7 +142,7 @@ export class SetupPoseBoundsProvider implements SpineBoundsProvider {
skeleton.setupPose(); skeleton.setupPose();
skeleton.updateWorldTransform(Physics.update); skeleton.updateWorldTransform(Physics.update);
const bounds = skeleton.getBoundsRect(this.clipping ? new SkeletonClipping() : undefined); 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 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} }
@ -180,7 +180,7 @@ export class SkinsAndAnimationBoundsProvider
const clipper = this.clipping ? new SkeletonClipping() : undefined; const clipper = this.clipping ? new SkeletonClipping() : undefined;
const data = skeleton.data; const data = skeleton.data;
if (this.skins.length > 0) { if (this.skins.length > 0) {
let customSkin = new Skin("custom-skin"); const customSkin = new Skin("custom-skin");
for (const skinName of this.skins) { for (const skinName of this.skins) {
const skin = data.findSkin(skinName); const skin = data.findSkin(skinName);
if (skin == null) continue; if (skin == null) continue;
@ -190,12 +190,12 @@ export class SkinsAndAnimationBoundsProvider
} }
skeleton.setupPose(); 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) { if (animation == null) {
skeleton.updateWorldTransform(Physics.update); skeleton.updateWorldTransform(Physics.update);
const bounds = skeleton.getBoundsRect(clipper); 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 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} else { } else {
@ -225,7 +225,7 @@ export class SkinsAndAnimationBoundsProvider
width: maxX - minX, width: maxX - minX,
height: maxY - minY, height: maxY - minY,
}; };
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width === Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : 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. */ /** 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 { 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. // Because reasons, pixi uses deltaFrames at 60fps.
// We ignore the default deltaFrames and use the deltaSeconds from pixi ticker. // We ignore the default deltaFrames and use the deltaSeconds from pixi ticker.
this._updateAndApplyState(deltaSeconds ?? Ticker.shared.deltaMS / 1000); this._updateAndApplyState(deltaSeconds ?? Ticker.shared.deltaMS / 1000);
@ -568,18 +568,18 @@ export class Spine extends ViewContainer {
const pose = slot.applied; const pose = slot.applied;
const attachment = pose.attachment; const attachment = pose.attachment;
if (attachment && attachment instanceof ClippingAttachment) { if (attachment && attachment instanceof ClippingAttachment) {
const clip = (this.clippingSlotToPixiMasks[slot.data.name] ||= { slot, vertices: new Array<number>() }); const clip = (this.clippingSlotToPixiMasks[slot.data.name] ||= { slot, vertices: [] as number[] });
clip.maskComputed = false; clip.maskComputed = false;
this.currentClippingSlot = this.clippingSlotToPixiMasks[slot.data.name]; this.currentClippingSlot = this.clippingSlotToPixiMasks[slot.data.name];
return; return;
} }
// assign the currentClippingSlot mask to the slot object // assign the currentClippingSlot mask to the slot object
let currentClippingSlot = this.currentClippingSlot; const currentClippingSlot = this.currentClippingSlot;
let slotObject = this._slotsObject[slot.data.name]; const slotObject = this._slotsObject[slot.data.name];
if (currentClippingSlot && slotObject) { if (currentClippingSlot && slotObject) {
let slotClipping = currentClippingSlot.slot; const slotClipping = currentClippingSlot.slot;
let clippingAttachment = slotClipping.pose.attachment as ClippingAttachment; 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 // 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; 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 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; this.currentClippingSlot = undefined;
} }
@ -711,10 +711,10 @@ export class Spine extends ViewContainer {
cacheData.uvs, cacheData.uvs,
); );
const { clippedVertices, clippedUVs, clippedTriangles } = clipper; const { clippedVerticesTyped, clippedUVsTyped, clippedTrianglesTyped } = clipper;
const verticesCount = clippedVertices.length / 2; const verticesCount = clipper.clippedVerticesLength / 2;
const indicesCount = clippedTriangles.length; const indicesCount = clipper.clippedTrianglesLength;
if (!cacheData.clippedData) { if (!cacheData.clippedData) {
cacheData.clippedData = { cacheData.clippedData = {
@ -749,24 +749,10 @@ export class Spine extends ViewContainer {
} }
const { vertices, uvs, indices } = clippedData; const { vertices, uvs, indices } = clippedData;
vertices.set(clippedVerticesTyped);
for (let i = 0; i < verticesCount; i++) { uvs.set(clippedUVsTyped);
vertices[i * 2] = clippedVertices[i * 2]; indices.set(clippedTrianglesTyped);
vertices[(i * 2) + 1] = clippedVertices[(i * 2) + 1];
uvs[i * 2] = clippedUVs[(i * 2)];
uvs[(i * 2) + 1] = clippedUVs[(i * 2) + 1];
}
clippedData.vertexCount = verticesCount; 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; clippedData.indicesCount = indicesCount;
} }