@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 +18 -18
- package/dist/index.cjs +19 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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 {
|