@doki-land/live2d-renderer 0.0.18 → 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) {
@@ -3611,16 +3648,81 @@ function moc2ParamGetterFromValues(params, values) {
3611
3648
  }
3612
3649
  return (id) => byId.get(id) ?? 0;
3613
3650
  }
3614
- function normalizePositions(positions, canvasWidth, canvasHeight) {
3651
+ function normalizePositionsInto(positions, canvasWidth, canvasHeight, out) {
3615
3652
  const w = canvasWidth > 0 ? canvasWidth : 1;
3616
3653
  const h = canvasHeight > 0 ? canvasHeight : 1;
3617
- const out = new Float32Array(positions.length);
3618
3654
  for (let i = 0; i + 1 < positions.length; i += 2) {
3619
3655
  out[i] = positions[i] / w * 2 - 1;
3620
3656
  out[i + 1] = 1 - positions[i + 1] / h * 2;
3621
3657
  }
3658
+ }
3659
+ function normalizePositions(positions, canvasWidth, canvasHeight) {
3660
+ const out = new Float32Array(positions.length);
3661
+ normalizePositionsInto(positions, canvasWidth, canvasHeight, out);
3622
3662
  return out;
3623
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
+ }
3624
3726
  function lowerDrawable(mesh, getParam, ops, canvasWidth, canvasHeight, partVisible) {
3625
3727
  const floatCount = mesh.numPoints * 2;
3626
3728
  const local = interpolateKeyforms(
@@ -4851,15 +4953,24 @@ function meanGlueSeamDistance(positionsByMesh, glue) {
4851
4953
  }
4852
4954
 
4853
4955
  // src/moc/moc3-to-program.ts
4854
- function normalizePositions2(positions, canvasWidth, canvasHeight, pixelsPerUnit) {
4956
+ function normalizePositionsInto2(positions, canvasWidth, canvasHeight, pixelsPerUnit, out) {
4855
4957
  const ppu = pixelsPerUnit > 0 ? pixelsPerUnit : 1;
4856
4958
  const hw = canvasWidth > 0 ? canvasWidth / ppu * 0.5 : 0.5;
4857
4959
  const hh = canvasHeight > 0 ? canvasHeight / ppu * 0.5 : 0.5;
4858
- const out = new Float32Array(positions.length);
4859
4960
  for (let i = 0; i + 1 < positions.length; i += 2) {
4860
4961
  out[i] = positions[i] / hw;
4861
4962
  out[i + 1] = -positions[i + 1] / hh;
4862
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
+ );
4863
4974
  return out;
4864
4975
  }
4865
4976
  function moc3DocumentToProgram(doc, options = {}) {
@@ -5045,6 +5156,155 @@ function moc3DocumentToProgram(doc, options = {}) {
5045
5156
  drawables
5046
5157
  };
5047
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
+ }
5048
5308
 
5049
5309
  // src/moc/decode.ts
5050
5310
  function readMagic2(bytes) {
@@ -5095,49 +5355,90 @@ async function decodeMoc3(bytes) {
5095
5355
 
5096
5356
  // src/moc/moc2.ts
5097
5357
  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
- };
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 };
5113
5400
  }
5114
- function paramFingerprint(values) {
5115
- let s = "";
5116
- for (let i = 0; i < values.length; i++) {
5117
- 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;
5118
5405
  }
5119
- return s;
5120
5406
  }
5121
- 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
+ }
5122
5413
  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;
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
+ });
5128
5440
  }
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);
5441
+ return { timeSeconds, drawables };
5141
5442
  }
5142
5443
  var Moc2Backend = class {
5143
5444
  format = "moc2";
@@ -5158,17 +5459,30 @@ var Moc2Backend = class {
5158
5459
  const moc = shared?.moc2Model ?? new Moc2Parser(bytes).parseModel();
5159
5460
  const program = moc2ModelToProgram(moc);
5160
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 = [];
5161
5469
  const model = {
5162
5470
  id: settings.name ?? settings.url,
5163
5471
  settings,
5164
5472
  format: "moc2"
5165
5473
  };
5166
- stateByModel.set(model, {
5474
+ const state = {
5167
5475
  moc,
5168
5476
  instance,
5169
- lastFrame: null,
5170
- bakedFingerprint: paramFingerprint(instance.parameterValues)
5171
- });
5477
+ meshes,
5478
+ drawView,
5479
+ byId,
5480
+ poseDirty: true,
5481
+ bindings,
5482
+ paramIndexById
5483
+ };
5484
+ bakePose(state);
5485
+ stateByModel.set(model, state);
5172
5486
  return model;
5173
5487
  }
5174
5488
  updateModel(model, deltaTimeSeconds) {
@@ -5176,46 +5490,43 @@ var Moc2Backend = class {
5176
5490
  if (!state) return;
5177
5491
  state.instance.timeSeconds += deltaTimeSeconds;
5178
5492
  bakePose(state);
5179
- if (state.lastFrame) {
5180
- state.lastFrame = {
5181
- ...state.lastFrame,
5182
- timeSeconds: state.instance.timeSeconds
5183
- };
5184
- }
5185
5493
  }
5186
5494
  getDrawables(model) {
5187
5495
  const state = stateByModel.get(model);
5188
5496
  if (!state) return [];
5189
5497
  bakePose(state);
5190
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5191
- state.lastFrame = frame;
5192
- return frame.drawables.map(toDrawableMesh);
5498
+ return state.drawView;
5193
5499
  }
5194
5500
  captureFrame(model) {
5195
5501
  const state = stateByModel.get(model);
5196
5502
  if (!state) return null;
5197
5503
  bakePose(state);
5198
- const frame = state.lastFrame ?? evaluateFrame(state.instance);
5199
- state.lastFrame = frame;
5200
- return frame;
5504
+ return frameFromMeshes(state.drawView, state.instance.timeSeconds);
5201
5505
  }
5202
5506
  setParameter(model, id, value) {
5203
5507
  const state = stateByModel.get(model);
5204
5508
  if (!state) return;
5205
5509
  setParameterValue(state.instance, id, value);
5206
- state.lastFrame = null;
5207
- 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);
5208
5523
  }
5209
5524
  listParameters(model) {
5210
5525
  const state = stateByModel.get(model);
5211
5526
  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
- }));
5527
+ bakePose(state);
5528
+ syncBindingValues(state);
5529
+ return state.bindings;
5219
5530
  }
5220
5531
  hitTest(_model, _x, _y) {
5221
5532
  return null;
@@ -5223,6 +5534,10 @@ var Moc2Backend = class {
5223
5534
  destroyModel(model) {
5224
5535
  stateByModel.delete(model);
5225
5536
  }
5537
+ /** Test/harness: stable program + instance refs. */
5538
+ getResidentInstance(model) {
5539
+ return stateByModel.get(model)?.instance ?? null;
5540
+ }
5226
5541
  };
5227
5542
  function createMoc2Backend() {
5228
5543
  return new Moc2Backend();
@@ -5261,11 +5576,12 @@ function cascadedPartOpacity(tables, artMeshIndex, overrides) {
5261
5576
  }
5262
5577
 
5263
5578
  // src/moc/moc3.ts
5264
- function toDrawableMesh2(d) {
5265
- return {
5579
+ var stateByModel2 = /* @__PURE__ */ new WeakMap();
5580
+ function allocateMeshesFromProgram2(program) {
5581
+ return program.drawables.map((d) => ({
5266
5582
  index: d.index,
5267
5583
  textureIndex: d.textureIndex,
5268
- vertexPositions: d.positions,
5584
+ vertexPositions: new Float32Array(d.positions),
5269
5585
  uvs: d.uvs,
5270
5586
  indices: d.indices,
5271
5587
  opacity: d.opacity,
@@ -5275,34 +5591,82 @@ function toDrawableMesh2(d) {
5275
5591
  dynamicFlag: true,
5276
5592
  maskIndices: [...d.maskIndices],
5277
5593
  visible: d.visible
5278
- };
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 };
5279
5611
  }
5280
- function paramFingerprint2(values) {
5281
- let s = "";
5282
- for (let i = 0; i < values.length; i++) {
5283
- 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);
5284
5633
  }
5285
- return s;
5286
5634
  }
5287
- var stateByModel2 = /* @__PURE__ */ new WeakMap();
5288
5635
  function bakePose2(state) {
5289
- if (!state.doc) {
5290
- state.lastFrame = evaluateFrame(state.instance);
5291
- 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);
5292
5647
  }
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);
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 };
5306
5670
  }
5307
5671
  var Moc3Backend = class {
5308
5672
  format = "moc3";
@@ -5330,18 +5694,39 @@ var Moc3Backend = class {
5330
5694
  program = moc3DocumentToProgram(doc);
5331
5695
  }
5332
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);
5333
5710
  const model = {
5334
5711
  id: settings.name ?? settings.url,
5335
5712
  settings,
5336
5713
  format: "moc3"
5337
5714
  };
5338
- stateByModel2.set(model, {
5715
+ const state = {
5339
5716
  doc,
5340
5717
  instance,
5341
- lastFrame: null,
5342
- bakedFingerprint: paramFingerprint2(instance.parameterValues),
5343
- partOpacity: /* @__PURE__ */ new Map()
5344
- });
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);
5345
5730
  return model;
5346
5731
  }
5347
5732
  updateModel(model, deltaTimeSeconds) {
@@ -5349,49 +5734,34 @@ var Moc3Backend = class {
5349
5734
  if (!state) return;
5350
5735
  state.instance.timeSeconds += deltaTimeSeconds;
5351
5736
  bakePose2(state);
5352
- if (state.lastFrame) {
5353
- state.lastFrame = {
5354
- ...state.lastFrame,
5355
- timeSeconds: state.instance.timeSeconds
5356
- };
5357
- }
5737
+ applyPartOpacity(state);
5358
5738
  }
5359
5739
  getDrawables(model) {
5360
5740
  const state = stateByModel2.get(model);
5361
5741
  if (!state) return [];
5362
5742
  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
- });
5743
+ applyPartOpacity(state);
5744
+ return state.drawView;
5372
5745
  }
5373
5746
  captureFrame(model) {
5374
5747
  const state = stateByModel2.get(model);
5375
5748
  if (!state) return null;
5376
5749
  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
- };
5750
+ applyPartOpacity(state);
5751
+ return frameFromMeshes2(state.drawView, state.instance.timeSeconds);
5388
5752
  }
5389
5753
  setParameter(model, id, value) {
5390
5754
  const state = stateByModel2.get(model);
5391
5755
  if (!state) return;
5392
5756
  setParameterValue(state.instance, id, value);
5393
- state.lastFrame = null;
5394
- 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;
5395
5765
  }
5396
5766
  setPartOpacity(model, id, value) {
5397
5767
  const state = stateByModel2.get(model);
@@ -5399,16 +5769,17 @@ var Moc3Backend = class {
5399
5769
  const v = Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 1;
5400
5770
  state.partOpacity.set(id, v);
5401
5771
  }
5772
+ resolveParameter(model, id) {
5773
+ const state = stateByModel2.get(model);
5774
+ if (!state) return void 0;
5775
+ return state.paramIndexById.get(id);
5776
+ }
5402
5777
  listParameters(model) {
5403
5778
  const state = stateByModel2.get(model);
5404
5779
  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
- }));
5780
+ bakePose2(state);
5781
+ syncBindingValues2(state);
5782
+ return state.bindings;
5412
5783
  }
5413
5784
  hitTest(_model, _x, _y) {
5414
5785
  return null;
@@ -5416,6 +5787,9 @@ var Moc3Backend = class {
5416
5787
  destroyModel(model) {
5417
5788
  stateByModel2.delete(model);
5418
5789
  }
5790
+ getResidentInstance(model) {
5791
+ return stateByModel2.get(model)?.instance ?? null;
5792
+ }
5419
5793
  };
5420
5794
  function createMoc3Backend() {
5421
5795
  return new Moc3Backend();