@doki-land/live2d-renderer 0.0.17 → 0.0.19

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
@@ -2551,10 +2551,21 @@ function createModelInstance(program) {
2551
2551
  timeSeconds: 0
2552
2552
  };
2553
2553
  }
2554
+ var paramIndexCache = /* @__PURE__ */ new WeakMap();
2555
+ function parameterIndex(instance, parameterId) {
2556
+ let map = paramIndexCache.get(instance.program);
2557
+ if (!map) {
2558
+ map = /* @__PURE__ */ new Map();
2559
+ for (let i = 0; i < instance.program.parameters.length; i++) {
2560
+ map.set(instance.program.parameters[i].id, i);
2561
+ }
2562
+ paramIndexCache.set(instance.program, map);
2563
+ }
2564
+ const index = map.get(parameterId);
2565
+ return index ?? -1;
2566
+ }
2554
2567
  function setParameterValue(instance, parameterId, value) {
2555
- const index = instance.program.parameters.findIndex(
2556
- (p2) => p2.id === parameterId
2557
- );
2568
+ const index = parameterIndex(instance, parameterId);
2558
2569
  if (index < 0) {
2559
2570
  throw new Error(
2560
2571
  `@doki-land/live2d-renderer: unknown parameter "${parameterId}"`
@@ -2604,6 +2615,32 @@ function evaluateFrame(instance) {
2604
2615
  drawables
2605
2616
  };
2606
2617
  }
2618
+ function evaluateFrameInto(instance, meshes, poseOpacity) {
2619
+ const byIndex = /* @__PURE__ */ new Map();
2620
+ for (const m of meshes) byIndex.set(m.index, m);
2621
+ for (const d of instance.program.drawables) {
2622
+ const sink = byIndex.get(d.index);
2623
+ if (!sink) continue;
2624
+ let positions = sink.vertexPositions;
2625
+ if (positions.length !== d.positions.length) {
2626
+ positions = new Float32Array(d.positions.length);
2627
+ sink.vertexPositions = positions;
2628
+ }
2629
+ positions.set(d.positions);
2630
+ if (d.deformParamIndex >= 0 && d.deformDeltas) {
2631
+ const w = paramWeight(instance, d.deformParamIndex);
2632
+ for (let i = 0; i < positions.length; i++) {
2633
+ positions[i] = positions[i] + w * d.deformDeltas[i];
2634
+ }
2635
+ }
2636
+ sink.opacity = d.opacity;
2637
+ sink.renderOrder = d.renderOrder;
2638
+ sink.visible = d.visible;
2639
+ if (d.index >= 0 && d.index < poseOpacity.length) {
2640
+ poseOpacity[d.index] = d.opacity;
2641
+ }
2642
+ }
2643
+ }
2607
2644
  function fingerprintSnapshot(snapshot) {
2608
2645
  const parts = [`t=${snapshot.timeSeconds.toFixed(4)}`];
2609
2646
  for (const d of snapshot.drawables) {
@@ -2620,97 +2657,6 @@ function fingerprintSnapshot(snapshot) {
2620
2657
  return parts.join("|");
2621
2658
  }
2622
2659
 
2623
- // src/runtime/create-renderer.ts
2624
- var DEFAULT_PREFER = ["webgpu", "webgl2", "canvas2d"];
2625
- var INIT_TIMEOUT_MS = {
2626
- webgpu: 2500,
2627
- webgl2: 1500,
2628
- canvas2d: 1e3
2629
- };
2630
- function instantiate(kind, options) {
2631
- if (kind === "webgpu") return createWebGpuRenderer(options.webgpu);
2632
- if (kind === "webgl2") return createWebGl2Renderer(options.webgl2);
2633
- return createCanvas2DRenderer(options.canvas2d);
2634
- }
2635
- async function initializeWithTimeout(kind, candidate, canvas) {
2636
- const ms = INIT_TIMEOUT_MS[kind];
2637
- let timer;
2638
- try {
2639
- await Promise.race([
2640
- candidate.initialize(canvas),
2641
- new Promise((_, reject) => {
2642
- timer = setTimeout(() => {
2643
- reject(
2644
- new Error(
2645
- `@doki-land/live2d-renderer: ${kind} initialize timed out after ${ms}ms`
2646
- )
2647
- );
2648
- }, ms);
2649
- })
2650
- ]);
2651
- } finally {
2652
- if (timer !== void 0) clearTimeout(timer);
2653
- }
2654
- }
2655
- var FallbackRenderer = class {
2656
- #inner = null;
2657
- #kind = "webgpu";
2658
- #prefer;
2659
- #options;
2660
- constructor(prefer, options) {
2661
- this.#prefer = prefer;
2662
- this.#options = options;
2663
- }
2664
- get kind() {
2665
- return this.#inner?.kind ?? this.#kind;
2666
- }
2667
- async initialize(canvas) {
2668
- const errors = [];
2669
- for (const kind of this.#prefer) {
2670
- const candidate = instantiate(kind, this.#options);
2671
- try {
2672
- await initializeWithTimeout(kind, candidate, canvas);
2673
- this.#inner = candidate;
2674
- this.#kind = candidate.kind;
2675
- return;
2676
- } catch (err) {
2677
- candidate.destroy();
2678
- errors.push(
2679
- `${kind}: ${err instanceof Error ? err.message : String(err)}`
2680
- );
2681
- }
2682
- }
2683
- throw new Error(
2684
- `@doki-land/live2d-renderer: no renderer initialized (${errors.join("; ")})`
2685
- );
2686
- }
2687
- createModelDrawPass() {
2688
- if (!this.#inner) {
2689
- throw new Error(
2690
- "@doki-land/live2d-renderer: renderer not initialized"
2691
- );
2692
- }
2693
- return this.#inner.createModelDrawPass();
2694
- }
2695
- beginFrame() {
2696
- this.#inner?.beginFrame();
2697
- }
2698
- endFrame() {
2699
- this.#inner?.endFrame();
2700
- }
2701
- resize(width, height) {
2702
- this.#inner?.resize(width, height);
2703
- }
2704
- destroy() {
2705
- this.#inner?.destroy();
2706
- this.#inner = null;
2707
- }
2708
- };
2709
- function createRenderer(options = {}) {
2710
- const prefer = options.prefer?.length ? options.prefer : DEFAULT_PREFER;
2711
- return new FallbackRenderer(prefer, options);
2712
- }
2713
-
2714
2660
  // src/format/detect-format.ts
2715
2661
  import { detectModelSettingsFormat } from "@doki-land/live2d-core";
2716
2662
  function detectMocBinaryFormat(bytes) {
@@ -3702,16 +3648,81 @@ function moc2ParamGetterFromValues(params, values) {
3702
3648
  }
3703
3649
  return (id) => byId.get(id) ?? 0;
3704
3650
  }
3705
- function normalizePositions(positions, canvasWidth, canvasHeight) {
3651
+ function normalizePositionsInto(positions, canvasWidth, canvasHeight, out) {
3706
3652
  const w = canvasWidth > 0 ? canvasWidth : 1;
3707
3653
  const h = canvasHeight > 0 ? canvasHeight : 1;
3708
- const out = new Float32Array(positions.length);
3709
3654
  for (let i = 0; i + 1 < positions.length; i += 2) {
3710
3655
  out[i] = positions[i] / w * 2 - 1;
3711
3656
  out[i + 1] = 1 - positions[i + 1] / h * 2;
3712
3657
  }
3658
+ }
3659
+ function normalizePositions(positions, canvasWidth, canvasHeight) {
3660
+ const out = new Float32Array(positions.length);
3661
+ normalizePositionsInto(positions, canvasWidth, canvasHeight, out);
3713
3662
  return out;
3714
3663
  }
3664
+ function evaluateMoc2PoseInto(model, getParam, byId) {
3665
+ const ops = bakeDeformerOps(model, getParam);
3666
+ const w = model.canvasWidth;
3667
+ const h = model.canvasHeight;
3668
+ for (const part of model.parts) {
3669
+ for (const mesh of part.drawData) {
3670
+ const sink = byId.get(mesh.id);
3671
+ if (!sink) continue;
3672
+ const floatCount = mesh.numPoints * 2;
3673
+ const local = interpolateKeyforms(
3674
+ mesh.keyforms,
3675
+ mesh.pivotManager,
3676
+ getParam,
3677
+ floatCount
3678
+ );
3679
+ const world = transformDrawablePositions(mesh, local, ops);
3680
+ let pos = sink.vertexPositions;
3681
+ if (pos.length !== world.length) {
3682
+ pos = new Float32Array(world.length);
3683
+ sink.vertexPositions = pos;
3684
+ }
3685
+ normalizePositionsInto(world, w, h, pos);
3686
+ sink.opacity = interpolateScalarTable(
3687
+ mesh.opacities,
3688
+ mesh.pivotManager,
3689
+ getParam,
3690
+ 1
3691
+ );
3692
+ sink.renderOrder = Math.round(
3693
+ interpolateScalarTable(
3694
+ mesh.drawOrders,
3695
+ mesh.pivotManager,
3696
+ getParam,
3697
+ mesh.averageDrawOrder
3698
+ )
3699
+ );
3700
+ sink.visible = part.visible;
3701
+ }
3702
+ }
3703
+ }
3704
+ function moc2DrawableIdsInProgramOrder(model, getParam) {
3705
+ const paramDefs = model.paramDefSet?.params ?? [];
3706
+ const gp = getParam ?? buildParamGetter(paramDefs);
3707
+ const drafts = [];
3708
+ for (const part of model.parts) {
3709
+ for (const mesh of part.drawData) {
3710
+ drafts.push({
3711
+ id: mesh.id,
3712
+ renderOrder: Math.round(
3713
+ interpolateScalarTable(
3714
+ mesh.drawOrders,
3715
+ mesh.pivotManager,
3716
+ gp,
3717
+ mesh.averageDrawOrder
3718
+ )
3719
+ )
3720
+ });
3721
+ }
3722
+ }
3723
+ drafts.sort((a, b) => a.renderOrder - b.renderOrder);
3724
+ return drafts.map((d) => d.id);
3725
+ }
3715
3726
  function lowerDrawable(mesh, getParam, ops, canvasWidth, canvasHeight, partVisible) {
3716
3727
  const floatCount = mesh.numPoints * 2;
3717
3728
  const local = interpolateKeyforms(
@@ -4942,15 +4953,24 @@ function meanGlueSeamDistance(positionsByMesh, glue) {
4942
4953
  }
4943
4954
 
4944
4955
  // src/moc/moc3-to-program.ts
4945
- function normalizePositions2(positions, canvasWidth, canvasHeight, pixelsPerUnit) {
4956
+ function normalizePositionsInto2(positions, canvasWidth, canvasHeight, pixelsPerUnit, out) {
4946
4957
  const ppu = pixelsPerUnit > 0 ? pixelsPerUnit : 1;
4947
4958
  const hw = canvasWidth > 0 ? canvasWidth / ppu * 0.5 : 0.5;
4948
4959
  const hh = canvasHeight > 0 ? canvasHeight / ppu * 0.5 : 0.5;
4949
- const out = new Float32Array(positions.length);
4950
4960
  for (let i = 0; i + 1 < positions.length; i += 2) {
4951
4961
  out[i] = positions[i] / hw;
4952
4962
  out[i + 1] = -positions[i + 1] / hh;
4953
4963
  }
4964
+ }
4965
+ function normalizePositions2(positions, canvasWidth, canvasHeight, pixelsPerUnit) {
4966
+ const out = new Float32Array(positions.length);
4967
+ normalizePositionsInto2(
4968
+ positions,
4969
+ canvasWidth,
4970
+ canvasHeight,
4971
+ pixelsPerUnit,
4972
+ out
4973
+ );
4954
4974
  return out;
4955
4975
  }
4956
4976
  function moc3DocumentToProgram(doc, options = {}) {
@@ -5136,6 +5156,155 @@ function moc3DocumentToProgram(doc, options = {}) {
5136
5156
  drawables
5137
5157
  };
5138
5158
  }
5159
+ function moc3ArtMeshIndicesInProgramOrder(doc, getParamByIndex) {
5160
+ const meshCount = doc.counts[CountIdx.ART_MESHES] ?? 0;
5161
+ const defaultValues = moc3SectionF32(doc, "parameter.default_values");
5162
+ const gp = getParamByIndex ?? ((index) => defaultValues[index] ?? 0);
5163
+ const keyTables = loadMoc3KeyTables(doc);
5164
+ const enables = moc3SectionI32(doc, "art_mesh.enables");
5165
+ const vertexCounts = moc3SectionI32(doc, "art_mesh.vertex_counts");
5166
+ const indexCounts = moc3SectionI32(doc, "art_mesh.position_index_counts");
5167
+ const keyformBegins = moc3SectionI32(doc, "art_mesh.keyform_begin_indices");
5168
+ const keyformCounts = moc3SectionI32(doc, "art_mesh.keyform_counts");
5169
+ const bandIndices = moc3SectionI32(
5170
+ doc,
5171
+ "art_mesh.keyform_binding_band_indices"
5172
+ );
5173
+ const keyformDrawOrders = moc3SectionF32(
5174
+ doc,
5175
+ "art_mesh_keyform.draw_orders"
5176
+ );
5177
+ const drafts = [];
5178
+ for (let i = 0; i < meshCount; i++) {
5179
+ if ((enables[i] ?? 1) === 0) continue;
5180
+ const vertexCount = vertexCounts[i] ?? 0;
5181
+ const indexCount = indexCounts[i] ?? 0;
5182
+ if (vertexCount <= 0 || indexCount < 3) continue;
5183
+ const kfBegin = keyformBegins[i] ?? 0;
5184
+ const kfCount = keyformCounts[i] ?? 0;
5185
+ if (kfCount <= 0) continue;
5186
+ const band = bandIndices[i] ?? -1;
5187
+ const blend = resolveMoc3KeyformBlend(keyTables, band, gp);
5188
+ drafts.push({
5189
+ artMeshIndex: i,
5190
+ renderOrder: Math.round(
5191
+ blendKeyformScalar(
5192
+ keyformDrawOrders,
5193
+ kfBegin,
5194
+ kfCount,
5195
+ blend,
5196
+ i
5197
+ )
5198
+ )
5199
+ });
5200
+ }
5201
+ drafts.sort((a, b) => a.renderOrder - b.renderOrder);
5202
+ return drafts.map((d) => d.artMeshIndex);
5203
+ }
5204
+ function evaluateMoc3PoseInto(doc, getParamByIndex, byArtMesh, poseOpacity) {
5205
+ const meshCount = doc.counts[CountIdx.ART_MESHES] ?? 0;
5206
+ const keyTables = loadMoc3KeyTables(doc);
5207
+ const deformers = bakeMoc3Deformers(doc, keyTables, getParamByIndex);
5208
+ const glues = loadMoc3Glues(doc);
5209
+ const glueIntensities = moc3SectionF32(doc, "glue_keyform.intensities");
5210
+ const visibles = moc3SectionI32(doc, "art_mesh.visibles");
5211
+ const enables = moc3SectionI32(doc, "art_mesh.enables");
5212
+ const vertexCounts = moc3SectionI32(doc, "art_mesh.vertex_counts");
5213
+ const indexCounts = moc3SectionI32(doc, "art_mesh.position_index_counts");
5214
+ const keyformBegins = moc3SectionI32(doc, "art_mesh.keyform_begin_indices");
5215
+ const keyformCounts = moc3SectionI32(doc, "art_mesh.keyform_counts");
5216
+ const bandIndices = moc3SectionI32(
5217
+ doc,
5218
+ "art_mesh.keyform_binding_band_indices"
5219
+ );
5220
+ const parentDeformers = moc3SectionI32(
5221
+ doc,
5222
+ "art_mesh.parent_deformer_indices"
5223
+ );
5224
+ const keyformOpacities = moc3SectionF32(doc, "art_mesh_keyform.opacities");
5225
+ const keyformDrawOrders = moc3SectionF32(
5226
+ doc,
5227
+ "art_mesh_keyform.draw_orders"
5228
+ );
5229
+ const keyformPosBegins = moc3SectionI32(
5230
+ doc,
5231
+ "art_mesh_keyform.keyform_position_begin_indices"
5232
+ );
5233
+ const keyformPositions = moc3SectionF32(doc, "keyform_position.xys");
5234
+ const cw = doc.canvas.canvasWidth;
5235
+ const ch = doc.canvas.canvasHeight;
5236
+ const ppu = doc.canvas.pixelsPerUnit;
5237
+ const worldByMesh = /* @__PURE__ */ new Map();
5238
+ const opacityByMesh = /* @__PURE__ */ new Map();
5239
+ const orderByMesh = /* @__PURE__ */ new Map();
5240
+ const visibleByMesh = /* @__PURE__ */ new Map();
5241
+ for (let i = 0; i < meshCount; i++) {
5242
+ if (!byArtMesh.has(i)) continue;
5243
+ if ((enables[i] ?? 1) === 0) continue;
5244
+ const vertexCount = vertexCounts[i] ?? 0;
5245
+ const indexCount = indexCounts[i] ?? 0;
5246
+ if (vertexCount <= 0 || indexCount < 3) continue;
5247
+ const kfBegin = keyformBegins[i] ?? 0;
5248
+ const kfCount = keyformCounts[i] ?? 0;
5249
+ if (kfCount <= 0) continue;
5250
+ const band = bandIndices[i] ?? -1;
5251
+ const blend = resolveMoc3KeyformBlend(keyTables, band, getParamByIndex);
5252
+ const local = blendKeyformFloats(
5253
+ keyformPositions,
5254
+ keyformPosBegins,
5255
+ kfBegin,
5256
+ kfCount,
5257
+ vertexCount * 2,
5258
+ blend
5259
+ );
5260
+ const parentIndex = parentDeformers[i] ?? -1;
5261
+ const parent = parentIndex >= 0 ? deformers[parentIndex] ?? null : null;
5262
+ const world = parent ? applyParentToPoints(local, parent) : local;
5263
+ worldByMesh.set(i, world);
5264
+ opacityByMesh.set(
5265
+ i,
5266
+ blendKeyformScalar(keyformOpacities, kfBegin, kfCount, blend, 1)
5267
+ );
5268
+ orderByMesh.set(
5269
+ i,
5270
+ Math.round(
5271
+ blendKeyformScalar(
5272
+ keyformDrawOrders,
5273
+ kfBegin,
5274
+ kfCount,
5275
+ blend,
5276
+ i
5277
+ )
5278
+ )
5279
+ );
5280
+ visibleByMesh.set(i, (visibles[i] ?? 1) !== 0);
5281
+ }
5282
+ applyMoc3Glues(
5283
+ worldByMesh,
5284
+ glues,
5285
+ keyTables,
5286
+ getParamByIndex,
5287
+ glueIntensities
5288
+ );
5289
+ for (const [artMeshIndex, world] of worldByMesh) {
5290
+ const sink = byArtMesh.get(artMeshIndex);
5291
+ if (!sink) continue;
5292
+ let pos = sink.vertexPositions;
5293
+ if (pos.length !== world.length) {
5294
+ pos = new Float32Array(world.length);
5295
+ sink.vertexPositions = pos;
5296
+ }
5297
+ normalizePositionsInto2(world, cw, ch, ppu, pos);
5298
+ const opacity = opacityByMesh.get(artMeshIndex) ?? 1;
5299
+ sink.renderOrder = orderByMesh.get(artMeshIndex) ?? sink.renderOrder;
5300
+ sink.visible = visibleByMesh.get(artMeshIndex) ?? true;
5301
+ const slot = sink.index;
5302
+ if (slot >= 0 && slot < poseOpacity.length) {
5303
+ poseOpacity[slot] = opacity;
5304
+ }
5305
+ sink.opacity = opacity;
5306
+ }
5307
+ }
5139
5308
 
5140
5309
  // src/moc/decode.ts
5141
5310
  function readMagic2(bytes) {
@@ -5186,49 +5355,90 @@ async function decodeMoc3(bytes) {
5186
5355
 
5187
5356
  // src/moc/moc2.ts
5188
5357
  import { detectModelSettingsFormat as detectModelSettingsFormat2 } from "@doki-land/live2d-core";
5189
- function toDrawableMesh(d) {
5190
- return {
5191
- index: d.index,
5192
- textureIndex: d.textureIndex,
5193
- vertexPositions: d.positions,
5194
- uvs: d.uvs,
5195
- indices: d.indices,
5196
- opacity: d.opacity,
5197
- blendMode: d.blendMode,
5198
- invertedMask: d.invertedMask,
5199
- renderOrder: d.renderOrder,
5200
- dynamicFlag: true,
5201
- maskIndices: [...d.maskIndices],
5202
- visible: d.visible
5203
- };
5358
+ var stateByModel = /* @__PURE__ */ new WeakMap();
5359
+ function allocateMeshesFromProgram(program, drawableIds) {
5360
+ const meshes = [];
5361
+ const byId = /* @__PURE__ */ new Map();
5362
+ for (let i = 0; i < program.drawables.length; i++) {
5363
+ const d = program.drawables[i];
5364
+ const mesh = {
5365
+ index: d.index,
5366
+ textureIndex: d.textureIndex,
5367
+ vertexPositions: new Float32Array(d.positions),
5368
+ uvs: d.uvs,
5369
+ indices: d.indices,
5370
+ opacity: d.opacity,
5371
+ blendMode: d.blendMode,
5372
+ invertedMask: d.invertedMask,
5373
+ renderOrder: d.renderOrder,
5374
+ dynamicFlag: true,
5375
+ maskIndices: [...d.maskIndices],
5376
+ visible: d.visible
5377
+ };
5378
+ meshes.push(mesh);
5379
+ const id = drawableIds[i];
5380
+ if (id) byId.set(id, mesh);
5381
+ }
5382
+ return { meshes, byId };
5383
+ }
5384
+ function buildBindings(instance) {
5385
+ const bindings = [];
5386
+ const paramIndexById = /* @__PURE__ */ new Map();
5387
+ for (let i = 0; i < instance.program.parameters.length; i++) {
5388
+ const p = instance.program.parameters[i];
5389
+ const binding = {
5390
+ id: p.id,
5391
+ min: p.min,
5392
+ max: p.max,
5393
+ defaultValue: p.defaultValue,
5394
+ value: instance.parameterValues[i] ?? p.defaultValue
5395
+ };
5396
+ bindings.push(binding);
5397
+ paramIndexById.set(p.id, i);
5398
+ }
5399
+ return { bindings, paramIndexById };
5204
5400
  }
5205
- function paramFingerprint(values) {
5206
- let s = "";
5207
- for (let i = 0; i < values.length; i++) {
5208
- s += `${values[i]?.toFixed(5)},`;
5401
+ function syncBindingValues(state) {
5402
+ for (let i = 0; i < state.bindings.length; i++) {
5403
+ const b = state.bindings[i];
5404
+ b.value = state.instance.parameterValues[i] ?? b.defaultValue;
5209
5405
  }
5210
- return s;
5211
5406
  }
5212
- var stateByModel = /* @__PURE__ */ new WeakMap();
5407
+ function refreshDrawView(state) {
5408
+ const view = state.drawView;
5409
+ view.length = 0;
5410
+ for (const m of state.meshes) view.push(m);
5411
+ view.sort((a, b) => a.renderOrder - b.renderOrder || a.index - b.index);
5412
+ }
5213
5413
  function bakePose(state) {
5214
- const values = Float32Array.from(state.instance.parameterValues);
5215
- const timeSeconds = state.instance.timeSeconds;
5216
- const fp = paramFingerprint(values);
5217
- if (fp === state.bakedFingerprint && state.lastFrame) {
5218
- return;
5414
+ if (!state.poseDirty) return;
5415
+ const getParam = moc2ParamGetterFromValues(
5416
+ state.instance.program.parameters,
5417
+ state.instance.parameterValues
5418
+ );
5419
+ evaluateMoc2PoseInto(state.moc, getParam, state.byId);
5420
+ refreshDrawView(state);
5421
+ syncBindingValues(state);
5422
+ state.poseDirty = false;
5423
+ }
5424
+ function frameFromMeshes(meshes, timeSeconds) {
5425
+ const drawables = [];
5426
+ for (const m of meshes) {
5427
+ drawables.push({
5428
+ index: m.index,
5429
+ textureIndex: m.textureIndex,
5430
+ positions: m.vertexPositions,
5431
+ uvs: m.uvs,
5432
+ indices: m.indices,
5433
+ opacity: m.opacity,
5434
+ blendMode: m.blendMode,
5435
+ renderOrder: m.renderOrder,
5436
+ visible: m.visible,
5437
+ invertedMask: m.invertedMask,
5438
+ maskIndices: m.maskIndices
5439
+ });
5219
5440
  }
5220
- const program = moc2ModelToProgram(state.moc, {
5221
- getParam: moc2ParamGetterFromValues(
5222
- state.moc.paramDefSet.params,
5223
- values
5224
- )
5225
- });
5226
- const next = createModelInstance(program);
5227
- next.parameterValues.set(values);
5228
- next.timeSeconds = timeSeconds;
5229
- state.instance = next;
5230
- state.bakedFingerprint = fp;
5231
- state.lastFrame = evaluateFrame(next);
5441
+ return { timeSeconds, drawables };
5232
5442
  }
5233
5443
  var Moc2Backend = class {
5234
5444
  format = "moc2";
@@ -5249,17 +5459,30 @@ var Moc2Backend = class {
5249
5459
  const moc = shared?.moc2Model ?? new Moc2Parser(bytes).parseModel();
5250
5460
  const program = moc2ModelToProgram(moc);
5251
5461
  const instance = createModelInstance(program);
5462
+ const drawableIds = moc2DrawableIdsInProgramOrder(moc);
5463
+ const { meshes, byId } = allocateMeshesFromProgram(
5464
+ program,
5465
+ drawableIds
5466
+ );
5467
+ const { bindings, paramIndexById } = buildBindings(instance);
5468
+ const drawView = [];
5252
5469
  const model = {
5253
5470
  id: settings.name ?? settings.url,
5254
5471
  settings,
5255
5472
  format: "moc2"
5256
5473
  };
5257
- stateByModel.set(model, {
5474
+ const state = {
5258
5475
  moc,
5259
5476
  instance,
5260
- lastFrame: null,
5261
- bakedFingerprint: paramFingerprint(instance.parameterValues)
5262
- });
5477
+ meshes,
5478
+ drawView,
5479
+ byId,
5480
+ poseDirty: true,
5481
+ bindings,
5482
+ paramIndexById
5483
+ };
5484
+ bakePose(state);
5485
+ stateByModel.set(model, state);
5263
5486
  return model;
5264
5487
  }
5265
5488
  updateModel(model, deltaTimeSeconds) {
@@ -5267,46 +5490,43 @@ var Moc2Backend = class {
5267
5490
  if (!state) return;
5268
5491
  state.instance.timeSeconds += deltaTimeSeconds;
5269
5492
  bakePose(state);
5270
- if (state.lastFrame) {
5271
- state.lastFrame = {
5272
- ...state.lastFrame,
5273
- timeSeconds: state.instance.timeSeconds
5274
- };
5275
- }
5276
5493
  }
5277
5494
  getDrawables(model) {
5278
5495
  const state = stateByModel.get(model);
5279
5496
  if (!state) return [];
5280
5497
  bakePose(state);
5281
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5282
- state.lastFrame = frame;
5283
- return frame.drawables.map(toDrawableMesh);
5498
+ return state.drawView;
5284
5499
  }
5285
5500
  captureFrame(model) {
5286
5501
  const state = stateByModel.get(model);
5287
5502
  if (!state) return null;
5288
5503
  bakePose(state);
5289
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5290
- state.lastFrame = frame;
5291
- return frame;
5504
+ return frameFromMeshes(state.drawView, state.instance.timeSeconds);
5292
5505
  }
5293
5506
  setParameter(model, id, value) {
5294
5507
  const state = stateByModel.get(model);
5295
5508
  if (!state) return;
5296
5509
  setParameterValue(state.instance, id, value);
5297
- state.lastFrame = null;
5298
- state.bakedFingerprint = "";
5510
+ const index = state.paramIndexById.get(id);
5511
+ if (index !== void 0) {
5512
+ const binding = state.bindings[index];
5513
+ if (binding) {
5514
+ binding.value = state.instance.parameterValues[index] ?? binding.defaultValue;
5515
+ }
5516
+ }
5517
+ state.poseDirty = true;
5518
+ }
5519
+ resolveParameter(model, id) {
5520
+ const state = stateByModel.get(model);
5521
+ if (!state) return void 0;
5522
+ return state.paramIndexById.get(id);
5299
5523
  }
5300
5524
  listParameters(model) {
5301
5525
  const state = stateByModel.get(model);
5302
5526
  if (!state) return [];
5303
- return state.instance.program.parameters.map((p, i) => ({
5304
- id: p.id,
5305
- min: p.min,
5306
- max: p.max,
5307
- defaultValue: p.defaultValue,
5308
- value: state.instance.parameterValues[i] ?? p.defaultValue
5309
- }));
5527
+ bakePose(state);
5528
+ syncBindingValues(state);
5529
+ return state.bindings;
5310
5530
  }
5311
5531
  hitTest(_model, _x, _y) {
5312
5532
  return null;
@@ -5314,6 +5534,10 @@ var Moc2Backend = class {
5314
5534
  destroyModel(model) {
5315
5535
  stateByModel.delete(model);
5316
5536
  }
5537
+ /** Test/harness: stable program + instance refs. */
5538
+ getResidentInstance(model) {
5539
+ return stateByModel.get(model)?.instance ?? null;
5540
+ }
5317
5541
  };
5318
5542
  function createMoc2Backend() {
5319
5543
  return new Moc2Backend();
@@ -5352,11 +5576,12 @@ function cascadedPartOpacity(tables, artMeshIndex, overrides) {
5352
5576
  }
5353
5577
 
5354
5578
  // src/moc/moc3.ts
5355
- function toDrawableMesh2(d) {
5356
- return {
5579
+ var stateByModel2 = /* @__PURE__ */ new WeakMap();
5580
+ function allocateMeshesFromProgram2(program) {
5581
+ return program.drawables.map((d) => ({
5357
5582
  index: d.index,
5358
5583
  textureIndex: d.textureIndex,
5359
- vertexPositions: d.positions,
5584
+ vertexPositions: new Float32Array(d.positions),
5360
5585
  uvs: d.uvs,
5361
5586
  indices: d.indices,
5362
5587
  opacity: d.opacity,
@@ -5366,34 +5591,82 @@ function toDrawableMesh2(d) {
5366
5591
  dynamicFlag: true,
5367
5592
  maskIndices: [...d.maskIndices],
5368
5593
  visible: d.visible
5369
- };
5594
+ }));
5595
+ }
5596
+ function buildBindings2(instance) {
5597
+ const bindings = [];
5598
+ const paramIndexById = /* @__PURE__ */ new Map();
5599
+ for (let i = 0; i < instance.program.parameters.length; i++) {
5600
+ const p = instance.program.parameters[i];
5601
+ bindings.push({
5602
+ id: p.id,
5603
+ min: p.min,
5604
+ max: p.max,
5605
+ defaultValue: p.defaultValue,
5606
+ value: instance.parameterValues[i] ?? p.defaultValue
5607
+ });
5608
+ paramIndexById.set(p.id, i);
5609
+ }
5610
+ return { bindings, paramIndexById };
5370
5611
  }
5371
- function paramFingerprint2(values) {
5372
- let s = "";
5373
- for (let i = 0; i < values.length; i++) {
5374
- s += `${values[i]?.toFixed(5)},`;
5612
+ function syncBindingValues2(state) {
5613
+ for (let i = 0; i < state.bindings.length; i++) {
5614
+ const b = state.bindings[i];
5615
+ b.value = state.instance.parameterValues[i] ?? b.defaultValue;
5616
+ }
5617
+ }
5618
+ function refreshDrawView2(state) {
5619
+ const view = state.drawView;
5620
+ view.length = 0;
5621
+ for (const m of state.meshes) view.push(m);
5622
+ view.sort((a, b) => a.renderOrder - b.renderOrder || a.index - b.index);
5623
+ }
5624
+ function applyPartOpacity(state) {
5625
+ const tables = state.doc ? readMoc3PartTables(state.doc) : null;
5626
+ for (const mesh of state.meshes) {
5627
+ const base = state.poseOpacity[mesh.index] ?? mesh.opacity;
5628
+ if (!tables || state.partOpacity.size === 0) {
5629
+ mesh.opacity = base;
5630
+ continue;
5631
+ }
5632
+ mesh.opacity = base * cascadedPartOpacity(tables, mesh.index, state.partOpacity);
5375
5633
  }
5376
- return s;
5377
5634
  }
5378
- var stateByModel2 = /* @__PURE__ */ new WeakMap();
5379
5635
  function bakePose2(state) {
5380
- if (!state.doc) {
5381
- state.lastFrame = evaluateFrame(state.instance);
5382
- return;
5636
+ if (!state.poseDirty) return;
5637
+ if (state.doc) {
5638
+ const values = state.instance.parameterValues;
5639
+ evaluateMoc3PoseInto(
5640
+ state.doc,
5641
+ (i) => values[i] ?? 0,
5642
+ state.byArtMesh,
5643
+ state.poseOpacity
5644
+ );
5645
+ } else {
5646
+ evaluateFrameInto(state.instance, state.meshes, state.poseOpacity);
5383
5647
  }
5384
- const values = Float32Array.from(state.instance.parameterValues);
5385
- const timeSeconds = state.instance.timeSeconds;
5386
- const fp = paramFingerprint2(values);
5387
- if (fp === state.bakedFingerprint && state.lastFrame) return;
5388
- const program = moc3DocumentToProgram(state.doc, {
5389
- getParamByIndex: (i) => values[i] ?? 0
5390
- });
5391
- const next = createModelInstance(program);
5392
- next.parameterValues.set(values);
5393
- next.timeSeconds = timeSeconds;
5394
- state.instance = next;
5395
- state.bakedFingerprint = fp;
5396
- state.lastFrame = evaluateFrame(next);
5648
+ refreshDrawView2(state);
5649
+ syncBindingValues2(state);
5650
+ state.poseDirty = false;
5651
+ }
5652
+ function frameFromMeshes2(meshes, timeSeconds) {
5653
+ const drawables = [];
5654
+ for (const m of meshes) {
5655
+ drawables.push({
5656
+ index: m.index,
5657
+ textureIndex: m.textureIndex,
5658
+ positions: m.vertexPositions,
5659
+ uvs: m.uvs,
5660
+ indices: m.indices,
5661
+ opacity: m.opacity,
5662
+ blendMode: m.blendMode,
5663
+ renderOrder: m.renderOrder,
5664
+ visible: m.visible,
5665
+ invertedMask: m.invertedMask,
5666
+ maskIndices: m.maskIndices
5667
+ });
5668
+ }
5669
+ return { timeSeconds, drawables };
5397
5670
  }
5398
5671
  var Moc3Backend = class {
5399
5672
  format = "moc3";
@@ -5421,18 +5694,39 @@ var Moc3Backend = class {
5421
5694
  program = moc3DocumentToProgram(doc);
5422
5695
  }
5423
5696
  const instance = createModelInstance(program);
5697
+ const meshes = allocateMeshesFromProgram2(program);
5698
+ const byArtMesh = /* @__PURE__ */ new Map();
5699
+ if (doc) {
5700
+ const artOrder = moc3ArtMeshIndicesInProgramOrder(doc);
5701
+ for (let i = 0; i < artOrder.length; i++) {
5702
+ const art = artOrder[i];
5703
+ const mesh = meshes[i];
5704
+ if (mesh) byArtMesh.set(art, mesh);
5705
+ }
5706
+ }
5707
+ const poseOpacity = new Float32Array(meshes.length);
5708
+ for (const m of meshes) poseOpacity[m.index] = m.opacity;
5709
+ const { bindings, paramIndexById } = buildBindings2(instance);
5424
5710
  const model = {
5425
5711
  id: settings.name ?? settings.url,
5426
5712
  settings,
5427
5713
  format: "moc3"
5428
5714
  };
5429
- stateByModel2.set(model, {
5715
+ const state = {
5430
5716
  doc,
5431
5717
  instance,
5432
- lastFrame: null,
5433
- bakedFingerprint: paramFingerprint2(instance.parameterValues),
5434
- partOpacity: /* @__PURE__ */ new Map()
5435
- });
5718
+ meshes,
5719
+ drawView: [],
5720
+ byArtMesh,
5721
+ poseOpacity,
5722
+ poseDirty: true,
5723
+ partOpacity: /* @__PURE__ */ new Map(),
5724
+ bindings,
5725
+ paramIndexById
5726
+ };
5727
+ bakePose2(state);
5728
+ applyPartOpacity(state);
5729
+ stateByModel2.set(model, state);
5436
5730
  return model;
5437
5731
  }
5438
5732
  updateModel(model, deltaTimeSeconds) {
@@ -5440,49 +5734,34 @@ var Moc3Backend = class {
5440
5734
  if (!state) return;
5441
5735
  state.instance.timeSeconds += deltaTimeSeconds;
5442
5736
  bakePose2(state);
5443
- if (state.lastFrame) {
5444
- state.lastFrame = {
5445
- ...state.lastFrame,
5446
- timeSeconds: state.instance.timeSeconds
5447
- };
5448
- }
5737
+ applyPartOpacity(state);
5449
5738
  }
5450
5739
  getDrawables(model) {
5451
5740
  const state = stateByModel2.get(model);
5452
5741
  if (!state) return [];
5453
5742
  bakePose2(state);
5454
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5455
- state.lastFrame = frame;
5456
- const tables = state.doc ? readMoc3PartTables(state.doc) : null;
5457
- return frame.drawables.map((d) => {
5458
- const mesh = toDrawableMesh2(d);
5459
- if (!tables || state.partOpacity.size === 0) return mesh;
5460
- const mul = cascadedPartOpacity(tables, d.index, state.partOpacity);
5461
- return { ...mesh, opacity: mesh.opacity * mul };
5462
- });
5743
+ applyPartOpacity(state);
5744
+ return state.drawView;
5463
5745
  }
5464
5746
  captureFrame(model) {
5465
5747
  const state = stateByModel2.get(model);
5466
5748
  if (!state) return null;
5467
5749
  bakePose2(state);
5468
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5469
- state.lastFrame = frame;
5470
- const tables = state.doc ? readMoc3PartTables(state.doc) : null;
5471
- if (!tables || state.partOpacity.size === 0) return frame;
5472
- return {
5473
- ...frame,
5474
- drawables: frame.drawables.map((d) => ({
5475
- ...d,
5476
- opacity: d.opacity * cascadedPartOpacity(tables, d.index, state.partOpacity)
5477
- }))
5478
- };
5750
+ applyPartOpacity(state);
5751
+ return frameFromMeshes2(state.drawView, state.instance.timeSeconds);
5479
5752
  }
5480
5753
  setParameter(model, id, value) {
5481
5754
  const state = stateByModel2.get(model);
5482
5755
  if (!state) return;
5483
5756
  setParameterValue(state.instance, id, value);
5484
- state.lastFrame = null;
5485
- state.bakedFingerprint = "";
5757
+ const index = state.paramIndexById.get(id);
5758
+ if (index !== void 0) {
5759
+ const binding = state.bindings[index];
5760
+ if (binding) {
5761
+ binding.value = state.instance.parameterValues[index] ?? binding.defaultValue;
5762
+ }
5763
+ }
5764
+ state.poseDirty = true;
5486
5765
  }
5487
5766
  setPartOpacity(model, id, value) {
5488
5767
  const state = stateByModel2.get(model);
@@ -5490,16 +5769,17 @@ var Moc3Backend = class {
5490
5769
  const v = Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 1;
5491
5770
  state.partOpacity.set(id, v);
5492
5771
  }
5772
+ resolveParameter(model, id) {
5773
+ const state = stateByModel2.get(model);
5774
+ if (!state) return void 0;
5775
+ return state.paramIndexById.get(id);
5776
+ }
5493
5777
  listParameters(model) {
5494
5778
  const state = stateByModel2.get(model);
5495
5779
  if (!state) return [];
5496
- return state.instance.program.parameters.map((p, i) => ({
5497
- id: p.id,
5498
- min: p.min,
5499
- max: p.max,
5500
- defaultValue: p.defaultValue,
5501
- value: state.instance.parameterValues[i] ?? p.defaultValue
5502
- }));
5780
+ bakePose2(state);
5781
+ syncBindingValues2(state);
5782
+ return state.bindings;
5503
5783
  }
5504
5784
  hitTest(_model, _x, _y) {
5505
5785
  return null;
@@ -5507,11 +5787,105 @@ var Moc3Backend = class {
5507
5787
  destroyModel(model) {
5508
5788
  stateByModel2.delete(model);
5509
5789
  }
5790
+ getResidentInstance(model) {
5791
+ return stateByModel2.get(model)?.instance ?? null;
5792
+ }
5510
5793
  };
5511
5794
  function createMoc3Backend() {
5512
5795
  return new Moc3Backend();
5513
5796
  }
5514
5797
 
5798
+ // src/runtime/create-renderer.ts
5799
+ var DEFAULT_PREFER = ["webgpu", "webgl2", "canvas2d"];
5800
+ var INIT_TIMEOUT_MS = {
5801
+ webgpu: 2500,
5802
+ webgl2: 1500,
5803
+ canvas2d: 1e3
5804
+ };
5805
+ function instantiate(kind, options) {
5806
+ if (kind === "webgpu") return createWebGpuRenderer(options.webgpu);
5807
+ if (kind === "webgl2") return createWebGl2Renderer(options.webgl2);
5808
+ return createCanvas2DRenderer(options.canvas2d);
5809
+ }
5810
+ async function initializeWithTimeout(kind, candidate, canvas) {
5811
+ const ms = INIT_TIMEOUT_MS[kind];
5812
+ let timer;
5813
+ try {
5814
+ await Promise.race([
5815
+ candidate.initialize(canvas),
5816
+ new Promise((_, reject) => {
5817
+ timer = setTimeout(() => {
5818
+ reject(
5819
+ new Error(
5820
+ `@doki-land/live2d-renderer: ${kind} initialize timed out after ${ms}ms`
5821
+ )
5822
+ );
5823
+ }, ms);
5824
+ })
5825
+ ]);
5826
+ } finally {
5827
+ if (timer !== void 0) clearTimeout(timer);
5828
+ }
5829
+ }
5830
+ var FallbackRenderer = class {
5831
+ #inner = null;
5832
+ #kind = "webgpu";
5833
+ #prefer;
5834
+ #options;
5835
+ constructor(prefer, options) {
5836
+ this.#prefer = prefer;
5837
+ this.#options = options;
5838
+ }
5839
+ get kind() {
5840
+ return this.#inner?.kind ?? this.#kind;
5841
+ }
5842
+ async initialize(canvas) {
5843
+ const errors = [];
5844
+ for (const kind of this.#prefer) {
5845
+ const candidate = instantiate(kind, this.#options);
5846
+ try {
5847
+ await initializeWithTimeout(kind, candidate, canvas);
5848
+ this.#inner = candidate;
5849
+ this.#kind = candidate.kind;
5850
+ return;
5851
+ } catch (err) {
5852
+ candidate.destroy();
5853
+ errors.push(
5854
+ `${kind}: ${err instanceof Error ? err.message : String(err)}`
5855
+ );
5856
+ }
5857
+ }
5858
+ throw new Error(
5859
+ `@doki-land/live2d-renderer: no renderer initialized (${errors.join("; ")})`
5860
+ );
5861
+ }
5862
+ createModelDrawPass() {
5863
+ if (!this.#inner) {
5864
+ throw new Error(
5865
+ "@doki-land/live2d-renderer: renderer not initialized"
5866
+ );
5867
+ }
5868
+ return this.#inner.createModelDrawPass();
5869
+ }
5870
+ beginFrame() {
5871
+ this.#inner?.beginFrame();
5872
+ }
5873
+ endFrame() {
5874
+ this.#inner?.endFrame();
5875
+ }
5876
+ resize(width, height) {
5877
+ this.#inner?.resize(width, height);
5878
+ }
5879
+ destroy() {
5880
+ this.#inner?.destroy();
5881
+ this.#inner = null;
5882
+ }
5883
+ };
5884
+ function createRenderer(options = {}) {
5885
+ const prefer = options.prefer?.length ? options.prefer : DEFAULT_PREFER;
5886
+ return new FallbackRenderer(prefer, options);
5887
+ }
5888
+
5515
5889
  // src/runtime/model-runtime.ts
5516
5890
  function selectModelBackend(backends, json) {
5517
5891
  const hit = backends.find((b) => b.canHandle(json));