mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-15 19:41:36 +08:00
[ts] Add SkeletonRendererCore (missing file).
This commit is contained in:
parent
3fa97353f1
commit
7c28c3c1c7
333
spine-ts/spine-core/src/SkeletonRendererCore.ts
Normal file
333
spine-ts/spine-core/src/SkeletonRendererCore.ts
Normal file
@ -0,0 +1,333 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
import { ClippingAttachment, MeshAttachment, RegionAttachment } from "./attachments";
|
||||
import type { Skeleton } from "./Skeleton";
|
||||
import { SkeletonClipping } from "./SkeletonClipping";
|
||||
import { BlendMode } from "./SlotData";
|
||||
import type { Color, NumberArrayLike } from "./Utils";
|
||||
|
||||
export class SkeletonRendererCore {
|
||||
private commandPool = new CommandPool();
|
||||
private worldVertices = new Float32Array(12 * 1024);
|
||||
private quadIndices = new Uint32Array([0, 1, 2, 2, 3, 0]);
|
||||
private clipping = new SkeletonClipping();
|
||||
private renderCommands: RenderCommand[] = [];
|
||||
|
||||
render (skeleton: Skeleton): RenderCommand | undefined {
|
||||
this.commandPool.reset();
|
||||
this.renderCommands.length = 0;
|
||||
|
||||
const clipper = this.clipping;
|
||||
|
||||
for (let i = 0; i < skeleton.slots.length; i++) {
|
||||
const slot = skeleton.drawOrder[i];
|
||||
const attachment = slot.applied.attachment;
|
||||
|
||||
if (!attachment) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
const slotApplied = slot.applied;
|
||||
const color = slotApplied.color;
|
||||
const alpha = color.a;
|
||||
if ((alpha === 0 || !slot.bone.active) && !(attachment instanceof ClippingAttachment)) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
let vertices: NumberArrayLike;
|
||||
let verticesCount: number;
|
||||
let uvs: NumberArrayLike;
|
||||
let indices: number[] | Uint32Array;
|
||||
let indicesCount: number;
|
||||
let attachmentColor: Color;
|
||||
let texture: any;
|
||||
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
attachmentColor = attachment.color;
|
||||
|
||||
if (attachmentColor.a === 0) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
attachment.computeWorldVertices(slot, this.worldVertices, 0, 2);
|
||||
vertices = this.worldVertices;
|
||||
verticesCount = 4;
|
||||
uvs = attachment.uvs as Float32Array;
|
||||
indices = this.quadIndices;
|
||||
indicesCount = 6;
|
||||
texture = attachment.region?.texture;
|
||||
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
attachmentColor = attachment.color;
|
||||
|
||||
if (attachmentColor.a === 0) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.worldVertices.length < attachment.worldVerticesLength)
|
||||
this.worldVertices = new Float32Array(attachment.worldVerticesLength);
|
||||
|
||||
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, 2);
|
||||
vertices = this.worldVertices;
|
||||
verticesCount = attachment.worldVerticesLength >> 1;
|
||||
uvs = attachment.uvs as Float32Array;
|
||||
indices = attachment.triangles;
|
||||
indicesCount = indices.length;
|
||||
texture = attachment.region?.texture;
|
||||
|
||||
} else if (attachment instanceof ClippingAttachment) {
|
||||
clipper.clipStart(skeleton, slot, attachment);
|
||||
continue;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
const skelColor = skeleton.color;
|
||||
const r = Math.floor(skelColor.r * slotApplied.color.r * attachmentColor.r * 255);
|
||||
const g = Math.floor(skelColor.g * slotApplied.color.g * attachmentColor.g * 255);
|
||||
const b = Math.floor(skelColor.b * slotApplied.color.b * attachmentColor.b * 255);
|
||||
const a = Math.floor(skelColor.a * slotApplied.color.a * attachmentColor.a * 255);
|
||||
|
||||
let darkColor = 0xff000000;
|
||||
if (slotApplied.darkColor) {
|
||||
const { r, g, b } = slotApplied.darkColor;
|
||||
darkColor = 0xff000000 |
|
||||
(Math.floor(r * 255) << 16) |
|
||||
(Math.floor(g * 255) << 8) |
|
||||
Math.floor(b * 255);
|
||||
}
|
||||
|
||||
if (clipper.isClipping()) {
|
||||
clipper.clipTrianglesUnpacked(vertices, indices, indicesCount, uvs);
|
||||
vertices = clipper.clippedVerticesTyped;
|
||||
verticesCount = clipper.clippedVerticesLength >> 1;
|
||||
uvs = clipper.clippedUVsTyped;
|
||||
indices = clipper.clippedTrianglesTyped;
|
||||
indicesCount = clipper.clippedTrianglesLength;
|
||||
}
|
||||
|
||||
const cmd = this.commandPool.getCommand(verticesCount, indicesCount);
|
||||
cmd.blendMode = slot.data.blendMode;
|
||||
cmd.texture = texture;
|
||||
|
||||
cmd.positions.set(vertices.subarray(0, verticesCount << 1));
|
||||
cmd.uvs.set(uvs.subarray(0, verticesCount << 1));
|
||||
|
||||
for (let j = 0; j < verticesCount; j++) {
|
||||
cmd.colors[j] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
cmd.darkColors[j] = darkColor;
|
||||
}
|
||||
|
||||
if (indices instanceof Uint16Array) {
|
||||
cmd.indices.set(indices.subarray(0, indicesCount));
|
||||
} else {
|
||||
cmd.indices.set(indices.slice(0, indicesCount));
|
||||
}
|
||||
|
||||
this.renderCommands.push(cmd);
|
||||
clipper.clipEnd(slot);
|
||||
}
|
||||
|
||||
clipper.clipEnd();
|
||||
return this.batchCommands();
|
||||
}
|
||||
|
||||
private batchSubCommands (commands: RenderCommand[], first: number, last: number,
|
||||
numVertices: number, numIndices: number): RenderCommand {
|
||||
|
||||
const firstCmd = commands[first];
|
||||
const batched = this.commandPool.getCommand(numVertices, numIndices);
|
||||
|
||||
batched.blendMode = firstCmd.blendMode;
|
||||
batched.texture = firstCmd.texture;
|
||||
|
||||
let positionsOffset = 0;
|
||||
let uvsOffset = 0;
|
||||
let colorsOffset = 0;
|
||||
let indicesOffset = 0;
|
||||
let vertexOffset = 0;
|
||||
|
||||
for (let i = first; i <= last; i++) {
|
||||
const cmd = commands[i];
|
||||
|
||||
batched.positions.set(cmd.positions, positionsOffset);
|
||||
positionsOffset += cmd.numVertices << 1;
|
||||
|
||||
batched.uvs.set(cmd.uvs, uvsOffset);
|
||||
uvsOffset += cmd.numVertices << 1;
|
||||
|
||||
batched.colors.set(cmd.colors, colorsOffset);
|
||||
batched.darkColors.set(cmd.darkColors, colorsOffset);
|
||||
colorsOffset += cmd.numVertices;
|
||||
|
||||
// cannot fast copy - indices need vertex offset adjustment
|
||||
for (let j = 0; j < cmd.numIndices; j++)
|
||||
batched.indices[indicesOffset + j] = cmd.indices[j] + vertexOffset;
|
||||
|
||||
indicesOffset += cmd.numIndices;
|
||||
vertexOffset += cmd.numVertices;
|
||||
}
|
||||
|
||||
return batched;
|
||||
}
|
||||
|
||||
private batchCommands (): RenderCommand | undefined {
|
||||
if (this.renderCommands.length === 0) return undefined;
|
||||
|
||||
let root: RenderCommand | undefined;
|
||||
let last: RenderCommand | undefined;
|
||||
|
||||
let first = this.renderCommands[0];
|
||||
let startIndex = 0;
|
||||
let i = 1;
|
||||
let numVertices = first.numVertices;
|
||||
let numIndices = first.numIndices;
|
||||
|
||||
while (i <= this.renderCommands.length) {
|
||||
const cmd = i < this.renderCommands.length ? this.renderCommands[i] : null;
|
||||
|
||||
if (cmd && cmd.numVertices === 0 && cmd.numIndices === 0) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const canBatch = cmd !== null &&
|
||||
cmd.texture === first.texture &&
|
||||
cmd.blendMode === first.blendMode &&
|
||||
cmd.colors[0] === first.colors[0] &&
|
||||
cmd.darkColors[0] === first.darkColors[0] &&
|
||||
numIndices + cmd.numIndices < 0xffff;
|
||||
if (canBatch) {
|
||||
numVertices += cmd.numVertices;
|
||||
numIndices += cmd.numIndices;
|
||||
} else {
|
||||
const batched = this.batchSubCommands(this.renderCommands, startIndex, i - 1,
|
||||
numVertices, numIndices);
|
||||
|
||||
if (!last) {
|
||||
root = last = batched;
|
||||
} else {
|
||||
last.next = batched;
|
||||
last = batched;
|
||||
}
|
||||
|
||||
if (i === this.renderCommands.length) break;
|
||||
|
||||
first = this.renderCommands[i];
|
||||
startIndex = i;
|
||||
numVertices = first.numVertices;
|
||||
numIndices = first.numIndices;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
interface RenderCommand {
|
||||
positions: Float32Array;
|
||||
uvs: Float32Array;
|
||||
colors: Uint32Array;
|
||||
darkColors: Uint32Array;
|
||||
indices: Uint16Array;
|
||||
_positions: Float32Array;
|
||||
_uvs: Float32Array;
|
||||
_colors: Uint32Array;
|
||||
_darkColors: Uint32Array;
|
||||
_indices: Uint16Array;
|
||||
numVertices: number;
|
||||
numIndices: number;
|
||||
blendMode: BlendMode;
|
||||
texture: any;
|
||||
next?: RenderCommand;
|
||||
}
|
||||
|
||||
class CommandPool {
|
||||
private pool: RenderCommand[] = [];
|
||||
private inUse: RenderCommand[] = [];
|
||||
|
||||
getCommand (numVertices: number, numIndices: number): RenderCommand {
|
||||
let cmd: RenderCommand | undefined;
|
||||
for (const c of this.pool) {
|
||||
if (c._positions.length >= numVertices << 1 && c._indices.length >= numIndices) {
|
||||
cmd = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cmd) {
|
||||
const _positions = new Float32Array(numVertices << 1);
|
||||
const _uvs = new Float32Array(numVertices << 1);
|
||||
const _colors = new Uint32Array(numVertices);
|
||||
const _darkColors = new Uint32Array(numVertices);
|
||||
const _indices = new Uint16Array(numIndices);
|
||||
cmd = {
|
||||
positions: _positions,
|
||||
uvs: _uvs,
|
||||
colors: _colors,
|
||||
darkColors: _darkColors,
|
||||
indices: _indices,
|
||||
_positions,
|
||||
_uvs,
|
||||
_colors,
|
||||
_darkColors,
|
||||
_indices,
|
||||
numVertices,
|
||||
numIndices,
|
||||
blendMode: BlendMode.Normal,
|
||||
texture: null
|
||||
};
|
||||
} else {
|
||||
this.pool.splice(this.pool.indexOf(cmd), 1);
|
||||
cmd.next = undefined;
|
||||
cmd.numVertices = numVertices;
|
||||
cmd.numIndices = numIndices;
|
||||
|
||||
cmd.positions = cmd._positions.subarray(0, numVertices << 1);
|
||||
cmd.uvs = cmd._uvs.subarray(0, numVertices * 2);
|
||||
cmd.colors = cmd._colors.subarray(0, numVertices);
|
||||
cmd.darkColors = cmd._darkColors.subarray(0, numVertices);
|
||||
cmd.indices = cmd._indices.subarray(0, numIndices);
|
||||
}
|
||||
|
||||
this.inUse.push(cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
reset (): void {
|
||||
this.pool.push(...this.inUse);
|
||||
this.inUse.length = 0;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user