@lovelace_lol/loom3 1.0.32 → 1.0.34

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/README.md CHANGED
@@ -27,9 +27,9 @@ Use the README in one of these paths:
27
27
  - Skeletal-only character: [Creating Skeletal Animation Presets](#6-creating-skeletal-animation-presets) -> [Baked Animations](#16-baked-animations) -> [Regions & Geometry Helpers](#17-regions--geometry-helpers)
28
28
  - Annotation or camera tooling: [Preset Selection & Validation](#3-preset-selection--validation) -> [Getting to Know Your Character](#4-getting-to-know-your-character) -> [Regions & Geometry Helpers](#17-regions--geometry-helpers)
29
29
 
30
- ## Production Playground
30
+ ## Demo Site Links
31
31
 
32
- These links open the production LoomLarge drawer on the matching tab. Production currently supports stable `drawer` + `tab` deep links, so the README should lean on tab-specific links instead of pretending it can deep-link to a fully reconstructed authoring state.
32
+ These demo site links open the LoomLarge drawer on the matching tab. The demo site currently supports stable `drawer` + `tab` deep links, so the README should lean on tab-specific links instead of pretending it can deep-link to a fully reconstructed authoring state.
33
33
 
34
34
  | Goal | Open in LoomLarge |
35
35
  |------|-------------------|
@@ -308,7 +308,7 @@ import { Loom3, CC4_PRESET } from '@lovelace_lol/loom3';
308
308
  const loom = new Loom3({ profile: CC4_PRESET });
309
309
  ```
310
310
 
311
- You can also resolve presets by name and apply overrides without cloning the full preset:
311
+ You can also look up presets by name and extend them without cloning the full preset:
312
312
 
313
313
  ```typescript
314
314
  import { Loom3 } from '@lovelace_lol/loom3';
@@ -386,13 +386,13 @@ Use preset helpers when you want a stable entry point by model class instead of
386
386
 
387
387
  ```typescript
388
388
  import {
389
- resolvePreset,
390
- resolvePresetWithOverrides,
389
+ getPreset,
390
+ getPresetWithProfile,
391
391
  } from '@lovelace_lol/loom3';
392
392
 
393
- const preset = resolvePreset('cc4');
393
+ const preset = getPreset('cc4');
394
394
 
395
- const resolved = resolvePresetWithOverrides('cc4', {
395
+ const extended = getPresetWithProfile('cc4', {
396
396
  morphToMesh: { face: ['Object_9'] },
397
397
  });
398
398
  ```
@@ -566,10 +566,10 @@ import {
566
566
  analyzeModel,
567
567
  validateMappings,
568
568
  generateMappingCorrections,
569
- resolvePreset,
569
+ getPreset,
570
570
  } from '@lovelace_lol/loom3';
571
571
 
572
- const preset = resolvePreset('cc4');
572
+ const preset = getPreset('cc4');
573
573
  const modelData = extractModelData(model, meshes, animations);
574
574
  const gltfData = extractFromGLTF(gltf); // Same ModelData shape, one-step GLTF wrapper
575
575
 
@@ -673,12 +673,12 @@ Open in LoomLarge: [Properties tab](https://loomlarge.web.app/?drawer=open&tab=p
673
673
 
674
674
  ### Extending an existing preset
675
675
 
676
- Use `resolveProfile` to override specific mappings while keeping the rest:
676
+ Use `extendPresetWithProfile` to override specific mappings while keeping the rest:
677
677
 
678
678
  ```typescript
679
- import { CC4_PRESET, resolveProfile } from '@lovelace_lol/loom3';
679
+ import { CC4_PRESET, extendPresetWithProfile } from '@lovelace_lol/loom3';
680
680
 
681
- const MY_PRESET = resolveProfile(CC4_PRESET, {
681
+ const MY_PRESET = extendPresetWithProfile(CC4_PRESET, {
682
682
 
683
683
  // Override AU12 (smile) with custom morph names
684
684
  auToMorphs: {
@@ -2117,8 +2117,8 @@ This is a compact reference for the public surface exported by `@lovelace_lol/lo
2117
2117
 
2118
2118
  ### Presets and profiles
2119
2119
 
2120
- - Presets: `CC4_PRESET`, `BETTA_FISH_PRESET`, `resolvePreset()`, `resolvePresetWithOverrides()`.
2121
- - Profile composition: `resolveProfile()`.
2120
+ - Presets: `CC4_PRESET`, `BETTA_FISH_PRESET`, `getPreset()`, `getPresetWithProfile()`.
2121
+ - Profile composition: `extendPresetWithProfile()`.
2122
2122
  - CC4 exports: `VISEME_KEYS`, `VISEME_JAW_AMOUNTS`, `CONTINUUM_PAIRS_MAP`, `CONTINUUM_LABELS`, `AU_INFO`, `COMPOSITE_ROTATIONS`, `AU_MIX_DEFAULTS`.
2123
2123
  - Compatibility helpers: `isMixedAU()`, `hasLeftRightMorphs()`.
2124
2124
 
package/dist/index.cjs CHANGED
@@ -2900,7 +2900,7 @@ var HairPhysicsController = class {
2900
2900
  }
2901
2901
  };
2902
2902
 
2903
- // src/mappings/resolveProfile.ts
2903
+ // src/mappings/extendPresetWithProfile.ts
2904
2904
  var isPlainObject = (value) => {
2905
2905
  return typeof value === "object" && value !== null && !Array.isArray(value);
2906
2906
  };
@@ -2992,31 +2992,33 @@ var mergeHairPhysicsConfig = (base, override) => {
2992
2992
  }
2993
2993
  return merged;
2994
2994
  };
2995
- function resolveProfile(base, override) {
2995
+ function extendPresetWithProfile(base, extension) {
2996
+ if (!extension) {
2997
+ return base;
2998
+ }
2999
+ const disabledRegions = extension.disabledRegions ? [...extension.disabledRegions] : base.disabledRegions ? [...base.disabledRegions] : void 0;
2996
3000
  return {
2997
3001
  ...base,
2998
- ...override,
2999
- auToMorphs: mergeRecord(base.auToMorphs, override.auToMorphs),
3000
- auToBones: mergeRecord(base.auToBones, override.auToBones),
3001
- boneNodes: mergeRecord(base.boneNodes, override.boneNodes),
3002
- morphToMesh: mergeRecord(base.morphToMesh, override.morphToMesh),
3003
- auFacePartToMeshCategory: base.auFacePartToMeshCategory || override.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, override.auFacePartToMeshCategory || {}) : void 0,
3004
- visemeKeys: override.visemeKeys ? [...override.visemeKeys] : [...base.visemeKeys],
3005
- visemeMeshCategory: override.visemeMeshCategory ?? base.visemeMeshCategory,
3006
- auMixDefaults: base.auMixDefaults || override.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, override.auMixDefaults || {}) : void 0,
3007
- auInfo: base.auInfo || override.auInfo ? mergeRecord(base.auInfo || {}, override.auInfo || {}) : void 0,
3008
- eyeMeshNodes: override.eyeMeshNodes ?? base.eyeMeshNodes,
3009
- compositeRotations: override.compositeRotations ?? base.compositeRotations,
3010
- meshes: base.meshes || override.meshes ? mergeRecord(base.meshes || {}, override.meshes || {}) : void 0,
3011
- continuumPairs: base.continuumPairs || override.continuumPairs ? mergeRecord(base.continuumPairs || {}, override.continuumPairs || {}) : void 0,
3012
- continuumLabels: base.continuumLabels || override.continuumLabels ? mergeRecord(base.continuumLabels || {}, override.continuumLabels || {}) : void 0,
3013
- annotationRegions: mergeAnnotationRegions(base.annotationRegions, override.annotationRegions),
3014
- hairPhysics: mergeHairPhysicsConfig(base.hairPhysics, override.hairPhysics)
3002
+ ...extension,
3003
+ auToMorphs: mergeRecord(base.auToMorphs, extension.auToMorphs),
3004
+ auToBones: mergeRecord(base.auToBones, extension.auToBones),
3005
+ boneNodes: mergeRecord(base.boneNodes, extension.boneNodes),
3006
+ morphToMesh: mergeRecord(base.morphToMesh, extension.morphToMesh),
3007
+ auFacePartToMeshCategory: base.auFacePartToMeshCategory || extension.auFacePartToMeshCategory ? mergeRecord(base.auFacePartToMeshCategory || {}, extension.auFacePartToMeshCategory || {}) : void 0,
3008
+ visemeKeys: extension.visemeKeys ? [...extension.visemeKeys] : [...base.visemeKeys],
3009
+ visemeMeshCategory: extension.visemeMeshCategory ?? base.visemeMeshCategory,
3010
+ auMixDefaults: base.auMixDefaults || extension.auMixDefaults ? mergeRecord(base.auMixDefaults || {}, extension.auMixDefaults || {}) : void 0,
3011
+ auInfo: base.auInfo || extension.auInfo ? mergeRecord(base.auInfo || {}, extension.auInfo || {}) : void 0,
3012
+ eyeMeshNodes: extension.eyeMeshNodes ?? base.eyeMeshNodes,
3013
+ compositeRotations: extension.compositeRotations ?? base.compositeRotations,
3014
+ meshes: base.meshes || extension.meshes ? mergeRecord(base.meshes || {}, extension.meshes || {}) : void 0,
3015
+ continuumPairs: base.continuumPairs || extension.continuumPairs ? mergeRecord(base.continuumPairs || {}, extension.continuumPairs || {}) : void 0,
3016
+ continuumLabels: base.continuumLabels || extension.continuumLabels ? mergeRecord(base.continuumLabels || {}, extension.continuumLabels || {}) : void 0,
3017
+ annotationRegions: mergeAnnotationRegions(base.annotationRegions, extension.annotationRegions),
3018
+ disabledRegions,
3019
+ hairPhysics: mergeHairPhysicsConfig(base.hairPhysics, extension.hairPhysics)
3015
3020
  };
3016
3021
  }
3017
- function applyProfileToPreset(base, override) {
3018
- return override ? resolveProfile(base, override) : base;
3019
- }
3020
3022
 
3021
3023
  // src/presets/bettaFish.ts
3022
3024
  var BONES = [
@@ -3803,22 +3805,24 @@ var ANNOTATION_REGIONS = [
3803
3805
  name: "gills",
3804
3806
  bones: ["GILL_L", "GILL_R"],
3805
3807
  paddingFactor: 1.6,
3806
- children: ["throat", "gill"],
3808
+ children: ["gill_left", "gill_right"],
3807
3809
  expandAnimation: "outward"
3808
3810
  },
3809
3811
  {
3810
- name: "throat",
3812
+ name: "gill_left",
3811
3813
  bones: ["GILL_L", "GILL_L_MID", "GILL_L_TIP"],
3812
3814
  parent: "gills",
3813
3815
  paddingFactor: 1.4,
3814
- cameraAngle: 270
3816
+ cameraAngle: 270,
3817
+ style: { lineDirection: "left" }
3815
3818
  },
3816
3819
  {
3817
- name: "gill",
3820
+ name: "gill_right",
3818
3821
  bones: ["GILL_R", "GILL_R_MID", "GILL_R_TIP"],
3819
3822
  parent: "gills",
3820
3823
  paddingFactor: 1.4,
3821
- cameraAngle: 90
3824
+ cameraAngle: 90,
3825
+ style: { lineDirection: "right" }
3822
3826
  }
3823
3827
  ];
3824
3828
  var BETTA_FISH_PROFILE = {
@@ -3855,7 +3859,7 @@ var AU_MAPPING_CONFIG = BETTA_FISH_PRESET;
3855
3859
  var FISH_AU_MAPPING_CONFIG = BETTA_FISH_PRESET;
3856
3860
 
3857
3861
  // src/presets/index.ts
3858
- function resolvePreset(presetType) {
3862
+ function getPreset(presetType) {
3859
3863
  switch (presetType) {
3860
3864
  case "fish":
3861
3865
  case "skeletal":
@@ -3866,9 +3870,8 @@ function resolvePreset(presetType) {
3866
3870
  return CC4_PRESET;
3867
3871
  }
3868
3872
  }
3869
- function resolvePresetWithOverrides(presetType, overrides) {
3870
- const base = resolvePreset(presetType);
3871
- return applyProfileToPreset(base, overrides);
3873
+ function getPresetWithProfile(presetType, profile) {
3874
+ return extendPresetWithProfile(getPreset(presetType), profile);
3872
3875
  }
3873
3876
 
3874
3877
  // src/engines/three/Loom3.ts
@@ -3944,8 +3947,8 @@ var _Loom3 = class _Loom3 {
3944
3947
  __publicField(this, "isRunning", false);
3945
3948
  /** Store original emissive colors for highlight reset */
3946
3949
  __publicField(this, "originalEmissive", /* @__PURE__ */ new Map());
3947
- const basePreset = config.presetType ? resolvePreset(config.presetType) : CC4_PRESET;
3948
- this.config = applyProfileToPreset(basePreset, config.profile);
3950
+ const basePreset = config.presetType ? getPreset(config.presetType) : CC4_PRESET;
3951
+ this.config = extendPresetWithProfile(basePreset, config.profile);
3949
3952
  this.mixWeights = { ...this.config.auMixDefaults };
3950
3953
  this.animation = animation || new AnimationThree();
3951
3954
  this.compositeRotations = this.config.compositeRotations || COMPOSITE_ROTATIONS;
@@ -5354,7 +5357,30 @@ var BLENDING_MODES = {
5354
5357
  // THREE.NoBlending
5355
5358
  };
5356
5359
 
5357
- // src/characters/resolveCharacterConfig.ts
5360
+ // src/regions/normalizeRegionTree.ts
5361
+ function normalizeDisabledNames(disabledNames) {
5362
+ return new Set((disabledNames ?? []).filter((name) => Boolean(name)));
5363
+ }
5364
+ function normalizeRegionTree(regions, disabledNames) {
5365
+ if (!regions) return void 0;
5366
+ const disabled = normalizeDisabledNames(disabledNames);
5367
+ const nextRegions = regions.filter((region) => !disabled.has(region.name)).map((region) => ({
5368
+ ...region,
5369
+ children: region.children ? [...region.children] : void 0
5370
+ }));
5371
+ const remainingNames = new Set(nextRegions.map((region) => region.name));
5372
+ return nextRegions.map((region) => {
5373
+ const nextChildren = region.children?.filter((child) => remainingNames.has(child));
5374
+ const nextParent = region.parent && remainingNames.has(region.parent) ? region.parent : void 0;
5375
+ return {
5376
+ ...region,
5377
+ parent: nextParent,
5378
+ children: nextChildren && nextChildren.length > 0 ? nextChildren : void 0
5379
+ };
5380
+ });
5381
+ }
5382
+
5383
+ // src/characters/extendCharacterConfigWithPreset.ts
5358
5384
  var PROFILE_OVERRIDE_KEYS = [
5359
5385
  "name",
5360
5386
  "animalType",
@@ -5382,6 +5408,7 @@ var PROFILE_OVERRIDE_KEYS = [
5382
5408
  "continuumPairs",
5383
5409
  "continuumLabels",
5384
5410
  "annotationRegions",
5411
+ "disabledRegions",
5385
5412
  "hairPhysics"
5386
5413
  ];
5387
5414
  function isPlainObject2(value) {
@@ -5500,11 +5527,11 @@ function applyCharacterProfileToPreset(config) {
5500
5527
  if (!presetType) {
5501
5528
  return null;
5502
5529
  }
5503
- return applyProfileToPreset(resolvePreset(presetType), extractProfileOverrides(config));
5530
+ return extendPresetWithProfile(getPreset(presetType), extractProfileOverrides(config));
5504
5531
  }
5505
- function orderResolvedRegions(resolvedRegions, prioritizedLists) {
5506
- if (!resolvedRegions) return void 0;
5507
- const resolvedByName = new Map(resolvedRegions.map((region) => [region.name, region]));
5532
+ function orderExtendedRegions(extendedRegions, prioritizedLists) {
5533
+ if (!extendedRegions) return void 0;
5534
+ const extendedByName = new Map(extendedRegions.map((region) => [region.name, region]));
5508
5535
  const orderedNames = [];
5509
5536
  const seen = /* @__PURE__ */ new Set();
5510
5537
  for (const regions of prioritizedLists) {
@@ -5514,12 +5541,12 @@ function orderResolvedRegions(resolvedRegions, prioritizedLists) {
5514
5541
  orderedNames.push(region.name);
5515
5542
  }
5516
5543
  }
5517
- for (const region of resolvedRegions) {
5544
+ for (const region of extendedRegions) {
5518
5545
  if (seen.has(region.name)) continue;
5519
5546
  seen.add(region.name);
5520
5547
  orderedNames.push(region.name);
5521
5548
  }
5522
- return orderedNames.map((name) => resolvedByName.get(name)).filter((region) => Boolean(region));
5549
+ return orderedNames.map((name) => extendedByName.get(name)).filter((region) => Boolean(region));
5523
5550
  }
5524
5551
  function extendCharacterConfigWithPreset(config) {
5525
5552
  const presetType = config.auPresetType;
@@ -5527,25 +5554,26 @@ function extendCharacterConfigWithPreset(config) {
5527
5554
  return config;
5528
5555
  }
5529
5556
  const profileOverrides = extractProfileOverrides(config);
5530
- const presetResolvedProfile = applyCharacterProfileToPreset(config);
5531
- if (!presetResolvedProfile) {
5557
+ const extendedPresetProfile = applyCharacterProfileToPreset(config);
5558
+ if (!extendedPresetProfile) {
5532
5559
  return config;
5533
5560
  }
5534
- const presetRegions = presetResolvedProfile.annotationRegions;
5561
+ const presetRegions = extendedPresetProfile.annotationRegions;
5535
5562
  const mergedRegions = mergeRegionsByName(presetRegions, config.regions);
5536
- const resolvedRegions = orderResolvedRegions(
5563
+ const normalizedRegions = normalizeRegionTree(
5537
5564
  mergedRegions,
5565
+ profileOverrides.disabledRegions
5566
+ );
5567
+ const extendedRegions = orderExtendedRegions(
5568
+ normalizedRegions,
5538
5569
  [config.regions, profileOverrides.annotationRegions, presetRegions]
5539
5570
  );
5540
5571
  return {
5541
5572
  ...config,
5542
- ...presetResolvedProfile,
5543
- regions: resolvedRegions ?? config.regions
5573
+ ...extendedPresetProfile,
5574
+ regions: extendedRegions ?? config.regions
5544
5575
  };
5545
5576
  }
5546
- function resolveCharacterConfig(config) {
5547
- return extendCharacterConfigWithPreset(config);
5548
- }
5549
5577
  var DEFAULT_HEAD_BONE_NAMES = ["CC_Base_Head", "Head", "head", "Bip01_Head"];
5550
5578
  var DEFAULT_REFERENCE_HEIGHT = 1.8;
5551
5579
  var DEFAULT_FORWARD_OFFSET = 0.08;
@@ -6892,10 +6920,10 @@ exports.VISEME_JAW_AMOUNTS = VISEME_JAW_AMOUNTS;
6892
6920
  exports.VISEME_KEYS = VISEME_KEYS;
6893
6921
  exports.analyzeModel = analyzeModel;
6894
6922
  exports.applyCharacterProfileToPreset = applyCharacterProfileToPreset;
6895
- exports.applyProfileToPreset = applyProfileToPreset;
6896
6923
  exports.collectMorphMeshes = collectMorphMeshes;
6897
6924
  exports.detectFacingDirection = detectFacingDirection;
6898
6925
  exports.extendCharacterConfigWithPreset = extendCharacterConfigWithPreset;
6926
+ exports.extendPresetWithProfile = extendPresetWithProfile;
6899
6927
  exports.extractFromGLTF = extractFromGLTF;
6900
6928
  exports.extractModelData = extractModelData;
6901
6929
  exports.extractProfileOverrides = extractProfileOverrides;
@@ -6903,17 +6931,15 @@ exports.findFaceCenter = findFaceCenter;
6903
6931
  exports.fuzzyNameMatch = fuzzyNameMatch;
6904
6932
  exports.generateMappingCorrections = generateMappingCorrections;
6905
6933
  exports.getModelForwardDirection = getModelForwardDirection;
6934
+ exports.getPreset = getPreset;
6935
+ exports.getPresetWithProfile = getPresetWithProfile;
6906
6936
  exports.hasLeftRightMorphs = hasLeftRightMorphs;
6907
6937
  exports.isMixedAU = isMixedAU;
6908
6938
  exports.isPresetCompatible = isPresetCompatible;
6909
6939
  exports.mergeCharacterRegionsByName = mergeRegionsByName;
6910
6940
  exports.resolveBoneName = resolveBoneName;
6911
6941
  exports.resolveBoneNames = resolveBoneNames;
6912
- exports.resolveCharacterConfig = resolveCharacterConfig;
6913
6942
  exports.resolveFaceCenter = resolveFaceCenter;
6914
- exports.resolvePreset = resolvePreset;
6915
- exports.resolvePresetWithOverrides = resolvePresetWithOverrides;
6916
- exports.resolveProfile = resolveProfile;
6917
6943
  exports.suggestBestPreset = suggestBestPreset;
6918
6944
  exports.validateMappingConfig = validateMappingConfig;
6919
6945
  exports.validateMappings = validateMappings;