@lovelace_lol/loom3 1.0.0 → 1.0.2

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
@@ -4,7 +4,7 @@ The missing character controller for Three.js, allowing you to bring humanoid an
4
4
 
5
5
  Loom3 provides mappings that connect [Facial Action Coding System (FACS)](https://en.wikipedia.org/wiki/Facial_Action_Coding_System) Action Units to the morph targets and bone transforms found in Character Creator 4 (CC4) characters. Instead of manually figuring out which blend shapes correspond to which facial movements, you can simply say `setAU(12, 0.8)` and the library handles the rest.
6
6
 
7
- > **Note:** If you previously used the `loomlarge` npm package, it has been renamed to `loom3`.
7
+ > **Note:** If you previously used the `loomlarge` npm package, it has been renamed to `@lovelace_lol/loom3`.
8
8
 
9
9
  > **Screenshot placeholder:** Add a hero image showing a character with facial expressions controlled by Loom3
10
10
 
@@ -37,7 +37,7 @@ Loom3 provides mappings that connect [Facial Action Coding System (FACS)](https:
37
37
  ### Install the package
38
38
 
39
39
  ```bash
40
- npm install loom3
40
+ npm install @lovelace_lol/loom3
41
41
  ```
42
42
 
43
43
  ### Peer dependency
@@ -53,7 +53,7 @@ npm install three
53
53
  ```typescript
54
54
  import * as THREE from 'three';
55
55
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
56
- import { Loom3, collectMorphMeshes, CC4_PRESET } from 'loom3';
56
+ import { Loom3, collectMorphMeshes, CC4_PRESET } from '@lovelace_lol/loom3';
57
57
 
58
58
  // 1. Create the Loom3 controller with a preset
59
59
  const loom = new Loom3({ profile: CC4_PRESET });
@@ -81,7 +81,7 @@ loader.load('/character.glb', (gltf) => {
81
81
  // This drives all transitions and animations
82
82
  ```
83
83
 
84
- If you’re implementing a custom renderer, target the `LoomLarge` interface exported from `loom3`.
84
+ If you’re implementing a custom renderer, target the `LoomLarge` interface exported from `@lovelace_lol/loom3`.
85
85
 
86
86
  ### Quick start examples
87
87
 
@@ -123,7 +123,7 @@ await loom.transitionAU(12, 0, 300).promise;
123
123
  This utility function traverses a Three.js scene and returns all meshes that have `morphTargetInfluences` (i.e., blend shapes). It's the recommended way to gather meshes for Loom3:
124
124
 
125
125
  ```typescript
126
- import { collectMorphMeshes } from 'loom3';
126
+ import { collectMorphMeshes } from '@lovelace_lol/loom3';
127
127
 
128
128
  const meshes = collectMorphMeshes(gltf.scene);
129
129
  // Returns: Array of THREE.Mesh objects with morph targets
@@ -142,7 +142,7 @@ Presets define how FACS Action Units map to your character's morph targets and b
142
142
  ### What's in a preset?
143
143
 
144
144
  ```typescript
145
- import { CC4_PRESET } from 'loom3';
145
+ import { CC4_PRESET } from '@lovelace_lol/loom3';
146
146
 
147
147
  // CC4_PRESET contains:
148
148
  {
@@ -206,7 +206,7 @@ import { CC4_PRESET } from 'loom3';
206
206
  ### Passing a preset to Loom3
207
207
 
208
208
  ```typescript
209
- import { Loom3, CC4_PRESET } from 'loom3';
209
+ import { Loom3, CC4_PRESET } from '@lovelace_lol/loom3';
210
210
 
211
211
  const loom = new Loom3({ profile: CC4_PRESET });
212
212
  ```
@@ -214,7 +214,7 @@ const loom = new Loom3({ profile: CC4_PRESET });
214
214
  You can also resolve presets by name and apply overrides without cloning the full preset:
215
215
 
216
216
  ```typescript
217
- import { Loom3 } from 'loom3';
217
+ import { Loom3 } from '@lovelace_lol/loom3';
218
218
 
219
219
  const loom = new Loom3({
220
220
  presetType: 'cc4',
@@ -231,8 +231,8 @@ const loom = new Loom3({
231
231
  A **profile** is a partial override object that extends a base preset. Use it to customize a single character without copying the full preset:
232
232
 
233
233
  ```typescript
234
- import type { Profile } from 'loom3';
235
- import { Loom3 } from 'loom3';
234
+ import type { Profile } from '@lovelace_lol/loom3';
235
+ import { Loom3 } from '@lovelace_lol/loom3';
236
236
 
237
237
  const DAISY_PROFILE: Profile = {
238
238
  morphToMesh: { face: ['Object_9'] },
@@ -324,7 +324,7 @@ import {
324
324
  validateMappings,
325
325
  generateMappingCorrections,
326
326
  resolvePreset,
327
- } from 'loom3';
327
+ } from '@lovelace_lol/loom3';
328
328
 
329
329
  const preset = resolvePreset('cc4');
330
330
  const modelData = extractModelData({ model, meshes, animations });
@@ -394,7 +394,7 @@ This is especially useful for:
394
394
  Use `mergePreset` to override specific mappings while keeping the rest:
395
395
 
396
396
  ```typescript
397
- import { CC4_PRESET, mergePreset } from 'loom3';
397
+ import { CC4_PRESET, mergePreset } from '@lovelace_lol/loom3';
398
398
 
399
399
  const MY_PRESET = mergePreset(CC4_PRESET, {
400
400
 
@@ -420,7 +420,7 @@ const loom = new Loom3({ profile: MY_PRESET });
420
420
  ### Creating a preset from scratch
421
421
 
422
422
  ```typescript
423
- import { Profile } from 'loom3';
423
+ import { Profile } from '@lovelace_lol/loom3';
424
424
 
425
425
  const CUSTOM_PRESET: Profile = {
426
426
  auToMorphs: {
@@ -480,7 +480,7 @@ Some models (like fish) rely entirely on bone rotations for animation:
480
480
  Here's a complete example of a preset for a betta fish:
481
481
 
482
482
  ```typescript
483
- import type { BoneBinding, AUInfo, CompositeRotation } from 'loom3';
483
+ import type { BoneBinding, AUInfo, CompositeRotation } from '@lovelace_lol/loom3';
484
484
 
485
485
  // Define semantic bone mappings
486
486
  export const FISH_BONE_NODES = {
@@ -685,7 +685,7 @@ export const FISH_AU_MAPPING_CONFIG = {
685
685
  ### Using the fish preset
686
686
 
687
687
  ```typescript
688
- import { Loom3 } from 'loom3';
688
+ import { Loom3 } from '@lovelace_lol/loom3';
689
689
  import { FISH_AU_MAPPING_CONFIG, FishAction } from './presets/bettaFish';
690
690
 
691
691
  const fishController = new Loom3({
@@ -857,7 +857,7 @@ Only AUs that have both `auToMorphs` AND `auToBones` entries support mixing. Com
857
857
  - AU61-64 (Eye movements)
858
858
 
859
859
  ```typescript
860
- import { isMixedAU } from 'loom3';
860
+ import { isMixedAU } from '@lovelace_lol/loom3';
861
861
 
862
862
  if (isMixedAU(26)) {
863
863
  console.log('AU26 supports morph/bone mixing');
@@ -1018,7 +1018,7 @@ loom.setAU(52, 0.7);
1018
1018
  You can access pair information programmatically:
1019
1019
 
1020
1020
  ```typescript
1021
- import { CONTINUUM_PAIRS_MAP } from 'loom3';
1021
+ import { CONTINUUM_PAIRS_MAP } from '@lovelace_lol/loom3';
1022
1022
 
1023
1023
  const pair = CONTINUUM_PAIRS_MAP[51];
1024
1024
  // { pairId: 52, isNegative: true, axis: 'yaw', node: 'HEAD' }
@@ -1337,7 +1337,7 @@ loader.load('/character.glb', (gltf) => {
1337
1337
  Hair physics defaults live in the preset/profile and are applied automatically at init:
1338
1338
 
1339
1339
  ```typescript
1340
- import type { Profile } from 'loom3';
1340
+ import type { Profile } from '@lovelace_lol/loom3';
1341
1341
 
1342
1342
  const profile: Profile = {
1343
1343
  // ...all your usual AU mappings...
package/dist/index.cjs CHANGED
@@ -25,6 +25,20 @@ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
25
25
  var __defProp = Object.defineProperty;
26
26
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
27
27
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
28
+
29
+ // src/engines/three/balanceUtils.ts
30
+ function clampBalance(value) {
31
+ if (!Number.isFinite(value)) return 0;
32
+ return Math.max(-1, Math.min(1, value));
33
+ }
34
+ function resolveCurveBalance(curveId, globalBalance, balanceMap) {
35
+ if (balanceMap && Object.prototype.hasOwnProperty.call(balanceMap, curveId)) {
36
+ return clampBalance(Number(balanceMap[curveId]));
37
+ }
38
+ return clampBalance(globalBalance);
39
+ }
40
+
41
+ // src/engines/three/AnimationThree.ts
28
42
  var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
29
43
  var AnimationThree = class {
30
44
  constructor() {
@@ -384,7 +398,8 @@ var BakedAnimationController = class {
384
398
  }
385
399
  const tracks = [];
386
400
  const intensityScale = options?.intensityScale ?? 1;
387
- const balance = options?.balance ?? 0;
401
+ const globalBalance = options?.balance ?? 0;
402
+ const balanceMap = options?.balanceMap;
388
403
  const meshNames = options?.meshNames;
389
404
  let maxTime = 0;
390
405
  const isNumericAU = (id) => /^\d+$/.test(id);
@@ -440,9 +455,10 @@ var BakedAnimationController = class {
440
455
  const leftKeys = morphsBySide?.left ?? [];
441
456
  const rightKeys = morphsBySide?.right ?? [];
442
457
  const centerKeys = morphsBySide?.center ?? [];
458
+ const curveBalance = resolveCurveBalance(curveId, globalBalance, balanceMap);
443
459
  for (const morphKey of leftKeys) {
444
460
  let effectiveScale = intensityScale * mixWeight;
445
- if (balance > 0) effectiveScale *= 1 - balance;
461
+ if (curveBalance > 0) effectiveScale *= 1 - curveBalance;
446
462
  if (typeof morphKey === "number") {
447
463
  this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
448
464
  } else {
@@ -451,7 +467,7 @@ var BakedAnimationController = class {
451
467
  }
452
468
  for (const morphKey of rightKeys) {
453
469
  let effectiveScale = intensityScale * mixWeight;
454
- if (balance < 0) effectiveScale *= 1 + balance;
470
+ if (curveBalance < 0) effectiveScale *= 1 + curveBalance;
455
471
  if (typeof morphKey === "number") {
456
472
  this.addMorphIndexTracks(tracks, morphKey, keyframes, effectiveScale, meshNames);
457
473
  } else {