@lovelace_lol/embody 0.0.4 → 0.0.6

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/core.js CHANGED
@@ -185,536 +185,18 @@ function numericArrayToNumbers(values) {
185
185
  return Array.from(values);
186
186
  }
187
187
 
188
- // src/core/compositeAxis.ts
189
- function toAUList(value) {
190
- if (value === void 0) return [];
191
- return Array.isArray(value) ? value : [value];
192
- }
193
- function findBoneBindingForNode(auToBones, auId, nodeKey) {
194
- return auToBones[auId]?.find((candidate) => candidate.node === nodeKey) ?? null;
195
- }
196
- function getCompositeAxisValue(axisConfig, getValue) {
197
- if (!axisConfig) return 0;
198
- const negativeAUs = toAUList(axisConfig.negative);
199
- const positiveAUs = toAUList(axisConfig.positive);
200
- if (negativeAUs.length > 0 && positiveAUs.length > 0) {
201
- const negativeValue = Math.max(...negativeAUs.map(getValue), 0);
202
- const positiveValue = Math.max(...positiveAUs.map(getValue), 0);
203
- return positiveValue - negativeValue;
204
- }
205
- if (axisConfig.aus.length > 1) {
206
- return Math.max(...axisConfig.aus.map(getValue), 0);
207
- }
208
- return getValue(axisConfig.aus[0]);
209
- }
210
- function getCompositeAxisBinding(nodeKey, axisConfig, direction, getValue, auToBones) {
211
- if (!axisConfig) return null;
212
- const directionalAUs = direction < 0 ? toAUList(axisConfig.negative) : toAUList(axisConfig.positive);
213
- const candidates = directionalAUs.length > 0 ? directionalAUs : axisConfig.aus;
214
- const ranked = [...candidates].sort((a, b) => getValue(b) - getValue(a));
215
- for (const auId of ranked) {
216
- const binding = findBoneBindingForNode(auToBones, auId, nodeKey);
217
- if (binding) return binding;
218
- }
219
- return null;
220
- }
221
-
222
- // src/mappings/visemeSystem.ts
223
- function hasOwn(value, key) {
224
- return Boolean(value && Object.prototype.hasOwnProperty.call(value, key));
225
- }
226
- function toSlotId(label, index) {
227
- const normalized = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
228
- return normalized || `viseme-${index}`;
229
- }
230
- function normalizeBindingWeight(weight) {
231
- return Number.isFinite(weight) ? Math.max(0, weight ?? 1) : 1;
232
- }
233
- function getProfileVisemeSlots(profile) {
234
- if (profile.visemeSlots && profile.visemeSlots.length > 0) {
235
- return [...profile.visemeSlots].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
236
- }
237
- return (profile.visemeKeys || []).map((key, index) => {
238
- const label = typeof key === "string" && key ? key : `Viseme ${index}`;
239
- return {
240
- id: toSlotId(label, index),
241
- label,
242
- order: index,
243
- defaultJawAmount: profile.visemeJawAmounts?.[index]
244
- };
245
- });
246
- }
247
- function getVisemeSlotIndex(profile, slotId) {
248
- return getProfileVisemeSlots(profile).findIndex((slot) => slot.id === slotId);
249
- }
250
- function getVisemeBindingTargets(profile, visemeIndex) {
251
- const slots = getProfileVisemeSlots(profile);
252
- const slot = slots[visemeIndex];
253
- const binding = slot ? profile.visemeBindings?.[slot.id] : void 0;
254
- const boundTargets = binding?.targets?.filter((target) => target.morph !== void 0 && target.morph !== "").map((target) => ({
255
- morph: target.morph,
256
- weight: normalizeBindingWeight(target.weight)
257
- }));
258
- if (boundTargets && boundTargets.length > 0) {
259
- return boundTargets;
260
- }
261
- if (binding?.morph !== void 0 && binding.morph !== "") {
262
- return [{ morph: binding.morph, weight: 1 }];
263
- }
264
- const legacyTarget = profile.visemeKeys?.[visemeIndex];
265
- return legacyTarget !== void 0 && legacyTarget !== "" ? [{ morph: legacyTarget, weight: 1 }] : [];
266
- }
267
- function getVisemeJawAmounts(profile) {
268
- const slots = getProfileVisemeSlots(profile);
269
- if (slots.length === 0) return profile.visemeJawAmounts ? [...profile.visemeJawAmounts] : void 0;
270
- return slots.map((slot, index) => slot.defaultJawAmount ?? profile.visemeJawAmounts?.[index] ?? 0);
271
- }
272
- function resolveVisemeMeshCategory(profile) {
273
- const morphToMesh = profile.morphToMesh || {};
274
- if (profile.visemeMeshCategory) return profile.visemeMeshCategory;
275
- if (hasOwn(morphToMesh, "viseme")) return "viseme";
276
- return "face";
277
- }
278
- function getMeshNamesForVisemeProfile(profile) {
279
- const morphToMesh = profile.morphToMesh || {};
280
- const category = resolveVisemeMeshCategory(profile);
281
- if (hasOwn(morphToMesh, category)) {
282
- return Array.isArray(morphToMesh[category]) ? [...morphToMesh[category]] : [];
283
- }
284
- return profile.visemeMeshCategory ? [] : [...morphToMesh.face || []];
285
- }
286
- function getMeshNamesForAUProfile(profile, auId) {
287
- const morphToMesh = profile.morphToMesh || {};
288
- const facePart = profile.auInfo?.[String(auId)]?.facePart;
289
- const category = facePart ? profile.auFacePartToMeshCategory?.[facePart] : void 0;
290
- if (category) return Array.isArray(morphToMesh[category]) ? [...morphToMesh[category]] : [];
291
- return [...morphToMesh.face || []];
292
- }
293
-
294
- // src/core/TsRuntimeCore.ts
295
- var identityQuat = { x: 0, y: 0, z: 0, w: 1 };
296
- function isRotationBoneBinding(binding) {
297
- return binding?.channel === "rx" || binding?.channel === "ry" || binding?.channel === "rz";
298
- }
299
- var TsRuntimeCore = class {
300
- constructor(options) {
301
- __publicField(this, "profile");
302
- __publicField(this, "model");
303
- __publicField(this, "auValues", {});
304
- __publicField(this, "auBalances", {});
305
- __publicField(this, "visemeValues");
306
- __publicField(this, "visemeJawScales");
307
- __publicField(this, "mixWeights");
308
- this.profile = options.profile;
309
- this.model = options.model;
310
- this.visemeValues = new Array(getProfileVisemeSlots(this.profile).length).fill(0);
311
- this.visemeJawScales = new Array(this.visemeValues.length).fill(1);
312
- this.mixWeights = { ...this.profile.auMixDefaults };
313
- }
314
- setProfile(profile) {
315
- this.profile = profile;
316
- this.visemeValues = new Array(getProfileVisemeSlots(this.profile).length).fill(0);
317
- this.visemeJawScales = new Array(this.visemeValues.length).fill(1);
318
- this.mixWeights = { ...this.profile.auMixDefaults };
319
- }
320
- setModelDescriptor(model) {
321
- this.model = model;
322
- }
323
- setAU(id, value, balance) {
324
- if (value < 0 && this.profile.continuumPairs) {
325
- const pairInfo = this.profile.continuumPairs[id];
326
- if (pairInfo) {
327
- const negAU = pairInfo.isNegative ? id : pairInfo.pairId;
328
- const posAU = pairInfo.isNegative ? pairInfo.pairId : id;
329
- this.setContinuum(negAU, posAU, pairInfo.isNegative ? -value : value, balance);
330
- return;
331
- }
332
- }
333
- this.auValues[id] = clamp01(value);
334
- if (balance !== void 0) {
335
- this.auBalances[id] = clampBalance(balance);
336
- }
337
- }
338
- getAU(id) {
339
- return this.auValues[id] ?? 0;
340
- }
341
- setContinuum(negAU, posAU, continuumValue, balance) {
342
- const value = Math.max(-1, Math.min(1, continuumValue));
343
- if (value < 0) {
344
- this.setAU(posAU, 0, balance);
345
- this.setAU(negAU, Math.abs(value), balance);
346
- return;
347
- }
348
- if (value > 0) {
349
- this.setAU(negAU, 0, balance);
350
- this.setAU(posAU, value, balance);
351
- return;
352
- }
353
- this.setAU(negAU, 0, balance);
354
- this.setAU(posAU, 0, balance);
355
- }
356
- setViseme(visemeIndex, value, jawScale = 1) {
357
- if (visemeIndex < 0 || visemeIndex >= this.visemeValues.length) return;
358
- this.visemeValues[visemeIndex] = clamp01(value);
359
- this.visemeJawScales[visemeIndex] = jawScale;
360
- }
361
- setVisemeById(slotId, value, jawScale = 1) {
362
- const index = getVisemeSlotIndex(this.profile, slotId);
363
- if (index < 0) return;
364
- this.setViseme(index, value, jawScale);
365
- }
366
- setAUMixWeight(id, weight) {
367
- this.mixWeights[id] = clamp01(weight);
368
- }
369
- evaluateFrameDelta(deltaSeconds) {
370
- const morphWrites = /* @__PURE__ */ new Map();
371
- this.collectAUWrites(morphWrites);
372
- this.collectVisemeWrites(morphWrites);
373
- return {
374
- deltaSeconds,
375
- morphTargets: Array.from(morphWrites.values()),
376
- bones: this.collectBoneWrites()
377
- };
378
- }
379
- collectAUWrites(morphWrites) {
380
- for (const [auIdText, entry] of Object.entries(this.profile.auToMorphs || {})) {
381
- const auId = Number(auIdText);
382
- if (Number.isNaN(auId) || !entry) continue;
383
- const mixWeight = this.isMixedAU(auId) ? this.getAUMixWeight(auId) : 1;
384
- const base = clamp01(this.auValues[auId] ?? 0) * mixWeight;
385
- const { left, right } = computeSideValues(base, this.auBalances[auId]);
386
- const meshNames = getMeshNamesForAUProfile(this.profile, auId);
387
- for (const morph of entry.left || []) {
388
- this.addMorphWrites(morphWrites, morph, meshNames, left);
389
- }
390
- for (const morph of entry.right || []) {
391
- this.addMorphWrites(morphWrites, morph, meshNames, right);
392
- }
393
- for (const morph of entry.center || []) {
394
- this.addMorphWrites(morphWrites, morph, meshNames, base);
395
- }
396
- }
397
- }
398
- collectVisemeWrites(morphWrites) {
399
- const meshNames = getMeshNamesForVisemeProfile(this.profile);
400
- const localMax = /* @__PURE__ */ new Map();
401
- for (let index = 0; index < this.visemeValues.length; index += 1) {
402
- const value = clamp01(this.visemeValues[index] ?? 0);
403
- for (const bindingTarget of getVisemeBindingTargets(this.profile, index)) {
404
- const weighted = clamp01(value * bindingTarget.weight);
405
- for (const resolved of this.resolveMorphTargets(bindingTarget.morph, meshNames)) {
406
- const key = morphWriteKey(resolved.mesh.id, resolved.morphTarget.id);
407
- const existing = localMax.get(key);
408
- if (!existing || weighted > existing.value) {
409
- localMax.set(key, {
410
- meshId: resolved.mesh.id,
411
- morphTargetId: resolved.morphTarget.id,
412
- value: weighted,
413
- mode: "absolute"
414
- });
415
- }
416
- }
417
- }
418
- }
419
- for (const [key, write] of localMax) {
420
- const existing = morphWrites.get(key);
421
- if (!existing || write.value > existing.value) {
422
- morphWrites.set(key, write);
423
- }
424
- }
425
- }
426
- collectBoneWrites() {
427
- const writes = [];
428
- const rotations = this.computeRotationState();
429
- for (const composite of this.profile.compositeRotations || []) {
430
- const bone = this.findBoneDescriptor(composite.node);
431
- if (!bone) continue;
432
- const transform = this.evaluateCompositeTransform(bone, composite, rotations[composite.node]);
433
- if (transform) {
434
- writes.push({
435
- boneId: bone.id,
436
- transform,
437
- mode: "absolute",
438
- space: "local"
439
- });
440
- }
441
- }
442
- for (const [nodeKey, translation] of Object.entries(this.computeTranslations())) {
443
- const bone = this.findBoneDescriptor(nodeKey);
444
- if (!bone) continue;
445
- const position = addVec3(bone.restTransform?.position, translation);
446
- upsertBoneWrite(writes, bone.id, { position });
447
- }
448
- const jawAmount = this.getActiveVisemeJawAmount();
449
- const jawBinding = this.findAutoVisemeJawBinding();
450
- const jawBone = jawBinding ? this.findBoneDescriptor(jawBinding.node) : void 0;
451
- if (jawBone && jawBinding?.maxDegrees && jawBinding.channel) {
452
- const current = writes.find((write) => write.boneId === jawBone.id);
453
- if (jawAmount > 1e-6 || !current?.transform.rotation) {
454
- const rest = jawBone.restTransform?.rotation ?? identityQuat;
455
- const rotation = jawAmount > 1e-6 ? multiplyQuat(
456
- rest,
457
- quatFromChannel(jawBinding.channel, deg2rad(jawBinding.maxDegrees) * jawAmount * jawBinding.scale)
458
- ) : rest;
459
- upsertBoneWrite(writes, jawBone.id, { rotation });
460
- }
461
- }
462
- return writes;
463
- }
464
- computeRotationState() {
465
- const rotations = {};
466
- for (const composite of this.profile.compositeRotations || []) {
467
- rotations[composite.node] = {
468
- pitch: this.getCompositeAxisValueForNode(composite.node, composite.pitch),
469
- yaw: this.getCompositeAxisValueForNode(composite.node, composite.yaw),
470
- roll: this.getCompositeAxisValueForNode(composite.node, composite.roll)
471
- };
472
- }
473
- return rotations;
474
- }
475
- evaluateCompositeTransform(bone, composite, state) {
476
- if (!state) return null;
477
- let rotation = bone.restTransform?.rotation ?? identityQuat;
478
- const applyAxis = (axisConfig, direction) => {
479
- if (!axisConfig || Math.abs(direction) <= 1e-6) return;
480
- const binding = getCompositeAxisBinding(
481
- composite.node,
482
- axisConfig,
483
- direction,
484
- (auId) => this.getEffectiveBoneAUValue(auId, composite.node),
485
- this.profile.auToBones
486
- );
487
- if (!binding?.maxDegrees || !binding.channel) return;
488
- rotation = multiplyQuat(
489
- rotation,
490
- quatFromChannel(binding.channel, deg2rad(binding.maxDegrees) * Math.abs(direction) * binding.scale)
491
- );
492
- };
493
- applyAxis(composite.yaw, state.yaw);
494
- applyAxis(composite.pitch, state.pitch);
495
- applyAxis(composite.roll, state.roll);
496
- return { rotation };
497
- }
498
- computeTranslations() {
499
- const translations = {};
500
- for (const [auIdText, bindings] of Object.entries(this.profile.auToBones || {})) {
501
- const auId = Number(auIdText);
502
- if (Number.isNaN(auId)) continue;
503
- const value = clamp01(this.auValues[auId] ?? 0);
504
- for (const binding of bindings || []) {
505
- if (binding.channel !== "tx" && binding.channel !== "ty" && binding.channel !== "tz") continue;
506
- if (binding.maxUnits === void 0) continue;
507
- const current = translations[binding.node] ?? { x: 0, y: 0, z: 0 };
508
- translations[binding.node] = current;
509
- if (value <= 1e-6) continue;
510
- const offset = clampSigned(value * binding.scale) * binding.maxUnits;
511
- translations[binding.node] = {
512
- x: binding.channel === "tx" ? offset : current.x,
513
- y: binding.channel === "ty" ? offset : current.y,
514
- z: binding.channel === "tz" ? offset : current.z
515
- };
516
- }
517
- }
518
- return translations;
519
- }
520
- addMorphWrites(morphWrites, morph, meshNames, value) {
521
- for (const resolved of this.resolveMorphTargets(morph, meshNames)) {
522
- const key = morphWriteKey(resolved.mesh.id, resolved.morphTarget.id);
523
- const existing = morphWrites.get(key);
524
- if (existing && existing.value >= value) continue;
525
- morphWrites.set(key, {
526
- meshId: resolved.mesh.id,
527
- morphTargetId: resolved.morphTarget.id,
528
- value,
529
- mode: "absolute"
530
- });
531
- }
532
- }
533
- resolveMorphTargets(morph, meshNames) {
534
- const result = [];
535
- for (const meshName of meshNames) {
536
- const mesh = this.model.meshes.find((candidate) => candidate.name === meshName);
537
- if (!mesh) continue;
538
- const morphTargets = mesh.morphTargetIds.map((id) => this.model.morphTargets.find((candidate) => candidate.id === id)).filter((target2) => !!target2);
539
- const target = typeof morph === "number" ? morphTargets.find((candidate) => candidate.hostIndex === morph) : resolveMorphTargetByName(morphTargets, morph, this.profile);
540
- if (target) {
541
- result.push({ mesh, morphTarget: target });
542
- }
543
- }
544
- return result;
545
- }
546
- findBoneDescriptor(nodeKey) {
547
- const configuredName = this.profile.boneNodes[nodeKey] || nodeKey;
548
- const prefix = this.profile.bonePrefix ?? "";
549
- const suffix = this.profile.boneSuffix ?? "";
550
- const prefixedName = prefix && !configuredName.startsWith(prefix) ? `${prefix}${configuredName}` : configuredName;
551
- const fullName = suffix && !prefixedName.endsWith(suffix) ? `${prefixedName}${suffix}` : prefixedName;
552
- const candidates = /* @__PURE__ */ new Set([nodeKey, configuredName, fullName]);
553
- return this.model.bones.find((bone) => candidates.has(bone.name));
554
- }
555
- findAutoVisemeJawBinding() {
556
- const candidates = [
557
- this.profile.auToBones[103]?.find(isRotationBoneBinding),
558
- this.profile.auToBones[26]?.find(isRotationBoneBinding)
559
- ].filter((binding) => !!binding);
560
- return candidates.find((binding) => !!this.findBoneDescriptor(binding.node)) ?? candidates[0] ?? null;
561
- }
562
- getActiveVisemeJawAmount() {
563
- const jawAmounts = getVisemeJawAmounts(this.profile) ?? [];
564
- let jawAmount = 0;
565
- for (let index = 0; index < this.visemeValues.length; index += 1) {
566
- const value = clamp01(this.visemeValues[index] ?? 0);
567
- if (value <= 1e-6) continue;
568
- const jawScale = this.visemeJawScales[index] ?? 1;
569
- if (Math.abs(jawScale) <= 1e-6) continue;
570
- jawAmount = Math.max(jawAmount, (jawAmounts[index] ?? 0) * value * jawScale);
571
- }
572
- return jawAmount;
573
- }
574
- getCompositeAxisValueForNode(nodeKey, axisConfig) {
575
- return getCompositeAxisValue(axisConfig, (auId) => this.getEffectiveBoneAUValue(auId, nodeKey));
576
- }
577
- getEffectiveBoneAUValue(auId, nodeKey) {
578
- const rawValue = clamp01(this.auValues[auId] ?? 0);
579
- if (rawValue <= 1e-6) return 0;
580
- const binding = this.profile.auToBones[auId]?.find((candidate) => candidate.node === nodeKey) ?? null;
581
- if (!binding?.side) return rawValue;
582
- return rawValue * getSideScale(this.auBalances[auId] ?? 0, binding.side);
583
- }
584
- getAUMixWeight(auId) {
585
- return this.mixWeights[auId] ?? this.profile.auMixDefaults?.[auId] ?? 1;
586
- }
587
- isMixedAU(auId) {
588
- const morphs = this.profile.auToMorphs[auId];
589
- const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
590
- return !!(hasMorphs && this.profile.auToBones[auId]?.length);
591
- }
592
- };
593
- function resolveMorphTargetByName(targets, key, profile) {
594
- const prefix = profile.morphPrefix || "";
595
- const suffix = profile.morphSuffix || "";
596
- const fullName = prefix + key + suffix;
597
- const exact = targets.find((target) => target.name === fullName);
598
- if (exact) return exact;
599
- const suffixRegex = profile.suffixPattern ? new RegExp(profile.suffixPattern) : null;
600
- if (!suffixRegex) return void 0;
601
- return targets.find((target) => {
602
- if (!target.name.startsWith(fullName)) return false;
603
- const candidateSuffix = target.name.slice(fullName.length);
604
- return candidateSuffix === "" || suffixRegex.test(candidateSuffix);
605
- });
606
- }
607
- function computeSideValues(base, balance) {
608
- const b = clampBalance(balance ?? 0);
609
- if (b === 0) return { left: base, right: base };
610
- if (b < 0) return { left: base, right: base * (1 + b) };
611
- return { left: base * (1 - b), right: base };
612
- }
613
- function getSideScale(balance, side) {
614
- if (side !== "left" && side !== "right") return 1;
615
- const b = clampBalance(balance);
616
- if (side === "left") return b > 0 ? 1 - b : 1;
617
- return b < 0 ? 1 + b : 1;
618
- }
619
- function addVec3(base, offset) {
620
- return {
621
- x: (base?.x ?? 0) + offset.x,
622
- y: (base?.y ?? 0) + offset.y,
623
- z: (base?.z ?? 0) + offset.z
624
- };
625
- }
626
- function upsertBoneWrite(writes, boneId, transform) {
627
- const existingIndex = writes.findIndex((write) => write.boneId === boneId);
628
- if (existingIndex >= 0) {
629
- const existing = writes[existingIndex];
630
- writes[existingIndex] = {
631
- ...existing,
632
- transform: {
633
- ...existing.transform,
634
- ...transform
635
- }
636
- };
637
- return;
638
- }
639
- writes.push({
640
- boneId,
641
- transform,
642
- mode: "absolute",
643
- space: "local"
644
- });
645
- }
646
- function quatFromChannel(channel, radians) {
647
- if (channel === "rx") return quatFromAxis({ x: 1, y: 0, z: 0 }, radians);
648
- if (channel === "ry") return quatFromAxis({ x: 0, y: 1, z: 0 }, radians);
649
- return quatFromAxis({ x: 0, y: 0, z: 1 }, radians);
650
- }
651
- function quatFromAxis(axis, radians) {
652
- const half = radians / 2;
653
- const s = Math.sin(half);
654
- return normalizeQuat({
655
- x: axis.x * s,
656
- y: axis.y * s,
657
- z: axis.z * s,
658
- w: Math.cos(half)
659
- });
660
- }
661
- function multiplyQuat(a, b) {
662
- return normalizeQuat({
663
- x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
664
- y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
665
- z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
666
- w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
667
- });
668
- }
669
- function normalizeQuat(q) {
670
- const len = Math.hypot(q.x, q.y, q.z, q.w);
671
- if (len <= 1e-12) return identityQuat;
672
- return {
673
- x: q.x / len,
674
- y: q.y / len,
675
- z: q.z / len,
676
- w: q.w / len
677
- };
678
- }
679
- function deg2rad(degrees) {
680
- return degrees * Math.PI / 180;
681
- }
682
- function clamp01(value) {
683
- if (!Number.isFinite(value)) return 0;
684
- return value < 0 ? 0 : value > 1 ? 1 : value;
685
- }
686
- function clampSigned(value) {
687
- if (!Number.isFinite(value)) return 0;
688
- return value < -1 ? -1 : value > 1 ? 1 : value;
689
- }
690
- function clampBalance(value) {
691
- if (!Number.isFinite(value)) return 0;
692
- return Math.max(-1, Math.min(1, value));
693
- }
694
- function morphWriteKey(meshId, morphTargetId) {
695
- return `${meshId}:${morphTargetId}`;
696
- }
697
-
698
188
  // src/core/WasmRuntimeCore.ts
699
- var AU_MORPH_BINDING_STRIDE = 5;
700
- var VISEME_MORPH_BINDING_STRIDE = 4;
701
- var AXIS_GROUP_NEGATIVE = 0;
702
- var AXIS_GROUP_POSITIVE = 1;
703
- var AXIS_GROUP_PLAIN = 2;
704
- var BONE_SIDE_NONE = 0;
705
- var BONE_SIDE_LEFT = 1;
706
- var BONE_SIDE_RIGHT = 2;
707
- var ROTATION_CHANNELS = { rx: 0, ry: 1, rz: 2 };
708
- var TRANSLATION_CHANNELS = { tx: 0, ty: 1, tz: 2 };
709
189
  var WasmRuntimeCore = class {
710
190
  constructor(options) {
711
191
  __publicField(this, "wasm");
712
192
  __publicField(this, "core");
193
+ __publicField(this, "allowEmptyBindings");
713
194
  __publicField(this, "profile");
714
195
  __publicField(this, "model");
715
196
  this.wasm = options.wasm;
716
197
  this.profile = options.profile;
717
198
  this.model = options.model;
199
+ this.allowEmptyBindings = options.allowEmptyBindings ?? false;
718
200
  const RuntimeCtor = options.wasm.RuntimeCore;
719
201
  if (!RuntimeCtor) {
720
202
  throw new Error("Embody Wasm module does not export RuntimeCore");
@@ -733,6 +215,24 @@ var WasmRuntimeCore = class {
733
215
  setAU(id, value, balance = 0) {
734
216
  this.core.set_au(id >>> 0, value, balance);
735
217
  }
218
+ setContinuum(negativeAU, positiveAU, value, balance = 0) {
219
+ this.core.set_continuum(negativeAU >>> 0, positiveAU >>> 0, value, balance);
220
+ }
221
+ transitionAU(id, to, durationMs, balance = Number.NaN) {
222
+ this.core.transition_au(id >>> 0, to, durationMs, balance);
223
+ }
224
+ transitionViseme(visemeIndex, to, durationMs, jawScale = 1) {
225
+ this.core.transition_viseme(visemeIndex >>> 0, to, durationMs, jawScale);
226
+ }
227
+ update(deltaSeconds) {
228
+ return this.core.update(deltaSeconds);
229
+ }
230
+ activeTransitionCount() {
231
+ return this.core.active_transition_count();
232
+ }
233
+ clearTransitions() {
234
+ this.core.clear_transitions();
235
+ }
736
236
  getAU(id) {
737
237
  return this.core.get_au(id >>> 0);
738
238
  }
@@ -768,182 +268,15 @@ var WasmRuntimeCore = class {
768
268
  };
769
269
  }
770
270
  reloadBindings() {
771
- this.core.configure_with_profile(JSON.stringify(this.profile), JSON.stringify(this.model));
772
- }
773
- };
774
- function compileMorphBindings(profile, model) {
775
- const auRows = [];
776
- const mixed = [];
777
- for (const [auIdText, entry] of Object.entries(profile.auToMorphs || {})) {
778
- const auId = Number(auIdText);
779
- if (Number.isNaN(auId) || !entry) continue;
780
- const meshNames = getMeshNamesForAUProfile(profile, auId);
781
- const hasMorphs = !!(entry.left?.length || entry.right?.length || entry.center?.length);
782
- if (hasMorphs && profile.auToBones?.[auId]?.length) {
783
- mixed.push(auId);
784
- }
785
- pushAuSide(auRows, auId, 0, entry.left || [], meshNames, model, profile);
786
- pushAuSide(auRows, auId, 1, entry.right || [], meshNames, model, profile);
787
- pushAuSide(auRows, auId, 2, entry.center || [], meshNames, model, profile);
788
- }
789
- const visemeRows = [];
790
- const visemeMeshNames = getMeshNamesForVisemeProfile(profile);
791
- const slotCount = getProfileVisemeSlots(profile).length;
792
- for (let index = 0; index < slotCount; index += 1) {
793
- for (const bindingTarget of getVisemeBindingTargets(profile, index)) {
794
- for (const resolved of resolveMorphTargets(model, profile, bindingTarget.morph, visemeMeshNames)) {
795
- visemeRows.push(
796
- index,
797
- Number(resolved.mesh.id),
798
- Number(resolved.morphTarget.id),
799
- bindingTarget.weight
800
- );
801
- }
271
+ const profileJson = JSON.stringify(this.profile);
272
+ const modelJson = JSON.stringify(this.model);
273
+ if (this.allowEmptyBindings) {
274
+ this.core.configure(profileJson, modelJson);
275
+ } else {
276
+ this.core.configure_with_profile(profileJson, modelJson);
802
277
  }
803
278
  }
804
- return {
805
- auBindings: Float32Array.from(auRows),
806
- visemeBindings: Float32Array.from(visemeRows),
807
- mixedAus: Uint32Array.from(mixed)
808
- };
809
- }
810
- function compileBoneBindings(profile, model) {
811
- const referencedBones = /* @__PURE__ */ new Map();
812
- const findBone = (nodeKey) => {
813
- const bone = findBoneDescriptor(profile, model, nodeKey);
814
- if (bone) referencedBones.set(bone.id, bone);
815
- return bone;
816
- };
817
- const boneSideForAU = (auId, nodeKey) => {
818
- const side = profile.auToBones?.[auId]?.find((candidate) => candidate.node === nodeKey)?.side;
819
- if (side === "left") return BONE_SIDE_LEFT;
820
- if (side === "right") return BONE_SIDE_RIGHT;
821
- return BONE_SIDE_NONE;
822
- };
823
- const compositeRows = [];
824
- for (const composite of profile.compositeRotations || []) {
825
- const bone = findBone(composite.node);
826
- if (!bone) continue;
827
- const axes = [
828
- { axis: 1, config: composite.yaw },
829
- { axis: 0, config: composite.pitch },
830
- { axis: 2, config: composite.roll }
831
- ];
832
- for (const { axis, config } of axes) {
833
- if (!config) continue;
834
- const negativeAUs = toAUList(config.negative);
835
- const positiveAUs = toAUList(config.positive);
836
- const hasDirectional = negativeAUs.length > 0 && positiveAUs.length > 0;
837
- const valueRows = [];
838
- if (hasDirectional) {
839
- for (const auId of negativeAUs) {
840
- valueRows.push([auId, AXIS_GROUP_NEGATIVE, boneSideForAU(auId, composite.node)]);
841
- }
842
- for (const auId of positiveAUs) {
843
- valueRows.push([auId, AXIS_GROUP_POSITIVE, boneSideForAU(auId, composite.node)]);
844
- }
845
- } else {
846
- for (const auId of config.aus) {
847
- valueRows.push([auId, AXIS_GROUP_PLAIN, boneSideForAU(auId, composite.node)]);
848
- }
849
- }
850
- const bindingRows = [];
851
- const pushBindingRows = (auIds, group) => {
852
- for (const auId of auIds) {
853
- const binding = profile.auToBones?.[auId]?.find(
854
- (candidate) => candidate.node === composite.node
855
- );
856
- if (!binding?.maxDegrees) continue;
857
- const channel = ROTATION_CHANNELS[binding.channel];
858
- if (channel === void 0) continue;
859
- bindingRows.push([
860
- auId,
861
- group,
862
- boneSideForAU(auId, composite.node),
863
- channel,
864
- binding.scale,
865
- binding.maxDegrees
866
- ]);
867
- }
868
- };
869
- pushBindingRows(negativeAUs, AXIS_GROUP_NEGATIVE);
870
- pushBindingRows(positiveAUs, AXIS_GROUP_POSITIVE);
871
- pushBindingRows(config.aus, AXIS_GROUP_PLAIN);
872
- compositeRows.push(
873
- Number(bone.id),
874
- axis,
875
- hasDirectional ? 1 : 0,
876
- valueRows.length,
877
- bindingRows.length,
878
- 0,
879
- 0,
880
- 0
881
- );
882
- for (const row of valueRows) compositeRows.push(...row);
883
- for (const row of bindingRows) compositeRows.push(...row);
884
- }
885
- }
886
- const translationRows = [];
887
- for (const [auIdText, bindings] of Object.entries(profile.auToBones || {})) {
888
- const auId = Number(auIdText);
889
- if (Number.isNaN(auId)) continue;
890
- for (const binding of bindings || []) {
891
- const channel = TRANSLATION_CHANNELS[binding.channel];
892
- if (channel === void 0 || binding.maxUnits === void 0) continue;
893
- const bone = findBone(binding.node);
894
- if (!bone) continue;
895
- translationRows.push(auId, Number(bone.id), channel, binding.scale, binding.maxUnits);
896
- }
897
- }
898
- const jawRow = [];
899
- const jawBinding = findAutoVisemeJawBinding(profile, model);
900
- if (jawBinding?.maxDegrees) {
901
- const bone = findBone(jawBinding.node);
902
- const channel = ROTATION_CHANNELS[jawBinding.channel];
903
- if (bone && channel !== void 0) {
904
- jawRow.push(Number(bone.id), channel, jawBinding.scale, jawBinding.maxDegrees);
905
- }
906
- }
907
- const restRows = [];
908
- for (const bone of referencedBones.values()) {
909
- const position = bone.restTransform?.position;
910
- const rotation = bone.restTransform?.rotation;
911
- restRows.push(
912
- Number(bone.id),
913
- position?.x ?? 0,
914
- position?.y ?? 0,
915
- position?.z ?? 0,
916
- rotation?.x ?? 0,
917
- rotation?.y ?? 0,
918
- rotation?.z ?? 0,
919
- rotation?.w ?? 1
920
- );
921
- }
922
- return {
923
- restTransforms: Float32Array.from(restRows),
924
- compositeAxes: Float32Array.from(compositeRows),
925
- translations: Float32Array.from(translationRows),
926
- jawBinding: Float32Array.from(jawRow),
927
- visemeJawAmounts: Float32Array.from(getVisemeJawAmounts(profile) ?? [])
928
- };
929
- }
930
- function findBoneDescriptor(profile, model, nodeKey) {
931
- const configuredName = profile.boneNodes?.[nodeKey] || nodeKey;
932
- const prefix = profile.bonePrefix ?? "";
933
- const suffix = profile.boneSuffix ?? "";
934
- const prefixedName = prefix && !configuredName.startsWith(prefix) ? `${prefix}${configuredName}` : configuredName;
935
- const fullName = suffix && !prefixedName.endsWith(suffix) ? `${prefixedName}${suffix}` : prefixedName;
936
- const candidates = /* @__PURE__ */ new Set([nodeKey, configuredName, fullName]);
937
- return model.bones.find((bone) => candidates.has(bone.name));
938
- }
939
- function findAutoVisemeJawBinding(profile, model) {
940
- const isRotation = (binding) => binding?.channel === "rx" || binding?.channel === "ry" || binding?.channel === "rz";
941
- const candidates = [
942
- profile.auToBones?.[103]?.find(isRotation),
943
- profile.auToBones?.[26]?.find(isRotation)
944
- ].filter((binding) => !!binding);
945
- return candidates.find((binding) => !!findBoneDescriptor(profile, model, binding.node)) ?? candidates[0] ?? null;
946
- }
279
+ };
947
280
  function unpackBoneFrameDelta(values) {
948
281
  const stride = PACKED_BONE_FRAME_DELTA_STRIDE;
949
282
  const writes = [];
@@ -984,47 +317,7 @@ function unpackMorphFrameDelta(values) {
984
317
  }
985
318
  return writes;
986
319
  }
987
- function pushAuSide(rows, auId, side, morphs, meshNames, model, profile) {
988
- for (const morph of morphs) {
989
- for (const resolved of resolveMorphTargets(model, profile, morph, meshNames)) {
990
- rows.push(
991
- auId,
992
- side,
993
- Number(resolved.mesh.id),
994
- Number(resolved.morphTarget.id),
995
- 1
996
- );
997
- }
998
- }
999
- }
1000
- function resolveMorphTargets(model, profile, morph, meshNames) {
1001
- const result = [];
1002
- for (const meshName of meshNames) {
1003
- const mesh = model.meshes.find((candidate) => candidate.name === meshName);
1004
- if (!mesh) continue;
1005
- const morphTargets = mesh.morphTargetIds.map((id) => model.morphTargets.find((candidate) => candidate.id === id)).filter((target2) => !!target2);
1006
- const target = typeof morph === "number" ? morphTargets.find((candidate) => candidate.hostIndex === morph) : resolveMorphTargetByName2(morphTargets, morph, profile);
1007
- if (target) {
1008
- result.push({ mesh, morphTarget: target });
1009
- }
1010
- }
1011
- return result;
1012
- }
1013
- function resolveMorphTargetByName2(targets, key, profile) {
1014
- const prefix = profile.morphPrefix || "";
1015
- const suffix = profile.morphSuffix || "";
1016
- const fullName = prefix + key + suffix;
1017
- const exact = targets.find((target) => target.name === fullName);
1018
- if (exact) return exact;
1019
- const suffixRegex = profile.suffixPattern ? new RegExp(profile.suffixPattern) : null;
1020
- if (!suffixRegex) return void 0;
1021
- return targets.find((target) => {
1022
- if (!target.name.startsWith(fullName)) return false;
1023
- const candidateSuffix = target.name.slice(fullName.length);
1024
- return candidateSuffix === "" || suffixRegex.test(candidateSuffix);
1025
- });
1026
- }
1027
320
 
1028
- export { AU_MORPH_BINDING_STRIDE, MESH_PROPORTIONS_FIELDS, MESH_PROPORTIONS_STRIDE, TEMPLATE_SKELETON_FIT_METADATA_KIND, TEMPLATE_SKELETON_FIT_METADATA_VERSION, TEMPLATE_SKELETON_FIT_SOLUTION_FIELDS, TEMPLATE_SKELETON_FIT_SOLUTION_STRIDE, TEMPLATE_SKELETON_FIT_STATUSES, TEMPLATE_SKELETON_FIT_TRANSFORM_FIELDS, TEMPLATE_SKELETON_FIT_TRANSFORM_STRIDE, TEMPLATE_SKELETON_FIT_VERTICAL_ANCHORS, TEMPLATE_SKELETON_FIT_VERTICAL_AXES, TsClipCompiler, TsRuntimeCore, VISEME_MORPH_BINDING_STRIDE, WasmRuntimeCore, compileBoneBindings, compileMorphBindings, composeTemplateSkeletonFitTransform, isTemplateSkeletonFitStatus, numericArrayToNumbers, unpackBoneFrameDelta, unpackMorphFrameDelta, validateTemplateSkeletonFitMetadata };
321
+ export { MESH_PROPORTIONS_FIELDS, MESH_PROPORTIONS_STRIDE, TEMPLATE_SKELETON_FIT_METADATA_KIND, TEMPLATE_SKELETON_FIT_METADATA_VERSION, TEMPLATE_SKELETON_FIT_SOLUTION_FIELDS, TEMPLATE_SKELETON_FIT_SOLUTION_STRIDE, TEMPLATE_SKELETON_FIT_STATUSES, TEMPLATE_SKELETON_FIT_TRANSFORM_FIELDS, TEMPLATE_SKELETON_FIT_TRANSFORM_STRIDE, TEMPLATE_SKELETON_FIT_VERTICAL_ANCHORS, TEMPLATE_SKELETON_FIT_VERTICAL_AXES, TsClipCompiler, WasmRuntimeCore, composeTemplateSkeletonFitTransform, isTemplateSkeletonFitStatus, numericArrayToNumbers, unpackBoneFrameDelta, unpackMorphFrameDelta, validateTemplateSkeletonFitMetadata };
1029
322
  //# sourceMappingURL=core.js.map
1030
323
  //# sourceMappingURL=core.js.map