Skip to content

Commit 7c28c3c

Browse files
committed
[ts] Add SkeletonRendererCore (missing file).
1 parent 3fa9735 commit 7c28c3c

File tree

1 file changed

+333
-0
lines changed

1 file changed

+333
-0
lines changed
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
/******************************************************************************
2+
* Spine Runtimes License Agreement
3+
* Last updated April 5, 2025. Replaces all prior versions.
4+
*
5+
* Copyright (c) 2013-2025, Esoteric Software LLC
6+
*
7+
* Integration of the Spine Runtimes into software or otherwise creating
8+
* derivative works of the Spine Runtimes is permitted under the terms and
9+
* conditions of Section 2 of the Spine Editor License Agreement:
10+
* http://esotericsoftware.com/spine-editor-license
11+
*
12+
* Otherwise, it is permitted to integrate the Spine Runtimes into software
13+
* or otherwise create derivative works of the Spine Runtimes (collectively,
14+
* "Products"), provided that each user of the Products must obtain their own
15+
* Spine Editor license and redistribution of the Products in any form must
16+
* include this license and copyright notice.
17+
*
18+
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
19+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
22+
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
24+
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
25+
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27+
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28+
*****************************************************************************/
29+
30+
import { ClippingAttachment, MeshAttachment, RegionAttachment } from "./attachments";
31+
import type { Skeleton } from "./Skeleton";
32+
import { SkeletonClipping } from "./SkeletonClipping";
33+
import { BlendMode } from "./SlotData";
34+
import type { Color, NumberArrayLike } from "./Utils";
35+
36+
export class SkeletonRendererCore {
37+
private commandPool = new CommandPool();
38+
private worldVertices = new Float32Array(12 * 1024);
39+
private quadIndices = new Uint32Array([0, 1, 2, 2, 3, 0]);
40+
private clipping = new SkeletonClipping();
41+
private renderCommands: RenderCommand[] = [];
42+
43+
render (skeleton: Skeleton): RenderCommand | undefined {
44+
this.commandPool.reset();
45+
this.renderCommands.length = 0;
46+
47+
const clipper = this.clipping;
48+
49+
for (let i = 0; i < skeleton.slots.length; i++) {
50+
const slot = skeleton.drawOrder[i];
51+
const attachment = slot.applied.attachment;
52+
53+
if (!attachment) {
54+
clipper.clipEnd(slot);
55+
continue;
56+
}
57+
58+
const slotApplied = slot.applied;
59+
const color = slotApplied.color;
60+
const alpha = color.a;
61+
if ((alpha === 0 || !slot.bone.active) && !(attachment instanceof ClippingAttachment)) {
62+
clipper.clipEnd(slot);
63+
continue;
64+
}
65+
66+
let vertices: NumberArrayLike;
67+
let verticesCount: number;
68+
let uvs: NumberArrayLike;
69+
let indices: number[] | Uint32Array;
70+
let indicesCount: number;
71+
let attachmentColor: Color;
72+
let texture: any;
73+
74+
if (attachment instanceof RegionAttachment) {
75+
attachmentColor = attachment.color;
76+
77+
if (attachmentColor.a === 0) {
78+
clipper.clipEnd(slot);
79+
continue;
80+
}
81+
82+
attachment.computeWorldVertices(slot, this.worldVertices, 0, 2);
83+
vertices = this.worldVertices;
84+
verticesCount = 4;
85+
uvs = attachment.uvs as Float32Array;
86+
indices = this.quadIndices;
87+
indicesCount = 6;
88+
texture = attachment.region?.texture;
89+
90+
} else if (attachment instanceof MeshAttachment) {
91+
attachmentColor = attachment.color;
92+
93+
if (attachmentColor.a === 0) {
94+
clipper.clipEnd(slot);
95+
continue;
96+
}
97+
98+
if (this.worldVertices.length < attachment.worldVerticesLength)
99+
this.worldVertices = new Float32Array(attachment.worldVerticesLength);
100+
101+
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, 2);
102+
vertices = this.worldVertices;
103+
verticesCount = attachment.worldVerticesLength >> 1;
104+
uvs = attachment.uvs as Float32Array;
105+
indices = attachment.triangles;
106+
indicesCount = indices.length;
107+
texture = attachment.region?.texture;
108+
109+
} else if (attachment instanceof ClippingAttachment) {
110+
clipper.clipStart(skeleton, slot, attachment);
111+
continue;
112+
} else {
113+
continue;
114+
}
115+
116+
const skelColor = skeleton.color;
117+
const r = Math.floor(skelColor.r * slotApplied.color.r * attachmentColor.r * 255);
118+
const g = Math.floor(skelColor.g * slotApplied.color.g * attachmentColor.g * 255);
119+
const b = Math.floor(skelColor.b * slotApplied.color.b * attachmentColor.b * 255);
120+
const a = Math.floor(skelColor.a * slotApplied.color.a * attachmentColor.a * 255);
121+
122+
let darkColor = 0xff000000;
123+
if (slotApplied.darkColor) {
124+
const { r, g, b } = slotApplied.darkColor;
125+
darkColor = 0xff000000 |
126+
(Math.floor(r * 255) << 16) |
127+
(Math.floor(g * 255) << 8) |
128+
Math.floor(b * 255);
129+
}
130+
131+
if (clipper.isClipping()) {
132+
clipper.clipTrianglesUnpacked(vertices, indices, indicesCount, uvs);
133+
vertices = clipper.clippedVerticesTyped;
134+
verticesCount = clipper.clippedVerticesLength >> 1;
135+
uvs = clipper.clippedUVsTyped;
136+
indices = clipper.clippedTrianglesTyped;
137+
indicesCount = clipper.clippedTrianglesLength;
138+
}
139+
140+
const cmd = this.commandPool.getCommand(verticesCount, indicesCount);
141+
cmd.blendMode = slot.data.blendMode;
142+
cmd.texture = texture;
143+
144+
cmd.positions.set(vertices.subarray(0, verticesCount << 1));
145+
cmd.uvs.set(uvs.subarray(0, verticesCount << 1));
146+
147+
for (let j = 0; j < verticesCount; j++) {
148+
cmd.colors[j] = (a << 24) | (r << 16) | (g << 8) | b;
149+
cmd.darkColors[j] = darkColor;
150+
}
151+
152+
if (indices instanceof Uint16Array) {
153+
cmd.indices.set(indices.subarray(0, indicesCount));
154+
} else {
155+
cmd.indices.set(indices.slice(0, indicesCount));
156+
}
157+
158+
this.renderCommands.push(cmd);
159+
clipper.clipEnd(slot);
160+
}
161+
162+
clipper.clipEnd();
163+
return this.batchCommands();
164+
}
165+
166+
private batchSubCommands (commands: RenderCommand[], first: number, last: number,
167+
numVertices: number, numIndices: number): RenderCommand {
168+
169+
const firstCmd = commands[first];
170+
const batched = this.commandPool.getCommand(numVertices, numIndices);
171+
172+
batched.blendMode = firstCmd.blendMode;
173+
batched.texture = firstCmd.texture;
174+
175+
let positionsOffset = 0;
176+
let uvsOffset = 0;
177+
let colorsOffset = 0;
178+
let indicesOffset = 0;
179+
let vertexOffset = 0;
180+
181+
for (let i = first; i <= last; i++) {
182+
const cmd = commands[i];
183+
184+
batched.positions.set(cmd.positions, positionsOffset);
185+
positionsOffset += cmd.numVertices << 1;
186+
187+
batched.uvs.set(cmd.uvs, uvsOffset);
188+
uvsOffset += cmd.numVertices << 1;
189+
190+
batched.colors.set(cmd.colors, colorsOffset);
191+
batched.darkColors.set(cmd.darkColors, colorsOffset);
192+
colorsOffset += cmd.numVertices;
193+
194+
// cannot fast copy - indices need vertex offset adjustment
195+
for (let j = 0; j < cmd.numIndices; j++)
196+
batched.indices[indicesOffset + j] = cmd.indices[j] + vertexOffset;
197+
198+
indicesOffset += cmd.numIndices;
199+
vertexOffset += cmd.numVertices;
200+
}
201+
202+
return batched;
203+
}
204+
205+
private batchCommands (): RenderCommand | undefined {
206+
if (this.renderCommands.length === 0) return undefined;
207+
208+
let root: RenderCommand | undefined;
209+
let last: RenderCommand | undefined;
210+
211+
let first = this.renderCommands[0];
212+
let startIndex = 0;
213+
let i = 1;
214+
let numVertices = first.numVertices;
215+
let numIndices = first.numIndices;
216+
217+
while (i <= this.renderCommands.length) {
218+
const cmd = i < this.renderCommands.length ? this.renderCommands[i] : null;
219+
220+
if (cmd && cmd.numVertices === 0 && cmd.numIndices === 0) {
221+
i++;
222+
continue;
223+
}
224+
225+
const canBatch = cmd !== null &&
226+
cmd.texture === first.texture &&
227+
cmd.blendMode === first.blendMode &&
228+
cmd.colors[0] === first.colors[0] &&
229+
cmd.darkColors[0] === first.darkColors[0] &&
230+
numIndices + cmd.numIndices < 0xffff;
231+
if (canBatch) {
232+
numVertices += cmd.numVertices;
233+
numIndices += cmd.numIndices;
234+
} else {
235+
const batched = this.batchSubCommands(this.renderCommands, startIndex, i - 1,
236+
numVertices, numIndices);
237+
238+
if (!last) {
239+
root = last = batched;
240+
} else {
241+
last.next = batched;
242+
last = batched;
243+
}
244+
245+
if (i === this.renderCommands.length) break;
246+
247+
first = this.renderCommands[i];
248+
startIndex = i;
249+
numVertices = first.numVertices;
250+
numIndices = first.numIndices;
251+
}
252+
i++;
253+
}
254+
255+
return root;
256+
}
257+
}
258+
259+
interface RenderCommand {
260+
positions: Float32Array;
261+
uvs: Float32Array;
262+
colors: Uint32Array;
263+
darkColors: Uint32Array;
264+
indices: Uint16Array;
265+
_positions: Float32Array;
266+
_uvs: Float32Array;
267+
_colors: Uint32Array;
268+
_darkColors: Uint32Array;
269+
_indices: Uint16Array;
270+
numVertices: number;
271+
numIndices: number;
272+
blendMode: BlendMode;
273+
texture: any;
274+
next?: RenderCommand;
275+
}
276+
277+
class CommandPool {
278+
private pool: RenderCommand[] = [];
279+
private inUse: RenderCommand[] = [];
280+
281+
getCommand (numVertices: number, numIndices: number): RenderCommand {
282+
let cmd: RenderCommand | undefined;
283+
for (const c of this.pool) {
284+
if (c._positions.length >= numVertices << 1 && c._indices.length >= numIndices) {
285+
cmd = c;
286+
break;
287+
}
288+
}
289+
290+
if (!cmd) {
291+
const _positions = new Float32Array(numVertices << 1);
292+
const _uvs = new Float32Array(numVertices << 1);
293+
const _colors = new Uint32Array(numVertices);
294+
const _darkColors = new Uint32Array(numVertices);
295+
const _indices = new Uint16Array(numIndices);
296+
cmd = {
297+
positions: _positions,
298+
uvs: _uvs,
299+
colors: _colors,
300+
darkColors: _darkColors,
301+
indices: _indices,
302+
_positions,
303+
_uvs,
304+
_colors,
305+
_darkColors,
306+
_indices,
307+
numVertices,
308+
numIndices,
309+
blendMode: BlendMode.Normal,
310+
texture: null
311+
};
312+
} else {
313+
this.pool.splice(this.pool.indexOf(cmd), 1);
314+
cmd.next = undefined;
315+
cmd.numVertices = numVertices;
316+
cmd.numIndices = numIndices;
317+
318+
cmd.positions = cmd._positions.subarray(0, numVertices << 1);
319+
cmd.uvs = cmd._uvs.subarray(0, numVertices * 2);
320+
cmd.colors = cmd._colors.subarray(0, numVertices);
321+
cmd.darkColors = cmd._darkColors.subarray(0, numVertices);
322+
cmd.indices = cmd._indices.subarray(0, numIndices);
323+
}
324+
325+
this.inUse.push(cmd);
326+
return cmd;
327+
}
328+
329+
reset (): void {
330+
this.pool.push(...this.inUse);
331+
this.inUse.length = 0;
332+
}
333+
}

0 commit comments

Comments
 (0)