@doki-land/live2d-renderer 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -322,12 +322,112 @@ function fitClippingContexts(contexts, byIndex, margin = 0.05) {
322
322
  )
323
323
  }));
324
324
  }
325
- function maskLayoutVec4(layout) {
326
- return new Float32Array([layout.x, layout.y, layout.width, layout.height]);
325
+ function maskLayoutVec4(layout, into) {
326
+ const out = into && into.length >= 4 ? into : new Float32Array(4);
327
+ out[0] = layout.x;
328
+ out[1] = layout.y;
329
+ out[2] = layout.width;
330
+ out[3] = layout.height;
331
+ return out;
332
+ }
333
+ function maskChannelVec4(flag, into) {
334
+ const out = into && into.length >= 4 ? into : new Float32Array(4);
335
+ out[0] = flag[0];
336
+ out[1] = flag[1];
337
+ out[2] = flag[2];
338
+ out[3] = flag[3];
339
+ return out;
340
+ }
341
+
342
+ // src/render/clipping-plan.ts
343
+ function clippingTopologyKey(drawables) {
344
+ let key = `n:${drawables.length}`;
345
+ for (let i = 0; i < drawables.length; i++) {
346
+ const d = drawables[i];
347
+ key += `|${d.index}:${d.invertedMask ? 1 : 0}:`;
348
+ const masks = d.maskIndices;
349
+ for (let j = 0; j < masks.length; j++) {
350
+ key += `${masks[j]},`;
351
+ }
352
+ }
353
+ return key;
327
354
  }
328
- function maskChannelVec4(flag) {
329
- return new Float32Array([flag[0], flag[1], flag[2], flag[3]]);
355
+ function atlasOptionsKey(options) {
356
+ return `${options.mode ?? "rgba"}:${options.inset ?? ""}:${options.renderTextureCount ?? ""}`;
330
357
  }
358
+ function fitClippingContextsInPlace(contexts, byIndex, margin = 0.05) {
359
+ for (let i = 0; i < contexts.length; i++) {
360
+ const ctx = contexts[i];
361
+ ctx.modelBounds = calcClippedDrawableBounds(
362
+ ctx.clippedIndices,
363
+ byIndex,
364
+ margin
365
+ );
366
+ }
367
+ }
368
+ var ResidentClippingPlan = class {
369
+ #cacheKey = "";
370
+ #contexts = [];
371
+ #clipped = /* @__PURE__ */ new Set();
372
+ #maskOnly = /* @__PURE__ */ new Set();
373
+ #byIndex = /* @__PURE__ */ new Map();
374
+ /**
375
+ * Returns a partition whose `contexts` array identity is stable across
376
+ * frames with the same topology. `modelBounds` are refreshed in place.
377
+ */
378
+ resolve(drawables, options = {}) {
379
+ const nextKey = `${clippingTopologyKey(drawables)}|${atlasOptionsKey(options)}`;
380
+ if (nextKey !== this.#cacheKey) {
381
+ const partitioned = partitionForClipping(drawables, options);
382
+ this.#contexts = partitioned.contexts.map((ctx) => ({
383
+ key: ctx.key,
384
+ maskIndices: ctx.maskIndices,
385
+ clippedIndices: ctx.clippedIndices,
386
+ invertedMask: ctx.invertedMask,
387
+ layout: ctx.layout,
388
+ channelIndex: ctx.channelIndex,
389
+ channelFlag: ctx.channelFlag,
390
+ bufferIndex: ctx.bufferIndex,
391
+ modelBounds: { ...ctx.modelBounds }
392
+ }));
393
+ this.#clipped = new Set(partitioned.clipped);
394
+ this.#maskOnly = new Set(partitioned.maskOnly);
395
+ this.#cacheKey = nextKey;
396
+ }
397
+ this.#byIndex.clear();
398
+ for (let i = 0; i < drawables.length; i++) {
399
+ const d = drawables[i];
400
+ if ("vertexPositions" in d) {
401
+ this.#byIndex.set(d.index, d);
402
+ }
403
+ }
404
+ fitClippingContextsInPlace(this.#contexts, this.#byIndex);
405
+ return {
406
+ contexts: this.#contexts,
407
+ clipped: this.#clipped,
408
+ maskOnly: this.#maskOnly
409
+ };
410
+ }
411
+ /** Convenience for full drawable meshes (GPU backends). */
412
+ resolveMeshes(drawables, options = {}) {
413
+ return this.resolve(drawables, options);
414
+ }
415
+ /** True when the last {@link resolve} reused the cached atlas layout. */
416
+ get hasPlan() {
417
+ return this.#cacheKey.length > 0;
418
+ }
419
+ /** Exposed for tests — context array identity while topology holds. */
420
+ get residentContexts() {
421
+ return this.#contexts;
422
+ }
423
+ clear() {
424
+ this.#cacheKey = "";
425
+ this.#contexts = [];
426
+ this.#clipped = /* @__PURE__ */ new Set();
427
+ this.#maskOnly = /* @__PURE__ */ new Set();
428
+ this.#byIndex.clear();
429
+ }
430
+ };
331
431
 
332
432
  // src/render/coords.ts
333
433
  function modelYUpToCanvasPixelY(modelY, canvasHeight) {
@@ -443,6 +543,7 @@ var Canvas2DModelDrawPass = class {
443
543
  #ctx;
444
544
  #options;
445
545
  #textures = [];
546
+ #clipPlan = new ResidentClippingPlan();
446
547
  constructor(ctx, options) {
447
548
  this.#ctx = ctx;
448
549
  this.#options = options;
@@ -459,10 +560,10 @@ var Canvas2DModelDrawPass = class {
459
560
  const h = ctx.canvas.height;
460
561
  const byIndex = /* @__PURE__ */ new Map();
461
562
  for (const d of drawables) byIndex.set(d.index, d);
462
- const partitioned = partitionForClipping(drawables, {
563
+ const partitioned = this.#clipPlan.resolveMeshes(drawables, {
463
564
  mode: "uv-grid"
464
565
  });
465
- const contexts = fitClippingContexts(partitioned.contexts, byIndex);
566
+ const contexts = partitioned.contexts;
466
567
  const { maskOnly } = partitioned;
467
568
  if (contexts.length === 0) {
468
569
  for (const d of drawables) {
@@ -627,6 +728,7 @@ var Canvas2DModelDrawPass = class {
627
728
  }
628
729
  destroy() {
629
730
  this.#textures = [];
731
+ this.#clipPlan.clear();
630
732
  }
631
733
  };
632
734
  var Canvas2DRendererImpl = class {
@@ -683,6 +785,25 @@ function isCanvas2DAvailable() {
683
785
  return !!canvas.getContext("2d");
684
786
  }
685
787
 
788
+ // src/render/mesh-interleave.ts
789
+ function interleavePosUv(positions, uvs, into) {
790
+ const n = Math.floor(positions.length / 2);
791
+ const need = n * 4;
792
+ const out = into && into.length >= need ? into : new Float32Array(need);
793
+ for (let i = 0; i < n; i++) {
794
+ const o = i * 4;
795
+ const p = i * 2;
796
+ out[o] = positions[p];
797
+ out[o + 1] = positions[p + 1];
798
+ out[o + 2] = uvs[p] ?? 0;
799
+ out[o + 3] = uvs[p + 1] ?? 0;
800
+ }
801
+ return out;
802
+ }
803
+ function interleaveByteLength(positions) {
804
+ return Math.floor(positions.length / 2) * 16;
805
+ }
806
+
686
807
  // src/backends/webgl2.ts
687
808
  var VS = `#version 300 es
688
809
  layout(location = 0) in vec2 a_pos;
@@ -791,19 +912,6 @@ function linkProgram(gl, vsSource, fsSource) {
791
912
  }
792
913
  return program;
793
914
  }
794
- function interleavePosUv(positions, uvs) {
795
- const n = Math.floor(positions.length / 2);
796
- const out = new Float32Array(n * 4);
797
- for (let i = 0; i < n; i++) {
798
- const o = i * 4;
799
- const p = i * 2;
800
- out[o] = positions[p];
801
- out[o + 1] = positions[p + 1];
802
- out[o + 2] = uvs[p] ?? 0;
803
- out[o + 3] = uvs[p + 1] ?? 0;
804
- }
805
- return out;
806
- }
807
915
  function requireUniform(gl, program, name) {
808
916
  const loc = gl.getUniformLocation(program, name);
809
917
  if (!loc) {
@@ -841,6 +949,13 @@ var WebGl2ModelDrawPass = class {
841
949
  #maskTex = null;
842
950
  #maskW = 0;
843
951
  #maskH = 0;
952
+ #clipPlan = new ResidentClippingPlan();
953
+ #interleaved = new Float32Array(64);
954
+ #indicesRef = null;
955
+ #layoutScratch = new Float32Array(4);
956
+ #boundsScratch = new Float32Array(4);
957
+ #channelScratch = new Float32Array(4);
958
+ #defaultChannel = new Float32Array([0, 0, 0, 1]);
844
959
  constructor(gl) {
845
960
  this.#gl = gl;
846
961
  this.#program = linkProgram(gl, VS, FS);
@@ -1012,13 +1127,25 @@ var WebGl2ModelDrawPass = class {
1012
1127
  }
1013
1128
  gl.bindTexture(gl.TEXTURE_2D, null);
1014
1129
  }
1015
- #uploadMesh(d) {
1130
+ #uploadMesh(d, uploadIndices = true) {
1016
1131
  const gl = this.#gl;
1017
- const interleaved = interleavePosUv(d.vertexPositions, d.uvs);
1132
+ this.#interleaved = interleavePosUv(
1133
+ d.vertexPositions,
1134
+ d.uvs,
1135
+ this.#interleaved
1136
+ );
1137
+ const vBytes = interleaveByteLength(d.vertexPositions);
1018
1138
  gl.bindBuffer(gl.ARRAY_BUFFER, this.#vbo);
1019
- gl.bufferData(gl.ARRAY_BUFFER, interleaved, gl.DYNAMIC_DRAW);
1139
+ gl.bufferData(
1140
+ gl.ARRAY_BUFFER,
1141
+ this.#interleaved.subarray(0, vBytes / 4),
1142
+ gl.DYNAMIC_DRAW
1143
+ );
1020
1144
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.#ibo);
1021
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.indices, gl.DYNAMIC_DRAW);
1145
+ if (uploadIndices && this.#indicesRef !== d.indices) {
1146
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.indices, gl.STATIC_DRAW);
1147
+ this.#indicesRef = d.indices;
1148
+ }
1022
1149
  }
1023
1150
  #drawMeshColor(d, useMask, invertMask, layout, modelBounds, channelFlag) {
1024
1151
  const gl = this.#gl;
@@ -1041,19 +1168,23 @@ var WebGl2ModelDrawPass = class {
1041
1168
  );
1042
1169
  gl.uniform1f(this.#useMaskLoc, useMask ? 1 : 0);
1043
1170
  gl.uniform1f(this.#invertMaskLoc, invertMask ? 1 : 0);
1044
- const layoutVec = maskLayoutVec4(
1045
- layout ?? { x: 0, y: 0, width: 1, height: 1 }
1171
+ gl.uniform4fv(
1172
+ this.#maskLayoutLoc,
1173
+ maskLayoutVec4(
1174
+ layout ?? { x: 0, y: 0, width: 1, height: 1 },
1175
+ this.#layoutScratch
1176
+ )
1046
1177
  );
1047
- gl.uniform4fv(this.#maskLayoutLoc, layoutVec);
1048
1178
  gl.uniform4fv(
1049
1179
  this.#maskBoundsLoc,
1050
1180
  maskLayoutVec4(
1051
- modelBounds ?? { x: -1, y: -1, width: 2, height: 2 }
1181
+ modelBounds ?? { x: -1, y: -1, width: 2, height: 2 },
1182
+ this.#boundsScratch
1052
1183
  )
1053
1184
  );
1054
1185
  gl.uniform4fv(
1055
1186
  this.#channelFlagLoc,
1056
- channelFlag ?? new Float32Array([0, 0, 0, 1])
1187
+ channelFlag ?? this.#defaultChannel
1057
1188
  );
1058
1189
  if (useMask && this.#maskTex) {
1059
1190
  gl.activeTexture(gl.TEXTURE1);
@@ -1072,6 +1203,7 @@ var WebGl2ModelDrawPass = class {
1072
1203
  PREVIEW_STROKE.a * d.opacity
1073
1204
  );
1074
1205
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, lines, gl.DYNAMIC_DRAW);
1206
+ this.#indicesRef = null;
1075
1207
  gl.drawElements(gl.LINES, lines.length, gl.UNSIGNED_SHORT, 0);
1076
1208
  }
1077
1209
  }
@@ -1085,8 +1217,14 @@ var WebGl2ModelDrawPass = class {
1085
1217
  gl.uniform1i(this.#maskTexLoc, 0);
1086
1218
  gl.uniform1f(this.#maskUseTexLoc, gpuTex ? 1 : 0);
1087
1219
  gl.uniform1f(this.#maskOpacityLoc, Math.max(d.opacity, 1));
1088
- gl.uniform4fv(this.#maskWriteLayoutLoc, maskLayoutVec4(layout));
1089
- gl.uniform4fv(this.#maskWriteBoundsLoc, maskLayoutVec4(modelBounds));
1220
+ gl.uniform4fv(
1221
+ this.#maskWriteLayoutLoc,
1222
+ maskLayoutVec4(layout, this.#layoutScratch)
1223
+ );
1224
+ gl.uniform4fv(
1225
+ this.#maskWriteBoundsLoc,
1226
+ maskLayoutVec4(modelBounds, this.#boundsScratch)
1227
+ );
1090
1228
  gl.uniform4fv(this.#maskWriteChannelLoc, channelFlag);
1091
1229
  gl.drawElements(gl.TRIANGLES, d.indices.length, gl.UNSIGNED_SHORT, 0);
1092
1230
  }
@@ -1097,8 +1235,8 @@ var WebGl2ModelDrawPass = class {
1097
1235
  const height = canvas instanceof HTMLCanvasElement ? canvas.height : canvas.height;
1098
1236
  const byIndex = /* @__PURE__ */ new Map();
1099
1237
  for (const d of drawables) byIndex.set(d.index, d);
1100
- const partitioned = partitionForClipping(drawables);
1101
- const contexts = fitClippingContexts(partitioned.contexts, byIndex);
1238
+ const partitioned = this.#clipPlan.resolveMeshes(drawables);
1239
+ const contexts = partitioned.contexts;
1102
1240
  const { maskOnly } = partitioned;
1103
1241
  gl.bindVertexArray(this.#vao);
1104
1242
  if (contexts.length > 0) {
@@ -1113,7 +1251,10 @@ var WebGl2ModelDrawPass = class {
1113
1251
  gl.enable(gl.BLEND);
1114
1252
  gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
1115
1253
  for (const ctx of contexts) {
1116
- const flag = maskChannelVec4(ctx.channelFlag);
1254
+ const flag = maskChannelVec4(
1255
+ ctx.channelFlag,
1256
+ this.#channelScratch
1257
+ );
1117
1258
  for (const mi of ctx.maskIndices) {
1118
1259
  const maskMesh = byIndex.get(mi);
1119
1260
  if (maskMesh) {
@@ -1152,7 +1293,7 @@ var WebGl2ModelDrawPass = class {
1152
1293
  clip.invertedMask,
1153
1294
  clip.layout,
1154
1295
  clip.modelBounds,
1155
- maskChannelVec4(clip.channelFlag)
1296
+ maskChannelVec4(clip.channelFlag, this.#channelScratch)
1156
1297
  );
1157
1298
  } else {
1158
1299
  this.#drawMeshColor(d, false, false, null, null, null);
@@ -1167,6 +1308,8 @@ var WebGl2ModelDrawPass = class {
1167
1308
  destroy() {
1168
1309
  const gl = this.#gl;
1169
1310
  this.#clearGpuTextures();
1311
+ this.#clipPlan.clear();
1312
+ this.#indicesRef = null;
1170
1313
  if (this.#maskTex) gl.deleteTexture(this.#maskTex);
1171
1314
  if (this.#maskFbo) gl.deleteFramebuffer(this.#maskFbo);
1172
1315
  gl.deleteBuffer(this.#vbo);
@@ -1240,6 +1383,155 @@ function isWebGl2Available() {
1240
1383
  return !!gl;
1241
1384
  }
1242
1385
 
1386
+ // src/backends/webgpu-mesh-cache.ts
1387
+ function indexBufferByteSize(byteLength) {
1388
+ return byteLength + 3 & ~3;
1389
+ }
1390
+ function createOrGrowBuffer(device, prev, prevBytes, needBytes, usage) {
1391
+ const bytes = Math.max(4, needBytes);
1392
+ if (prev && prevBytes >= bytes) return { buffer: prev, bytes: prevBytes };
1393
+ prev?.destroy();
1394
+ return {
1395
+ buffer: device.createBuffer({
1396
+ size: bytes,
1397
+ usage
1398
+ }),
1399
+ bytes
1400
+ };
1401
+ }
1402
+ var WebGpuMeshCache = class {
1403
+ #slots = /* @__PURE__ */ new Map();
1404
+ #maskGeneration = 0;
1405
+ bumpMaskGeneration() {
1406
+ this.#maskGeneration += 1;
1407
+ }
1408
+ get maskGeneration() {
1409
+ return this.#maskGeneration;
1410
+ }
1411
+ clear() {
1412
+ for (const slot of this.#slots.values()) {
1413
+ slot.vbo.destroy();
1414
+ slot.ibo.destroy();
1415
+ slot.ubo.destroy();
1416
+ }
1417
+ this.#slots.clear();
1418
+ }
1419
+ /** Drop bind groups that reference model textures (after setTextures). */
1420
+ invalidateTextureBindGroups() {
1421
+ for (const slot of this.#slots.values()) {
1422
+ slot.bgMask = null;
1423
+ slot.bgTextured = null;
1424
+ slot.bgClippedTextured = null;
1425
+ slot.bgSolid = null;
1426
+ slot.bgClippedSolid = null;
1427
+ slot.texIndex = -1;
1428
+ }
1429
+ }
1430
+ ensureSlot(device, drawableIndex) {
1431
+ let slot = this.#slots.get(drawableIndex);
1432
+ if (slot) return slot;
1433
+ const vbo = device.createBuffer({
1434
+ size: 64,
1435
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
1436
+ });
1437
+ const ibo = device.createBuffer({
1438
+ size: 4,
1439
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
1440
+ });
1441
+ const ubo = device.createBuffer({
1442
+ size: 80,
1443
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1444
+ });
1445
+ slot = {
1446
+ index: drawableIndex,
1447
+ vbo,
1448
+ vboBytes: 64,
1449
+ interleaved: new Float32Array(16),
1450
+ ibo,
1451
+ iboBytes: 4,
1452
+ indicesRef: null,
1453
+ uvsRef: null,
1454
+ ubo,
1455
+ uboFloats: new Float32Array(20),
1456
+ bgMask: null,
1457
+ bgTextured: null,
1458
+ bgClippedTextured: null,
1459
+ bgSolid: null,
1460
+ bgClippedSolid: null,
1461
+ texIndex: -1,
1462
+ useWhite: false,
1463
+ maskGeneration: -1
1464
+ };
1465
+ this.#slots.set(drawableIndex, slot);
1466
+ return slot;
1467
+ }
1468
+ /**
1469
+ * Upload interleaved pos/uv when positions change every frame; refresh
1470
+ * index buffer only when the `indices` TypedArray identity changes.
1471
+ */
1472
+ uploadMesh(device, slot, positions, uvs, indices) {
1473
+ slot.interleaved = interleavePosUv(positions, uvs, slot.interleaved);
1474
+ const vBytes = interleaveByteLength(positions);
1475
+ const grownV = createOrGrowBuffer(
1476
+ device,
1477
+ slot.vbo,
1478
+ slot.vboBytes,
1479
+ vBytes,
1480
+ GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
1481
+ );
1482
+ slot.vbo = grownV.buffer;
1483
+ slot.vboBytes = grownV.bytes;
1484
+ device.queue.writeBuffer(
1485
+ slot.vbo,
1486
+ 0,
1487
+ slot.interleaved.buffer,
1488
+ slot.interleaved.byteOffset,
1489
+ vBytes
1490
+ );
1491
+ const iBytes = indexBufferByteSize(indices.byteLength);
1492
+ const grownI = createOrGrowBuffer(
1493
+ device,
1494
+ slot.ibo,
1495
+ slot.iboBytes,
1496
+ iBytes,
1497
+ GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
1498
+ );
1499
+ if (grownI.buffer !== slot.ibo) {
1500
+ slot.indicesRef = null;
1501
+ }
1502
+ slot.ibo = grownI.buffer;
1503
+ slot.iboBytes = grownI.bytes;
1504
+ if (slot.indicesRef !== indices || slot.uvsRef !== uvs) {
1505
+ if (iBytes === indices.byteLength) {
1506
+ device.queue.writeBuffer(
1507
+ slot.ibo,
1508
+ 0,
1509
+ indices.buffer,
1510
+ indices.byteOffset,
1511
+ indices.byteLength
1512
+ );
1513
+ } else {
1514
+ const padded = new Uint16Array(iBytes / 2);
1515
+ padded.set(indices);
1516
+ device.queue.writeBuffer(slot.ibo, 0, padded);
1517
+ }
1518
+ slot.indicesRef = indices;
1519
+ slot.uvsRef = uvs;
1520
+ }
1521
+ }
1522
+ writeUbo(device, slot, floats) {
1523
+ const n = floats.length;
1524
+ for (let i = 0; i < n; i++) slot.uboFloats[i] = floats[i];
1525
+ device.queue.writeBuffer(
1526
+ slot.ubo,
1527
+ 0,
1528
+ slot.uboFloats.buffer,
1529
+ slot.uboFloats.byteOffset,
1530
+ Math.max(16, n * 4 + 15 & ~15)
1531
+ );
1532
+ }
1533
+ };
1534
+
1243
1535
  // src/backends/webgpu.ts
1244
1536
  var SOLID_WGSL = (
1245
1537
  /* wgsl */
@@ -1432,33 +1724,6 @@ var BLEND_MODES = [
1432
1724
  BlendMode.Additive,
1433
1725
  BlendMode.Multiplicative
1434
1726
  ];
1435
- function indexBufferSize(byteLength) {
1436
- return byteLength + 3 & ~3;
1437
- }
1438
- function writeIndexBuffer(device, indices) {
1439
- const size = indexBufferSize(indices.byteLength);
1440
- const ibo = device.createBuffer({
1441
- size: Math.max(4, size),
1442
- usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
1443
- mappedAtCreation: true
1444
- });
1445
- new Uint16Array(ibo.getMappedRange()).set(indices);
1446
- ibo.unmap();
1447
- return ibo;
1448
- }
1449
- function interleavePosUv2(positions, uvs) {
1450
- const n = Math.floor(positions.length / 2);
1451
- const out = new Float32Array(n * 4);
1452
- for (let i = 0; i < n; i++) {
1453
- const o = i * 4;
1454
- const p = i * 2;
1455
- out[o] = positions[p];
1456
- out[o + 1] = positions[p + 1];
1457
- out[o + 2] = uvs[p] ?? 0;
1458
- out[o + 3] = uvs[p + 1] ?? 0;
1459
- }
1460
- return out;
1461
- }
1462
1727
  function createWhiteTexture(device) {
1463
1728
  const tex = device.createTexture({
1464
1729
  size: [1, 1],
@@ -1514,8 +1779,17 @@ var WebGpuRendererImpl = class {
1514
1779
  #maskW = 0;
1515
1780
  #maskH = 0;
1516
1781
  #whiteTexture = null;
1517
- #transient = [];
1782
+ #whiteView = null;
1783
+ #maskView = null;
1518
1784
  #gpuTextures = [];
1785
+ #gpuTextureViews = [];
1786
+ #meshCache = new WebGpuMeshCache();
1787
+ #clipPlan = new ResidentClippingPlan();
1788
+ #layoutScratch = new Float32Array(4);
1789
+ #channelScratch = new Float32Array(4);
1790
+ #boundsScratch = new Float32Array(4);
1791
+ /** Preview-only line index buffers destroyed at endFrame. */
1792
+ #transient = [];
1519
1793
  #options;
1520
1794
  constructor(options = {}) {
1521
1795
  this.#options = options;
@@ -1885,6 +2159,7 @@ var WebGpuRendererImpl = class {
1885
2159
  addressModeV: "clamp-to-edge"
1886
2160
  });
1887
2161
  this.#whiteTexture = createWhiteTexture(device);
2162
+ this.#whiteView = this.#whiteTexture.createView();
1888
2163
  this.#ensureMsaa();
1889
2164
  }
1890
2165
  #ensureMsaa() {
@@ -1923,11 +2198,15 @@ var WebGpuRendererImpl = class {
1923
2198
  });
1924
2199
  this.#maskW = w;
1925
2200
  this.#maskH = h;
2201
+ this.#maskView = this.#maskTexture.createView();
2202
+ this.#meshCache.bumpMaskGeneration();
1926
2203
  return this.#maskTexture;
1927
2204
  }
1928
2205
  #clearGpuTextures() {
1929
2206
  for (const t of this.#gpuTextures) t?.destroy();
1930
2207
  this.#gpuTextures = [];
2208
+ this.#gpuTextureViews = [];
2209
+ this.#meshCache.invalidateTextureBindGroups();
1931
2210
  }
1932
2211
  setTextures(textures) {
1933
2212
  const device = this.#device;
@@ -1936,6 +2215,7 @@ var WebGpuRendererImpl = class {
1936
2215
  let maxIndex = -1;
1937
2216
  for (const t of textures) maxIndex = Math.max(maxIndex, t.index);
1938
2217
  this.#gpuTextures = new Array(Math.max(0, maxIndex + 1)).fill(null);
2218
+ this.#gpuTextureViews = new Array(Math.max(0, maxIndex + 1)).fill(null);
1939
2219
  for (const t of textures) {
1940
2220
  const gpuTex = device.createTexture({
1941
2221
  size: [t.width, t.height],
@@ -1948,6 +2228,7 @@ var WebGpuRendererImpl = class {
1948
2228
  [t.width, t.height]
1949
2229
  );
1950
2230
  this.#gpuTextures[t.index] = gpuTex;
2231
+ this.#gpuTextureViews[t.index] = gpuTex.createView();
1951
2232
  }
1952
2233
  }
1953
2234
  createModelDrawPass() {
@@ -2018,8 +2299,8 @@ var WebGpuRendererImpl = class {
2018
2299
  }
2019
2300
  const byIndex = /* @__PURE__ */ new Map();
2020
2301
  for (const d of drawables) byIndex.set(d.index, d);
2021
- const partitioned = partitionForClipping(drawables);
2022
- const contexts = fitClippingContexts(partitioned.contexts, byIndex);
2302
+ const partitioned = this.#clipPlan.resolveMeshes(drawables);
2303
+ const contexts = partitioned.contexts;
2023
2304
  const { maskOnly } = partitioned;
2024
2305
  let maskTex = null;
2025
2306
  if (contexts.length > 0) {
@@ -2090,27 +2371,23 @@ var WebGpuRendererImpl = class {
2090
2371
  }
2091
2372
  #drawMaskMesh(d, pass, layout, sampler, whiteTex, atlasLayout, modelBounds, channelFlag) {
2092
2373
  const device = this.#device;
2093
- if (!device || d.opacity <= 0) return;
2094
- const interleaved = interleavePosUv2(d.vertexPositions, d.uvs);
2095
- const vbo = device.createBuffer({
2096
- size: interleaved.byteLength,
2097
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
2098
- mappedAtCreation: true
2099
- });
2100
- new Float32Array(vbo.getMappedRange()).set(interleaved);
2101
- vbo.unmap();
2102
- const gpuTex = this.#gpuTextures[d.textureIndex] ?? whiteTex;
2103
- const ubo = device.createBuffer({
2104
- size: 64,
2105
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
2106
- mappedAtCreation: true
2107
- });
2108
- const layoutVec = maskLayoutVec4(atlasLayout);
2109
- const ch = maskChannelVec4(channelFlag);
2110
- const boundsVec = maskLayoutVec4(modelBounds);
2111
- new Float32Array(ubo.getMappedRange()).set([
2374
+ const whiteView = this.#whiteView;
2375
+ if (!device || !whiteView || d.opacity <= 0) return;
2376
+ const slot = this.#meshCache.ensureSlot(device, d.index);
2377
+ this.#meshCache.uploadMesh(
2378
+ device,
2379
+ slot,
2380
+ d.vertexPositions,
2381
+ d.uvs,
2382
+ d.indices
2383
+ );
2384
+ const layoutVec = maskLayoutVec4(atlasLayout, this.#layoutScratch);
2385
+ const ch = maskChannelVec4(channelFlag, this.#channelScratch);
2386
+ const boundsVec = maskLayoutVec4(modelBounds, this.#boundsScratch);
2387
+ const useWhite = !this.#gpuTextures[d.textureIndex];
2388
+ this.#meshCache.writeUbo(device, slot, [
2112
2389
  Math.max(d.opacity, 1),
2113
- this.#gpuTextures[d.textureIndex] ? 1 : 0,
2390
+ useWhite ? 0 : 1,
2114
2391
  0,
2115
2392
  0,
2116
2393
  layoutVec[0],
@@ -2126,23 +2403,24 @@ var WebGpuRendererImpl = class {
2126
2403
  boundsVec[2],
2127
2404
  boundsVec[3]
2128
2405
  ]);
2129
- ubo.unmap();
2130
- const ibo = writeIndexBuffer(device, d.indices);
2131
- pass.setBindGroup(
2132
- 0,
2133
- device.createBindGroup({
2406
+ const texView = this.#gpuTextureViews[d.textureIndex] ?? whiteView;
2407
+ if (!slot.bgMask || slot.texIndex !== d.textureIndex || slot.useWhite !== useWhite) {
2408
+ slot.bgMask = device.createBindGroup({
2134
2409
  layout,
2135
2410
  entries: [
2136
- { binding: 0, resource: { buffer: ubo } },
2411
+ { binding: 0, resource: { buffer: slot.ubo } },
2137
2412
  { binding: 1, resource: sampler },
2138
- { binding: 2, resource: gpuTex.createView() }
2413
+ { binding: 2, resource: texView }
2139
2414
  ]
2140
- })
2141
- );
2142
- pass.setVertexBuffer(0, vbo);
2143
- pass.setIndexBuffer(ibo, "uint16");
2415
+ });
2416
+ slot.texIndex = d.textureIndex;
2417
+ slot.useWhite = useWhite;
2418
+ }
2419
+ pass.setBindGroup(0, slot.bgMask);
2420
+ pass.setVertexBuffer(0, slot.vbo);
2421
+ pass.setIndexBuffer(slot.ibo, "uint16");
2144
2422
  pass.drawIndexed(d.indices.length);
2145
- this.#transient.push(vbo, ubo, ibo);
2423
+ void whiteTex;
2146
2424
  }
2147
2425
  #drawColorMesh(d, pass, useMask, invertMask, maskTex, atlasLayout, channelFlag, modelBounds) {
2148
2426
  const device = this.#device;
@@ -2151,6 +2429,7 @@ var WebGpuRendererImpl = class {
2151
2429
  const texturedLayout = this.#texturedBindGroupLayout;
2152
2430
  const clippedSolidLayout = this.#clippedSolidBindGroupLayout;
2153
2431
  const clippedTexturedLayout = this.#clippedTexturedBindGroupLayout;
2432
+ const maskView = this.#maskView;
2154
2433
  if (!device || !sampler || !solidLayout || !texturedLayout || !clippedSolidLayout || !clippedTexturedLayout) {
2155
2434
  return;
2156
2435
  }
@@ -2158,30 +2437,30 @@ var WebGpuRendererImpl = class {
2158
2437
  const set = this.#pipelines[d.blendMode] ?? this.#pipelines[BlendMode.Normal];
2159
2438
  if (!set) return;
2160
2439
  const layoutVec = maskLayoutVec4(
2161
- atlasLayout ?? { x: 0, y: 0, width: 1, height: 1 }
2440
+ atlasLayout ?? { x: 0, y: 0, width: 1, height: 1 },
2441
+ this.#layoutScratch
2442
+ );
2443
+ const ch = maskChannelVec4(
2444
+ channelFlag ?? [0, 0, 0, 1],
2445
+ this.#channelScratch
2162
2446
  );
2163
- const ch = maskChannelVec4(channelFlag ?? [0, 0, 0, 1]);
2164
2447
  const boundsVec = maskLayoutVec4(
2165
- modelBounds ?? { x: -1, y: -1, width: 2, height: 2 }
2448
+ modelBounds ?? { x: -1, y: -1, width: 2, height: 2 },
2449
+ this.#boundsScratch
2166
2450
  );
2451
+ const slot = this.#meshCache.ensureSlot(device, d.index);
2167
2452
  const gpuTex = this.#gpuTextures[d.textureIndex] ?? null;
2168
- if (gpuTex) {
2169
- const interleaved = interleavePosUv2(d.vertexPositions, d.uvs);
2170
- const vbo2 = device.createBuffer({
2171
- size: interleaved.byteLength,
2172
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
2173
- mappedAtCreation: true
2174
- });
2175
- new Float32Array(vbo2.getMappedRange()).set(interleaved);
2176
- vbo2.unmap();
2177
- const uboSize = useMask && maskTex ? 64 : 16;
2178
- const ubo = device.createBuffer({
2179
- size: uboSize,
2180
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
2181
- mappedAtCreation: true
2182
- });
2183
- if (useMask && maskTex) {
2184
- new Float32Array(ubo.getMappedRange()).set([
2453
+ const texView = this.#gpuTextureViews[d.textureIndex] ?? null;
2454
+ if (gpuTex && texView) {
2455
+ this.#meshCache.uploadMesh(
2456
+ device,
2457
+ slot,
2458
+ d.vertexPositions,
2459
+ d.uvs,
2460
+ d.indices
2461
+ );
2462
+ if (useMask && maskTex && maskView) {
2463
+ this.#meshCache.writeUbo(device, slot, [
2185
2464
  d.opacity,
2186
2465
  invertMask ? 1 : 0,
2187
2466
  0,
@@ -2199,64 +2478,51 @@ var WebGpuRendererImpl = class {
2199
2478
  boundsVec[2],
2200
2479
  boundsVec[3]
2201
2480
  ]);
2202
- } else {
2203
- new Float32Array(ubo.getMappedRange()).set([
2204
- d.opacity,
2205
- 0,
2206
- 0,
2207
- 0
2208
- ]);
2209
- }
2210
- ubo.unmap();
2211
- const ibo = writeIndexBuffer(device, d.indices);
2212
- if (useMask && maskTex) {
2213
- pass.setPipeline(set.clippedTextured);
2214
- pass.setBindGroup(
2215
- 0,
2216
- device.createBindGroup({
2481
+ if (!slot.bgClippedTextured || slot.texIndex !== d.textureIndex || slot.maskGeneration !== this.#meshCache.maskGeneration) {
2482
+ slot.bgClippedTextured = device.createBindGroup({
2217
2483
  layout: clippedTexturedLayout,
2218
2484
  entries: [
2219
- { binding: 0, resource: { buffer: ubo } },
2485
+ { binding: 0, resource: { buffer: slot.ubo } },
2220
2486
  { binding: 1, resource: sampler },
2221
- { binding: 2, resource: gpuTex.createView() },
2222
- { binding: 3, resource: maskTex.createView() }
2487
+ { binding: 2, resource: texView },
2488
+ { binding: 3, resource: maskView }
2223
2489
  ]
2224
- })
2225
- );
2490
+ });
2491
+ slot.texIndex = d.textureIndex;
2492
+ slot.maskGeneration = this.#meshCache.maskGeneration;
2493
+ }
2494
+ pass.setPipeline(set.clippedTextured);
2495
+ pass.setBindGroup(0, slot.bgClippedTextured);
2226
2496
  } else {
2227
- pass.setPipeline(set.textured);
2228
- pass.setBindGroup(
2229
- 0,
2230
- device.createBindGroup({
2497
+ this.#meshCache.writeUbo(device, slot, [d.opacity, 0, 0, 0]);
2498
+ if (!slot.bgTextured || slot.texIndex !== d.textureIndex) {
2499
+ slot.bgTextured = device.createBindGroup({
2231
2500
  layout: texturedLayout,
2232
2501
  entries: [
2233
- { binding: 0, resource: { buffer: ubo } },
2502
+ { binding: 0, resource: { buffer: slot.ubo } },
2234
2503
  { binding: 1, resource: sampler },
2235
- { binding: 2, resource: gpuTex.createView() }
2504
+ { binding: 2, resource: texView }
2236
2505
  ]
2237
- })
2238
- );
2506
+ });
2507
+ slot.texIndex = d.textureIndex;
2508
+ }
2509
+ pass.setPipeline(set.textured);
2510
+ pass.setBindGroup(0, slot.bgTextured);
2239
2511
  }
2240
- pass.setVertexBuffer(0, vbo2);
2241
- pass.setIndexBuffer(ibo, "uint16");
2512
+ pass.setVertexBuffer(0, slot.vbo);
2513
+ pass.setIndexBuffer(slot.ibo, "uint16");
2242
2514
  pass.drawIndexed(d.indices.length);
2243
- this.#transient.push(vbo2, ubo, ibo);
2244
2515
  return;
2245
2516
  }
2246
- const vbo = device.createBuffer({
2247
- size: d.vertexPositions.byteLength,
2248
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
2249
- mappedAtCreation: true
2250
- });
2251
- new Float32Array(vbo.getMappedRange()).set(d.vertexPositions);
2252
- vbo.unmap();
2253
- if (useMask && maskTex) {
2254
- const ubo = device.createBuffer({
2255
- size: 80,
2256
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
2257
- mappedAtCreation: true
2258
- });
2259
- new Float32Array(ubo.getMappedRange()).set([
2517
+ this.#meshCache.uploadMesh(
2518
+ device,
2519
+ slot,
2520
+ d.vertexPositions,
2521
+ d.uvs,
2522
+ d.indices
2523
+ );
2524
+ if (useMask && maskTex && maskView) {
2525
+ this.#meshCache.writeUbo(device, slot, [
2260
2526
  PREVIEW_FILL.r,
2261
2527
  PREVIEW_FILL.g,
2262
2528
  PREVIEW_FILL.b,
@@ -2278,54 +2544,61 @@ var WebGpuRendererImpl = class {
2278
2544
  boundsVec[2],
2279
2545
  boundsVec[3]
2280
2546
  ]);
2281
- ubo.unmap();
2282
- const ibo = writeIndexBuffer(device, d.indices);
2283
- pass.setPipeline(set.clippedFill);
2284
- pass.setBindGroup(
2285
- 0,
2286
- device.createBindGroup({
2547
+ if (!slot.bgClippedSolid || slot.maskGeneration !== this.#meshCache.maskGeneration) {
2548
+ slot.bgClippedSolid = device.createBindGroup({
2287
2549
  layout: clippedSolidLayout,
2288
2550
  entries: [
2289
- { binding: 0, resource: { buffer: ubo } },
2551
+ { binding: 0, resource: { buffer: slot.ubo } },
2290
2552
  { binding: 1, resource: sampler },
2291
- { binding: 2, resource: maskTex.createView() }
2553
+ { binding: 2, resource: maskView }
2292
2554
  ]
2293
- })
2294
- );
2295
- pass.setVertexBuffer(0, vbo);
2296
- pass.setIndexBuffer(ibo, "uint16");
2555
+ });
2556
+ slot.maskGeneration = this.#meshCache.maskGeneration;
2557
+ }
2558
+ pass.setPipeline(set.clippedFill);
2559
+ pass.setBindGroup(0, slot.bgClippedSolid);
2560
+ pass.setVertexBuffer(0, slot.vbo);
2561
+ pass.setIndexBuffer(slot.ibo, "uint16");
2297
2562
  pass.drawIndexed(d.indices.length);
2298
- this.#transient.push(vbo, ubo, ibo);
2299
2563
  return;
2300
2564
  }
2301
- const fillUbo = this.#makeColorUbo(
2302
- device,
2565
+ this.#meshCache.writeUbo(device, slot, [
2303
2566
  PREVIEW_FILL.r,
2304
2567
  PREVIEW_FILL.g,
2305
2568
  PREVIEW_FILL.b,
2306
2569
  PREVIEW_FILL.a * d.opacity
2307
- );
2308
- const fillIbo = writeIndexBuffer(device, d.indices);
2309
- pass.setPipeline(set.fill);
2310
- pass.setBindGroup(
2311
- 0,
2312
- device.createBindGroup({
2570
+ ]);
2571
+ if (!slot.bgSolid) {
2572
+ slot.bgSolid = device.createBindGroup({
2313
2573
  layout: solidLayout,
2314
- entries: [{ binding: 0, resource: { buffer: fillUbo } }]
2315
- })
2316
- );
2317
- pass.setVertexBuffer(0, vbo);
2318
- pass.setIndexBuffer(fillIbo, "uint16");
2574
+ entries: [{ binding: 0, resource: { buffer: slot.ubo } }]
2575
+ });
2576
+ }
2577
+ pass.setPipeline(set.fill);
2578
+ pass.setBindGroup(0, slot.bgSolid);
2579
+ pass.setVertexBuffer(0, slot.vbo);
2580
+ pass.setIndexBuffer(slot.ibo, "uint16");
2319
2581
  pass.drawIndexed(d.indices.length);
2320
2582
  const lines = triangleEdgesToLineList(d.indices);
2321
- const lineUbo = this.#makeColorUbo(
2322
- device,
2583
+ const lineUbo = device.createBuffer({
2584
+ size: 16,
2585
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
2586
+ mappedAtCreation: true
2587
+ });
2588
+ new Float32Array(lineUbo.getMappedRange()).set([
2323
2589
  PREVIEW_STROKE.r,
2324
2590
  PREVIEW_STROKE.g,
2325
2591
  PREVIEW_STROKE.b,
2326
2592
  PREVIEW_STROKE.a * d.opacity
2327
- );
2328
- const lineIbo = writeIndexBuffer(device, lines);
2593
+ ]);
2594
+ lineUbo.unmap();
2595
+ const lineIbo = device.createBuffer({
2596
+ size: Math.max(4, lines.byteLength + 3 & ~3),
2597
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
2598
+ mappedAtCreation: true
2599
+ });
2600
+ new Uint16Array(lineIbo.getMappedRange()).set(lines);
2601
+ lineIbo.unmap();
2329
2602
  pass.setPipeline(set.line);
2330
2603
  pass.setBindGroup(
2331
2604
  0,
@@ -2334,20 +2607,10 @@ var WebGpuRendererImpl = class {
2334
2607
  entries: [{ binding: 0, resource: { buffer: lineUbo } }]
2335
2608
  })
2336
2609
  );
2337
- pass.setVertexBuffer(0, vbo);
2610
+ pass.setVertexBuffer(0, slot.vbo);
2338
2611
  pass.setIndexBuffer(lineIbo, "uint16");
2339
2612
  pass.drawIndexed(lines.length);
2340
- this.#transient.push(vbo, fillUbo, fillIbo, lineUbo, lineIbo);
2341
- }
2342
- #makeColorUbo(device, r, g, b, a) {
2343
- const ubo = device.createBuffer({
2344
- size: 16,
2345
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
2346
- mappedAtCreation: true
2347
- });
2348
- new Float32Array(ubo.getMappedRange()).set([r, g, b, a]);
2349
- ubo.unmap();
2350
- return ubo;
2613
+ this.#transient.push(lineUbo, lineIbo);
2351
2614
  }
2352
2615
  endFrame() {
2353
2616
  const device = this.#device;
@@ -2376,13 +2639,17 @@ var WebGpuRendererImpl = class {
2376
2639
  destroy() {
2377
2640
  for (const b of this.#transient) b.destroy();
2378
2641
  this.#transient = [];
2642
+ this.#meshCache.clear();
2643
+ this.#clipPlan.clear();
2379
2644
  this.#clearGpuTextures();
2380
2645
  this.#msaaTexture?.destroy();
2381
2646
  this.#msaaTexture = null;
2382
2647
  this.#maskTexture?.destroy();
2383
2648
  this.#maskTexture = null;
2649
+ this.#maskView = null;
2384
2650
  this.#whiteTexture?.destroy();
2385
2651
  this.#whiteTexture = null;
2652
+ this.#whiteView = null;
2386
2653
  this.#device?.destroy();
2387
2654
  this.#device = null;
2388
2655
  this.#context = null;
@@ -2551,10 +2818,21 @@ function createModelInstance(program) {
2551
2818
  timeSeconds: 0
2552
2819
  };
2553
2820
  }
2821
+ var paramIndexCache = /* @__PURE__ */ new WeakMap();
2822
+ function parameterIndex(instance, parameterId) {
2823
+ let map = paramIndexCache.get(instance.program);
2824
+ if (!map) {
2825
+ map = /* @__PURE__ */ new Map();
2826
+ for (let i = 0; i < instance.program.parameters.length; i++) {
2827
+ map.set(instance.program.parameters[i].id, i);
2828
+ }
2829
+ paramIndexCache.set(instance.program, map);
2830
+ }
2831
+ const index = map.get(parameterId);
2832
+ return index ?? -1;
2833
+ }
2554
2834
  function setParameterValue(instance, parameterId, value) {
2555
- const index = instance.program.parameters.findIndex(
2556
- (p2) => p2.id === parameterId
2557
- );
2835
+ const index = parameterIndex(instance, parameterId);
2558
2836
  if (index < 0) {
2559
2837
  throw new Error(
2560
2838
  `@doki-land/live2d-renderer: unknown parameter "${parameterId}"`
@@ -2604,6 +2882,32 @@ function evaluateFrame(instance) {
2604
2882
  drawables
2605
2883
  };
2606
2884
  }
2885
+ function evaluateFrameInto(instance, meshes, poseOpacity) {
2886
+ const byIndex = /* @__PURE__ */ new Map();
2887
+ for (const m of meshes) byIndex.set(m.index, m);
2888
+ for (const d of instance.program.drawables) {
2889
+ const sink = byIndex.get(d.index);
2890
+ if (!sink) continue;
2891
+ let positions = sink.vertexPositions;
2892
+ if (positions.length !== d.positions.length) {
2893
+ positions = new Float32Array(d.positions.length);
2894
+ sink.vertexPositions = positions;
2895
+ }
2896
+ positions.set(d.positions);
2897
+ if (d.deformParamIndex >= 0 && d.deformDeltas) {
2898
+ const w = paramWeight(instance, d.deformParamIndex);
2899
+ for (let i = 0; i < positions.length; i++) {
2900
+ positions[i] = positions[i] + w * d.deformDeltas[i];
2901
+ }
2902
+ }
2903
+ sink.opacity = d.opacity;
2904
+ sink.renderOrder = d.renderOrder;
2905
+ sink.visible = d.visible;
2906
+ if (d.index >= 0 && d.index < poseOpacity.length) {
2907
+ poseOpacity[d.index] = d.opacity;
2908
+ }
2909
+ }
2910
+ }
2607
2911
  function fingerprintSnapshot(snapshot) {
2608
2912
  const parts = [`t=${snapshot.timeSeconds.toFixed(4)}`];
2609
2913
  for (const d of snapshot.drawables) {
@@ -3611,16 +3915,81 @@ function moc2ParamGetterFromValues(params, values) {
3611
3915
  }
3612
3916
  return (id) => byId.get(id) ?? 0;
3613
3917
  }
3614
- function normalizePositions(positions, canvasWidth, canvasHeight) {
3918
+ function normalizePositionsInto(positions, canvasWidth, canvasHeight, out) {
3615
3919
  const w = canvasWidth > 0 ? canvasWidth : 1;
3616
3920
  const h = canvasHeight > 0 ? canvasHeight : 1;
3617
- const out = new Float32Array(positions.length);
3618
3921
  for (let i = 0; i + 1 < positions.length; i += 2) {
3619
3922
  out[i] = positions[i] / w * 2 - 1;
3620
3923
  out[i + 1] = 1 - positions[i + 1] / h * 2;
3621
3924
  }
3925
+ }
3926
+ function normalizePositions(positions, canvasWidth, canvasHeight) {
3927
+ const out = new Float32Array(positions.length);
3928
+ normalizePositionsInto(positions, canvasWidth, canvasHeight, out);
3622
3929
  return out;
3623
3930
  }
3931
+ function evaluateMoc2PoseInto(model, getParam, byId) {
3932
+ const ops = bakeDeformerOps(model, getParam);
3933
+ const w = model.canvasWidth;
3934
+ const h = model.canvasHeight;
3935
+ for (const part of model.parts) {
3936
+ for (const mesh of part.drawData) {
3937
+ const sink = byId.get(mesh.id);
3938
+ if (!sink) continue;
3939
+ const floatCount = mesh.numPoints * 2;
3940
+ const local = interpolateKeyforms(
3941
+ mesh.keyforms,
3942
+ mesh.pivotManager,
3943
+ getParam,
3944
+ floatCount
3945
+ );
3946
+ const world = transformDrawablePositions(mesh, local, ops);
3947
+ let pos = sink.vertexPositions;
3948
+ if (pos.length !== world.length) {
3949
+ pos = new Float32Array(world.length);
3950
+ sink.vertexPositions = pos;
3951
+ }
3952
+ normalizePositionsInto(world, w, h, pos);
3953
+ sink.opacity = interpolateScalarTable(
3954
+ mesh.opacities,
3955
+ mesh.pivotManager,
3956
+ getParam,
3957
+ 1
3958
+ );
3959
+ sink.renderOrder = Math.round(
3960
+ interpolateScalarTable(
3961
+ mesh.drawOrders,
3962
+ mesh.pivotManager,
3963
+ getParam,
3964
+ mesh.averageDrawOrder
3965
+ )
3966
+ );
3967
+ sink.visible = part.visible;
3968
+ }
3969
+ }
3970
+ }
3971
+ function moc2DrawableIdsInProgramOrder(model, getParam) {
3972
+ const paramDefs = model.paramDefSet?.params ?? [];
3973
+ const gp = getParam ?? buildParamGetter(paramDefs);
3974
+ const drafts = [];
3975
+ for (const part of model.parts) {
3976
+ for (const mesh of part.drawData) {
3977
+ drafts.push({
3978
+ id: mesh.id,
3979
+ renderOrder: Math.round(
3980
+ interpolateScalarTable(
3981
+ mesh.drawOrders,
3982
+ mesh.pivotManager,
3983
+ gp,
3984
+ mesh.averageDrawOrder
3985
+ )
3986
+ )
3987
+ });
3988
+ }
3989
+ }
3990
+ drafts.sort((a, b) => a.renderOrder - b.renderOrder);
3991
+ return drafts.map((d) => d.id);
3992
+ }
3624
3993
  function lowerDrawable(mesh, getParam, ops, canvasWidth, canvasHeight, partVisible) {
3625
3994
  const floatCount = mesh.numPoints * 2;
3626
3995
  const local = interpolateKeyforms(
@@ -4851,15 +5220,24 @@ function meanGlueSeamDistance(positionsByMesh, glue) {
4851
5220
  }
4852
5221
 
4853
5222
  // src/moc/moc3-to-program.ts
4854
- function normalizePositions2(positions, canvasWidth, canvasHeight, pixelsPerUnit) {
5223
+ function normalizePositionsInto2(positions, canvasWidth, canvasHeight, pixelsPerUnit, out) {
4855
5224
  const ppu = pixelsPerUnit > 0 ? pixelsPerUnit : 1;
4856
5225
  const hw = canvasWidth > 0 ? canvasWidth / ppu * 0.5 : 0.5;
4857
5226
  const hh = canvasHeight > 0 ? canvasHeight / ppu * 0.5 : 0.5;
4858
- const out = new Float32Array(positions.length);
4859
5227
  for (let i = 0; i + 1 < positions.length; i += 2) {
4860
5228
  out[i] = positions[i] / hw;
4861
5229
  out[i + 1] = -positions[i + 1] / hh;
4862
5230
  }
5231
+ }
5232
+ function normalizePositions2(positions, canvasWidth, canvasHeight, pixelsPerUnit) {
5233
+ const out = new Float32Array(positions.length);
5234
+ normalizePositionsInto2(
5235
+ positions,
5236
+ canvasWidth,
5237
+ canvasHeight,
5238
+ pixelsPerUnit,
5239
+ out
5240
+ );
4863
5241
  return out;
4864
5242
  }
4865
5243
  function moc3DocumentToProgram(doc, options = {}) {
@@ -5045,6 +5423,155 @@ function moc3DocumentToProgram(doc, options = {}) {
5045
5423
  drawables
5046
5424
  };
5047
5425
  }
5426
+ function moc3ArtMeshIndicesInProgramOrder(doc, getParamByIndex) {
5427
+ const meshCount = doc.counts[CountIdx.ART_MESHES] ?? 0;
5428
+ const defaultValues = moc3SectionF32(doc, "parameter.default_values");
5429
+ const gp = getParamByIndex ?? ((index) => defaultValues[index] ?? 0);
5430
+ const keyTables = loadMoc3KeyTables(doc);
5431
+ const enables = moc3SectionI32(doc, "art_mesh.enables");
5432
+ const vertexCounts = moc3SectionI32(doc, "art_mesh.vertex_counts");
5433
+ const indexCounts = moc3SectionI32(doc, "art_mesh.position_index_counts");
5434
+ const keyformBegins = moc3SectionI32(doc, "art_mesh.keyform_begin_indices");
5435
+ const keyformCounts = moc3SectionI32(doc, "art_mesh.keyform_counts");
5436
+ const bandIndices = moc3SectionI32(
5437
+ doc,
5438
+ "art_mesh.keyform_binding_band_indices"
5439
+ );
5440
+ const keyformDrawOrders = moc3SectionF32(
5441
+ doc,
5442
+ "art_mesh_keyform.draw_orders"
5443
+ );
5444
+ const drafts = [];
5445
+ for (let i = 0; i < meshCount; i++) {
5446
+ if ((enables[i] ?? 1) === 0) continue;
5447
+ const vertexCount = vertexCounts[i] ?? 0;
5448
+ const indexCount = indexCounts[i] ?? 0;
5449
+ if (vertexCount <= 0 || indexCount < 3) continue;
5450
+ const kfBegin = keyformBegins[i] ?? 0;
5451
+ const kfCount = keyformCounts[i] ?? 0;
5452
+ if (kfCount <= 0) continue;
5453
+ const band = bandIndices[i] ?? -1;
5454
+ const blend = resolveMoc3KeyformBlend(keyTables, band, gp);
5455
+ drafts.push({
5456
+ artMeshIndex: i,
5457
+ renderOrder: Math.round(
5458
+ blendKeyformScalar(
5459
+ keyformDrawOrders,
5460
+ kfBegin,
5461
+ kfCount,
5462
+ blend,
5463
+ i
5464
+ )
5465
+ )
5466
+ });
5467
+ }
5468
+ drafts.sort((a, b) => a.renderOrder - b.renderOrder);
5469
+ return drafts.map((d) => d.artMeshIndex);
5470
+ }
5471
+ function evaluateMoc3PoseInto(doc, getParamByIndex, byArtMesh, poseOpacity) {
5472
+ const meshCount = doc.counts[CountIdx.ART_MESHES] ?? 0;
5473
+ const keyTables = loadMoc3KeyTables(doc);
5474
+ const deformers = bakeMoc3Deformers(doc, keyTables, getParamByIndex);
5475
+ const glues = loadMoc3Glues(doc);
5476
+ const glueIntensities = moc3SectionF32(doc, "glue_keyform.intensities");
5477
+ const visibles = moc3SectionI32(doc, "art_mesh.visibles");
5478
+ const enables = moc3SectionI32(doc, "art_mesh.enables");
5479
+ const vertexCounts = moc3SectionI32(doc, "art_mesh.vertex_counts");
5480
+ const indexCounts = moc3SectionI32(doc, "art_mesh.position_index_counts");
5481
+ const keyformBegins = moc3SectionI32(doc, "art_mesh.keyform_begin_indices");
5482
+ const keyformCounts = moc3SectionI32(doc, "art_mesh.keyform_counts");
5483
+ const bandIndices = moc3SectionI32(
5484
+ doc,
5485
+ "art_mesh.keyform_binding_band_indices"
5486
+ );
5487
+ const parentDeformers = moc3SectionI32(
5488
+ doc,
5489
+ "art_mesh.parent_deformer_indices"
5490
+ );
5491
+ const keyformOpacities = moc3SectionF32(doc, "art_mesh_keyform.opacities");
5492
+ const keyformDrawOrders = moc3SectionF32(
5493
+ doc,
5494
+ "art_mesh_keyform.draw_orders"
5495
+ );
5496
+ const keyformPosBegins = moc3SectionI32(
5497
+ doc,
5498
+ "art_mesh_keyform.keyform_position_begin_indices"
5499
+ );
5500
+ const keyformPositions = moc3SectionF32(doc, "keyform_position.xys");
5501
+ const cw = doc.canvas.canvasWidth;
5502
+ const ch = doc.canvas.canvasHeight;
5503
+ const ppu = doc.canvas.pixelsPerUnit;
5504
+ const worldByMesh = /* @__PURE__ */ new Map();
5505
+ const opacityByMesh = /* @__PURE__ */ new Map();
5506
+ const orderByMesh = /* @__PURE__ */ new Map();
5507
+ const visibleByMesh = /* @__PURE__ */ new Map();
5508
+ for (let i = 0; i < meshCount; i++) {
5509
+ if (!byArtMesh.has(i)) continue;
5510
+ if ((enables[i] ?? 1) === 0) continue;
5511
+ const vertexCount = vertexCounts[i] ?? 0;
5512
+ const indexCount = indexCounts[i] ?? 0;
5513
+ if (vertexCount <= 0 || indexCount < 3) continue;
5514
+ const kfBegin = keyformBegins[i] ?? 0;
5515
+ const kfCount = keyformCounts[i] ?? 0;
5516
+ if (kfCount <= 0) continue;
5517
+ const band = bandIndices[i] ?? -1;
5518
+ const blend = resolveMoc3KeyformBlend(keyTables, band, getParamByIndex);
5519
+ const local = blendKeyformFloats(
5520
+ keyformPositions,
5521
+ keyformPosBegins,
5522
+ kfBegin,
5523
+ kfCount,
5524
+ vertexCount * 2,
5525
+ blend
5526
+ );
5527
+ const parentIndex = parentDeformers[i] ?? -1;
5528
+ const parent = parentIndex >= 0 ? deformers[parentIndex] ?? null : null;
5529
+ const world = parent ? applyParentToPoints(local, parent) : local;
5530
+ worldByMesh.set(i, world);
5531
+ opacityByMesh.set(
5532
+ i,
5533
+ blendKeyformScalar(keyformOpacities, kfBegin, kfCount, blend, 1)
5534
+ );
5535
+ orderByMesh.set(
5536
+ i,
5537
+ Math.round(
5538
+ blendKeyformScalar(
5539
+ keyformDrawOrders,
5540
+ kfBegin,
5541
+ kfCount,
5542
+ blend,
5543
+ i
5544
+ )
5545
+ )
5546
+ );
5547
+ visibleByMesh.set(i, (visibles[i] ?? 1) !== 0);
5548
+ }
5549
+ applyMoc3Glues(
5550
+ worldByMesh,
5551
+ glues,
5552
+ keyTables,
5553
+ getParamByIndex,
5554
+ glueIntensities
5555
+ );
5556
+ for (const [artMeshIndex, world] of worldByMesh) {
5557
+ const sink = byArtMesh.get(artMeshIndex);
5558
+ if (!sink) continue;
5559
+ let pos = sink.vertexPositions;
5560
+ if (pos.length !== world.length) {
5561
+ pos = new Float32Array(world.length);
5562
+ sink.vertexPositions = pos;
5563
+ }
5564
+ normalizePositionsInto2(world, cw, ch, ppu, pos);
5565
+ const opacity = opacityByMesh.get(artMeshIndex) ?? 1;
5566
+ sink.renderOrder = orderByMesh.get(artMeshIndex) ?? sink.renderOrder;
5567
+ sink.visible = visibleByMesh.get(artMeshIndex) ?? true;
5568
+ const slot = sink.index;
5569
+ if (slot >= 0 && slot < poseOpacity.length) {
5570
+ poseOpacity[slot] = opacity;
5571
+ }
5572
+ sink.opacity = opacity;
5573
+ }
5574
+ }
5048
5575
 
5049
5576
  // src/moc/decode.ts
5050
5577
  function readMagic2(bytes) {
@@ -5095,49 +5622,90 @@ async function decodeMoc3(bytes) {
5095
5622
 
5096
5623
  // src/moc/moc2.ts
5097
5624
  import { detectModelSettingsFormat as detectModelSettingsFormat2 } from "@doki-land/live2d-core";
5098
- function toDrawableMesh(d) {
5099
- return {
5100
- index: d.index,
5101
- textureIndex: d.textureIndex,
5102
- vertexPositions: d.positions,
5103
- uvs: d.uvs,
5104
- indices: d.indices,
5105
- opacity: d.opacity,
5106
- blendMode: d.blendMode,
5107
- invertedMask: d.invertedMask,
5108
- renderOrder: d.renderOrder,
5109
- dynamicFlag: true,
5110
- maskIndices: [...d.maskIndices],
5111
- visible: d.visible
5112
- };
5625
+ var stateByModel = /* @__PURE__ */ new WeakMap();
5626
+ function allocateMeshesFromProgram(program, drawableIds) {
5627
+ const meshes = [];
5628
+ const byId = /* @__PURE__ */ new Map();
5629
+ for (let i = 0; i < program.drawables.length; i++) {
5630
+ const d = program.drawables[i];
5631
+ const mesh = {
5632
+ index: d.index,
5633
+ textureIndex: d.textureIndex,
5634
+ vertexPositions: new Float32Array(d.positions),
5635
+ uvs: d.uvs,
5636
+ indices: d.indices,
5637
+ opacity: d.opacity,
5638
+ blendMode: d.blendMode,
5639
+ invertedMask: d.invertedMask,
5640
+ renderOrder: d.renderOrder,
5641
+ dynamicFlag: true,
5642
+ maskIndices: [...d.maskIndices],
5643
+ visible: d.visible
5644
+ };
5645
+ meshes.push(mesh);
5646
+ const id = drawableIds[i];
5647
+ if (id) byId.set(id, mesh);
5648
+ }
5649
+ return { meshes, byId };
5650
+ }
5651
+ function buildBindings(instance) {
5652
+ const bindings = [];
5653
+ const paramIndexById = /* @__PURE__ */ new Map();
5654
+ for (let i = 0; i < instance.program.parameters.length; i++) {
5655
+ const p = instance.program.parameters[i];
5656
+ const binding = {
5657
+ id: p.id,
5658
+ min: p.min,
5659
+ max: p.max,
5660
+ defaultValue: p.defaultValue,
5661
+ value: instance.parameterValues[i] ?? p.defaultValue
5662
+ };
5663
+ bindings.push(binding);
5664
+ paramIndexById.set(p.id, i);
5665
+ }
5666
+ return { bindings, paramIndexById };
5113
5667
  }
5114
- function paramFingerprint(values) {
5115
- let s = "";
5116
- for (let i = 0; i < values.length; i++) {
5117
- s += `${values[i]?.toFixed(5)},`;
5668
+ function syncBindingValues(state) {
5669
+ for (let i = 0; i < state.bindings.length; i++) {
5670
+ const b = state.bindings[i];
5671
+ b.value = state.instance.parameterValues[i] ?? b.defaultValue;
5118
5672
  }
5119
- return s;
5120
5673
  }
5121
- var stateByModel = /* @__PURE__ */ new WeakMap();
5674
+ function refreshDrawView(state) {
5675
+ const view = state.drawView;
5676
+ view.length = 0;
5677
+ for (const m of state.meshes) view.push(m);
5678
+ view.sort((a, b) => a.renderOrder - b.renderOrder || a.index - b.index);
5679
+ }
5122
5680
  function bakePose(state) {
5123
- const values = Float32Array.from(state.instance.parameterValues);
5124
- const timeSeconds = state.instance.timeSeconds;
5125
- const fp = paramFingerprint(values);
5126
- if (fp === state.bakedFingerprint && state.lastFrame) {
5127
- return;
5681
+ if (!state.poseDirty) return;
5682
+ const getParam = moc2ParamGetterFromValues(
5683
+ state.instance.program.parameters,
5684
+ state.instance.parameterValues
5685
+ );
5686
+ evaluateMoc2PoseInto(state.moc, getParam, state.byId);
5687
+ refreshDrawView(state);
5688
+ syncBindingValues(state);
5689
+ state.poseDirty = false;
5690
+ }
5691
+ function frameFromMeshes(meshes, timeSeconds) {
5692
+ const drawables = [];
5693
+ for (const m of meshes) {
5694
+ drawables.push({
5695
+ index: m.index,
5696
+ textureIndex: m.textureIndex,
5697
+ positions: m.vertexPositions,
5698
+ uvs: m.uvs,
5699
+ indices: m.indices,
5700
+ opacity: m.opacity,
5701
+ blendMode: m.blendMode,
5702
+ renderOrder: m.renderOrder,
5703
+ visible: m.visible,
5704
+ invertedMask: m.invertedMask,
5705
+ maskIndices: m.maskIndices
5706
+ });
5128
5707
  }
5129
- const program = moc2ModelToProgram(state.moc, {
5130
- getParam: moc2ParamGetterFromValues(
5131
- state.moc.paramDefSet.params,
5132
- values
5133
- )
5134
- });
5135
- const next = createModelInstance(program);
5136
- next.parameterValues.set(values);
5137
- next.timeSeconds = timeSeconds;
5138
- state.instance = next;
5139
- state.bakedFingerprint = fp;
5140
- state.lastFrame = evaluateFrame(next);
5708
+ return { timeSeconds, drawables };
5141
5709
  }
5142
5710
  var Moc2Backend = class {
5143
5711
  format = "moc2";
@@ -5158,17 +5726,30 @@ var Moc2Backend = class {
5158
5726
  const moc = shared?.moc2Model ?? new Moc2Parser(bytes).parseModel();
5159
5727
  const program = moc2ModelToProgram(moc);
5160
5728
  const instance = createModelInstance(program);
5729
+ const drawableIds = moc2DrawableIdsInProgramOrder(moc);
5730
+ const { meshes, byId } = allocateMeshesFromProgram(
5731
+ program,
5732
+ drawableIds
5733
+ );
5734
+ const { bindings, paramIndexById } = buildBindings(instance);
5735
+ const drawView = [];
5161
5736
  const model = {
5162
5737
  id: settings.name ?? settings.url,
5163
5738
  settings,
5164
5739
  format: "moc2"
5165
5740
  };
5166
- stateByModel.set(model, {
5741
+ const state = {
5167
5742
  moc,
5168
5743
  instance,
5169
- lastFrame: null,
5170
- bakedFingerprint: paramFingerprint(instance.parameterValues)
5171
- });
5744
+ meshes,
5745
+ drawView,
5746
+ byId,
5747
+ poseDirty: true,
5748
+ bindings,
5749
+ paramIndexById
5750
+ };
5751
+ bakePose(state);
5752
+ stateByModel.set(model, state);
5172
5753
  return model;
5173
5754
  }
5174
5755
  updateModel(model, deltaTimeSeconds) {
@@ -5176,46 +5757,43 @@ var Moc2Backend = class {
5176
5757
  if (!state) return;
5177
5758
  state.instance.timeSeconds += deltaTimeSeconds;
5178
5759
  bakePose(state);
5179
- if (state.lastFrame) {
5180
- state.lastFrame = {
5181
- ...state.lastFrame,
5182
- timeSeconds: state.instance.timeSeconds
5183
- };
5184
- }
5185
5760
  }
5186
5761
  getDrawables(model) {
5187
5762
  const state = stateByModel.get(model);
5188
5763
  if (!state) return [];
5189
5764
  bakePose(state);
5190
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5191
- state.lastFrame = frame;
5192
- return frame.drawables.map(toDrawableMesh);
5765
+ return state.drawView;
5193
5766
  }
5194
5767
  captureFrame(model) {
5195
5768
  const state = stateByModel.get(model);
5196
5769
  if (!state) return null;
5197
5770
  bakePose(state);
5198
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5199
- state.lastFrame = frame;
5200
- return frame;
5771
+ return frameFromMeshes(state.drawView, state.instance.timeSeconds);
5201
5772
  }
5202
5773
  setParameter(model, id, value) {
5203
5774
  const state = stateByModel.get(model);
5204
5775
  if (!state) return;
5205
5776
  setParameterValue(state.instance, id, value);
5206
- state.lastFrame = null;
5207
- state.bakedFingerprint = "";
5777
+ const index = state.paramIndexById.get(id);
5778
+ if (index !== void 0) {
5779
+ const binding = state.bindings[index];
5780
+ if (binding) {
5781
+ binding.value = state.instance.parameterValues[index] ?? binding.defaultValue;
5782
+ }
5783
+ }
5784
+ state.poseDirty = true;
5785
+ }
5786
+ resolveParameter(model, id) {
5787
+ const state = stateByModel.get(model);
5788
+ if (!state) return void 0;
5789
+ return state.paramIndexById.get(id);
5208
5790
  }
5209
5791
  listParameters(model) {
5210
5792
  const state = stateByModel.get(model);
5211
5793
  if (!state) return [];
5212
- return state.instance.program.parameters.map((p, i) => ({
5213
- id: p.id,
5214
- min: p.min,
5215
- max: p.max,
5216
- defaultValue: p.defaultValue,
5217
- value: state.instance.parameterValues[i] ?? p.defaultValue
5218
- }));
5794
+ bakePose(state);
5795
+ syncBindingValues(state);
5796
+ return state.bindings;
5219
5797
  }
5220
5798
  hitTest(_model, _x, _y) {
5221
5799
  return null;
@@ -5223,6 +5801,10 @@ var Moc2Backend = class {
5223
5801
  destroyModel(model) {
5224
5802
  stateByModel.delete(model);
5225
5803
  }
5804
+ /** Test/harness: stable program + instance refs. */
5805
+ getResidentInstance(model) {
5806
+ return stateByModel.get(model)?.instance ?? null;
5807
+ }
5226
5808
  };
5227
5809
  function createMoc2Backend() {
5228
5810
  return new Moc2Backend();
@@ -5261,11 +5843,12 @@ function cascadedPartOpacity(tables, artMeshIndex, overrides) {
5261
5843
  }
5262
5844
 
5263
5845
  // src/moc/moc3.ts
5264
- function toDrawableMesh2(d) {
5265
- return {
5846
+ var stateByModel2 = /* @__PURE__ */ new WeakMap();
5847
+ function allocateMeshesFromProgram2(program) {
5848
+ return program.drawables.map((d) => ({
5266
5849
  index: d.index,
5267
5850
  textureIndex: d.textureIndex,
5268
- vertexPositions: d.positions,
5851
+ vertexPositions: new Float32Array(d.positions),
5269
5852
  uvs: d.uvs,
5270
5853
  indices: d.indices,
5271
5854
  opacity: d.opacity,
@@ -5275,34 +5858,82 @@ function toDrawableMesh2(d) {
5275
5858
  dynamicFlag: true,
5276
5859
  maskIndices: [...d.maskIndices],
5277
5860
  visible: d.visible
5278
- };
5861
+ }));
5862
+ }
5863
+ function buildBindings2(instance) {
5864
+ const bindings = [];
5865
+ const paramIndexById = /* @__PURE__ */ new Map();
5866
+ for (let i = 0; i < instance.program.parameters.length; i++) {
5867
+ const p = instance.program.parameters[i];
5868
+ bindings.push({
5869
+ id: p.id,
5870
+ min: p.min,
5871
+ max: p.max,
5872
+ defaultValue: p.defaultValue,
5873
+ value: instance.parameterValues[i] ?? p.defaultValue
5874
+ });
5875
+ paramIndexById.set(p.id, i);
5876
+ }
5877
+ return { bindings, paramIndexById };
5279
5878
  }
5280
- function paramFingerprint2(values) {
5281
- let s = "";
5282
- for (let i = 0; i < values.length; i++) {
5283
- s += `${values[i]?.toFixed(5)},`;
5879
+ function syncBindingValues2(state) {
5880
+ for (let i = 0; i < state.bindings.length; i++) {
5881
+ const b = state.bindings[i];
5882
+ b.value = state.instance.parameterValues[i] ?? b.defaultValue;
5883
+ }
5884
+ }
5885
+ function refreshDrawView2(state) {
5886
+ const view = state.drawView;
5887
+ view.length = 0;
5888
+ for (const m of state.meshes) view.push(m);
5889
+ view.sort((a, b) => a.renderOrder - b.renderOrder || a.index - b.index);
5890
+ }
5891
+ function applyPartOpacity(state) {
5892
+ const tables = state.doc ? readMoc3PartTables(state.doc) : null;
5893
+ for (const mesh of state.meshes) {
5894
+ const base = state.poseOpacity[mesh.index] ?? mesh.opacity;
5895
+ if (!tables || state.partOpacity.size === 0) {
5896
+ mesh.opacity = base;
5897
+ continue;
5898
+ }
5899
+ mesh.opacity = base * cascadedPartOpacity(tables, mesh.index, state.partOpacity);
5284
5900
  }
5285
- return s;
5286
5901
  }
5287
- var stateByModel2 = /* @__PURE__ */ new WeakMap();
5288
5902
  function bakePose2(state) {
5289
- if (!state.doc) {
5290
- state.lastFrame = evaluateFrame(state.instance);
5291
- return;
5903
+ if (!state.poseDirty) return;
5904
+ if (state.doc) {
5905
+ const values = state.instance.parameterValues;
5906
+ evaluateMoc3PoseInto(
5907
+ state.doc,
5908
+ (i) => values[i] ?? 0,
5909
+ state.byArtMesh,
5910
+ state.poseOpacity
5911
+ );
5912
+ } else {
5913
+ evaluateFrameInto(state.instance, state.meshes, state.poseOpacity);
5292
5914
  }
5293
- const values = Float32Array.from(state.instance.parameterValues);
5294
- const timeSeconds = state.instance.timeSeconds;
5295
- const fp = paramFingerprint2(values);
5296
- if (fp === state.bakedFingerprint && state.lastFrame) return;
5297
- const program = moc3DocumentToProgram(state.doc, {
5298
- getParamByIndex: (i) => values[i] ?? 0
5299
- });
5300
- const next = createModelInstance(program);
5301
- next.parameterValues.set(values);
5302
- next.timeSeconds = timeSeconds;
5303
- state.instance = next;
5304
- state.bakedFingerprint = fp;
5305
- state.lastFrame = evaluateFrame(next);
5915
+ refreshDrawView2(state);
5916
+ syncBindingValues2(state);
5917
+ state.poseDirty = false;
5918
+ }
5919
+ function frameFromMeshes2(meshes, timeSeconds) {
5920
+ const drawables = [];
5921
+ for (const m of meshes) {
5922
+ drawables.push({
5923
+ index: m.index,
5924
+ textureIndex: m.textureIndex,
5925
+ positions: m.vertexPositions,
5926
+ uvs: m.uvs,
5927
+ indices: m.indices,
5928
+ opacity: m.opacity,
5929
+ blendMode: m.blendMode,
5930
+ renderOrder: m.renderOrder,
5931
+ visible: m.visible,
5932
+ invertedMask: m.invertedMask,
5933
+ maskIndices: m.maskIndices
5934
+ });
5935
+ }
5936
+ return { timeSeconds, drawables };
5306
5937
  }
5307
5938
  var Moc3Backend = class {
5308
5939
  format = "moc3";
@@ -5330,18 +5961,39 @@ var Moc3Backend = class {
5330
5961
  program = moc3DocumentToProgram(doc);
5331
5962
  }
5332
5963
  const instance = createModelInstance(program);
5964
+ const meshes = allocateMeshesFromProgram2(program);
5965
+ const byArtMesh = /* @__PURE__ */ new Map();
5966
+ if (doc) {
5967
+ const artOrder = moc3ArtMeshIndicesInProgramOrder(doc);
5968
+ for (let i = 0; i < artOrder.length; i++) {
5969
+ const art = artOrder[i];
5970
+ const mesh = meshes[i];
5971
+ if (mesh) byArtMesh.set(art, mesh);
5972
+ }
5973
+ }
5974
+ const poseOpacity = new Float32Array(meshes.length);
5975
+ for (const m of meshes) poseOpacity[m.index] = m.opacity;
5976
+ const { bindings, paramIndexById } = buildBindings2(instance);
5333
5977
  const model = {
5334
5978
  id: settings.name ?? settings.url,
5335
5979
  settings,
5336
5980
  format: "moc3"
5337
5981
  };
5338
- stateByModel2.set(model, {
5982
+ const state = {
5339
5983
  doc,
5340
5984
  instance,
5341
- lastFrame: null,
5342
- bakedFingerprint: paramFingerprint2(instance.parameterValues),
5343
- partOpacity: /* @__PURE__ */ new Map()
5344
- });
5985
+ meshes,
5986
+ drawView: [],
5987
+ byArtMesh,
5988
+ poseOpacity,
5989
+ poseDirty: true,
5990
+ partOpacity: /* @__PURE__ */ new Map(),
5991
+ bindings,
5992
+ paramIndexById
5993
+ };
5994
+ bakePose2(state);
5995
+ applyPartOpacity(state);
5996
+ stateByModel2.set(model, state);
5345
5997
  return model;
5346
5998
  }
5347
5999
  updateModel(model, deltaTimeSeconds) {
@@ -5349,49 +6001,34 @@ var Moc3Backend = class {
5349
6001
  if (!state) return;
5350
6002
  state.instance.timeSeconds += deltaTimeSeconds;
5351
6003
  bakePose2(state);
5352
- if (state.lastFrame) {
5353
- state.lastFrame = {
5354
- ...state.lastFrame,
5355
- timeSeconds: state.instance.timeSeconds
5356
- };
5357
- }
6004
+ applyPartOpacity(state);
5358
6005
  }
5359
6006
  getDrawables(model) {
5360
6007
  const state = stateByModel2.get(model);
5361
6008
  if (!state) return [];
5362
6009
  bakePose2(state);
5363
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5364
- state.lastFrame = frame;
5365
- const tables = state.doc ? readMoc3PartTables(state.doc) : null;
5366
- return frame.drawables.map((d) => {
5367
- const mesh = toDrawableMesh2(d);
5368
- if (!tables || state.partOpacity.size === 0) return mesh;
5369
- const mul = cascadedPartOpacity(tables, d.index, state.partOpacity);
5370
- return { ...mesh, opacity: mesh.opacity * mul };
5371
- });
6010
+ applyPartOpacity(state);
6011
+ return state.drawView;
5372
6012
  }
5373
6013
  captureFrame(model) {
5374
6014
  const state = stateByModel2.get(model);
5375
6015
  if (!state) return null;
5376
6016
  bakePose2(state);
5377
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5378
- state.lastFrame = frame;
5379
- const tables = state.doc ? readMoc3PartTables(state.doc) : null;
5380
- if (!tables || state.partOpacity.size === 0) return frame;
5381
- return {
5382
- ...frame,
5383
- drawables: frame.drawables.map((d) => ({
5384
- ...d,
5385
- opacity: d.opacity * cascadedPartOpacity(tables, d.index, state.partOpacity)
5386
- }))
5387
- };
6017
+ applyPartOpacity(state);
6018
+ return frameFromMeshes2(state.drawView, state.instance.timeSeconds);
5388
6019
  }
5389
6020
  setParameter(model, id, value) {
5390
6021
  const state = stateByModel2.get(model);
5391
6022
  if (!state) return;
5392
6023
  setParameterValue(state.instance, id, value);
5393
- state.lastFrame = null;
5394
- state.bakedFingerprint = "";
6024
+ const index = state.paramIndexById.get(id);
6025
+ if (index !== void 0) {
6026
+ const binding = state.bindings[index];
6027
+ if (binding) {
6028
+ binding.value = state.instance.parameterValues[index] ?? binding.defaultValue;
6029
+ }
6030
+ }
6031
+ state.poseDirty = true;
5395
6032
  }
5396
6033
  setPartOpacity(model, id, value) {
5397
6034
  const state = stateByModel2.get(model);
@@ -5399,16 +6036,17 @@ var Moc3Backend = class {
5399
6036
  const v = Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 1;
5400
6037
  state.partOpacity.set(id, v);
5401
6038
  }
6039
+ resolveParameter(model, id) {
6040
+ const state = stateByModel2.get(model);
6041
+ if (!state) return void 0;
6042
+ return state.paramIndexById.get(id);
6043
+ }
5402
6044
  listParameters(model) {
5403
6045
  const state = stateByModel2.get(model);
5404
6046
  if (!state) return [];
5405
- return state.instance.program.parameters.map((p, i) => ({
5406
- id: p.id,
5407
- min: p.min,
5408
- max: p.max,
5409
- defaultValue: p.defaultValue,
5410
- value: state.instance.parameterValues[i] ?? p.defaultValue
5411
- }));
6047
+ bakePose2(state);
6048
+ syncBindingValues2(state);
6049
+ return state.bindings;
5412
6050
  }
5413
6051
  hitTest(_model, _x, _y) {
5414
6052
  return null;
@@ -5416,6 +6054,9 @@ var Moc3Backend = class {
5416
6054
  destroyModel(model) {
5417
6055
  stateByModel2.delete(model);
5418
6056
  }
6057
+ getResidentInstance(model) {
6058
+ return stateByModel2.get(model)?.instance ?? null;
6059
+ }
5419
6060
  };
5420
6061
  function createMoc3Backend() {
5421
6062
  return new Moc3Backend();
@@ -5553,6 +6194,7 @@ export {
5553
6194
  Moc3DrawableFlag,
5554
6195
  PREVIEW_FILL,
5555
6196
  PREVIEW_STROKE,
6197
+ ResidentClippingPlan,
5556
6198
  WebGl2RendererImpl,
5557
6199
  WebGpuRendererImpl,
5558
6200
  applyMoc3Glues,
@@ -5561,6 +6203,7 @@ export {
5561
6203
  calcClippedDrawableBounds,
5562
6204
  calcVertexBounds,
5563
6205
  canvasCompositeForBlendMode,
6206
+ clippingTopologyKey,
5564
6207
  compileSharedModelCompile,
5565
6208
  createCanvas2DRenderer,
5566
6209
  createMoc2Backend,
@@ -5580,6 +6223,9 @@ export {
5580
6223
  expandBounds,
5581
6224
  fingerprintSnapshot,
5582
6225
  fitClippingContexts,
6226
+ fitClippingContextsInPlace,
6227
+ interleaveByteLength,
6228
+ interleavePosUv,
5583
6229
  isCanvas2DAvailable,
5584
6230
  isCpuProgramBytes,
5585
6231
  isWebGl2Available,