@lovelace_lol/loom3 1.0.39 → 1.0.40

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.d.cts CHANGED
@@ -315,6 +315,33 @@ interface CompositeRotationState {
315
315
  roll: number;
316
316
  }
317
317
  type RotationsState = Record<string, CompositeRotationState>;
318
+ type MorphTargetAttributeData = Float32Array | number[];
319
+ interface MorphTargetDelta {
320
+ /** Mesh name from the loaded Three.js scene. */
321
+ meshName: string;
322
+ /** Morph target name to add to morphTargetDictionary. */
323
+ name: string;
324
+ /** POSITION deltas, usually XYZ values relative to the base mesh. */
325
+ position: MorphTargetAttributeData;
326
+ /** Optional NORMAL deltas. */
327
+ normal?: MorphTargetAttributeData;
328
+ /** Optional TANGENT deltas. */
329
+ tangent?: MorphTargetAttributeData;
330
+ /** Whether deltas are relative to base attributes. Defaults to true. */
331
+ relative?: boolean;
332
+ }
333
+ interface AddMorphTargetOptions {
334
+ /** Replace an existing morph target with the same name. */
335
+ replace?: boolean;
336
+ /** Initialize the new or replaced influence value to zero. Defaults to true. */
337
+ resetInfluence?: boolean;
338
+ /**
339
+ * Replace and dispose the BufferGeometry instead of mutating it in place.
340
+ * Defaults to true because Three.js does not support mutating morph attributes
341
+ * after a geometry has rendered.
342
+ */
343
+ forceGeometryReplacement?: boolean;
344
+ }
318
345
  /** Source category for mixer-driven animations. */
319
346
  type AnimationSource = 'baked' | 'clip' | 'snippet';
320
347
  /** Shared blend-mode surface for downstream animation UIs. */
@@ -958,6 +985,25 @@ interface Animation {
958
985
  * @param meshNames - Optional specific meshes to target
959
986
  */
960
987
  transitionMorphInfluence(index: number, to: number, durationMs?: number, meshNames?: string[]): TransitionHandle;
988
+ /**
989
+ * Add or replace a runtime morph target on a mesh.
990
+ * Returns the morphTargetInfluences index assigned to the target.
991
+ */
992
+ addMorphTarget(target: MorphTargetDelta, options?: AddMorphTargetOptions): number;
993
+ /**
994
+ * Add multiple runtime morph targets and return their assigned indices keyed
995
+ * by "meshName:name".
996
+ */
997
+ addMorphTargets(targets: MorphTargetDelta[], options?: AddMorphTargetOptions): Record<string, number>;
998
+ /**
999
+ * Ensure a named morph influence slot exists on the mesh.
1000
+ * If the target is missing, a zero-delta morph target is created.
1001
+ */
1002
+ ensureMorphInfluence(meshName: string, morphName: string): number;
1003
+ /**
1004
+ * Rebuild runtime morph caches after external geometry or dictionary changes.
1005
+ */
1006
+ refreshMorphTargets(meshNames?: string[]): void;
961
1007
  /**
962
1008
  * Set viseme value immediately (for lip-sync)
963
1009
  * @param visemeIndex - Viseme index 0-14
@@ -1461,6 +1507,10 @@ declare class Loom3 implements LoomLarge {
1461
1507
  * @param balance - Optional L/R balance for bilateral morphs
1462
1508
  */
1463
1509
  transitionContinuum(negAU: number, posAU: number, continuumValue: number, durationMs?: number, balance?: number): TransitionHandle;
1510
+ addMorphTarget(target: MorphTargetDelta, options?: AddMorphTargetOptions): number;
1511
+ addMorphTargets(targets: MorphTargetDelta[], options?: AddMorphTargetOptions): Record<string, number>;
1512
+ ensureMorphInfluence(meshName: string, morphName: string): number;
1513
+ refreshMorphTargets(_meshNames?: string[]): void;
1464
1514
  /**
1465
1515
  * Set a morph target value.
1466
1516
  *
@@ -1607,6 +1657,12 @@ declare class Loom3 implements LoomLarge {
1607
1657
  private applyMorphTargets;
1608
1658
  private getMorphValue;
1609
1659
  private getMorphValueByIndex;
1660
+ private applyMorphTargetDelta;
1661
+ private requireNamedMesh;
1662
+ private getMeshMorphDictionary;
1663
+ private setMorphAttributeAtIndex;
1664
+ private setZeroMorphAttributeAtIndex;
1665
+ private addRuntimeMorphMesh;
1610
1666
  private getMorphKeyCacheKey;
1611
1667
  private getMorphIndexCacheKey;
1612
1668
  private syncVisemeRuntimeState;
@@ -2462,4 +2518,4 @@ declare function detectFacingDirection(model: THREE.Object3D, eyeBoneNames?: {
2462
2518
  right: string[];
2463
2519
  }): 'forward' | 'backward' | 'unknown';
2464
2520
 
2465
- export { type AUInfo, type AUSelector, AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, type AnalyzeModelOptions, type Animation, type AnimationActionHandle, type AnimationAnalysis, type AnimationBlendMode, type AnimationClipInfo, type AnimationEasing, type AnimationInfo, type AnimationPlayOptions, type AnimationSource, type AnimationState, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, type BlendingMode, type BoneBinding, type BoneInfo, type BoneKey, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, type CameraRelativeGazeOffset, type CameraRelativeGazeOptions, type CharacterConfig, type CharacterRegistry, type ClipEvent, type ClipEventListener, type ClipHandle, type ClipOptions, type CompositeRotation, type CompositeRotationState, type CurvePoint, type CurvesMap, DEFAULT_HAIR_PHYSICS_CONFIG, type ExpandAnimation, type ExpandedRegionState, FISH_AU_MAPPING_CONFIG, type FaceCenterResult, type FallbackConfig, type FindFaceCenterOptions, type Hair, type HairMorphAxis, type HairMorphOutput$1 as HairMorphOutput, type HairMorphTargetMapping, type HairMorphTargetValueMapping, type HairMorphTargetsConfig, type HairObjectRef, type HairObjectState, HairPhysics, type HairPhysicsConfig$1 as HairPhysicsConfig, type HairPhysicsDirectionConfig, type HairPhysics$1 as HairPhysicsInterface, type HairMorphOutput as HairPhysicsMorphOutput, type HairPhysicsProfileConfig, type HairPhysicsRuntimeConfig, type HairPhysicsRuntimeConfigUpdate, type HairPhysicsState, type HairState, type HairStrand, type HeadState$1 as HeadState, type LineConfig, type LineCurve, type LineStyle, Loom3, type Loom3Config, Loom3 as Loom3Three, type LoomLarge, type LoomLargeConfig, Loom3 as LoomLargeThree, MORPH_TO_MESH, type MappingConsistencyResult, type MappingCorrection, type MappingCorrectionOptions, type MappingCorrectionResult, type MappingIssue, type MarkerGroup, type MarkerStyle, type MarkerStyleOverrides, type MeshCategory, type MeshInfo, type MeshMaterialSettings, type MixerLoopMode, type ModelAnalysisReport, type ModelData, type ModelMeshInfo, type MorphCategory, type MorphInfo, type MorphTargetRef, type MorphTargetsBySide, type NamedDirection, type PresetType, type Profile, type ReadyPayload, type Region, type RotationAxis, type RotationsState, type Snippet, type TrackInfo, type TransitionHandle, VISEME_JAW_AMOUNTS, VISEME_KEYS, type ValidateMappingOptions, type ValidationResult, analyzeModel, applyCharacterProfileToPreset, collectMorphMeshes, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getModelForwardDirection, getPreset, getPresetWithProfile, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, suggestBestPreset, validateMappingConfig, validateMappings };
2521
+ export { type AUInfo, type AUSelector, AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, type AddMorphTargetOptions, type AnalyzeModelOptions, type Animation, type AnimationActionHandle, type AnimationAnalysis, type AnimationBlendMode, type AnimationClipInfo, type AnimationEasing, type AnimationInfo, type AnimationPlayOptions, type AnimationSource, type AnimationState, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, type BlendingMode, type BoneBinding, type BoneInfo, type BoneKey, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, type CameraRelativeGazeOffset, type CameraRelativeGazeOptions, type CharacterConfig, type CharacterRegistry, type ClipEvent, type ClipEventListener, type ClipHandle, type ClipOptions, type CompositeRotation, type CompositeRotationState, type CurvePoint, type CurvesMap, DEFAULT_HAIR_PHYSICS_CONFIG, type ExpandAnimation, type ExpandedRegionState, FISH_AU_MAPPING_CONFIG, type FaceCenterResult, type FallbackConfig, type FindFaceCenterOptions, type Hair, type HairMorphAxis, type HairMorphOutput$1 as HairMorphOutput, type HairMorphTargetMapping, type HairMorphTargetValueMapping, type HairMorphTargetsConfig, type HairObjectRef, type HairObjectState, HairPhysics, type HairPhysicsConfig$1 as HairPhysicsConfig, type HairPhysicsDirectionConfig, type HairPhysics$1 as HairPhysicsInterface, type HairMorphOutput as HairPhysicsMorphOutput, type HairPhysicsProfileConfig, type HairPhysicsRuntimeConfig, type HairPhysicsRuntimeConfigUpdate, type HairPhysicsState, type HairState, type HairStrand, type HeadState$1 as HeadState, type LineConfig, type LineCurve, type LineStyle, Loom3, type Loom3Config, Loom3 as Loom3Three, type LoomLarge, type LoomLargeConfig, Loom3 as LoomLargeThree, MORPH_TO_MESH, type MappingConsistencyResult, type MappingCorrection, type MappingCorrectionOptions, type MappingCorrectionResult, type MappingIssue, type MarkerGroup, type MarkerStyle, type MarkerStyleOverrides, type MeshCategory, type MeshInfo, type MeshMaterialSettings, type MixerLoopMode, type ModelAnalysisReport, type ModelData, type ModelMeshInfo, type MorphCategory, type MorphInfo, type MorphTargetAttributeData, type MorphTargetDelta, type MorphTargetRef, type MorphTargetsBySide, type NamedDirection, type PresetType, type Profile, type ReadyPayload, type Region, type RotationAxis, type RotationsState, type Snippet, type TrackInfo, type TransitionHandle, VISEME_JAW_AMOUNTS, VISEME_KEYS, type ValidateMappingOptions, type ValidationResult, analyzeModel, applyCharacterProfileToPreset, collectMorphMeshes, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getModelForwardDirection, getPreset, getPresetWithProfile, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, suggestBestPreset, validateMappingConfig, validateMappings };
package/dist/index.d.ts CHANGED
@@ -315,6 +315,33 @@ interface CompositeRotationState {
315
315
  roll: number;
316
316
  }
317
317
  type RotationsState = Record<string, CompositeRotationState>;
318
+ type MorphTargetAttributeData = Float32Array | number[];
319
+ interface MorphTargetDelta {
320
+ /** Mesh name from the loaded Three.js scene. */
321
+ meshName: string;
322
+ /** Morph target name to add to morphTargetDictionary. */
323
+ name: string;
324
+ /** POSITION deltas, usually XYZ values relative to the base mesh. */
325
+ position: MorphTargetAttributeData;
326
+ /** Optional NORMAL deltas. */
327
+ normal?: MorphTargetAttributeData;
328
+ /** Optional TANGENT deltas. */
329
+ tangent?: MorphTargetAttributeData;
330
+ /** Whether deltas are relative to base attributes. Defaults to true. */
331
+ relative?: boolean;
332
+ }
333
+ interface AddMorphTargetOptions {
334
+ /** Replace an existing morph target with the same name. */
335
+ replace?: boolean;
336
+ /** Initialize the new or replaced influence value to zero. Defaults to true. */
337
+ resetInfluence?: boolean;
338
+ /**
339
+ * Replace and dispose the BufferGeometry instead of mutating it in place.
340
+ * Defaults to true because Three.js does not support mutating morph attributes
341
+ * after a geometry has rendered.
342
+ */
343
+ forceGeometryReplacement?: boolean;
344
+ }
318
345
  /** Source category for mixer-driven animations. */
319
346
  type AnimationSource = 'baked' | 'clip' | 'snippet';
320
347
  /** Shared blend-mode surface for downstream animation UIs. */
@@ -958,6 +985,25 @@ interface Animation {
958
985
  * @param meshNames - Optional specific meshes to target
959
986
  */
960
987
  transitionMorphInfluence(index: number, to: number, durationMs?: number, meshNames?: string[]): TransitionHandle;
988
+ /**
989
+ * Add or replace a runtime morph target on a mesh.
990
+ * Returns the morphTargetInfluences index assigned to the target.
991
+ */
992
+ addMorphTarget(target: MorphTargetDelta, options?: AddMorphTargetOptions): number;
993
+ /**
994
+ * Add multiple runtime morph targets and return their assigned indices keyed
995
+ * by "meshName:name".
996
+ */
997
+ addMorphTargets(targets: MorphTargetDelta[], options?: AddMorphTargetOptions): Record<string, number>;
998
+ /**
999
+ * Ensure a named morph influence slot exists on the mesh.
1000
+ * If the target is missing, a zero-delta morph target is created.
1001
+ */
1002
+ ensureMorphInfluence(meshName: string, morphName: string): number;
1003
+ /**
1004
+ * Rebuild runtime morph caches after external geometry or dictionary changes.
1005
+ */
1006
+ refreshMorphTargets(meshNames?: string[]): void;
961
1007
  /**
962
1008
  * Set viseme value immediately (for lip-sync)
963
1009
  * @param visemeIndex - Viseme index 0-14
@@ -1461,6 +1507,10 @@ declare class Loom3 implements LoomLarge {
1461
1507
  * @param balance - Optional L/R balance for bilateral morphs
1462
1508
  */
1463
1509
  transitionContinuum(negAU: number, posAU: number, continuumValue: number, durationMs?: number, balance?: number): TransitionHandle;
1510
+ addMorphTarget(target: MorphTargetDelta, options?: AddMorphTargetOptions): number;
1511
+ addMorphTargets(targets: MorphTargetDelta[], options?: AddMorphTargetOptions): Record<string, number>;
1512
+ ensureMorphInfluence(meshName: string, morphName: string): number;
1513
+ refreshMorphTargets(_meshNames?: string[]): void;
1464
1514
  /**
1465
1515
  * Set a morph target value.
1466
1516
  *
@@ -1607,6 +1657,12 @@ declare class Loom3 implements LoomLarge {
1607
1657
  private applyMorphTargets;
1608
1658
  private getMorphValue;
1609
1659
  private getMorphValueByIndex;
1660
+ private applyMorphTargetDelta;
1661
+ private requireNamedMesh;
1662
+ private getMeshMorphDictionary;
1663
+ private setMorphAttributeAtIndex;
1664
+ private setZeroMorphAttributeAtIndex;
1665
+ private addRuntimeMorphMesh;
1610
1666
  private getMorphKeyCacheKey;
1611
1667
  private getMorphIndexCacheKey;
1612
1668
  private syncVisemeRuntimeState;
@@ -2462,4 +2518,4 @@ declare function detectFacingDirection(model: THREE.Object3D, eyeBoneNames?: {
2462
2518
  right: string[];
2463
2519
  }): 'forward' | 'backward' | 'unknown';
2464
2520
 
2465
- export { type AUInfo, type AUSelector, AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, type AnalyzeModelOptions, type Animation, type AnimationActionHandle, type AnimationAnalysis, type AnimationBlendMode, type AnimationClipInfo, type AnimationEasing, type AnimationInfo, type AnimationPlayOptions, type AnimationSource, type AnimationState, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, type BlendingMode, type BoneBinding, type BoneInfo, type BoneKey, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, type CameraRelativeGazeOffset, type CameraRelativeGazeOptions, type CharacterConfig, type CharacterRegistry, type ClipEvent, type ClipEventListener, type ClipHandle, type ClipOptions, type CompositeRotation, type CompositeRotationState, type CurvePoint, type CurvesMap, DEFAULT_HAIR_PHYSICS_CONFIG, type ExpandAnimation, type ExpandedRegionState, FISH_AU_MAPPING_CONFIG, type FaceCenterResult, type FallbackConfig, type FindFaceCenterOptions, type Hair, type HairMorphAxis, type HairMorphOutput$1 as HairMorphOutput, type HairMorphTargetMapping, type HairMorphTargetValueMapping, type HairMorphTargetsConfig, type HairObjectRef, type HairObjectState, HairPhysics, type HairPhysicsConfig$1 as HairPhysicsConfig, type HairPhysicsDirectionConfig, type HairPhysics$1 as HairPhysicsInterface, type HairMorphOutput as HairPhysicsMorphOutput, type HairPhysicsProfileConfig, type HairPhysicsRuntimeConfig, type HairPhysicsRuntimeConfigUpdate, type HairPhysicsState, type HairState, type HairStrand, type HeadState$1 as HeadState, type LineConfig, type LineCurve, type LineStyle, Loom3, type Loom3Config, Loom3 as Loom3Three, type LoomLarge, type LoomLargeConfig, Loom3 as LoomLargeThree, MORPH_TO_MESH, type MappingConsistencyResult, type MappingCorrection, type MappingCorrectionOptions, type MappingCorrectionResult, type MappingIssue, type MarkerGroup, type MarkerStyle, type MarkerStyleOverrides, type MeshCategory, type MeshInfo, type MeshMaterialSettings, type MixerLoopMode, type ModelAnalysisReport, type ModelData, type ModelMeshInfo, type MorphCategory, type MorphInfo, type MorphTargetRef, type MorphTargetsBySide, type NamedDirection, type PresetType, type Profile, type ReadyPayload, type Region, type RotationAxis, type RotationsState, type Snippet, type TrackInfo, type TransitionHandle, VISEME_JAW_AMOUNTS, VISEME_KEYS, type ValidateMappingOptions, type ValidationResult, analyzeModel, applyCharacterProfileToPreset, collectMorphMeshes, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getModelForwardDirection, getPreset, getPresetWithProfile, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, suggestBestPreset, validateMappingConfig, validateMappings };
2521
+ export { type AUInfo, type AUSelector, AU_INFO, AU_MAPPING_CONFIG, AU_MIX_DEFAULTS, AU_TO_MORPHS, type AddMorphTargetOptions, type AnalyzeModelOptions, type Animation, type AnimationActionHandle, type AnimationAnalysis, type AnimationBlendMode, type AnimationClipInfo, type AnimationEasing, type AnimationInfo, type AnimationPlayOptions, type AnimationSource, type AnimationState, AnimationThree, BETTA_FISH_PRESET, BLENDING_MODES, BONE_AU_TO_BINDINGS, type BlendingMode, type BoneBinding, type BoneInfo, type BoneKey, CC4_BONE_NODES, CC4_BONE_PREFIX, CC4_EYE_MESH_NODES, CC4_MESHES, CC4_PRESET, CC4_SUFFIX_PATTERN, COMPOSITE_ROTATIONS, CONTINUUM_LABELS, CONTINUUM_PAIRS_MAP, type CameraRelativeGazeOffset, type CameraRelativeGazeOptions, type CharacterConfig, type CharacterRegistry, type ClipEvent, type ClipEventListener, type ClipHandle, type ClipOptions, type CompositeRotation, type CompositeRotationState, type CurvePoint, type CurvesMap, DEFAULT_HAIR_PHYSICS_CONFIG, type ExpandAnimation, type ExpandedRegionState, FISH_AU_MAPPING_CONFIG, type FaceCenterResult, type FallbackConfig, type FindFaceCenterOptions, type Hair, type HairMorphAxis, type HairMorphOutput$1 as HairMorphOutput, type HairMorphTargetMapping, type HairMorphTargetValueMapping, type HairMorphTargetsConfig, type HairObjectRef, type HairObjectState, HairPhysics, type HairPhysicsConfig$1 as HairPhysicsConfig, type HairPhysicsDirectionConfig, type HairPhysics$1 as HairPhysicsInterface, type HairMorphOutput as HairPhysicsMorphOutput, type HairPhysicsProfileConfig, type HairPhysicsRuntimeConfig, type HairPhysicsRuntimeConfigUpdate, type HairPhysicsState, type HairState, type HairStrand, type HeadState$1 as HeadState, type LineConfig, type LineCurve, type LineStyle, Loom3, type Loom3Config, Loom3 as Loom3Three, type LoomLarge, type LoomLargeConfig, Loom3 as LoomLargeThree, MORPH_TO_MESH, type MappingConsistencyResult, type MappingCorrection, type MappingCorrectionOptions, type MappingCorrectionResult, type MappingIssue, type MarkerGroup, type MarkerStyle, type MarkerStyleOverrides, type MeshCategory, type MeshInfo, type MeshMaterialSettings, type MixerLoopMode, type ModelAnalysisReport, type ModelData, type ModelMeshInfo, type MorphCategory, type MorphInfo, type MorphTargetAttributeData, type MorphTargetDelta, type MorphTargetRef, type MorphTargetsBySide, type NamedDirection, type PresetType, type Profile, type ReadyPayload, type Region, type RotationAxis, type RotationsState, type Snippet, type TrackInfo, type TransitionHandle, VISEME_JAW_AMOUNTS, VISEME_KEYS, type ValidateMappingOptions, type ValidationResult, analyzeModel, applyCharacterProfileToPreset, collectMorphMeshes, computeCameraRelativeGazeOffset, detectFacingDirection, extendCharacterConfigWithPreset, extendPresetWithProfile, extractFromGLTF, extractModelData, extractProfileOverrides, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getModelForwardDirection, getPreset, getPresetWithProfile, hasLeftRightMorphs, isMixedAU, isPresetCompatible, mergeRegionsByName as mergeCharacterRegionsByName, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, suggestBestPreset, validateMappingConfig, validateMappings };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as THREE2 from 'three';
2
- import { Vector3, Clock, Box3, Quaternion, AdditiveAnimationBlendMode, NormalAnimationBlendMode, LoopPingPong, LoopOnce, LoopRepeat, QuaternionKeyframeTrack, NumberKeyframeTrack, AnimationClip, AnimationMixer, Mesh, PropertyBinding } from 'three';
2
+ import { Vector3, Clock, Box3, BufferAttribute, Quaternion, AdditiveAnimationBlendMode, NormalAnimationBlendMode, LoopPingPong, LoopOnce, LoopRepeat, QuaternionKeyframeTrack, NumberKeyframeTrack, AnimationClip, AnimationMixer, Mesh, PropertyBinding } from 'three';
3
3
 
4
4
  var __defProp = Object.defineProperty;
5
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -4617,11 +4617,7 @@ var _Loom3 = class _Loom3 {
4617
4617
  this.morphIndexCache.clear();
4618
4618
  model.traverse((obj) => {
4619
4619
  if (obj.isMesh && obj.name) {
4620
- const infl = obj.morphTargetInfluences;
4621
- const dict = obj.morphTargetDictionary;
4622
- if (Array.isArray(infl) && infl.length > 0 || dict && Object.keys(dict).length > 0) {
4623
- this.meshByName.set(obj.name, obj);
4624
- }
4620
+ this.meshByName.set(obj.name, obj);
4625
4621
  }
4626
4622
  });
4627
4623
  this.bones = this.resolveBones(model);
@@ -4989,6 +4985,58 @@ var _Loom3 = class _Loom3 {
4989
4985
  const currentContinuum = currentPos - currentNeg;
4990
4986
  return this.animation.addTransition(driverKey, currentContinuum, target, durationMs, (value) => this.setContinuum(negAU, posAU, value, balance));
4991
4987
  }
4988
+ // ============================================================================
4989
+ // MORPH CONTROL
4990
+ // ============================================================================
4991
+ addMorphTarget(target, options = {}) {
4992
+ const staleMorphTargets = this.collectResolvedExpressionMorphTargets();
4993
+ const index = this.applyMorphTargetDelta(target, options);
4994
+ this.refreshMorphTargets([target.meshName]);
4995
+ this.reinitializeRuntimeStateFromCurrentControls(staleMorphTargets);
4996
+ return index;
4997
+ }
4998
+ addMorphTargets(targets, options = {}) {
4999
+ const staleMorphTargets = this.collectResolvedExpressionMorphTargets();
5000
+ const result = {};
5001
+ for (const target of targets) {
5002
+ const index = this.applyMorphTargetDelta(target, options);
5003
+ result[`${target.meshName}:${target.name}`] = index;
5004
+ }
5005
+ this.refreshMorphTargets(Array.from(new Set(targets.map((target) => target.meshName))));
5006
+ this.reinitializeRuntimeStateFromCurrentControls(staleMorphTargets);
5007
+ return result;
5008
+ }
5009
+ ensureMorphInfluence(meshName, morphName) {
5010
+ const mesh = this.requireNamedMesh(meshName);
5011
+ const dict = this.getMeshMorphDictionary(mesh);
5012
+ const existing = dict[morphName];
5013
+ if (existing !== void 0) return existing;
5014
+ const position = mesh.geometry.getAttribute("position");
5015
+ if (!position) {
5016
+ throw new Error(`Cannot create morph target "${morphName}" on mesh "${meshName}": geometry has no position attribute.`);
5017
+ }
5018
+ return this.addMorphTarget({
5019
+ meshName,
5020
+ name: morphName,
5021
+ position: new Float32Array(position.count * position.itemSize),
5022
+ relative: true
5023
+ });
5024
+ }
5025
+ refreshMorphTargets(_meshNames) {
5026
+ this.morphKeyCache.clear();
5027
+ this.morphIndexCache.clear();
5028
+ if (this.model) {
5029
+ this.meshByName.clear();
5030
+ this.model.traverse((obj) => {
5031
+ if (obj.isMesh && obj.name) {
5032
+ this.meshByName.set(obj.name, obj);
5033
+ }
5034
+ });
5035
+ this.meshes = collectMorphMeshes(this.model);
5036
+ }
5037
+ this.rebuildMorphTargetsCache();
5038
+ this.hairPhysics.refreshMeshSelection();
5039
+ }
4992
5040
  setMorph(key, v, meshNamesOrTargets) {
4993
5041
  const val = clamp012(v);
4994
5042
  if (Array.isArray(meshNamesOrTargets) && meshNamesOrTargets.length > 0 && typeof meshNamesOrTargets[0] === "object" && "infl" in meshNamesOrTargets[0]) {
@@ -5607,6 +5655,148 @@ var _Loom3 = class _Loom3 {
5607
5655
  }
5608
5656
  return 0;
5609
5657
  }
5658
+ applyMorphTargetDelta(target, options) {
5659
+ const mesh = this.requireNamedMesh(target.meshName);
5660
+ const sourceGeometry = mesh.geometry;
5661
+ const position = sourceGeometry.getAttribute("position");
5662
+ if (!position) {
5663
+ throw new Error(`Cannot add morph target "${target.name}" to mesh "${target.meshName}": geometry has no position attribute.`);
5664
+ }
5665
+ if (!target.name || !target.name.trim()) {
5666
+ throw new Error(`Cannot add morph target to mesh "${target.meshName}": target name is required.`);
5667
+ }
5668
+ const replace = options.replace === true;
5669
+ const resetInfluence = options.resetInfluence !== false;
5670
+ const forceGeometryReplacement = options.forceGeometryReplacement !== false;
5671
+ const previousInfluences = mesh.morphTargetInfluences ? [...mesh.morphTargetInfluences] : [];
5672
+ const previousDictionary = this.getMeshMorphDictionary(mesh);
5673
+ const existingIndex = previousDictionary[target.name];
5674
+ if (existingIndex !== void 0 && !replace) {
5675
+ throw new Error(`Morph target "${target.name}" already exists on mesh "${target.meshName}". Pass replace: true to overwrite it.`);
5676
+ }
5677
+ const geometry = forceGeometryReplacement ? sourceGeometry.clone() : sourceGeometry;
5678
+ const dictionary = { ...previousDictionary };
5679
+ const usedIndices = Object.values(dictionary).filter(Number.isInteger);
5680
+ const existingAttributeTargetCount = Math.max(
5681
+ 0,
5682
+ ...Object.values(geometry.morphAttributes).map((attributes) => attributes?.length ?? 0)
5683
+ );
5684
+ const nextIndex = Math.max(existingAttributeTargetCount, usedIndices.length ? Math.max(...usedIndices) + 1 : 0);
5685
+ const index = existingIndex ?? nextIndex;
5686
+ dictionary[target.name] = index;
5687
+ this.setMorphAttributeAtIndex(geometry, "position", target.position, position.itemSize, position.count, index, target.name);
5688
+ const normal = geometry.getAttribute("normal");
5689
+ if (target.normal) {
5690
+ this.setMorphAttributeAtIndex(geometry, "normal", target.normal, normal?.itemSize ?? 3, position.count, index, target.name);
5691
+ } else {
5692
+ this.setZeroMorphAttributeAtIndex(geometry, "normal", normal?.itemSize ?? 3, position.count, index, target.name);
5693
+ }
5694
+ const tangent = geometry.getAttribute("tangent");
5695
+ if (target.tangent) {
5696
+ this.setMorphAttributeAtIndex(geometry, "tangent", target.tangent, tangent?.itemSize ?? 4, position.count, index, target.name);
5697
+ } else {
5698
+ this.setZeroMorphAttributeAtIndex(geometry, "tangent", tangent?.itemSize ?? 4, position.count, index, target.name);
5699
+ }
5700
+ const color = geometry.getAttribute("color");
5701
+ const existingColorMorph = geometry.morphAttributes.color?.find(Boolean);
5702
+ this.setZeroMorphAttributeAtIndex(
5703
+ geometry,
5704
+ "color",
5705
+ color?.itemSize ?? existingColorMorph?.itemSize ?? 3,
5706
+ position.count,
5707
+ index,
5708
+ target.name
5709
+ );
5710
+ geometry.morphTargetsRelative = target.relative !== false;
5711
+ geometry.morphTargetDictionary = dictionary;
5712
+ if (forceGeometryReplacement) {
5713
+ mesh.geometry = geometry;
5714
+ sourceGeometry.dispose();
5715
+ }
5716
+ const influenceLength = Math.max(previousInfluences.length, index + 1);
5717
+ const influences = previousInfluences.slice(0, influenceLength);
5718
+ while (influences.length < influenceLength) {
5719
+ influences.push(0);
5720
+ }
5721
+ if (resetInfluence) {
5722
+ influences[index] = 0;
5723
+ }
5724
+ mesh.morphTargetDictionary = dictionary;
5725
+ mesh.morphTargetInfluences = influences;
5726
+ this.addRuntimeMorphMesh(mesh);
5727
+ if (!this.config.morphToMesh?.face?.length) {
5728
+ this.config.morphToMesh = {
5729
+ ...this.config.morphToMesh,
5730
+ face: [mesh.name]
5731
+ };
5732
+ }
5733
+ return index;
5734
+ }
5735
+ requireNamedMesh(meshName) {
5736
+ const mesh = this.meshByName.get(meshName);
5737
+ if (mesh) return mesh;
5738
+ if (this.model) {
5739
+ let found = null;
5740
+ this.model.traverse((obj) => {
5741
+ if (!found && obj.isMesh && obj.name === meshName) {
5742
+ found = obj;
5743
+ }
5744
+ });
5745
+ if (found) {
5746
+ this.meshByName.set(meshName, found);
5747
+ return found;
5748
+ }
5749
+ }
5750
+ throw new Error(`Mesh "${meshName}" was not found in the current model.`);
5751
+ }
5752
+ getMeshMorphDictionary(mesh) {
5753
+ const meshDictionary = mesh.morphTargetDictionary;
5754
+ const geometryDictionary = mesh.geometry.morphTargetDictionary;
5755
+ const dictionary = meshDictionary || geometryDictionary || {};
5756
+ mesh.morphTargetDictionary = dictionary;
5757
+ mesh.geometry.morphTargetDictionary = dictionary;
5758
+ return dictionary;
5759
+ }
5760
+ setMorphAttributeAtIndex(geometry, semantic, data, itemSize, vertexCount, index, name) {
5761
+ const expectedLength = vertexCount * itemSize;
5762
+ if (data.length !== expectedLength) {
5763
+ throw new Error(
5764
+ `Morph target "${name}" ${semantic} data has ${data.length} values; expected ${expectedLength} (${vertexCount} vertices * itemSize ${itemSize}).`
5765
+ );
5766
+ }
5767
+ const attributes = geometry.morphAttributes[semantic] ? [...geometry.morphAttributes[semantic]] : [];
5768
+ while (attributes.length < index) {
5769
+ const empty = new BufferAttribute(new Float32Array(expectedLength), itemSize);
5770
+ empty.name = `morph_${attributes.length}`;
5771
+ attributes.push(empty);
5772
+ }
5773
+ const values = data instanceof Float32Array ? new Float32Array(data) : Float32Array.from(data);
5774
+ const attribute = new BufferAttribute(values, itemSize);
5775
+ attribute.name = name;
5776
+ attributes[index] = attribute;
5777
+ geometry.morphAttributes[semantic] = attributes;
5778
+ }
5779
+ setZeroMorphAttributeAtIndex(geometry, semantic, itemSize, vertexCount, index, name) {
5780
+ if (!geometry.morphAttributes[semantic]?.length) return;
5781
+ const expectedLength = vertexCount * itemSize;
5782
+ const attributes = [...geometry.morphAttributes[semantic]];
5783
+ while (attributes.length < index) {
5784
+ const empty2 = new BufferAttribute(new Float32Array(expectedLength), itemSize);
5785
+ empty2.name = `morph_${attributes.length}`;
5786
+ attributes.push(empty2);
5787
+ }
5788
+ const empty = new BufferAttribute(new Float32Array(expectedLength), itemSize);
5789
+ empty.name = name;
5790
+ attributes[index] = empty;
5791
+ geometry.morphAttributes[semantic] = attributes;
5792
+ }
5793
+ addRuntimeMorphMesh(mesh) {
5794
+ const key = mesh.name || mesh.uuid;
5795
+ const exists = this.meshes.some((candidate) => (candidate.name || candidate.uuid) === key);
5796
+ if (!exists) {
5797
+ this.meshes.push(mesh);
5798
+ }
5799
+ }
5610
5800
  getMorphKeyCacheKey(key, meshNames) {
5611
5801
  return meshNames?.length ? `key:${key}@${meshNames.join(",")}` : `key:${key}`;
5612
5802
  }
@@ -7444,7 +7634,7 @@ function extractMorphs(meshes) {
7444
7634
  for (const mesh of meshes) {
7445
7635
  const geo = mesh.geometry;
7446
7636
  if (!geo.morphAttributes) continue;
7447
- const dict = geo.morphTargetDictionary;
7637
+ const dict = mesh.morphTargetDictionary || geo.morphTargetDictionary;
7448
7638
  if (dict) {
7449
7639
  for (const [name, index] of Object.entries(dict)) {
7450
7640
  morphs.push({