@lovelace_lol/loom3 1.0.0
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 +1667 -0
- package/dist/index.cjs +6226 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2384 -0
- package/dist/index.d.ts +2384 -0
- package/dist/index.js +6160 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,2384 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { Object3D, Mesh, AnimationClip } from 'three';
|
|
3
|
+
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CC4 Preset - Character Creator 4 AU Mappings
|
|
7
|
+
*
|
|
8
|
+
* Complete FACS Action Unit to morph target and bone mappings for CC4 rigs.
|
|
9
|
+
* This includes all the composite rotations, continuum pairs, mesh classifications,
|
|
10
|
+
* and AU metadata that we painstakingly worked through.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
declare const AU_TO_MORPHS: Record<number, MorphTargetsBySide>;
|
|
14
|
+
declare const BONE_AU_TO_BINDINGS: Record<number, BoneBinding[]>;
|
|
15
|
+
declare const VISEME_KEYS: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Jaw opening amounts for each viseme index (0-14).
|
|
18
|
+
* Values are 0-1 representing how much the jaw should open.
|
|
19
|
+
* Used by snippetToClip when autoVisemeJaw is enabled.
|
|
20
|
+
*/
|
|
21
|
+
declare const VISEME_JAW_AMOUNTS: number[];
|
|
22
|
+
/** Check if an AU has both morphs and bones (can blend between them) */
|
|
23
|
+
declare const isMixedAU: (id: number) => boolean;
|
|
24
|
+
/** Check if an AU has separate left/right morphs */
|
|
25
|
+
declare const hasLeftRightMorphs: (auId: number) => boolean;
|
|
26
|
+
declare const COMPOSITE_ROTATIONS: CompositeRotation[];
|
|
27
|
+
/**
|
|
28
|
+
* Continuum pair mappings - precomputed from COMPOSITE_ROTATIONS
|
|
29
|
+
* Maps AU ID to its continuum partner info for bidirectional axes
|
|
30
|
+
* (e.g., AU 51 "Head Left" is paired with AU 52 "Head Right")
|
|
31
|
+
*/
|
|
32
|
+
declare const CONTINUUM_PAIRS_MAP: Record<number, {
|
|
33
|
+
pairId: number;
|
|
34
|
+
isNegative: boolean;
|
|
35
|
+
axis: 'pitch' | 'yaw' | 'roll';
|
|
36
|
+
node: 'JAW' | 'HEAD' | 'EYE_L' | 'EYE_R' | 'TONGUE';
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Human-readable labels for continuum pairs
|
|
40
|
+
* Key format: "negativeAU-positiveAU"
|
|
41
|
+
* Used by UI components (ContinuumSlider, AUSection) to display friendly axis names
|
|
42
|
+
*/
|
|
43
|
+
declare const CONTINUUM_LABELS: Record<string, string>;
|
|
44
|
+
/**
|
|
45
|
+
* Bone name prefix for CC4 rigs.
|
|
46
|
+
* Base bone names are stored without this prefix in CC4_BONE_NODES.
|
|
47
|
+
* The engine will prepend this when resolving bones.
|
|
48
|
+
*/
|
|
49
|
+
declare const CC4_BONE_PREFIX = "CC_Base_";
|
|
50
|
+
/**
|
|
51
|
+
* Suffix pattern regex for fuzzy bone matching.
|
|
52
|
+
* Matches numbered suffixes like _01, _038 (common in Sketchfab exports)
|
|
53
|
+
* and .001, .002 (common in Blender exports).
|
|
54
|
+
*/
|
|
55
|
+
declare const CC4_SUFFIX_PATTERN = "_\\d+$|\\.\\d+$";
|
|
56
|
+
declare const CC4_BONE_NODES: {
|
|
57
|
+
readonly EYE_L: "L_Eye";
|
|
58
|
+
readonly EYE_R: "R_Eye";
|
|
59
|
+
readonly HEAD: "Head";
|
|
60
|
+
readonly NECK: "NeckTwist01";
|
|
61
|
+
readonly NECK_TWIST: "NeckTwist02";
|
|
62
|
+
readonly JAW: "JawRoot";
|
|
63
|
+
readonly TONGUE: "Tongue01";
|
|
64
|
+
};
|
|
65
|
+
declare const CC4_EYE_MESH_NODES: {
|
|
66
|
+
readonly LEFT: "CC_Base_Eye";
|
|
67
|
+
readonly RIGHT: "CC_Base_Eye_1";
|
|
68
|
+
};
|
|
69
|
+
declare const AU_INFO: Record<string, AUInfo>;
|
|
70
|
+
/** Default mix weights (0 = morph only, 1 = bone only) */
|
|
71
|
+
declare const AU_MIX_DEFAULTS: Record<number, number>;
|
|
72
|
+
/** Exact mesh name -> category mapping from the character GLB */
|
|
73
|
+
declare const CC4_MESHES: Record<string, MeshInfo>;
|
|
74
|
+
/** Which mesh each morph category applies to */
|
|
75
|
+
declare const MORPH_TO_MESH: Record<MorphCategory, string[]>;
|
|
76
|
+
declare const CC4_PRESET: Profile;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Merge a base preset with a profile override.
|
|
80
|
+
*
|
|
81
|
+
* Rules:
|
|
82
|
+
* - Scalars: override if provided.
|
|
83
|
+
* - Maps: shallow-merged by key (override wins), values cloned.
|
|
84
|
+
* - Arrays: replaced when override is provided (except annotationRegions).
|
|
85
|
+
* - annotationRegions: merged by region name, shallow field merge (override wins).
|
|
86
|
+
*/
|
|
87
|
+
declare function resolveProfile(base: Profile, override: Partial<Profile>): Profile;
|
|
88
|
+
|
|
89
|
+
declare const BETTA_FISH_PRESET: {
|
|
90
|
+
name: string;
|
|
91
|
+
animalType: string;
|
|
92
|
+
emoji: string;
|
|
93
|
+
bones: readonly ["Armature_rootJoint", "Bone_Armature", "Bone.001_Armature", "Bone.009_Armature", "Bone.027_Armature", "Bone.029_Armature", "Bone.031_Armature", "Bone.028_Armature", "Bone.030_Armature", "Bone.032_Armature", "Bone.010_Armature", "Bone.012_Armature", "Bone.014_Armature", "Bone.016_Armature", "Bone.011_Armature", "Bone.013_Armature", "Bone.015_Armature", "Bone.017_Armature", "Bone.002_Armature", "Bone.003_Armature", "Bone.004_Armature", "Bone.005_Armature", "Bone.020_Armature", "Bone.025_Armature", "Bone.026_Armature", "Bone.039_Armature", "Bone.040_Armature", "Bone.041_Armature", "Bone.042_Armature", "Bone.043_Armature", "Bone.044_Armature", "Bone.045_Armature", "Bone.019_Armature", "Bone.023_Armature", "Bone.024_Armature", "Bone.034_Armature", "Bone.036_Armature", "Bone.038_Armature", "Bone.018_Armature", "Bone.021_Armature", "Bone.022_Armature", "Bone.033_Armature", "Bone.035_Armature", "Bone.037_Armature", "Bone.046_Armature", "Bone.048_Armature", "Bone.050_Armature", "Bone.047_Armature", "Bone.049_Armature", "Bone.051_Armature", "Bone.006_Armature", "Bone.007_Armature", "Bone.008_Armature"];
|
|
94
|
+
boneNodes: {
|
|
95
|
+
readonly ROOT: "Armature_rootJoint";
|
|
96
|
+
readonly BODY_ROOT: "Bone_Armature";
|
|
97
|
+
readonly HEAD: "001";
|
|
98
|
+
readonly BODY_FRONT: "002";
|
|
99
|
+
readonly BODY_MID: "003";
|
|
100
|
+
readonly BODY_BACK: "004";
|
|
101
|
+
readonly TAIL_BASE: "005";
|
|
102
|
+
readonly GILL_L: "046";
|
|
103
|
+
readonly GILL_L_MID: "048";
|
|
104
|
+
readonly GILL_L_TIP: "050";
|
|
105
|
+
readonly GILL_R: "047";
|
|
106
|
+
readonly GILL_R_MID: "049";
|
|
107
|
+
readonly GILL_R_TIP: "051";
|
|
108
|
+
readonly DORSAL_ROOT: "006";
|
|
109
|
+
readonly DORSAL_L: "007";
|
|
110
|
+
readonly DORSAL_R: "008";
|
|
111
|
+
readonly VENTRAL_L: "018";
|
|
112
|
+
readonly VENTRAL_L_MID: "021";
|
|
113
|
+
readonly VENTRAL_L_TIP: "022";
|
|
114
|
+
readonly VENTRAL_R: "033";
|
|
115
|
+
readonly VENTRAL_R_MID: "035";
|
|
116
|
+
readonly VENTRAL_R_TIP: "037";
|
|
117
|
+
readonly PECTORAL_L_ROOT: "009";
|
|
118
|
+
readonly PECTORAL_L_CHAIN1: "027";
|
|
119
|
+
readonly PECTORAL_L_CHAIN1_MID: "029";
|
|
120
|
+
readonly PECTORAL_L_CHAIN1_TIP: "031";
|
|
121
|
+
readonly PECTORAL_L_CHAIN2: "028";
|
|
122
|
+
readonly PECTORAL_L_CHAIN2_MID: "030";
|
|
123
|
+
readonly PECTORAL_L_CHAIN2_TIP: "032";
|
|
124
|
+
readonly PECTORAL_R_ROOT: "010";
|
|
125
|
+
readonly PECTORAL_R_CHAIN1: "012";
|
|
126
|
+
readonly PECTORAL_R_CHAIN1_A: "014";
|
|
127
|
+
readonly PECTORAL_R_CHAIN1_B: "016";
|
|
128
|
+
readonly PECTORAL_R_ROOT2: "011";
|
|
129
|
+
readonly PECTORAL_R_CHAIN2: "013";
|
|
130
|
+
readonly PECTORAL_R_CHAIN2_A: "015";
|
|
131
|
+
readonly PECTORAL_R_CHAIN2_B: "017";
|
|
132
|
+
readonly EYE_L: "EYES_0";
|
|
133
|
+
readonly EYE_R: "EYES_0";
|
|
134
|
+
readonly TAIL_TOP: "020";
|
|
135
|
+
readonly TAIL_TOP_MID: "025";
|
|
136
|
+
readonly TAIL_TOP_TIP: "026";
|
|
137
|
+
readonly TAIL_MID: "039";
|
|
138
|
+
readonly TAIL_MID_A: "040";
|
|
139
|
+
readonly TAIL_MID_A_TIP: "041";
|
|
140
|
+
readonly TAIL_MID_B: "042";
|
|
141
|
+
readonly TAIL_MID_B_TIP: "043";
|
|
142
|
+
readonly TAIL_MID_C: "044";
|
|
143
|
+
readonly TAIL_MID_C_TIP: "045";
|
|
144
|
+
readonly TAIL_SIDE_L: "019";
|
|
145
|
+
readonly TAIL_SIDE_L_MID: "023";
|
|
146
|
+
readonly TAIL_SIDE_L_TIP: "024";
|
|
147
|
+
readonly TAIL_SIDE_R: "034";
|
|
148
|
+
readonly TAIL_SIDE_R_MID: "036";
|
|
149
|
+
readonly TAIL_SIDE_R_TIP: "038";
|
|
150
|
+
};
|
|
151
|
+
boneBindings: Record<number, BoneBinding[]>;
|
|
152
|
+
actionInfo: Record<string, AUInfo>;
|
|
153
|
+
eyeMeshNodes: {
|
|
154
|
+
readonly LEFT: "EYES_0";
|
|
155
|
+
readonly RIGHT: "EYES_0";
|
|
156
|
+
};
|
|
157
|
+
auToMorphs: Record<number, MorphTargetsBySide>;
|
|
158
|
+
morphToMesh: Record<string, string[]>;
|
|
159
|
+
visemeKeys: string[];
|
|
160
|
+
};
|
|
161
|
+
declare const AU_MAPPING_CONFIG: {
|
|
162
|
+
name: string;
|
|
163
|
+
animalType: string;
|
|
164
|
+
emoji: string;
|
|
165
|
+
auToBones: Record<number, BoneBinding[]>;
|
|
166
|
+
boneNodes: {
|
|
167
|
+
readonly ROOT: "Armature_rootJoint";
|
|
168
|
+
readonly BODY_ROOT: "Bone_Armature";
|
|
169
|
+
readonly HEAD: "001";
|
|
170
|
+
readonly BODY_FRONT: "002";
|
|
171
|
+
readonly BODY_MID: "003";
|
|
172
|
+
readonly BODY_BACK: "004";
|
|
173
|
+
readonly TAIL_BASE: "005";
|
|
174
|
+
readonly GILL_L: "046";
|
|
175
|
+
readonly GILL_L_MID: "048";
|
|
176
|
+
readonly GILL_L_TIP: "050";
|
|
177
|
+
readonly GILL_R: "047";
|
|
178
|
+
readonly GILL_R_MID: "049";
|
|
179
|
+
readonly GILL_R_TIP: "051";
|
|
180
|
+
readonly DORSAL_ROOT: "006";
|
|
181
|
+
readonly DORSAL_L: "007";
|
|
182
|
+
readonly DORSAL_R: "008";
|
|
183
|
+
readonly VENTRAL_L: "018";
|
|
184
|
+
readonly VENTRAL_L_MID: "021";
|
|
185
|
+
readonly VENTRAL_L_TIP: "022";
|
|
186
|
+
readonly VENTRAL_R: "033";
|
|
187
|
+
readonly VENTRAL_R_MID: "035";
|
|
188
|
+
readonly VENTRAL_R_TIP: "037";
|
|
189
|
+
readonly PECTORAL_L_ROOT: "009";
|
|
190
|
+
readonly PECTORAL_L_CHAIN1: "027";
|
|
191
|
+
readonly PECTORAL_L_CHAIN1_MID: "029";
|
|
192
|
+
readonly PECTORAL_L_CHAIN1_TIP: "031";
|
|
193
|
+
readonly PECTORAL_L_CHAIN2: "028";
|
|
194
|
+
readonly PECTORAL_L_CHAIN2_MID: "030";
|
|
195
|
+
readonly PECTORAL_L_CHAIN2_TIP: "032";
|
|
196
|
+
readonly PECTORAL_R_ROOT: "010";
|
|
197
|
+
readonly PECTORAL_R_CHAIN1: "012";
|
|
198
|
+
readonly PECTORAL_R_CHAIN1_A: "014";
|
|
199
|
+
readonly PECTORAL_R_CHAIN1_B: "016";
|
|
200
|
+
readonly PECTORAL_R_ROOT2: "011";
|
|
201
|
+
readonly PECTORAL_R_CHAIN2: "013";
|
|
202
|
+
readonly PECTORAL_R_CHAIN2_A: "015";
|
|
203
|
+
readonly PECTORAL_R_CHAIN2_B: "017";
|
|
204
|
+
readonly EYE_L: "EYES_0";
|
|
205
|
+
readonly EYE_R: "EYES_0";
|
|
206
|
+
readonly TAIL_TOP: "020";
|
|
207
|
+
readonly TAIL_TOP_MID: "025";
|
|
208
|
+
readonly TAIL_TOP_TIP: "026";
|
|
209
|
+
readonly TAIL_MID: "039";
|
|
210
|
+
readonly TAIL_MID_A: "040";
|
|
211
|
+
readonly TAIL_MID_A_TIP: "041";
|
|
212
|
+
readonly TAIL_MID_B: "042";
|
|
213
|
+
readonly TAIL_MID_B_TIP: "043";
|
|
214
|
+
readonly TAIL_MID_C: "044";
|
|
215
|
+
readonly TAIL_MID_C_TIP: "045";
|
|
216
|
+
readonly TAIL_SIDE_L: "019";
|
|
217
|
+
readonly TAIL_SIDE_L_MID: "023";
|
|
218
|
+
readonly TAIL_SIDE_L_TIP: "024";
|
|
219
|
+
readonly TAIL_SIDE_R: "034";
|
|
220
|
+
readonly TAIL_SIDE_R_MID: "036";
|
|
221
|
+
readonly TAIL_SIDE_R_TIP: "038";
|
|
222
|
+
};
|
|
223
|
+
bonePrefix: string;
|
|
224
|
+
boneSuffix: string;
|
|
225
|
+
suffixPattern: string;
|
|
226
|
+
auToMorphs: Record<number, MorphTargetsBySide>;
|
|
227
|
+
morphToMesh: Record<string, string[]>;
|
|
228
|
+
visemeKeys: string[];
|
|
229
|
+
auInfo: Record<string, AUInfo>;
|
|
230
|
+
compositeRotations: CompositeRotation[];
|
|
231
|
+
eyeMeshNodes: {
|
|
232
|
+
readonly LEFT: "EYES_0";
|
|
233
|
+
readonly RIGHT: "EYES_0";
|
|
234
|
+
};
|
|
235
|
+
meshes: Record<string, MeshInfo>;
|
|
236
|
+
auMixDefaults: Record<number, number>;
|
|
237
|
+
continuumPairs: Record<number, {
|
|
238
|
+
pairId: number;
|
|
239
|
+
isNegative: boolean;
|
|
240
|
+
axis: "pitch" | "yaw" | "roll";
|
|
241
|
+
node: string;
|
|
242
|
+
}>;
|
|
243
|
+
continuumLabels: Record<string, string>;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Preset types that can be passed to Loom3
|
|
248
|
+
*/
|
|
249
|
+
type PresetType = 'cc4' | 'skeletal' | 'fish' | 'custom';
|
|
250
|
+
/**
|
|
251
|
+
* Resolve a preset by type name.
|
|
252
|
+
* This allows frontend to pass a string instead of importing the full preset.
|
|
253
|
+
*/
|
|
254
|
+
declare function resolvePreset(presetType: PresetType | string | undefined): Profile | {
|
|
255
|
+
name: string;
|
|
256
|
+
animalType: string;
|
|
257
|
+
emoji: string;
|
|
258
|
+
auToBones: Record<number, BoneBinding[]>;
|
|
259
|
+
boneNodes: {
|
|
260
|
+
readonly ROOT: "Armature_rootJoint";
|
|
261
|
+
readonly BODY_ROOT: "Bone_Armature";
|
|
262
|
+
readonly HEAD: "001";
|
|
263
|
+
readonly BODY_FRONT: "002";
|
|
264
|
+
readonly BODY_MID: "003";
|
|
265
|
+
readonly BODY_BACK: "004";
|
|
266
|
+
readonly TAIL_BASE: "005";
|
|
267
|
+
readonly GILL_L: "046";
|
|
268
|
+
readonly GILL_L_MID: "048";
|
|
269
|
+
readonly GILL_L_TIP: "050";
|
|
270
|
+
readonly GILL_R: "047";
|
|
271
|
+
readonly GILL_R_MID: "049";
|
|
272
|
+
readonly GILL_R_TIP: "051";
|
|
273
|
+
readonly DORSAL_ROOT: "006";
|
|
274
|
+
readonly DORSAL_L: "007";
|
|
275
|
+
readonly DORSAL_R: "008";
|
|
276
|
+
readonly VENTRAL_L: "018";
|
|
277
|
+
readonly VENTRAL_L_MID: "021";
|
|
278
|
+
readonly VENTRAL_L_TIP: "022";
|
|
279
|
+
readonly VENTRAL_R: "033";
|
|
280
|
+
readonly VENTRAL_R_MID: "035";
|
|
281
|
+
readonly VENTRAL_R_TIP: "037";
|
|
282
|
+
readonly PECTORAL_L_ROOT: "009";
|
|
283
|
+
readonly PECTORAL_L_CHAIN1: "027";
|
|
284
|
+
readonly PECTORAL_L_CHAIN1_MID: "029";
|
|
285
|
+
readonly PECTORAL_L_CHAIN1_TIP: "031";
|
|
286
|
+
readonly PECTORAL_L_CHAIN2: "028";
|
|
287
|
+
readonly PECTORAL_L_CHAIN2_MID: "030";
|
|
288
|
+
readonly PECTORAL_L_CHAIN2_TIP: "032";
|
|
289
|
+
readonly PECTORAL_R_ROOT: "010";
|
|
290
|
+
readonly PECTORAL_R_CHAIN1: "012";
|
|
291
|
+
readonly PECTORAL_R_CHAIN1_A: "014";
|
|
292
|
+
readonly PECTORAL_R_CHAIN1_B: "016";
|
|
293
|
+
readonly PECTORAL_R_ROOT2: "011";
|
|
294
|
+
readonly PECTORAL_R_CHAIN2: "013";
|
|
295
|
+
readonly PECTORAL_R_CHAIN2_A: "015";
|
|
296
|
+
readonly PECTORAL_R_CHAIN2_B: "017";
|
|
297
|
+
readonly EYE_L: "EYES_0";
|
|
298
|
+
readonly EYE_R: "EYES_0";
|
|
299
|
+
readonly TAIL_TOP: "020";
|
|
300
|
+
readonly TAIL_TOP_MID: "025";
|
|
301
|
+
readonly TAIL_TOP_TIP: "026";
|
|
302
|
+
readonly TAIL_MID: "039";
|
|
303
|
+
readonly TAIL_MID_A: "040";
|
|
304
|
+
readonly TAIL_MID_A_TIP: "041";
|
|
305
|
+
readonly TAIL_MID_B: "042";
|
|
306
|
+
readonly TAIL_MID_B_TIP: "043";
|
|
307
|
+
readonly TAIL_MID_C: "044";
|
|
308
|
+
readonly TAIL_MID_C_TIP: "045";
|
|
309
|
+
readonly TAIL_SIDE_L: "019";
|
|
310
|
+
readonly TAIL_SIDE_L_MID: "023";
|
|
311
|
+
readonly TAIL_SIDE_L_TIP: "024";
|
|
312
|
+
readonly TAIL_SIDE_R: "034";
|
|
313
|
+
readonly TAIL_SIDE_R_MID: "036";
|
|
314
|
+
readonly TAIL_SIDE_R_TIP: "038";
|
|
315
|
+
};
|
|
316
|
+
bonePrefix: string;
|
|
317
|
+
boneSuffix: string;
|
|
318
|
+
suffixPattern: string;
|
|
319
|
+
auToMorphs: Record<number, MorphTargetsBySide>;
|
|
320
|
+
morphToMesh: Record<string, string[]>;
|
|
321
|
+
visemeKeys: string[];
|
|
322
|
+
auInfo: Record<string, AUInfo>;
|
|
323
|
+
compositeRotations: CompositeRotation[];
|
|
324
|
+
eyeMeshNodes: {
|
|
325
|
+
readonly LEFT: "EYES_0";
|
|
326
|
+
readonly RIGHT: "EYES_0";
|
|
327
|
+
};
|
|
328
|
+
meshes: Record<string, MeshInfo>;
|
|
329
|
+
auMixDefaults: Record<number, number>;
|
|
330
|
+
continuumPairs: Record<number, {
|
|
331
|
+
pairId: number;
|
|
332
|
+
isNegative: boolean;
|
|
333
|
+
axis: "pitch" | "yaw" | "roll";
|
|
334
|
+
node: string;
|
|
335
|
+
}>;
|
|
336
|
+
continuumLabels: Record<string, string>;
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* Resolve a preset and merge optional overrides.
|
|
340
|
+
*/
|
|
341
|
+
declare function resolvePresetWithOverrides(presetType: PresetType | string | undefined, overrides?: Partial<Profile>): Profile;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Loom3 - Core Type Definitions
|
|
345
|
+
*
|
|
346
|
+
* Type definitions for the 3D character animation engine.
|
|
347
|
+
* These are framework-agnostic interfaces that work with any 3D engine.
|
|
348
|
+
*/
|
|
349
|
+
/**
|
|
350
|
+
* TransitionHandle - returned from transition methods
|
|
351
|
+
* Provides promise-based completion notification plus fine-grained control.
|
|
352
|
+
*/
|
|
353
|
+
interface TransitionHandle {
|
|
354
|
+
/** Resolves when the transition completes (or is cancelled) */
|
|
355
|
+
promise: Promise<void>;
|
|
356
|
+
/** Pause this transition (holds at current value) */
|
|
357
|
+
pause: () => void;
|
|
358
|
+
/** Resume this transition after pause */
|
|
359
|
+
resume: () => void;
|
|
360
|
+
/** Cancel this transition immediately (resolves promise) */
|
|
361
|
+
cancel: () => void;
|
|
362
|
+
}
|
|
363
|
+
/** Standard bone keys used in AU bindings */
|
|
364
|
+
type BoneKey = 'EYE_L' | 'EYE_R' | 'JAW' | 'HEAD' | 'NECK' | 'TONGUE' | string;
|
|
365
|
+
/**
|
|
366
|
+
* BoneBinding - Defines how an AU maps to bone transformations
|
|
367
|
+
*/
|
|
368
|
+
interface BoneBinding {
|
|
369
|
+
node: BoneKey;
|
|
370
|
+
channel: 'rx' | 'ry' | 'rz' | 'tx' | 'ty' | 'tz';
|
|
371
|
+
scale: -1 | 1;
|
|
372
|
+
maxDegrees?: number;
|
|
373
|
+
maxUnits?: number;
|
|
374
|
+
/** Optional side hint for balance-aware AUs. */
|
|
375
|
+
side?: 'left' | 'right';
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* RotationAxis - Defines which AUs control a specific rotation axis
|
|
379
|
+
*/
|
|
380
|
+
interface RotationAxis {
|
|
381
|
+
aus: number[];
|
|
382
|
+
axis: 'rx' | 'ry' | 'rz';
|
|
383
|
+
negative?: number;
|
|
384
|
+
positive?: number;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* CompositeRotation - Defines unified rotation axes for bones
|
|
388
|
+
*/
|
|
389
|
+
interface CompositeRotation {
|
|
390
|
+
node: string;
|
|
391
|
+
pitch: RotationAxis | null;
|
|
392
|
+
yaw: RotationAxis | null;
|
|
393
|
+
roll: RotationAxis | null;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* AUInfo - Metadata about an Action Unit
|
|
397
|
+
*/
|
|
398
|
+
interface AUInfo {
|
|
399
|
+
id: string;
|
|
400
|
+
name: string;
|
|
401
|
+
muscularBasis?: string;
|
|
402
|
+
links?: string[];
|
|
403
|
+
faceArea?: 'Upper' | 'Lower';
|
|
404
|
+
facePart?: string;
|
|
405
|
+
}
|
|
406
|
+
/** Per-axis rotation state - simple -1 to 1 values like stable version */
|
|
407
|
+
interface CompositeRotationState {
|
|
408
|
+
pitch: number;
|
|
409
|
+
yaw: number;
|
|
410
|
+
roll: number;
|
|
411
|
+
}
|
|
412
|
+
type RotationsState = Record<string, CompositeRotationState>;
|
|
413
|
+
/**
|
|
414
|
+
* Options for playing a baked animation clip.
|
|
415
|
+
*/
|
|
416
|
+
interface AnimationPlayOptions {
|
|
417
|
+
/** Playback speed multiplier (default: 1.0) */
|
|
418
|
+
speed?: number;
|
|
419
|
+
/** Animation intensity/weight (0-1, default: 1.0) */
|
|
420
|
+
intensity?: number;
|
|
421
|
+
/** Whether the animation should loop (default: true) */
|
|
422
|
+
loop?: boolean;
|
|
423
|
+
/** Loop mode: 'repeat' (restart from beginning), 'pingpong' (reverse direction), 'once' (no loop) */
|
|
424
|
+
loopMode?: 'repeat' | 'pingpong' | 'once';
|
|
425
|
+
/** Number of repetitions when looping (default: Infinity for repeat/pingpong) */
|
|
426
|
+
repeatCount?: number;
|
|
427
|
+
/** Crossfade duration in seconds when transitioning from another animation (default: 0.3) */
|
|
428
|
+
crossfadeDuration?: number;
|
|
429
|
+
/** Clamp animation at end when not looping (default: true) */
|
|
430
|
+
clampWhenFinished?: boolean;
|
|
431
|
+
/** Start time offset in seconds (default: 0) */
|
|
432
|
+
startTime?: number;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Information about a loaded animation clip.
|
|
436
|
+
*/
|
|
437
|
+
interface AnimationClipInfo {
|
|
438
|
+
/** Name of the animation clip */
|
|
439
|
+
name: string;
|
|
440
|
+
/** Duration of the animation in seconds */
|
|
441
|
+
duration: number;
|
|
442
|
+
/** Number of tracks (bones/morphs being animated) */
|
|
443
|
+
trackCount: number;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* State of a currently playing animation.
|
|
447
|
+
*/
|
|
448
|
+
interface AnimationState {
|
|
449
|
+
/** Name of the animation */
|
|
450
|
+
name: string;
|
|
451
|
+
/** Whether the animation is currently playing */
|
|
452
|
+
isPlaying: boolean;
|
|
453
|
+
/** Whether the animation is paused */
|
|
454
|
+
isPaused: boolean;
|
|
455
|
+
/** Current playback time in seconds */
|
|
456
|
+
time: number;
|
|
457
|
+
/** Duration of the animation in seconds */
|
|
458
|
+
duration: number;
|
|
459
|
+
/** Current playback speed */
|
|
460
|
+
speed: number;
|
|
461
|
+
/** Current weight/intensity (0-1) */
|
|
462
|
+
weight: number;
|
|
463
|
+
/** Whether the animation is looping */
|
|
464
|
+
isLooping: boolean;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Handle returned when playing an animation, providing control methods.
|
|
468
|
+
*/
|
|
469
|
+
interface AnimationActionHandle {
|
|
470
|
+
/** Stop the animation */
|
|
471
|
+
stop: () => void;
|
|
472
|
+
/** Pause the animation */
|
|
473
|
+
pause: () => void;
|
|
474
|
+
/** Resume a paused animation */
|
|
475
|
+
resume: () => void;
|
|
476
|
+
/** Set playback speed */
|
|
477
|
+
setSpeed: (speed: number) => void;
|
|
478
|
+
/** Set animation weight/intensity (0-1) */
|
|
479
|
+
setWeight: (weight: number) => void;
|
|
480
|
+
/** Seek to a specific time in seconds */
|
|
481
|
+
seekTo: (time: number) => void;
|
|
482
|
+
/** Get current animation state */
|
|
483
|
+
getState: () => AnimationState;
|
|
484
|
+
/** Crossfade to another animation */
|
|
485
|
+
crossfadeTo: (clipName: string, duration?: number) => AnimationActionHandle | null;
|
|
486
|
+
/** Promise that resolves when animation completes (only for non-looping) */
|
|
487
|
+
finished: Promise<void>;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* A single keyframe point in an animation curve.
|
|
491
|
+
*/
|
|
492
|
+
interface CurvePoint {
|
|
493
|
+
/** Time in seconds */
|
|
494
|
+
time: number;
|
|
495
|
+
/** Intensity value (0-1) */
|
|
496
|
+
intensity: number;
|
|
497
|
+
/** When true, inherit current AU value at playback start */
|
|
498
|
+
inherit?: boolean;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Map of curve IDs (AU numbers or morph names) to keyframe arrays.
|
|
502
|
+
*/
|
|
503
|
+
type CurvesMap = Record<string, CurvePoint[]>;
|
|
504
|
+
/**
|
|
505
|
+
* Options for building and playing a clip from curves.
|
|
506
|
+
*/
|
|
507
|
+
interface ClipOptions {
|
|
508
|
+
/** Whether the clip should loop (default: false) */
|
|
509
|
+
loop?: boolean;
|
|
510
|
+
/** Loop mode: repeat (default), pingpong (forward/back), or once */
|
|
511
|
+
loopMode?: 'repeat' | 'pingpong' | 'once';
|
|
512
|
+
/** Number of repetitions when looping (default: Infinity for repeat/pingpong) */
|
|
513
|
+
repeatCount?: number;
|
|
514
|
+
/** Playback rate multiplier (default: 1.0) */
|
|
515
|
+
playbackRate?: number;
|
|
516
|
+
/** Play clip backwards when true (implemented via negative time scale) */
|
|
517
|
+
reverse?: boolean;
|
|
518
|
+
/** Mixer weight/intensity (default: 1.0) */
|
|
519
|
+
mixerWeight?: number;
|
|
520
|
+
/** Left/right balance for bilateral AUs (-1 to 1, default: 0) */
|
|
521
|
+
balance?: number;
|
|
522
|
+
/** Jaw scale for viseme playback (default: 1.0) */
|
|
523
|
+
jawScale?: number;
|
|
524
|
+
/** Intensity scale multiplier (default: 1.0) */
|
|
525
|
+
intensityScale?: number;
|
|
526
|
+
/** Optional morph target mesh names to constrain track creation */
|
|
527
|
+
meshNames?: string[];
|
|
528
|
+
/** Snippet category - when 'visemeSnippet', numeric curve IDs (0-14) are viseme indices; otherwise they're AU IDs */
|
|
529
|
+
snippetCategory?: 'auSnippet' | 'visemeSnippet';
|
|
530
|
+
/**
|
|
531
|
+
* When true, automatically generate jaw bone rotation from viseme curves.
|
|
532
|
+
* Uses VISEME_JAW_AMOUNTS to determine jaw opening per viseme index.
|
|
533
|
+
* Only applies when snippetCategory is 'visemeSnippet'.
|
|
534
|
+
* Default: true (for backwards compatibility with transitionViseme behavior)
|
|
535
|
+
*/
|
|
536
|
+
autoVisemeJaw?: boolean;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Handle returned when playing a dynamically-built clip.
|
|
540
|
+
*/
|
|
541
|
+
interface ClipHandle {
|
|
542
|
+
/** Name of the clip */
|
|
543
|
+
clipName: string;
|
|
544
|
+
/** Optional unique id for the underlying mixer action */
|
|
545
|
+
actionId?: string;
|
|
546
|
+
/** Start or restart playback */
|
|
547
|
+
play: () => void;
|
|
548
|
+
/** Stop playback and reset */
|
|
549
|
+
stop: () => void;
|
|
550
|
+
/** Pause playback at current position */
|
|
551
|
+
pause: () => void;
|
|
552
|
+
/** Resume paused playback */
|
|
553
|
+
resume: () => void;
|
|
554
|
+
/** Optional weight setter for live mixer updates */
|
|
555
|
+
setWeight?: (w: number) => void;
|
|
556
|
+
/** Optional playback-rate setter for live mixer updates */
|
|
557
|
+
setPlaybackRate?: (r: number) => void;
|
|
558
|
+
/** Optional loop setter for live mixer updates */
|
|
559
|
+
setLoop?: (mode: 'once' | 'repeat' | 'pingpong', repeatCount?: number) => void;
|
|
560
|
+
/** Optional time setter for scrubbing */
|
|
561
|
+
setTime?: (time: number) => void;
|
|
562
|
+
/** Get current playback time in seconds */
|
|
563
|
+
getTime: () => number;
|
|
564
|
+
/** Get total clip duration in seconds */
|
|
565
|
+
getDuration: () => number;
|
|
566
|
+
/** Promise that resolves when clip finishes (non-looping only) */
|
|
567
|
+
finished: Promise<void>;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Snippet definition for animation playback.
|
|
571
|
+
* Can be loaded from JSON and converted to a mixer clip.
|
|
572
|
+
*/
|
|
573
|
+
interface Snippet {
|
|
574
|
+
/** Unique name for this snippet */
|
|
575
|
+
name: string;
|
|
576
|
+
/** Optional description */
|
|
577
|
+
description?: string;
|
|
578
|
+
/** Map of AU/morph IDs to keyframe curves */
|
|
579
|
+
curves: CurvesMap;
|
|
580
|
+
/** Category for grouping (e.g., 'eyeHeadTracking', 'visemeSnippet') */
|
|
581
|
+
snippetCategory?: string;
|
|
582
|
+
/** Priority for scheduling conflicts */
|
|
583
|
+
snippetPriority?: number;
|
|
584
|
+
/** Whether to loop playback */
|
|
585
|
+
loop?: boolean;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Loom3 - Profile Types
|
|
590
|
+
*
|
|
591
|
+
* Type definitions for character profiles.
|
|
592
|
+
* Profiles define how Action Units map to morphs/bones for a specific rig.
|
|
593
|
+
*/
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Profile - Complete configuration for AU-to-morph/bone mappings
|
|
597
|
+
*
|
|
598
|
+
* This is the main configuration object that defines how Action Units
|
|
599
|
+
* map to morph targets and bone transformations for a specific rig type.
|
|
600
|
+
*/
|
|
601
|
+
interface Profile {
|
|
602
|
+
/** Human-readable name for this profile (e.g., 'Character Creator 4', 'Betta Fish') */
|
|
603
|
+
name?: string;
|
|
604
|
+
/** Type of animal/creature this profile is for (e.g., 'human', 'fish', 'dog') */
|
|
605
|
+
animalType?: string;
|
|
606
|
+
/** Emoji representing this animal type (e.g., '😊' for human, '🐟' for fish) */
|
|
607
|
+
emoji?: string;
|
|
608
|
+
/** AU ID to morph target names split by side */
|
|
609
|
+
auToMorphs: Record<number, MorphTargetsBySide>;
|
|
610
|
+
/** AU ID to bone bindings (e.g., AU 51 → [{ node: 'HEAD', channel: 'ry', scale: 1, maxDegrees: 30 }]) */
|
|
611
|
+
auToBones: Record<number, BoneBinding[]>;
|
|
612
|
+
/** Bone key to actual node name in the model (e.g., 'HEAD' → 'Head' or 'CC_Base_Head') */
|
|
613
|
+
boneNodes: Record<string, string>;
|
|
614
|
+
/**
|
|
615
|
+
* Optional: Prefix to prepend to bone names when resolving nodes.
|
|
616
|
+
* If set, boneNodes should contain base names without prefix (e.g., 'Head' instead of 'CC_Base_Head').
|
|
617
|
+
* The engine will try: prefix + baseName + suffix, then fuzzy match with suffixPattern.
|
|
618
|
+
*/
|
|
619
|
+
bonePrefix?: string;
|
|
620
|
+
/**
|
|
621
|
+
* Optional: Suffix to append to bone names when resolving nodes.
|
|
622
|
+
* Combined with bonePrefix: prefix + baseName + suffix (e.g., 'Bone.' + '001' + '_Armature' = 'Bone.001_Armature')
|
|
623
|
+
*/
|
|
624
|
+
boneSuffix?: string;
|
|
625
|
+
/**
|
|
626
|
+
* Optional: Prefix to prepend to morph target names when resolving.
|
|
627
|
+
* Similar to bonePrefix but for morph targets.
|
|
628
|
+
*/
|
|
629
|
+
morphPrefix?: string;
|
|
630
|
+
/**
|
|
631
|
+
* Optional: Suffix to append to morph target names when resolving.
|
|
632
|
+
* Similar to boneSuffix but for morph targets.
|
|
633
|
+
*/
|
|
634
|
+
morphSuffix?: string;
|
|
635
|
+
/**
|
|
636
|
+
* Optional: Regex pattern string for fuzzy matching bone/morph names with suffixes.
|
|
637
|
+
* Common patterns: "_\\d+$" for numbered suffixes (_01, _038), "\\.\\d+$" for Blender (.001)
|
|
638
|
+
* When set, the engine will try exact match first, then fuzzy match using this pattern.
|
|
639
|
+
*/
|
|
640
|
+
suffixPattern?: string;
|
|
641
|
+
/**
|
|
642
|
+
* Optional: Suffixes that indicate left-side morph targets.
|
|
643
|
+
* Used for auto-detecting laterality in the mapping editor.
|
|
644
|
+
* Default: ['_L', ' L']
|
|
645
|
+
*/
|
|
646
|
+
leftMorphSuffixes?: string[];
|
|
647
|
+
/**
|
|
648
|
+
* Optional: Suffixes that indicate right-side morph targets.
|
|
649
|
+
* Used for auto-detecting laterality in the mapping editor.
|
|
650
|
+
* Default: ['_R', ' R']
|
|
651
|
+
*/
|
|
652
|
+
rightMorphSuffixes?: string[];
|
|
653
|
+
/** Morph category to mesh names (e.g., 'face' → ['CC_Base_Body_1']) */
|
|
654
|
+
morphToMesh: Record<string, string[]>;
|
|
655
|
+
/** Viseme targets in order (typically 15 phoneme positions) */
|
|
656
|
+
visemeKeys: MorphTargetRef[];
|
|
657
|
+
/** Optional: Jaw opening amounts per viseme index (0-1). Used for auto-generating jaw rotation in clips. */
|
|
658
|
+
visemeJawAmounts?: number[];
|
|
659
|
+
/** Optional: Default mix weights for bone/morph blending (0 = morph only, 1 = bone only) */
|
|
660
|
+
auMixDefaults?: Record<number, number>;
|
|
661
|
+
/** Optional: AU metadata (names, muscle basis, etc.) */
|
|
662
|
+
auInfo?: Record<string, AUInfo>;
|
|
663
|
+
/** Optional: Eye mesh node fallbacks (some rigs use mesh nodes instead of bone nodes) */
|
|
664
|
+
eyeMeshNodes?: {
|
|
665
|
+
LEFT: string;
|
|
666
|
+
RIGHT: string;
|
|
667
|
+
};
|
|
668
|
+
/** Optional: Composite rotation definitions for bones (defaults to CC4 composites) */
|
|
669
|
+
compositeRotations?: CompositeRotation[];
|
|
670
|
+
/** Optional: Mesh info for material settings (depthWrite, blending, renderOrder, etc.) */
|
|
671
|
+
meshes?: Record<string, MeshInfo>;
|
|
672
|
+
/**
|
|
673
|
+
* Optional: Continuum pair mappings for bidirectional AU axes.
|
|
674
|
+
* Maps AU ID to its partner info (pairId, isNegative, axis, node).
|
|
675
|
+
* Enables negative value shorthand: setAU(51, -0.5) activates AU 52 at 0.5
|
|
676
|
+
*/
|
|
677
|
+
continuumPairs?: Record<number, {
|
|
678
|
+
pairId: number;
|
|
679
|
+
isNegative: boolean;
|
|
680
|
+
axis: 'pitch' | 'yaw' | 'roll';
|
|
681
|
+
node: string;
|
|
682
|
+
}>;
|
|
683
|
+
/**
|
|
684
|
+
* Optional: Human-readable labels for continuum pairs.
|
|
685
|
+
* Key format: "negativeAU-positiveAU" (e.g., "51-52")
|
|
686
|
+
* Value: Display label (e.g., "Head Turn — Left ↔ Right")
|
|
687
|
+
*/
|
|
688
|
+
continuumLabels?: Record<string, string>;
|
|
689
|
+
/**
|
|
690
|
+
* Optional: Annotation regions for camera/marker overlays.
|
|
691
|
+
*/
|
|
692
|
+
annotationRegions?: AnnotationRegion[];
|
|
693
|
+
/**
|
|
694
|
+
* Optional: Hair physics defaults for this preset/profile.
|
|
695
|
+
*/
|
|
696
|
+
hairPhysics?: HairPhysicsProfileConfig;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Hair physics morph mapping axis types.
|
|
700
|
+
*/
|
|
701
|
+
type HairMorphAxis = 'yaw' | 'pitch' | 'roll';
|
|
702
|
+
/**
|
|
703
|
+
* Single morph target mapping with axis metadata.
|
|
704
|
+
*/
|
|
705
|
+
interface HairMorphTargetMapping {
|
|
706
|
+
key: string;
|
|
707
|
+
axis: HairMorphAxis;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Morph target mapping with axis metadata and intensity value.
|
|
711
|
+
*/
|
|
712
|
+
interface HairMorphTargetValueMapping {
|
|
713
|
+
value: number;
|
|
714
|
+
axis: HairMorphAxis;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Hair physics defaults stored in presets/profiles.
|
|
718
|
+
*/
|
|
719
|
+
interface HairPhysicsProfileConfig {
|
|
720
|
+
stiffness?: number;
|
|
721
|
+
damping?: number;
|
|
722
|
+
inertia?: number;
|
|
723
|
+
gravity?: number;
|
|
724
|
+
responseScale?: number;
|
|
725
|
+
idleSwayAmount?: number;
|
|
726
|
+
idleSwaySpeed?: number;
|
|
727
|
+
windStrength?: number;
|
|
728
|
+
windDirectionX?: number;
|
|
729
|
+
windDirectionZ?: number;
|
|
730
|
+
windTurbulence?: number;
|
|
731
|
+
windFrequency?: number;
|
|
732
|
+
idleClipDuration?: number;
|
|
733
|
+
impulseClipDuration?: number;
|
|
734
|
+
direction?: {
|
|
735
|
+
yawSign?: 1 | -1;
|
|
736
|
+
pitchSign?: 1 | -1;
|
|
737
|
+
};
|
|
738
|
+
morphTargets?: {
|
|
739
|
+
swayLeft?: HairMorphTargetMapping;
|
|
740
|
+
swayRight?: HairMorphTargetMapping;
|
|
741
|
+
swayFront?: HairMorphTargetMapping;
|
|
742
|
+
fluffRight?: HairMorphTargetMapping;
|
|
743
|
+
fluffBottom?: HairMorphTargetMapping;
|
|
744
|
+
headUp?: Record<string, HairMorphTargetValueMapping>;
|
|
745
|
+
headDown?: Record<string, HairMorphTargetValueMapping>;
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Annotation region definition for camera markers.
|
|
750
|
+
*/
|
|
751
|
+
interface AnnotationRegion {
|
|
752
|
+
name: string;
|
|
753
|
+
bones?: string[];
|
|
754
|
+
meshes?: string[];
|
|
755
|
+
objects?: string[];
|
|
756
|
+
paddingFactor?: number;
|
|
757
|
+
cameraAngle?: number;
|
|
758
|
+
cameraOffset?: {
|
|
759
|
+
x?: number;
|
|
760
|
+
y?: number;
|
|
761
|
+
z?: number;
|
|
762
|
+
};
|
|
763
|
+
parent?: string;
|
|
764
|
+
children?: string[];
|
|
765
|
+
expandAnimation?: 'outward' | 'staggered';
|
|
766
|
+
showChildConnections?: boolean;
|
|
767
|
+
style?: {
|
|
768
|
+
markerColor?: number;
|
|
769
|
+
markerRadius?: number;
|
|
770
|
+
lineColor?: number;
|
|
771
|
+
labelColor?: string;
|
|
772
|
+
labelBackground?: string;
|
|
773
|
+
labelFontSize?: number;
|
|
774
|
+
opacity?: number;
|
|
775
|
+
lineDirection?: 'radial' | 'camera' | 'up' | 'down' | 'left' | 'right' | 'forward' | 'backward' | {
|
|
776
|
+
x: number;
|
|
777
|
+
y: number;
|
|
778
|
+
z: number;
|
|
779
|
+
};
|
|
780
|
+
line?: {
|
|
781
|
+
style?: 'solid' | 'dashed' | 'dotted';
|
|
782
|
+
curve?: 'straight' | 'bezier' | 'arc';
|
|
783
|
+
arrowHead?: boolean;
|
|
784
|
+
thickness?: number;
|
|
785
|
+
length?: number;
|
|
786
|
+
};
|
|
787
|
+
};
|
|
788
|
+
groupId?: string;
|
|
789
|
+
isFallback?: boolean;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Morph target key (name in morphTargetDictionary).
|
|
793
|
+
*/
|
|
794
|
+
type MorphTargetKey = string;
|
|
795
|
+
/**
|
|
796
|
+
* Morph target index (morphTargetInfluences slot).
|
|
797
|
+
*/
|
|
798
|
+
type MorphTargetIndex = number;
|
|
799
|
+
/**
|
|
800
|
+
* Morph target reference (key or index).
|
|
801
|
+
*/
|
|
802
|
+
type MorphTargetRef = MorphTargetKey | MorphTargetIndex;
|
|
803
|
+
interface MorphTargetsBySide {
|
|
804
|
+
left: MorphTargetRef[];
|
|
805
|
+
right: MorphTargetRef[];
|
|
806
|
+
center: MorphTargetRef[];
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Helper type for mesh categories in morphToMesh
|
|
810
|
+
*/
|
|
811
|
+
type MorphCategory = 'face' | 'viseme' | 'eye' | 'tongue' | 'hair';
|
|
812
|
+
/**
|
|
813
|
+
* Mesh category types for character mesh classification
|
|
814
|
+
*/
|
|
815
|
+
type MeshCategory = 'body' | 'eye' | 'eyeOcclusion' | 'tearLine' | 'teeth' | 'tongue' | 'hair' | 'eyebrow' | 'cornea' | 'eyelash';
|
|
816
|
+
/**
|
|
817
|
+
* Blending mode names (matches Three.js constants)
|
|
818
|
+
*/
|
|
819
|
+
type BlendingMode = 'Normal' | 'Additive' | 'Subtractive' | 'Multiply' | 'None';
|
|
820
|
+
/**
|
|
821
|
+
* Blending mode options for Three.js materials
|
|
822
|
+
* Maps mode name to Three.js blending constant value
|
|
823
|
+
*/
|
|
824
|
+
declare const BLENDING_MODES: Record<BlendingMode, number>;
|
|
825
|
+
/**
|
|
826
|
+
* Material settings for mesh rendering
|
|
827
|
+
*/
|
|
828
|
+
interface MeshMaterialSettings {
|
|
829
|
+
renderOrder?: number;
|
|
830
|
+
transparent?: boolean;
|
|
831
|
+
opacity?: number;
|
|
832
|
+
depthWrite?: boolean;
|
|
833
|
+
depthTest?: boolean;
|
|
834
|
+
blending?: BlendingMode;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Mesh info including category, morph count, and optional material settings.
|
|
838
|
+
* Used in presets, profiles, and runtime.
|
|
839
|
+
*/
|
|
840
|
+
interface MeshInfo {
|
|
841
|
+
name?: string;
|
|
842
|
+
visible?: boolean;
|
|
843
|
+
category: MeshCategory;
|
|
844
|
+
morphCount: number;
|
|
845
|
+
material?: MeshMaterialSettings;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Animation Interface
|
|
850
|
+
*
|
|
851
|
+
* Groups AU/morph/viseme methods, transitions, and mixer playback.
|
|
852
|
+
*/
|
|
853
|
+
|
|
854
|
+
/** Loop mode for mixer clips */
|
|
855
|
+
type MixerLoopMode = 'once' | 'repeat' | 'pingpong';
|
|
856
|
+
interface Animation {
|
|
857
|
+
/**
|
|
858
|
+
* Set AU value immediately (no transition)
|
|
859
|
+
* @param id - AU number (e.g., 12 for smile) or string ('12L' for left side)
|
|
860
|
+
* @param v - Value 0-1
|
|
861
|
+
* @param balance - Optional L/R balance: -1 = left only, 0 = both, +1 = right only
|
|
862
|
+
*/
|
|
863
|
+
setAU(id: number | string, v: number, balance?: number): void;
|
|
864
|
+
/**
|
|
865
|
+
* Transition AU value smoothly over time
|
|
866
|
+
* @param id - AU number or string
|
|
867
|
+
* @param to - Target value 0-1
|
|
868
|
+
* @param durationMs - Transition duration in milliseconds
|
|
869
|
+
* @param balance - Optional L/R balance
|
|
870
|
+
*/
|
|
871
|
+
transitionAU(id: number | string, to: number, durationMs?: number, balance?: number): TransitionHandle;
|
|
872
|
+
/**
|
|
873
|
+
* Get current AU value (from cached auValues)
|
|
874
|
+
*/
|
|
875
|
+
getAU(id: number): number;
|
|
876
|
+
/**
|
|
877
|
+
* Set a continuum AU pair immediately (no animation).
|
|
878
|
+
* @param negAU - AU ID for negative direction (e.g., 61 for eyes left)
|
|
879
|
+
* @param posAU - AU ID for positive direction (e.g., 62 for eyes right)
|
|
880
|
+
* @param continuumValue - Value from -1 (full negative) to +1 (full positive)
|
|
881
|
+
* @param balance - Optional L/R balance for bilateral morphs
|
|
882
|
+
*/
|
|
883
|
+
setContinuum(negAU: number, posAU: number, continuumValue: number, balance?: number): void;
|
|
884
|
+
/**
|
|
885
|
+
* Smoothly transition a continuum AU pair (e.g., eyes left/right, head up/down).
|
|
886
|
+
* @param negAU - AU ID for negative direction
|
|
887
|
+
* @param posAU - AU ID for positive direction
|
|
888
|
+
* @param continuumValue - Target value from -1 (full negative) to +1 (full positive)
|
|
889
|
+
* @param durationMs - Transition duration in milliseconds
|
|
890
|
+
* @param balance - Optional L/R balance for bilateral morphs
|
|
891
|
+
*/
|
|
892
|
+
transitionContinuum(negAU: number, posAU: number, continuumValue: number, durationMs?: number, balance?: number): TransitionHandle;
|
|
893
|
+
/**
|
|
894
|
+
* Set morph target value immediately (by key)
|
|
895
|
+
* @param key - Morph target name (morphTargetDictionary key)
|
|
896
|
+
* @param v - Value 0-1
|
|
897
|
+
* @param meshNames - Optional specific meshes to target
|
|
898
|
+
*/
|
|
899
|
+
setMorph(key: string, v: number, meshNames?: string[]): void;
|
|
900
|
+
/**
|
|
901
|
+
* Set morph influence value immediately (by index)
|
|
902
|
+
* @param index - Morph target index (morphTargetInfluences slot)
|
|
903
|
+
* @param v - Value 0-1
|
|
904
|
+
* @param meshNames - Optional specific meshes to target
|
|
905
|
+
*/
|
|
906
|
+
setMorphInfluence(index: number, v: number, meshNames?: string[]): void;
|
|
907
|
+
/**
|
|
908
|
+
* Transition morph target value smoothly (by key)
|
|
909
|
+
* @param key - Morph target name (morphTargetDictionary key)
|
|
910
|
+
* @param to - Target value 0-1
|
|
911
|
+
* @param durationMs - Transition duration in milliseconds
|
|
912
|
+
* @param meshNames - Optional specific meshes to target
|
|
913
|
+
*/
|
|
914
|
+
transitionMorph(key: string, to: number, durationMs?: number, meshNames?: string[]): TransitionHandle;
|
|
915
|
+
/**
|
|
916
|
+
* Transition morph influence value smoothly (by index)
|
|
917
|
+
* @param index - Morph target index (morphTargetInfluences slot)
|
|
918
|
+
* @param to - Target value 0-1
|
|
919
|
+
* @param durationMs - Transition duration in milliseconds
|
|
920
|
+
* @param meshNames - Optional specific meshes to target
|
|
921
|
+
*/
|
|
922
|
+
transitionMorphInfluence(index: number, to: number, durationMs?: number, meshNames?: string[]): TransitionHandle;
|
|
923
|
+
/**
|
|
924
|
+
* Set viseme value immediately (for lip-sync)
|
|
925
|
+
* @param visemeIndex - Viseme index 0-14
|
|
926
|
+
* @param value - Value 0-1
|
|
927
|
+
* @param jawScale - Jaw movement multiplier (default 1.0)
|
|
928
|
+
*/
|
|
929
|
+
setViseme(visemeIndex: number, value: number, jawScale?: number): void;
|
|
930
|
+
/**
|
|
931
|
+
* Transition viseme value smoothly
|
|
932
|
+
*/
|
|
933
|
+
transitionViseme(visemeIndex: number, to: number, durationMs?: number, jawScale?: number): TransitionHandle;
|
|
934
|
+
/**
|
|
935
|
+
* Set mix weight for an AU (blend between morph and bone contribution)
|
|
936
|
+
*/
|
|
937
|
+
setAUMixWeight(id: number, weight: number): void;
|
|
938
|
+
/**
|
|
939
|
+
* Get current mix weight for an AU
|
|
940
|
+
*/
|
|
941
|
+
getAUMixWeight(id: number): number;
|
|
942
|
+
/**
|
|
943
|
+
* Check if an AU has bilateral bone bindings (L and R nodes)
|
|
944
|
+
* Used to determine if a balance slider should be shown for bone-only bilateral AUs
|
|
945
|
+
*/
|
|
946
|
+
hasLeftRightBones(auId: number): boolean;
|
|
947
|
+
/**
|
|
948
|
+
* Pause all transitions
|
|
949
|
+
*/
|
|
950
|
+
pause(): void;
|
|
951
|
+
/**
|
|
952
|
+
* Resume all transitions
|
|
953
|
+
*/
|
|
954
|
+
resume(): void;
|
|
955
|
+
/**
|
|
956
|
+
* Check if engine is paused
|
|
957
|
+
*/
|
|
958
|
+
getPaused(): boolean;
|
|
959
|
+
/**
|
|
960
|
+
* Clear all active transitions
|
|
961
|
+
*/
|
|
962
|
+
clearTransitions(): void;
|
|
963
|
+
/**
|
|
964
|
+
* Get count of active transitions
|
|
965
|
+
*/
|
|
966
|
+
getActiveTransitionCount(): number;
|
|
967
|
+
/**
|
|
968
|
+
* Reset all facial animation to neutral state
|
|
969
|
+
*/
|
|
970
|
+
resetToNeutral(): void;
|
|
971
|
+
/**
|
|
972
|
+
* Load animation clips from a GLTF/GLB file.
|
|
973
|
+
* Call this after onReady() with the animations array from the GLTF loader.
|
|
974
|
+
* @param clips - Array of AnimationClip objects from GLTF loader
|
|
975
|
+
*/
|
|
976
|
+
loadAnimationClips(clips: unknown[]): void;
|
|
977
|
+
/**
|
|
978
|
+
* Get list of all loaded animation clips.
|
|
979
|
+
*/
|
|
980
|
+
getAnimationClips(): AnimationClipInfo[];
|
|
981
|
+
/**
|
|
982
|
+
* Play a baked animation by name.
|
|
983
|
+
* @param clipName - Name of the animation clip to play
|
|
984
|
+
* @param options - Playback options (speed, intensity, loop, etc.)
|
|
985
|
+
* @returns Handle for controlling the animation, or null if clip not found
|
|
986
|
+
*/
|
|
987
|
+
playAnimation(clipName: string, options?: AnimationPlayOptions): AnimationActionHandle | null;
|
|
988
|
+
/**
|
|
989
|
+
* Stop a specific animation by name.
|
|
990
|
+
* @param clipName - Name of the animation to stop
|
|
991
|
+
*/
|
|
992
|
+
stopAnimation(clipName: string): void;
|
|
993
|
+
/**
|
|
994
|
+
* Stop all currently playing animations.
|
|
995
|
+
*/
|
|
996
|
+
stopAllAnimations(): void;
|
|
997
|
+
/**
|
|
998
|
+
* Pause a specific animation by name.
|
|
999
|
+
* @param clipName - Name of the animation to pause
|
|
1000
|
+
*/
|
|
1001
|
+
pauseAnimation(clipName: string): void;
|
|
1002
|
+
/**
|
|
1003
|
+
* Resume a paused animation by name.
|
|
1004
|
+
* @param clipName - Name of the animation to resume
|
|
1005
|
+
*/
|
|
1006
|
+
resumeAnimation(clipName: string): void;
|
|
1007
|
+
/**
|
|
1008
|
+
* Pause all currently playing animations.
|
|
1009
|
+
*/
|
|
1010
|
+
pauseAllAnimations(): void;
|
|
1011
|
+
/**
|
|
1012
|
+
* Resume all paused animations.
|
|
1013
|
+
*/
|
|
1014
|
+
resumeAllAnimations(): void;
|
|
1015
|
+
/**
|
|
1016
|
+
* Set the playback speed for a specific animation.
|
|
1017
|
+
* @param clipName - Name of the animation
|
|
1018
|
+
* @param speed - Playback speed multiplier (1.0 = normal, 0.5 = half, 2.0 = double)
|
|
1019
|
+
*/
|
|
1020
|
+
setAnimationSpeed(clipName: string, speed: number): void;
|
|
1021
|
+
/**
|
|
1022
|
+
* Set the intensity/weight for a specific animation.
|
|
1023
|
+
* @param clipName - Name of the animation
|
|
1024
|
+
* @param intensity - Weight value from 0 (no effect) to 1 (full effect)
|
|
1025
|
+
*/
|
|
1026
|
+
setAnimationIntensity(clipName: string, intensity: number): void;
|
|
1027
|
+
/**
|
|
1028
|
+
* Set the global time scale for all animations.
|
|
1029
|
+
* @param timeScale - Global time scale multiplier
|
|
1030
|
+
*/
|
|
1031
|
+
setAnimationTimeScale(timeScale: number): void;
|
|
1032
|
+
/**
|
|
1033
|
+
* Get the current state of a specific animation.
|
|
1034
|
+
* @param clipName - Name of the animation
|
|
1035
|
+
* @returns Animation state or null if not found/playing
|
|
1036
|
+
*/
|
|
1037
|
+
getAnimationState(clipName: string): AnimationState | null;
|
|
1038
|
+
/**
|
|
1039
|
+
* Get states of all currently playing animations.
|
|
1040
|
+
*/
|
|
1041
|
+
getPlayingAnimations(): AnimationState[];
|
|
1042
|
+
/**
|
|
1043
|
+
* Crossfade from current animation(s) to a new animation.
|
|
1044
|
+
* @param clipName - Name of the target animation
|
|
1045
|
+
* @param duration - Crossfade duration in seconds
|
|
1046
|
+
* @param options - Additional playback options for the target animation
|
|
1047
|
+
*/
|
|
1048
|
+
crossfadeTo(clipName: string, duration?: number, options?: AnimationPlayOptions): AnimationActionHandle | null;
|
|
1049
|
+
/**
|
|
1050
|
+
* Get the composite rotations configuration for the current preset.
|
|
1051
|
+
* Used by animation schedulers for coordinated head/eye movements.
|
|
1052
|
+
*/
|
|
1053
|
+
getCompositeRotations(): CompositeRotation[];
|
|
1054
|
+
/**
|
|
1055
|
+
* Build and play an AnimationClip from curve data.
|
|
1056
|
+
* Used by animation schedulers to convert keyframe data to mixer clips.
|
|
1057
|
+
* @param clipName - Unique name for the clip
|
|
1058
|
+
* @param curves - Map of curve IDs to keyframe arrays
|
|
1059
|
+
* @param options - Playback options
|
|
1060
|
+
* @returns Handle for controlling the clip, or null if not supported
|
|
1061
|
+
*/
|
|
1062
|
+
buildClip(clipName: string, curves: Record<string, Array<CurvePoint>>, options?: ClipOptions): ClipHandle | null;
|
|
1063
|
+
/**
|
|
1064
|
+
* Update parameters on an active clip.
|
|
1065
|
+
* @param name - Name of the clip to update
|
|
1066
|
+
* @param params - Parameters to update
|
|
1067
|
+
* @returns true if clip was found and updated
|
|
1068
|
+
*/
|
|
1069
|
+
updateClipParams(name: string, params: {
|
|
1070
|
+
weight?: number;
|
|
1071
|
+
rate?: number;
|
|
1072
|
+
loop?: boolean;
|
|
1073
|
+
loopMode?: MixerLoopMode;
|
|
1074
|
+
repeatCount?: number;
|
|
1075
|
+
reverse?: boolean;
|
|
1076
|
+
actionId?: string;
|
|
1077
|
+
}): boolean;
|
|
1078
|
+
/**
|
|
1079
|
+
* Clean up resources for a snippet/clip.
|
|
1080
|
+
* @param name - Name of the snippet to clean up
|
|
1081
|
+
*/
|
|
1082
|
+
cleanupSnippet(name: string): void;
|
|
1083
|
+
/**
|
|
1084
|
+
* Check if the given curves can be played through buildClip.
|
|
1085
|
+
* Returns false if curves contain bone-only AUs that can't be baked.
|
|
1086
|
+
*/
|
|
1087
|
+
supportsClipCurves(curves: Record<string, Array<CurvePoint>>): boolean;
|
|
1088
|
+
/**
|
|
1089
|
+
* Callback when a snippet finishes playback.
|
|
1090
|
+
* Used by animation schedulers for sequencing.
|
|
1091
|
+
*/
|
|
1092
|
+
onSnippetEnd?(name: string): void;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Hair Interface
|
|
1097
|
+
*
|
|
1098
|
+
* Groups hair registration, styling, and physics methods.
|
|
1099
|
+
*/
|
|
1100
|
+
|
|
1101
|
+
interface HairObjectRef {
|
|
1102
|
+
name: string;
|
|
1103
|
+
isMesh: boolean;
|
|
1104
|
+
isEyebrow: boolean;
|
|
1105
|
+
}
|
|
1106
|
+
interface HairPhysicsDirectionConfig$1 {
|
|
1107
|
+
yawSign: 1 | -1;
|
|
1108
|
+
pitchSign: 1 | -1;
|
|
1109
|
+
}
|
|
1110
|
+
interface HairMorphTargetsConfig {
|
|
1111
|
+
swayLeft: string;
|
|
1112
|
+
swayRight: string;
|
|
1113
|
+
swayFront: string;
|
|
1114
|
+
fluffRight: string;
|
|
1115
|
+
fluffBottom: string;
|
|
1116
|
+
headUp: Record<string, number>;
|
|
1117
|
+
headDown: Record<string, number>;
|
|
1118
|
+
}
|
|
1119
|
+
interface HairPhysicsRuntimeConfig {
|
|
1120
|
+
stiffness: number;
|
|
1121
|
+
damping: number;
|
|
1122
|
+
inertia: number;
|
|
1123
|
+
gravity: number;
|
|
1124
|
+
responseScale: number;
|
|
1125
|
+
idleSwayAmount: number;
|
|
1126
|
+
idleSwaySpeed: number;
|
|
1127
|
+
windStrength: number;
|
|
1128
|
+
windDirectionX: number;
|
|
1129
|
+
windDirectionZ: number;
|
|
1130
|
+
windTurbulence: number;
|
|
1131
|
+
windFrequency: number;
|
|
1132
|
+
idleClipDuration: number;
|
|
1133
|
+
impulseClipDuration: number;
|
|
1134
|
+
direction: HairPhysicsDirectionConfig$1;
|
|
1135
|
+
morphTargets: HairMorphTargetsConfig;
|
|
1136
|
+
}
|
|
1137
|
+
type HairPhysicsRuntimeConfigUpdate = Partial<HairPhysicsRuntimeConfig> & {
|
|
1138
|
+
direction?: Partial<HairPhysicsDirectionConfig$1>;
|
|
1139
|
+
morphTargets?: Partial<HairMorphTargetsConfig>;
|
|
1140
|
+
};
|
|
1141
|
+
interface HairObjectState {
|
|
1142
|
+
color?: {
|
|
1143
|
+
baseColor: string;
|
|
1144
|
+
emissive: string;
|
|
1145
|
+
emissiveIntensity: number;
|
|
1146
|
+
};
|
|
1147
|
+
outline?: {
|
|
1148
|
+
show: boolean;
|
|
1149
|
+
color: string;
|
|
1150
|
+
opacity: number;
|
|
1151
|
+
};
|
|
1152
|
+
visible?: boolean;
|
|
1153
|
+
scale?: {
|
|
1154
|
+
x: number;
|
|
1155
|
+
y: number;
|
|
1156
|
+
z: number;
|
|
1157
|
+
};
|
|
1158
|
+
position?: {
|
|
1159
|
+
x: number;
|
|
1160
|
+
y: number;
|
|
1161
|
+
z: number;
|
|
1162
|
+
};
|
|
1163
|
+
isEyebrow?: boolean;
|
|
1164
|
+
}
|
|
1165
|
+
interface Hair {
|
|
1166
|
+
/**
|
|
1167
|
+
* Register hair objects from a scene.
|
|
1168
|
+
* Returns engine-agnostic references for UI and service layers.
|
|
1169
|
+
*/
|
|
1170
|
+
registerHairObjects(objects: Object3D[]): HairObjectRef[];
|
|
1171
|
+
/**
|
|
1172
|
+
* Get hair objects registered in the engine.
|
|
1173
|
+
*/
|
|
1174
|
+
getRegisteredHairObjects(): Mesh[];
|
|
1175
|
+
/**
|
|
1176
|
+
* Enable or disable hair physics simulation.
|
|
1177
|
+
*/
|
|
1178
|
+
setHairPhysicsEnabled(enabled: boolean): void;
|
|
1179
|
+
/**
|
|
1180
|
+
* Check if hair physics is enabled.
|
|
1181
|
+
*/
|
|
1182
|
+
isHairPhysicsEnabled(): boolean;
|
|
1183
|
+
/**
|
|
1184
|
+
* Update hair physics configuration.
|
|
1185
|
+
*/
|
|
1186
|
+
setHairPhysicsConfig(config: HairPhysicsRuntimeConfigUpdate): void;
|
|
1187
|
+
/**
|
|
1188
|
+
* Get current hair physics configuration.
|
|
1189
|
+
*/
|
|
1190
|
+
getHairPhysicsConfig(): HairPhysicsRuntimeConfig;
|
|
1191
|
+
/**
|
|
1192
|
+
* Validate hair morph target mappings against registered hair meshes.
|
|
1193
|
+
* Returns a list of missing morph keys (if any).
|
|
1194
|
+
*/
|
|
1195
|
+
validateHairMorphTargets(): string[];
|
|
1196
|
+
/**
|
|
1197
|
+
* Get head rotation values used for hair physics (range -1 to 1).
|
|
1198
|
+
*/
|
|
1199
|
+
getHeadRotation(): {
|
|
1200
|
+
yaw: number;
|
|
1201
|
+
pitch: number;
|
|
1202
|
+
roll: number;
|
|
1203
|
+
};
|
|
1204
|
+
/**
|
|
1205
|
+
* Manually tick hair physics (optional).
|
|
1206
|
+
*/
|
|
1207
|
+
updateHairPhysics(dt: number): void;
|
|
1208
|
+
/**
|
|
1209
|
+
* Get available hair morph targets for a mesh.
|
|
1210
|
+
*/
|
|
1211
|
+
getHairMorphTargets(meshName?: string): string[];
|
|
1212
|
+
/**
|
|
1213
|
+
* Set morph targets on specific meshes.
|
|
1214
|
+
*/
|
|
1215
|
+
setMorphOnMeshes(meshNames: string[], morphKey: string, value: number): void;
|
|
1216
|
+
/**
|
|
1217
|
+
* Apply hair/eyebrow styling state to a mesh.
|
|
1218
|
+
*/
|
|
1219
|
+
applyHairStateToObject(objectName: string, state: HairObjectState): void;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* LoomLarge Engine Interface
|
|
1224
|
+
*
|
|
1225
|
+
* Defines the contract for 3D character animation engines.
|
|
1226
|
+
* Uses Three.js types directly - no framework abstraction overhead.
|
|
1227
|
+
*/
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Payload for initializing the engine with a loaded model
|
|
1231
|
+
*/
|
|
1232
|
+
interface ReadyPayload {
|
|
1233
|
+
meshes: Mesh[];
|
|
1234
|
+
model: Object3D;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Configuration options for the Loom3 engine
|
|
1238
|
+
*/
|
|
1239
|
+
interface LoomLargeConfig {
|
|
1240
|
+
/** AU to morph target mappings (partial overrides merged via resolveProfile). */
|
|
1241
|
+
profile?: Partial<Profile>;
|
|
1242
|
+
/** Preset type to resolve if profile is not provided. */
|
|
1243
|
+
presetType?: PresetType | string;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* Loom3 Engine Interface
|
|
1248
|
+
*
|
|
1249
|
+
* The main interface for controlling 3D character facial animation.
|
|
1250
|
+
* Supports Action Units (AUs), morph targets, visemes, and bone control.
|
|
1251
|
+
*/
|
|
1252
|
+
interface LoomLarge extends Animation, Hair {
|
|
1253
|
+
/**
|
|
1254
|
+
* Initialize the engine with a loaded model.
|
|
1255
|
+
* Call this after loading your 3D model.
|
|
1256
|
+
*/
|
|
1257
|
+
onReady(payload: ReadyPayload): void;
|
|
1258
|
+
/**
|
|
1259
|
+
* Update animation state. Call each frame with delta time in seconds.
|
|
1260
|
+
* If using start(), this is called automatically.
|
|
1261
|
+
*/
|
|
1262
|
+
update(deltaSeconds: number): void;
|
|
1263
|
+
/**
|
|
1264
|
+
* Start the internal animation loop (RAF-based).
|
|
1265
|
+
* Automatically calls update() each frame with delta time.
|
|
1266
|
+
*/
|
|
1267
|
+
start(): void;
|
|
1268
|
+
/**
|
|
1269
|
+
* Stop the internal animation loop.
|
|
1270
|
+
*/
|
|
1271
|
+
stop(): void;
|
|
1272
|
+
/**
|
|
1273
|
+
* Dispose engine resources and cleanup.
|
|
1274
|
+
* Stops the animation loop and clears all transitions.
|
|
1275
|
+
*/
|
|
1276
|
+
dispose(): void;
|
|
1277
|
+
/**
|
|
1278
|
+
* Get list of all meshes in the model
|
|
1279
|
+
*/
|
|
1280
|
+
getMeshList(): MeshInfo[];
|
|
1281
|
+
/**
|
|
1282
|
+
* Set mesh visibility
|
|
1283
|
+
*/
|
|
1284
|
+
setMeshVisible(meshName: string, visible: boolean): void;
|
|
1285
|
+
/**
|
|
1286
|
+
* Highlight a mesh with an emissive glow effect
|
|
1287
|
+
* @param meshName - Name of the mesh to highlight (null to clear all highlights)
|
|
1288
|
+
* @param color - Highlight color (default: cyan)
|
|
1289
|
+
* @param intensity - Emissive intensity (default: 0.5)
|
|
1290
|
+
*/
|
|
1291
|
+
highlightMesh(meshName: string | null, color?: number, intensity?: number): void;
|
|
1292
|
+
/**
|
|
1293
|
+
* Update AU mappings configuration
|
|
1294
|
+
*/
|
|
1295
|
+
setProfile(profile: Profile): void;
|
|
1296
|
+
/**
|
|
1297
|
+
* Get current AU mappings configuration
|
|
1298
|
+
*/
|
|
1299
|
+
getProfile(): Profile;
|
|
1300
|
+
}
|
|
1301
|
+
type Loom3Config = LoomLargeConfig;
|
|
1302
|
+
|
|
1303
|
+
type HairPhysicsDirectionConfig = {
|
|
1304
|
+
yawSign: 1 | -1;
|
|
1305
|
+
pitchSign: 1 | -1;
|
|
1306
|
+
};
|
|
1307
|
+
type HairMorphTargets = {
|
|
1308
|
+
swayLeft: string;
|
|
1309
|
+
swayRight: string;
|
|
1310
|
+
swayFront: string;
|
|
1311
|
+
fluffRight: string;
|
|
1312
|
+
fluffBottom: string;
|
|
1313
|
+
headUp: Record<string, number>;
|
|
1314
|
+
headDown: Record<string, number>;
|
|
1315
|
+
};
|
|
1316
|
+
type HairPhysicsConfig$2 = {
|
|
1317
|
+
stiffness: number;
|
|
1318
|
+
damping: number;
|
|
1319
|
+
inertia: number;
|
|
1320
|
+
gravity: number;
|
|
1321
|
+
responseScale: number;
|
|
1322
|
+
idleSwayAmount: number;
|
|
1323
|
+
idleSwaySpeed: number;
|
|
1324
|
+
windStrength: number;
|
|
1325
|
+
windDirectionX: number;
|
|
1326
|
+
windDirectionZ: number;
|
|
1327
|
+
windTurbulence: number;
|
|
1328
|
+
windFrequency: number;
|
|
1329
|
+
idleClipDuration: number;
|
|
1330
|
+
impulseClipDuration: number;
|
|
1331
|
+
direction: HairPhysicsDirectionConfig;
|
|
1332
|
+
morphTargets: HairMorphTargets;
|
|
1333
|
+
};
|
|
1334
|
+
type HairPhysicsConfigUpdate = Partial<HairPhysicsConfig$2> & {
|
|
1335
|
+
direction?: Partial<HairPhysicsDirectionConfig>;
|
|
1336
|
+
morphTargets?: Partial<HairMorphTargets>;
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* Loom3 - Three.js Implementation
|
|
1341
|
+
*
|
|
1342
|
+
* Default implementation of the LoomLarge interface for Three.js.
|
|
1343
|
+
* Controls 3D character facial animation using Action Units (AUs),
|
|
1344
|
+
* morph targets, visemes, and bone transformations.
|
|
1345
|
+
*/
|
|
1346
|
+
|
|
1347
|
+
declare class Loom3 implements LoomLarge {
|
|
1348
|
+
onSnippetEnd?: (name: string) => void;
|
|
1349
|
+
private config;
|
|
1350
|
+
private animation;
|
|
1351
|
+
private compositeRotations;
|
|
1352
|
+
private auToCompositeMap;
|
|
1353
|
+
private auValues;
|
|
1354
|
+
private auBalances;
|
|
1355
|
+
private rigReady;
|
|
1356
|
+
private missingBoneWarnings;
|
|
1357
|
+
private rotations;
|
|
1358
|
+
private pendingCompositeNodes;
|
|
1359
|
+
private isPaused;
|
|
1360
|
+
private translations;
|
|
1361
|
+
private faceMesh;
|
|
1362
|
+
private resolvedFaceMeshes;
|
|
1363
|
+
private meshes;
|
|
1364
|
+
private model;
|
|
1365
|
+
private meshByName;
|
|
1366
|
+
private morphKeyCache;
|
|
1367
|
+
private morphIndexCache;
|
|
1368
|
+
private resolvedAUMorphTargets;
|
|
1369
|
+
private resolvedVisemeTargets;
|
|
1370
|
+
private bones;
|
|
1371
|
+
private mixWeights;
|
|
1372
|
+
private visemeValues;
|
|
1373
|
+
private static readonly VISEME_JAW_AMOUNTS;
|
|
1374
|
+
private bakedAnimations;
|
|
1375
|
+
private hairPhysics;
|
|
1376
|
+
private clock;
|
|
1377
|
+
private animationFrameId;
|
|
1378
|
+
private isRunning;
|
|
1379
|
+
constructor(config?: LoomLargeConfig, animation?: {
|
|
1380
|
+
tick(dtSeconds: number): void;
|
|
1381
|
+
addTransition(key: string, from: number, to: number, durationMs: number, apply: (value: number) => void, easing?: (t: number) => number): TransitionHandle;
|
|
1382
|
+
clearTransitions(): void;
|
|
1383
|
+
getActiveTransitionCount(): number;
|
|
1384
|
+
});
|
|
1385
|
+
onReady(payload: ReadyPayload): void;
|
|
1386
|
+
private rebuildMorphTargetsCache;
|
|
1387
|
+
private resolveFaceMeshes;
|
|
1388
|
+
update(deltaSeconds: number): void;
|
|
1389
|
+
/** Start the internal animation loop using Three.js Clock */
|
|
1390
|
+
start(): void;
|
|
1391
|
+
/** Stop the internal animation loop */
|
|
1392
|
+
stop(): void;
|
|
1393
|
+
dispose(): void;
|
|
1394
|
+
setAU(id: number | string, v: number, balance?: number): void;
|
|
1395
|
+
transitionAU(id: number | string, to: number, durationMs?: number, balance?: number): TransitionHandle;
|
|
1396
|
+
getAU(id: number): number;
|
|
1397
|
+
getCompositeRotations(): CompositeRotation[];
|
|
1398
|
+
/**
|
|
1399
|
+
* Set a continuum AU pair immediately (no animation).
|
|
1400
|
+
*
|
|
1401
|
+
* Sign convention:
|
|
1402
|
+
* - Negative value (-1 to 0): activates negAU (e.g., head left, eyes left)
|
|
1403
|
+
* - Positive value (0 to +1): activates posAU (e.g., head right, eyes right)
|
|
1404
|
+
*
|
|
1405
|
+
* @param negAU - AU ID for negative direction (e.g., 61 for eyes left)
|
|
1406
|
+
* @param posAU - AU ID for positive direction (e.g., 62 for eyes right)
|
|
1407
|
+
* @param continuumValue - Value from -1 (full negative) to +1 (full positive)
|
|
1408
|
+
* @param balance - Optional L/R balance for bilateral morphs
|
|
1409
|
+
*/
|
|
1410
|
+
setContinuum(negAU: number, posAU: number, continuumValue: number, balance?: number): void;
|
|
1411
|
+
/**
|
|
1412
|
+
* Smoothly transition a continuum AU pair (e.g., eyes left/right, head up/down).
|
|
1413
|
+
* Takes a continuum value from -1 to +1 and internally manages both AU values.
|
|
1414
|
+
*
|
|
1415
|
+
* @param negAU - AU ID for negative direction (e.g., 61 for eyes left)
|
|
1416
|
+
* @param posAU - AU ID for positive direction (e.g., 62 for eyes right)
|
|
1417
|
+
* @param continuumValue - Target value from -1 (full negative) to +1 (full positive)
|
|
1418
|
+
* @param durationMs - Transition duration in milliseconds
|
|
1419
|
+
* @param balance - Optional L/R balance for bilateral morphs
|
|
1420
|
+
*/
|
|
1421
|
+
transitionContinuum(negAU: number, posAU: number, continuumValue: number, durationMs?: number, balance?: number): TransitionHandle;
|
|
1422
|
+
/**
|
|
1423
|
+
* Set a morph target value.
|
|
1424
|
+
*
|
|
1425
|
+
* Fast paths (in order of speed):
|
|
1426
|
+
* 1. Pass pre-resolved { infl, idx } array directly - zero lookups
|
|
1427
|
+
* 2. String key with cache hit - one Map lookup
|
|
1428
|
+
* 3. String key cache miss - dictionary lookup, then cached for next time
|
|
1429
|
+
*/
|
|
1430
|
+
setMorph(key: string, v: number, meshNames?: string[]): void;
|
|
1431
|
+
setMorph(key: string, v: number, targets: {
|
|
1432
|
+
infl: number[];
|
|
1433
|
+
idx: number;
|
|
1434
|
+
}[]): void;
|
|
1435
|
+
setMorphInfluence(index: number, v: number, meshNames?: string[]): void;
|
|
1436
|
+
/**
|
|
1437
|
+
* Resolve morph key to direct targets for ultra-fast repeated access.
|
|
1438
|
+
* Use this when you need to set the same morph many times (e.g., in animation loops).
|
|
1439
|
+
*/
|
|
1440
|
+
resolveMorphTargets(key: string, meshNames?: string[]): {
|
|
1441
|
+
infl: number[];
|
|
1442
|
+
idx: number;
|
|
1443
|
+
}[];
|
|
1444
|
+
resolveMorphTargetsByIndex(index: number, meshNames?: string[]): {
|
|
1445
|
+
infl: number[];
|
|
1446
|
+
idx: number;
|
|
1447
|
+
}[];
|
|
1448
|
+
transitionMorph(key: string, to: number, durationMs?: number, meshNames?: string[]): TransitionHandle;
|
|
1449
|
+
transitionMorphInfluence(index: number, to: number, durationMs?: number, meshNames?: string[]): TransitionHandle;
|
|
1450
|
+
setViseme(visemeIndex: number, value: number, jawScale?: number): void;
|
|
1451
|
+
transitionViseme(visemeIndex: number, to: number, durationMs?: number, jawScale?: number): TransitionHandle;
|
|
1452
|
+
setAUMixWeight(id: number, weight: number): void;
|
|
1453
|
+
getAUMixWeight(id: number): number;
|
|
1454
|
+
/**
|
|
1455
|
+
* Check if an AU has bilateral bone bindings (left + right side hints).
|
|
1456
|
+
* Used to determine if a balance slider should be shown for bone-only bilateral AUs.
|
|
1457
|
+
*/
|
|
1458
|
+
hasLeftRightBones(auId: number): boolean;
|
|
1459
|
+
pause(): void;
|
|
1460
|
+
resume(): void;
|
|
1461
|
+
getPaused(): boolean;
|
|
1462
|
+
clearTransitions(): void;
|
|
1463
|
+
getActiveTransitionCount(): number;
|
|
1464
|
+
resetToNeutral(): void;
|
|
1465
|
+
getMeshList(): MeshInfo[];
|
|
1466
|
+
/** Get all morph targets grouped by mesh name */
|
|
1467
|
+
getMorphTargets(): Record<string, string[]>;
|
|
1468
|
+
/** Get morph target indices mapped to labels for each mesh */
|
|
1469
|
+
getMorphTargetIndices(): Record<string, {
|
|
1470
|
+
index: number;
|
|
1471
|
+
name: string;
|
|
1472
|
+
}[]>;
|
|
1473
|
+
/** Get all resolved bone names and their current transforms */
|
|
1474
|
+
getBones(): Record<string, {
|
|
1475
|
+
position: [number, number, number];
|
|
1476
|
+
rotation: [number, number, number];
|
|
1477
|
+
}>;
|
|
1478
|
+
setMeshVisible(meshName: string, visible: boolean): void;
|
|
1479
|
+
/** Store original emissive colors for highlight reset */
|
|
1480
|
+
private originalEmissive;
|
|
1481
|
+
/**
|
|
1482
|
+
* Highlight a mesh with an emissive glow effect
|
|
1483
|
+
* @param meshName - Name of the mesh to highlight (null to clear all highlights)
|
|
1484
|
+
* @param color - Highlight color (default: cyan 0x00ffff)
|
|
1485
|
+
* @param intensity - Emissive intensity (default: 0.5)
|
|
1486
|
+
*/
|
|
1487
|
+
highlightMesh(meshName: string | null, color?: number, intensity?: number): void;
|
|
1488
|
+
/** Blending mode options for Three.js materials */
|
|
1489
|
+
private static readonly BLENDING_MODES;
|
|
1490
|
+
/** Get material config for a mesh */
|
|
1491
|
+
getMeshMaterialConfig(meshName: string): {
|
|
1492
|
+
renderOrder: number;
|
|
1493
|
+
transparent: boolean;
|
|
1494
|
+
opacity: number;
|
|
1495
|
+
depthWrite: boolean;
|
|
1496
|
+
depthTest: boolean;
|
|
1497
|
+
blending: string;
|
|
1498
|
+
} | null;
|
|
1499
|
+
/** Set material config for a mesh */
|
|
1500
|
+
setMeshMaterialConfig(meshName: string, config: {
|
|
1501
|
+
renderOrder?: number;
|
|
1502
|
+
transparent?: boolean;
|
|
1503
|
+
opacity?: number;
|
|
1504
|
+
depthWrite?: boolean;
|
|
1505
|
+
depthTest?: boolean;
|
|
1506
|
+
blending?: string;
|
|
1507
|
+
}): void;
|
|
1508
|
+
private applyHairPhysicsProfileConfig;
|
|
1509
|
+
setProfile(profile: Profile): void;
|
|
1510
|
+
getProfile(): Profile;
|
|
1511
|
+
/**
|
|
1512
|
+
* Get the mesh names that should receive morph influences for a given AU.
|
|
1513
|
+
* Routes by facePart: Tongue → tongue, Eye → eye, everything else → face.
|
|
1514
|
+
*/
|
|
1515
|
+
getMeshNamesForAU(auId: number): string[];
|
|
1516
|
+
registerHairObjects(objects: Object3D[]): Array<{
|
|
1517
|
+
name: string;
|
|
1518
|
+
isMesh: boolean;
|
|
1519
|
+
isEyebrow: boolean;
|
|
1520
|
+
}>;
|
|
1521
|
+
getRegisteredHairObjects(): Mesh[];
|
|
1522
|
+
setHairPhysicsEnabled(enabled: boolean): void;
|
|
1523
|
+
isHairPhysicsEnabled(): boolean;
|
|
1524
|
+
setHairPhysicsConfig(config: HairPhysicsConfigUpdate): void;
|
|
1525
|
+
getHairPhysicsConfig(): HairPhysicsConfig$2;
|
|
1526
|
+
validateHairMorphTargets(): string[];
|
|
1527
|
+
/** Get head rotation values for hair physics (range -1 to 1) */
|
|
1528
|
+
getHeadRotation(): {
|
|
1529
|
+
yaw: number;
|
|
1530
|
+
pitch: number;
|
|
1531
|
+
roll: number;
|
|
1532
|
+
};
|
|
1533
|
+
updateHairPhysics(dt: number): void;
|
|
1534
|
+
getHairMorphTargets(meshName?: string): string[];
|
|
1535
|
+
setMorphOnMeshes(meshNames: string[], morphKey: string, value: number): void;
|
|
1536
|
+
applyHairStateToObject(objectName: string, state: {
|
|
1537
|
+
color?: {
|
|
1538
|
+
baseColor: string;
|
|
1539
|
+
emissive: string;
|
|
1540
|
+
emissiveIntensity: number;
|
|
1541
|
+
};
|
|
1542
|
+
outline?: {
|
|
1543
|
+
show: boolean;
|
|
1544
|
+
color: string;
|
|
1545
|
+
opacity: number;
|
|
1546
|
+
};
|
|
1547
|
+
visible?: boolean;
|
|
1548
|
+
scale?: {
|
|
1549
|
+
x: number;
|
|
1550
|
+
y: number;
|
|
1551
|
+
z: number;
|
|
1552
|
+
};
|
|
1553
|
+
position?: {
|
|
1554
|
+
x: number;
|
|
1555
|
+
y: number;
|
|
1556
|
+
z: number;
|
|
1557
|
+
};
|
|
1558
|
+
isEyebrow?: boolean;
|
|
1559
|
+
}): void;
|
|
1560
|
+
private computeSideValues;
|
|
1561
|
+
private getAUMorphsBySide;
|
|
1562
|
+
private applyMorphTargets;
|
|
1563
|
+
private getMorphValue;
|
|
1564
|
+
private getMorphValueByIndex;
|
|
1565
|
+
private getMorphKeyCacheKey;
|
|
1566
|
+
private getMorphIndexCacheKey;
|
|
1567
|
+
private isMixedAU;
|
|
1568
|
+
private initBoneRotations;
|
|
1569
|
+
/** Update rotation state - just stores -1 to 1 value like stable version */
|
|
1570
|
+
private updateBoneRotation;
|
|
1571
|
+
private updateBoneTranslation;
|
|
1572
|
+
private transitionBoneRotation;
|
|
1573
|
+
private transitionBoneTranslation;
|
|
1574
|
+
private flushPendingComposites;
|
|
1575
|
+
/**
|
|
1576
|
+
* Apply composite rotation using quaternion composition like stable version.
|
|
1577
|
+
* Looks up maxDegrees and channel from BONE_AU_TO_BINDINGS.
|
|
1578
|
+
*/
|
|
1579
|
+
private applyCompositeRotation;
|
|
1580
|
+
private resolveBones;
|
|
1581
|
+
private combineHandles;
|
|
1582
|
+
/**
|
|
1583
|
+
* Apply render order and material settings from CC4_MESHES to all meshes.
|
|
1584
|
+
* This ensures proper layering (e.g., hair renders on top of eyebrows).
|
|
1585
|
+
* Also auto-registers hair and eyebrow meshes for hair physics.
|
|
1586
|
+
*/
|
|
1587
|
+
private applyMeshMaterialSettings;
|
|
1588
|
+
loadAnimationClips(clips: unknown[]): void;
|
|
1589
|
+
getAnimationClips(): AnimationClipInfo[];
|
|
1590
|
+
playAnimation(clipName: string, options?: AnimationPlayOptions): AnimationActionHandle | null;
|
|
1591
|
+
stopAnimation(clipName: string): void;
|
|
1592
|
+
stopAllAnimations(): void;
|
|
1593
|
+
pauseAnimation(clipName: string): void;
|
|
1594
|
+
resumeAnimation(clipName: string): void;
|
|
1595
|
+
pauseAllAnimations(): void;
|
|
1596
|
+
resumeAllAnimations(): void;
|
|
1597
|
+
setAnimationSpeed(clipName: string, speed: number): void;
|
|
1598
|
+
setAnimationIntensity(clipName: string, intensity: number): void;
|
|
1599
|
+
setAnimationTimeScale(timeScale: number): void;
|
|
1600
|
+
getAnimationState(clipName: string): AnimationState | null;
|
|
1601
|
+
getPlayingAnimations(): AnimationState[];
|
|
1602
|
+
crossfadeTo(clipName: string, duration?: number, options?: AnimationPlayOptions): AnimationActionHandle | null;
|
|
1603
|
+
snippetToClip(clipName: string, curves: CurvesMap, options?: ClipOptions): AnimationClip | null;
|
|
1604
|
+
playClip(clip: AnimationClip, options?: ClipOptions): ClipHandle | null;
|
|
1605
|
+
playSnippet(snippet: Snippet | {
|
|
1606
|
+
name: string;
|
|
1607
|
+
curves: CurvesMap;
|
|
1608
|
+
}, options?: ClipOptions): ClipHandle | null;
|
|
1609
|
+
buildClip(clipName: string, curves: CurvesMap, options?: ClipOptions): ClipHandle | null;
|
|
1610
|
+
cleanupSnippet(name: string): void;
|
|
1611
|
+
updateClipParams(name: string, params: {
|
|
1612
|
+
weight?: number;
|
|
1613
|
+
rate?: number;
|
|
1614
|
+
loop?: boolean;
|
|
1615
|
+
loopMode?: 'once' | 'repeat' | 'pingpong';
|
|
1616
|
+
repeatCount?: number;
|
|
1617
|
+
reverse?: boolean;
|
|
1618
|
+
actionId?: string;
|
|
1619
|
+
}): boolean;
|
|
1620
|
+
/**
|
|
1621
|
+
* Check if curves can be played through buildClip.
|
|
1622
|
+
* Returns false if curves contain bone-only AUs that can't be baked to morph tracks.
|
|
1623
|
+
*/
|
|
1624
|
+
supportsClipCurves(curves: Record<string, Array<{
|
|
1625
|
+
time: number;
|
|
1626
|
+
intensity: number;
|
|
1627
|
+
inherit?: boolean;
|
|
1628
|
+
}>>): boolean;
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Helper function to collect meshes with morph targets from a scene.
|
|
1632
|
+
*/
|
|
1633
|
+
declare function collectMorphMeshes(root: Object3D): Mesh[];
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* AnimationThree - Lerp-based animation driver
|
|
1637
|
+
*
|
|
1638
|
+
* Internal driver for time-based interpolation with easing functions.
|
|
1639
|
+
*/
|
|
1640
|
+
|
|
1641
|
+
declare class AnimationThree {
|
|
1642
|
+
private transitions;
|
|
1643
|
+
/**
|
|
1644
|
+
* Tick all active transitions by dt seconds.
|
|
1645
|
+
* Applies eased interpolation and removes completed transitions.
|
|
1646
|
+
* Respects individual transition pause state.
|
|
1647
|
+
*/
|
|
1648
|
+
tick(dtSeconds: number): void;
|
|
1649
|
+
/**
|
|
1650
|
+
* Add or replace a transition for the given key.
|
|
1651
|
+
* If a transition with the same key exists, it is cancelled and replaced.
|
|
1652
|
+
* @returns TransitionHandle with { promise, pause, resume, cancel }
|
|
1653
|
+
*/
|
|
1654
|
+
addTransition(key: string, from: number, to: number, durationMs: number, apply: (value: number) => void, easing?: (t: number) => number): TransitionHandle;
|
|
1655
|
+
/** Clear all running transitions. */
|
|
1656
|
+
clearTransitions(): void;
|
|
1657
|
+
/** Get count of active transitions. */
|
|
1658
|
+
getActiveTransitionCount(): number;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* Hair Physics Interface
|
|
1663
|
+
*
|
|
1664
|
+
* Defines the contract for hair physics simulation systems.
|
|
1665
|
+
* Implementations can use different physics models (spring-damper, verlet, etc.)
|
|
1666
|
+
*/
|
|
1667
|
+
/**
|
|
1668
|
+
* Configuration for hair physics simulation
|
|
1669
|
+
*/
|
|
1670
|
+
interface HairPhysicsConfig$1 {
|
|
1671
|
+
mass: number;
|
|
1672
|
+
stiffness: number;
|
|
1673
|
+
damping: number;
|
|
1674
|
+
gravity: number;
|
|
1675
|
+
headInfluence: number;
|
|
1676
|
+
windEnabled: boolean;
|
|
1677
|
+
windStrength: number;
|
|
1678
|
+
windDirectionX: number;
|
|
1679
|
+
windDirectionZ: number;
|
|
1680
|
+
windTurbulence: number;
|
|
1681
|
+
windFrequency: number;
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Hair strand definition for multi-strand simulation
|
|
1685
|
+
*/
|
|
1686
|
+
interface HairStrand {
|
|
1687
|
+
id: string;
|
|
1688
|
+
morphKeys: {
|
|
1689
|
+
left: string;
|
|
1690
|
+
right: string;
|
|
1691
|
+
front?: string;
|
|
1692
|
+
back?: string;
|
|
1693
|
+
};
|
|
1694
|
+
mass?: number;
|
|
1695
|
+
stiffness?: number;
|
|
1696
|
+
damping?: number;
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Current physics state of hair simulation
|
|
1700
|
+
*/
|
|
1701
|
+
interface HairState {
|
|
1702
|
+
x: number;
|
|
1703
|
+
z: number;
|
|
1704
|
+
vx: number;
|
|
1705
|
+
vz: number;
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Head orientation state for physics input
|
|
1709
|
+
*/
|
|
1710
|
+
interface HeadState$1 {
|
|
1711
|
+
yaw: number;
|
|
1712
|
+
pitch: number;
|
|
1713
|
+
roll: number;
|
|
1714
|
+
yawVelocity: number;
|
|
1715
|
+
pitchVelocity: number;
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Output morph values from physics simulation
|
|
1719
|
+
*/
|
|
1720
|
+
interface HairMorphOutput$1 {
|
|
1721
|
+
[morphKey: string]: number;
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Hair Physics simulation interface
|
|
1725
|
+
*/
|
|
1726
|
+
interface HairPhysics$1 {
|
|
1727
|
+
/**
|
|
1728
|
+
* Update physics simulation
|
|
1729
|
+
* @param dt Delta time in seconds
|
|
1730
|
+
* @param headState Current head orientation and velocity
|
|
1731
|
+
* @returns Morph values to apply to hair meshes
|
|
1732
|
+
*/
|
|
1733
|
+
update(dt: number, headState: HeadState$1): HairMorphOutput$1;
|
|
1734
|
+
/**
|
|
1735
|
+
* Get current physics state (for debugging/UI)
|
|
1736
|
+
*/
|
|
1737
|
+
getState(): HairState;
|
|
1738
|
+
/**
|
|
1739
|
+
* Update configuration
|
|
1740
|
+
*/
|
|
1741
|
+
setConfig(config: Partial<HairPhysicsConfig$1>): void;
|
|
1742
|
+
/**
|
|
1743
|
+
* Get current configuration
|
|
1744
|
+
*/
|
|
1745
|
+
getConfig(): HairPhysicsConfig$1;
|
|
1746
|
+
/**
|
|
1747
|
+
* Reset physics state to rest position
|
|
1748
|
+
*/
|
|
1749
|
+
reset(): void;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
/** Line stroke style */
|
|
1753
|
+
type LineStyle = 'solid' | 'dashed' | 'dotted';
|
|
1754
|
+
/** Line curve type */
|
|
1755
|
+
type LineCurve = 'straight' | 'bezier' | 'arc';
|
|
1756
|
+
/** Named direction presets for line orientation */
|
|
1757
|
+
type NamedDirection = 'radial' | 'camera' | 'up' | 'down' | 'left' | 'right' | 'forward' | 'backward';
|
|
1758
|
+
/** Line configuration for markers */
|
|
1759
|
+
interface LineConfig {
|
|
1760
|
+
/** Stroke style. Default: 'solid' */
|
|
1761
|
+
style?: LineStyle;
|
|
1762
|
+
/** Curve type. Default: 'straight' */
|
|
1763
|
+
curve?: LineCurve;
|
|
1764
|
+
/** Show arrow head at end. Default: false */
|
|
1765
|
+
arrowHead?: boolean;
|
|
1766
|
+
/** Line thickness (affects dash size). Default: 2 */
|
|
1767
|
+
thickness?: number;
|
|
1768
|
+
/** Line length override (model units) */
|
|
1769
|
+
length?: number;
|
|
1770
|
+
}
|
|
1771
|
+
/** Style overrides that can be applied per-region */
|
|
1772
|
+
interface MarkerStyleOverrides {
|
|
1773
|
+
/** Override marker sphere color (hex) */
|
|
1774
|
+
markerColor?: number;
|
|
1775
|
+
/** Override marker sphere radius (model units) */
|
|
1776
|
+
markerRadius?: number;
|
|
1777
|
+
/** Override line color (hex) */
|
|
1778
|
+
lineColor?: number;
|
|
1779
|
+
/** Override label text color (CSS) */
|
|
1780
|
+
labelColor?: string;
|
|
1781
|
+
/** Override label background (CSS) */
|
|
1782
|
+
labelBackground?: string;
|
|
1783
|
+
/** Override label font size (pixels) */
|
|
1784
|
+
labelFontSize?: number;
|
|
1785
|
+
/** Override overall marker opacity (0-1) */
|
|
1786
|
+
opacity?: number;
|
|
1787
|
+
/** Custom line direction: named preset or explicit vector */
|
|
1788
|
+
lineDirection?: NamedDirection | {
|
|
1789
|
+
x: number;
|
|
1790
|
+
y: number;
|
|
1791
|
+
z: number;
|
|
1792
|
+
};
|
|
1793
|
+
/** Line styling options */
|
|
1794
|
+
line?: LineConfig;
|
|
1795
|
+
}
|
|
1796
|
+
/** Animation style for expanding/collapsing child markers */
|
|
1797
|
+
type ExpandAnimation = 'outward' | 'staggered';
|
|
1798
|
+
/** Expanded region state */
|
|
1799
|
+
interface ExpandedRegionState {
|
|
1800
|
+
regionName: string;
|
|
1801
|
+
isExpanded: boolean;
|
|
1802
|
+
children: string[];
|
|
1803
|
+
}
|
|
1804
|
+
/** Fallback behavior configuration */
|
|
1805
|
+
interface FallbackConfig {
|
|
1806
|
+
/** Name of the fallback marker to show when all group markers are occluded */
|
|
1807
|
+
fallbackMarker?: string;
|
|
1808
|
+
/** Behavior when clicking fallback: 'fit-all' tries to fit all in frame, 'rotate' rotates camera */
|
|
1809
|
+
clickBehavior?: 'fit-all' | 'rotate';
|
|
1810
|
+
}
|
|
1811
|
+
/** Marker group for fallback behavior */
|
|
1812
|
+
interface MarkerGroup {
|
|
1813
|
+
/** Unique group identifier */
|
|
1814
|
+
groupId: string;
|
|
1815
|
+
/** Region names in this group */
|
|
1816
|
+
regions: string[];
|
|
1817
|
+
/** Fallback configuration */
|
|
1818
|
+
fallback?: FallbackConfig;
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Single region definition - maps a name to geometry targets
|
|
1822
|
+
*/
|
|
1823
|
+
interface Region {
|
|
1824
|
+
/** Display name for the annotation */
|
|
1825
|
+
name: string;
|
|
1826
|
+
/** Bone names to focus on */
|
|
1827
|
+
bones?: string[];
|
|
1828
|
+
/** Mesh object names to focus on */
|
|
1829
|
+
meshes?: string[];
|
|
1830
|
+
/** Any object names (bones or meshes). Use ['*'] for all objects */
|
|
1831
|
+
objects?: string[];
|
|
1832
|
+
/** Override default padding factor for this annotation */
|
|
1833
|
+
paddingFactor?: number;
|
|
1834
|
+
/**
|
|
1835
|
+
* Camera angle in degrees around the Y axis (horizontal orbit).
|
|
1836
|
+
* 0 = front (default), 90 = right side, 180 = back, 270 = left side
|
|
1837
|
+
*/
|
|
1838
|
+
cameraAngle?: number;
|
|
1839
|
+
/** Fine-tune camera position offset */
|
|
1840
|
+
cameraOffset?: {
|
|
1841
|
+
x?: number;
|
|
1842
|
+
y?: number;
|
|
1843
|
+
z?: number;
|
|
1844
|
+
};
|
|
1845
|
+
/** Parent region name - children animate outward from parent when expanded */
|
|
1846
|
+
parent?: string;
|
|
1847
|
+
/** Child region names - shown when this region is expanded */
|
|
1848
|
+
children?: string[];
|
|
1849
|
+
/** Animation style for expand/collapse. Default: 'outward' */
|
|
1850
|
+
expandAnimation?: ExpandAnimation;
|
|
1851
|
+
/** Show connecting lines to children when expanded. Default: true */
|
|
1852
|
+
showChildConnections?: boolean;
|
|
1853
|
+
/** Per-marker style overrides */
|
|
1854
|
+
style?: MarkerStyleOverrides;
|
|
1855
|
+
/** Marker group ID for fallback behavior */
|
|
1856
|
+
groupId?: string;
|
|
1857
|
+
/** If true, this marker acts as a fallback for its group */
|
|
1858
|
+
isFallback?: boolean;
|
|
1859
|
+
/** Custom position override (user-adjusted). If set, overrides calculated position. */
|
|
1860
|
+
customPosition?: {
|
|
1861
|
+
x: number;
|
|
1862
|
+
y: number;
|
|
1863
|
+
z: number;
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Marker style for annotation visualization
|
|
1868
|
+
* - 'html': Simple HTML overlay markers with numbered dots directly over targets
|
|
1869
|
+
* - '3d': 3D markers with lines and labels rendered in scene space
|
|
1870
|
+
*/
|
|
1871
|
+
type MarkerStyle = 'html' | '3d';
|
|
1872
|
+
/**
|
|
1873
|
+
* Per-character configuration for camera + animation
|
|
1874
|
+
*/
|
|
1875
|
+
interface CharacterConfig {
|
|
1876
|
+
/** Unique identifier for the character */
|
|
1877
|
+
characterId: string;
|
|
1878
|
+
/** Display name */
|
|
1879
|
+
characterName: string;
|
|
1880
|
+
/** Path to GLB file (relative to public folder) */
|
|
1881
|
+
modelPath: string;
|
|
1882
|
+
/** Which region to focus on load */
|
|
1883
|
+
defaultRegion?: string;
|
|
1884
|
+
/** List of available regions */
|
|
1885
|
+
regions: Region[];
|
|
1886
|
+
/** Marker visualization style. Default: '3d' */
|
|
1887
|
+
markerStyle?: MarkerStyle;
|
|
1888
|
+
/** Play intro animation on load (orbit around model, then zoom to torso). Default: false */
|
|
1889
|
+
playIntroOnLoad?: boolean;
|
|
1890
|
+
/** Model position offset to apply on load (e.g., to raise fish above ground) */
|
|
1891
|
+
modelOffset?: {
|
|
1892
|
+
x?: number;
|
|
1893
|
+
y?: number;
|
|
1894
|
+
z?: number;
|
|
1895
|
+
};
|
|
1896
|
+
/** Model rotation in degrees to apply on load (e.g., to fix models exported with wrong orientation) */
|
|
1897
|
+
modelRotation?: {
|
|
1898
|
+
x?: number;
|
|
1899
|
+
y?: number;
|
|
1900
|
+
z?: number;
|
|
1901
|
+
};
|
|
1902
|
+
/** Ensure model's lowest point clears the ground by this amount (world units) */
|
|
1903
|
+
modelGroundClearance?: number;
|
|
1904
|
+
/** Preset type for animation mapping. Default: 'cc4' */
|
|
1905
|
+
auPresetType?: PresetType;
|
|
1906
|
+
/** Optional: profile overrides applied on top of the preset */
|
|
1907
|
+
profile?: Partial<Profile>;
|
|
1908
|
+
/** Prefix to prepend to bone names (e.g., 'Bone.' for fish) */
|
|
1909
|
+
bonePrefix?: string;
|
|
1910
|
+
/** Suffix to append to bone names (e.g., '_Armature' for fish) */
|
|
1911
|
+
boneSuffix?: string;
|
|
1912
|
+
/** Semantic bone name mapping (e.g., 'HEAD' → '001') */
|
|
1913
|
+
boneNodes?: Record<string, string>;
|
|
1914
|
+
/** Regex pattern for fuzzy bone/mesh name matching (e.g., '_\\d+$|\\.\\d+$') */
|
|
1915
|
+
suffixPattern?: string;
|
|
1916
|
+
/** Marker groups for fallback behavior */
|
|
1917
|
+
markerGroups?: MarkerGroup[];
|
|
1918
|
+
/** Global line styling defaults */
|
|
1919
|
+
lineDefaults?: LineConfig;
|
|
1920
|
+
/** Global marker style defaults (overridden by per-region style) */
|
|
1921
|
+
markerDefaults?: Partial<MarkerStyleOverrides>;
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Registry of all available characters
|
|
1925
|
+
*/
|
|
1926
|
+
interface CharacterRegistry {
|
|
1927
|
+
characters: CharacterConfig[];
|
|
1928
|
+
defaultCharacter?: string;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
interface ResolvedFaceCenter {
|
|
1932
|
+
center: THREE.Vector3;
|
|
1933
|
+
method: string;
|
|
1934
|
+
debugInfo: string[];
|
|
1935
|
+
}
|
|
1936
|
+
declare function resolveBoneName(semanticName: string, config?: CharacterConfig): string;
|
|
1937
|
+
declare function resolveBoneNames(names: string[] | undefined, config?: CharacterConfig): string[];
|
|
1938
|
+
declare function fuzzyNameMatch(objectName: string, targetName: string, suffixPattern?: string): boolean;
|
|
1939
|
+
declare function resolveFaceCenter(model: THREE.Object3D, region: Region, config?: CharacterConfig): ResolvedFaceCenter;
|
|
1940
|
+
|
|
1941
|
+
/**
|
|
1942
|
+
* HairPhysics - Spring-damper pendulum simulation for hair movement
|
|
1943
|
+
*
|
|
1944
|
+
* This is a pure physics simulation class with no Three.js dependencies.
|
|
1945
|
+
* It outputs normalized morph values (0-1) that can be applied to hair meshes
|
|
1946
|
+
* via EngineThree.setMorph() or transitionMorph().
|
|
1947
|
+
*
|
|
1948
|
+
* Physics model:
|
|
1949
|
+
* - Hair is modeled as a damped pendulum affected by:
|
|
1950
|
+
* - Gravity (constant downward force based on head orientation)
|
|
1951
|
+
* - Head velocity (inertia causes hair to lag behind head movement)
|
|
1952
|
+
* - Wind (oscillating force with turbulence)
|
|
1953
|
+
* - Spring restoration (hair returns to rest position)
|
|
1954
|
+
* - Damping (air resistance)
|
|
1955
|
+
*/
|
|
1956
|
+
interface HairPhysicsConfig {
|
|
1957
|
+
mass: number;
|
|
1958
|
+
stiffness: number;
|
|
1959
|
+
damping: number;
|
|
1960
|
+
gravity: number;
|
|
1961
|
+
headInfluence: number;
|
|
1962
|
+
windEnabled: boolean;
|
|
1963
|
+
windStrength: number;
|
|
1964
|
+
windDirectionX: number;
|
|
1965
|
+
windDirectionZ: number;
|
|
1966
|
+
windTurbulence: number;
|
|
1967
|
+
windFrequency: number;
|
|
1968
|
+
}
|
|
1969
|
+
interface HairPhysicsState {
|
|
1970
|
+
x: number;
|
|
1971
|
+
z: number;
|
|
1972
|
+
vx: number;
|
|
1973
|
+
vz: number;
|
|
1974
|
+
}
|
|
1975
|
+
interface HairMorphOutput {
|
|
1976
|
+
L_Hair_Left: number;
|
|
1977
|
+
L_Hair_Right: number;
|
|
1978
|
+
L_Hair_Front: number;
|
|
1979
|
+
R_Hair_Left: number;
|
|
1980
|
+
R_Hair_Right: number;
|
|
1981
|
+
R_Hair_Front: number;
|
|
1982
|
+
}
|
|
1983
|
+
interface HeadState {
|
|
1984
|
+
yaw: number;
|
|
1985
|
+
pitch: number;
|
|
1986
|
+
roll: number;
|
|
1987
|
+
yawVelocity: number;
|
|
1988
|
+
pitchVelocity: number;
|
|
1989
|
+
}
|
|
1990
|
+
declare const DEFAULT_HAIR_PHYSICS_CONFIG: HairPhysicsConfig;
|
|
1991
|
+
declare class HairPhysics {
|
|
1992
|
+
private config;
|
|
1993
|
+
private state;
|
|
1994
|
+
private time;
|
|
1995
|
+
private prevHeadYaw;
|
|
1996
|
+
private prevHeadPitch;
|
|
1997
|
+
constructor(config?: Partial<HairPhysicsConfig>);
|
|
1998
|
+
/**
|
|
1999
|
+
* Update physics simulation
|
|
2000
|
+
* @param dt Delta time in seconds
|
|
2001
|
+
* @param headState Current head orientation and velocity
|
|
2002
|
+
* @returns Morph values to apply to hair meshes
|
|
2003
|
+
*/
|
|
2004
|
+
update(dt: number, headState: HeadState): HairMorphOutput;
|
|
2005
|
+
/**
|
|
2006
|
+
* Convert physics state to morph target values
|
|
2007
|
+
* Maps pendulum position to left/right/front morphs for both sides
|
|
2008
|
+
*/
|
|
2009
|
+
private computeMorphOutput;
|
|
2010
|
+
/**
|
|
2011
|
+
* Get current physics state (for debugging/UI)
|
|
2012
|
+
*/
|
|
2013
|
+
getState(): HairPhysicsState;
|
|
2014
|
+
/**
|
|
2015
|
+
* Update configuration
|
|
2016
|
+
*/
|
|
2017
|
+
setConfig(config: Partial<HairPhysicsConfig>): void;
|
|
2018
|
+
/**
|
|
2019
|
+
* Get current configuration
|
|
2020
|
+
*/
|
|
2021
|
+
getConfig(): HairPhysicsConfig;
|
|
2022
|
+
/**
|
|
2023
|
+
* Reset physics state to rest position
|
|
2024
|
+
*/
|
|
2025
|
+
reset(): void;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/**
|
|
2029
|
+
* Loom3 - Mapping Corrections
|
|
2030
|
+
*
|
|
2031
|
+
* Attempts to generate a corrected mapping configuration using fuzzy matching.
|
|
2032
|
+
* This is a best-effort helper that can be layered on top of validation.
|
|
2033
|
+
*/
|
|
2034
|
+
|
|
2035
|
+
interface MorphMesh$1 {
|
|
2036
|
+
name: string;
|
|
2037
|
+
morphTargetDictionary?: Record<string, number>;
|
|
2038
|
+
morphTargetInfluences?: number[];
|
|
2039
|
+
}
|
|
2040
|
+
interface Skeleton$1 {
|
|
2041
|
+
bones: Array<{
|
|
2042
|
+
name: string;
|
|
2043
|
+
}>;
|
|
2044
|
+
}
|
|
2045
|
+
interface MappingCorrection {
|
|
2046
|
+
type: 'bone' | 'morph' | 'viseme' | 'mesh';
|
|
2047
|
+
source: string;
|
|
2048
|
+
target: string;
|
|
2049
|
+
confidence: number;
|
|
2050
|
+
reason: string;
|
|
2051
|
+
applied: boolean;
|
|
2052
|
+
auId?: number;
|
|
2053
|
+
key?: string;
|
|
2054
|
+
}
|
|
2055
|
+
interface MappingCorrectionResult {
|
|
2056
|
+
correctedConfig: Profile;
|
|
2057
|
+
corrections: MappingCorrection[];
|
|
2058
|
+
unresolved: MappingCorrection[];
|
|
2059
|
+
}
|
|
2060
|
+
interface MappingCorrectionOptions {
|
|
2061
|
+
minConfidence?: number;
|
|
2062
|
+
/**
|
|
2063
|
+
* When true, normalize names to resolved full names and clear prefix/suffix.
|
|
2064
|
+
* This is useful when target names don't follow the prefix/suffix pattern.
|
|
2065
|
+
*/
|
|
2066
|
+
useResolvedNames?: boolean;
|
|
2067
|
+
}
|
|
2068
|
+
declare function generateMappingCorrections(meshes: MorphMesh$1[], skeleton: Skeleton$1 | null, config: Profile, options?: MappingCorrectionOptions): MappingCorrectionResult;
|
|
2069
|
+
|
|
2070
|
+
/**
|
|
2071
|
+
* Loom3 - Mapping Validation
|
|
2072
|
+
*
|
|
2073
|
+
* Validates that AU mapping presets are compatible with a loaded character model.
|
|
2074
|
+
* Checks that bones, morph targets, and meshes referenced in the preset exist in the model.
|
|
2075
|
+
*/
|
|
2076
|
+
|
|
2077
|
+
/**
|
|
2078
|
+
* Result of validating a preset against a model
|
|
2079
|
+
*/
|
|
2080
|
+
interface ValidationResult {
|
|
2081
|
+
/** Overall validity - true if essential mappings are found */
|
|
2082
|
+
valid: boolean;
|
|
2083
|
+
/** Compatibility score from 0-100 */
|
|
2084
|
+
score: number;
|
|
2085
|
+
/** Morph targets referenced in preset but not found in model */
|
|
2086
|
+
missingMorphs: string[];
|
|
2087
|
+
/** Bones referenced in preset but not found in model */
|
|
2088
|
+
missingBones: string[];
|
|
2089
|
+
/** Morph targets successfully matched */
|
|
2090
|
+
foundMorphs: string[];
|
|
2091
|
+
/** Bones successfully matched */
|
|
2092
|
+
foundBones: string[];
|
|
2093
|
+
/** Morph targets in model not used by preset */
|
|
2094
|
+
unmappedMorphs: string[];
|
|
2095
|
+
/** Bones in model not used by preset */
|
|
2096
|
+
unmappedBones: string[];
|
|
2097
|
+
/** Meshes referenced by morphToMesh but not found in model */
|
|
2098
|
+
missingMeshes: string[];
|
|
2099
|
+
/** Meshes referenced by morphToMesh and found in model */
|
|
2100
|
+
foundMeshes: string[];
|
|
2101
|
+
/** Meshes in model not referenced by morphToMesh */
|
|
2102
|
+
unmappedMeshes: string[];
|
|
2103
|
+
/** Non-fatal warnings and suggestions */
|
|
2104
|
+
warnings: string[];
|
|
2105
|
+
/** Optional: corrected config when suggestions are requested */
|
|
2106
|
+
suggestedConfig?: Profile;
|
|
2107
|
+
/** Optional: corrections applied or suggested */
|
|
2108
|
+
corrections?: MappingCorrection[];
|
|
2109
|
+
/** Optional: unresolved mappings that could not be corrected */
|
|
2110
|
+
unresolved?: MappingCorrection[];
|
|
2111
|
+
}
|
|
2112
|
+
type IssueSeverity = 'error' | 'warning';
|
|
2113
|
+
interface MappingIssue {
|
|
2114
|
+
code: string;
|
|
2115
|
+
severity: IssueSeverity;
|
|
2116
|
+
message: string;
|
|
2117
|
+
data?: Record<string, unknown>;
|
|
2118
|
+
}
|
|
2119
|
+
interface MappingConsistencyResult {
|
|
2120
|
+
valid: boolean;
|
|
2121
|
+
errors: MappingIssue[];
|
|
2122
|
+
warnings: MappingIssue[];
|
|
2123
|
+
issues: MappingIssue[];
|
|
2124
|
+
}
|
|
2125
|
+
interface ValidateMappingOptions extends MappingCorrectionOptions {
|
|
2126
|
+
suggestCorrections?: boolean;
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Interface for mesh with morph targets (compatible with Three.js Mesh)
|
|
2130
|
+
*/
|
|
2131
|
+
interface MorphMesh {
|
|
2132
|
+
name: string;
|
|
2133
|
+
morphTargetDictionary?: Record<string, number>;
|
|
2134
|
+
morphTargetInfluences?: number[];
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Interface for skeleton (compatible with Three.js Skeleton)
|
|
2138
|
+
*/
|
|
2139
|
+
interface Skeleton {
|
|
2140
|
+
bones: Array<{
|
|
2141
|
+
name: string;
|
|
2142
|
+
}>;
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Validate that the mapping dictionaries are internally consistent.
|
|
2146
|
+
*/
|
|
2147
|
+
declare function validateMappingConfig(config: Profile): MappingConsistencyResult;
|
|
2148
|
+
declare function validateMappings(meshes: MorphMesh[], skeleton: Skeleton | null, config: Profile, options?: ValidateMappingOptions): ValidationResult;
|
|
2149
|
+
/**
|
|
2150
|
+
* Quick check if a preset is compatible with a model.
|
|
2151
|
+
* Returns true if at least 50% of mappings are found.
|
|
2152
|
+
*/
|
|
2153
|
+
declare function isPresetCompatible(meshes: MorphMesh[], skeleton: Skeleton | null, config: Profile): boolean;
|
|
2154
|
+
/**
|
|
2155
|
+
* Suggest the best preset from a list based on validation scores.
|
|
2156
|
+
*/
|
|
2157
|
+
declare function suggestBestPreset<T extends Profile>(meshes: MorphMesh[], skeleton: Skeleton | null, presets: T[]): {
|
|
2158
|
+
preset: T;
|
|
2159
|
+
score: number;
|
|
2160
|
+
} | null;
|
|
2161
|
+
|
|
2162
|
+
/**
|
|
2163
|
+
* Loom3 - Model Data Extraction
|
|
2164
|
+
*
|
|
2165
|
+
* Extracts bones, morph targets, meshes, and animations from 3D models.
|
|
2166
|
+
* Works with both GLTF files and runtime Three.js models.
|
|
2167
|
+
*/
|
|
2168
|
+
|
|
2169
|
+
/**
|
|
2170
|
+
* Bone information extracted from skeleton
|
|
2171
|
+
*/
|
|
2172
|
+
interface BoneInfo {
|
|
2173
|
+
name: string;
|
|
2174
|
+
parent: string | null;
|
|
2175
|
+
children: string[];
|
|
2176
|
+
worldPosition: {
|
|
2177
|
+
x: number;
|
|
2178
|
+
y: number;
|
|
2179
|
+
z: number;
|
|
2180
|
+
};
|
|
2181
|
+
/** Depth in hierarchy (0 = root) */
|
|
2182
|
+
depth: number;
|
|
2183
|
+
}
|
|
2184
|
+
/**
|
|
2185
|
+
* Morph target information
|
|
2186
|
+
*/
|
|
2187
|
+
interface MorphInfo {
|
|
2188
|
+
name: string;
|
|
2189
|
+
meshName: string;
|
|
2190
|
+
index: number;
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Model mesh information (from extraction)
|
|
2194
|
+
*/
|
|
2195
|
+
interface ModelMeshInfo {
|
|
2196
|
+
name: string;
|
|
2197
|
+
hasMorphTargets: boolean;
|
|
2198
|
+
morphCount: number;
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Animation track information
|
|
2202
|
+
*/
|
|
2203
|
+
interface TrackInfo {
|
|
2204
|
+
name: string;
|
|
2205
|
+
targetName: string;
|
|
2206
|
+
property: string;
|
|
2207
|
+
type: 'position' | 'rotation' | 'scale' | 'morph' | 'unknown';
|
|
2208
|
+
keyframeCount: number;
|
|
2209
|
+
valueRange?: {
|
|
2210
|
+
min: number[];
|
|
2211
|
+
max: number[];
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Animation clip information
|
|
2216
|
+
*/
|
|
2217
|
+
interface AnimationInfo {
|
|
2218
|
+
name: string;
|
|
2219
|
+
duration: number;
|
|
2220
|
+
tracks: TrackInfo[];
|
|
2221
|
+
animatedBones: string[];
|
|
2222
|
+
animatedMorphs: string[];
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Complete model data extraction result
|
|
2226
|
+
*/
|
|
2227
|
+
interface ModelData {
|
|
2228
|
+
bones: BoneInfo[];
|
|
2229
|
+
morphs: MorphInfo[];
|
|
2230
|
+
meshes: ModelMeshInfo[];
|
|
2231
|
+
animations: AnimationInfo[];
|
|
2232
|
+
/** Quick lookups */
|
|
2233
|
+
boneNames: string[];
|
|
2234
|
+
morphNames: string[];
|
|
2235
|
+
meshNames: string[];
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Extract all data from a runtime Three.js model
|
|
2239
|
+
*
|
|
2240
|
+
* @param model - The root Object3D of the loaded model
|
|
2241
|
+
* @param meshes - Array of meshes with morph targets (optional, will be collected if not provided)
|
|
2242
|
+
* @param animations - Array of AnimationClips from the GLTF (optional)
|
|
2243
|
+
*/
|
|
2244
|
+
declare function extractModelData(model: THREE.Object3D, meshes?: THREE.Mesh[], animations?: THREE.AnimationClip[]): ModelData;
|
|
2245
|
+
/**
|
|
2246
|
+
* Extract all data from a GLTF file
|
|
2247
|
+
*
|
|
2248
|
+
* @param gltf - The loaded GLTF object
|
|
2249
|
+
*/
|
|
2250
|
+
declare function extractFromGLTF(gltf: GLTF): ModelData;
|
|
2251
|
+
|
|
2252
|
+
/**
|
|
2253
|
+
* Loom3 - Unified Model Analysis API
|
|
2254
|
+
*
|
|
2255
|
+
* Combines model extraction, validation, and correction suggestions
|
|
2256
|
+
* into a single comprehensive analysis report.
|
|
2257
|
+
*/
|
|
2258
|
+
|
|
2259
|
+
/**
|
|
2260
|
+
* Animation analysis results
|
|
2261
|
+
*/
|
|
2262
|
+
interface AnimationAnalysis {
|
|
2263
|
+
count: number;
|
|
2264
|
+
hasIdleCandidate: boolean;
|
|
2265
|
+
clips: Array<{
|
|
2266
|
+
name: string;
|
|
2267
|
+
duration: number;
|
|
2268
|
+
affectedBones: string[];
|
|
2269
|
+
affectedMorphs: string[];
|
|
2270
|
+
}>;
|
|
2271
|
+
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Complete model analysis report
|
|
2274
|
+
*/
|
|
2275
|
+
interface ModelAnalysisReport {
|
|
2276
|
+
/** Extracted model data */
|
|
2277
|
+
model: ModelData;
|
|
2278
|
+
/** Validation results (if preset provided) */
|
|
2279
|
+
validation?: ValidationResult;
|
|
2280
|
+
/** Animation analysis */
|
|
2281
|
+
animations: AnimationAnalysis;
|
|
2282
|
+
/** Overall health score (0-100) */
|
|
2283
|
+
overallScore: number;
|
|
2284
|
+
/** Human-readable summary */
|
|
2285
|
+
summary: string;
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Options for model analysis
|
|
2289
|
+
*/
|
|
2290
|
+
interface AnalyzeModelOptions {
|
|
2291
|
+
/** The model source */
|
|
2292
|
+
source: {
|
|
2293
|
+
type: 'gltf';
|
|
2294
|
+
gltf: GLTF;
|
|
2295
|
+
} | {
|
|
2296
|
+
type: 'runtime';
|
|
2297
|
+
model: THREE.Object3D;
|
|
2298
|
+
meshes: THREE.Mesh[];
|
|
2299
|
+
animations: THREE.AnimationClip[];
|
|
2300
|
+
};
|
|
2301
|
+
/** Optional preset to validate against */
|
|
2302
|
+
preset?: Profile;
|
|
2303
|
+
/** Request correction suggestions */
|
|
2304
|
+
suggestCorrections?: boolean;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Analyze a 3D model comprehensively
|
|
2308
|
+
*
|
|
2309
|
+
* Extracts model data, validates against a preset (if provided),
|
|
2310
|
+
* analyzes animations, and returns a comprehensive report.
|
|
2311
|
+
*
|
|
2312
|
+
* @param options - Analysis options
|
|
2313
|
+
* @returns Complete analysis report
|
|
2314
|
+
*/
|
|
2315
|
+
declare function analyzeModel(options: AnalyzeModelOptions): Promise<ModelAnalysisReport>;
|
|
2316
|
+
|
|
2317
|
+
/**
|
|
2318
|
+
* Loom3 - Geometry Helpers
|
|
2319
|
+
*
|
|
2320
|
+
* Helper functions for finding face positions, annotation centers,
|
|
2321
|
+
* and other geometry-related calculations for character models.
|
|
2322
|
+
*/
|
|
2323
|
+
|
|
2324
|
+
/**
|
|
2325
|
+
* Result of finding a face center position
|
|
2326
|
+
*/
|
|
2327
|
+
interface FaceCenterResult {
|
|
2328
|
+
/** The calculated face center position in world space */
|
|
2329
|
+
center: THREE.Vector3;
|
|
2330
|
+
/** The head bone position (if found) */
|
|
2331
|
+
headBonePosition?: THREE.Vector3;
|
|
2332
|
+
/** How the center was determined */
|
|
2333
|
+
method: 'head-bone-offset' | 'bone-average' | 'mesh-center' | 'fallback';
|
|
2334
|
+
/** Debug info about what was found */
|
|
2335
|
+
debugInfo: string[];
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Options for finding face center
|
|
2339
|
+
*/
|
|
2340
|
+
interface FindFaceCenterOptions {
|
|
2341
|
+
/** Names of bones to search for head bone (e.g., ['CC_Base_Head']) */
|
|
2342
|
+
headBoneNames?: string[];
|
|
2343
|
+
/** Names of face meshes - when provided, uses mesh bounding box center for positioning */
|
|
2344
|
+
faceMeshNames?: string[];
|
|
2345
|
+
/** Forward offset from head bone to face surface (default: 0.08 = 8cm) */
|
|
2346
|
+
forwardOffset?: number;
|
|
2347
|
+
/** Reference model height for scaling (default: 1.8m) */
|
|
2348
|
+
referenceHeight?: number;
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Find the center position of a character's face.
|
|
2352
|
+
*
|
|
2353
|
+
* Strategy:
|
|
2354
|
+
* 1. If face mesh names provided and they're face-only meshes, use mesh center
|
|
2355
|
+
* 2. Otherwise find head bone and use EYE positions to determine forward direction
|
|
2356
|
+
* (Eyes are reliably IN FRONT of the head bone, unlike jaw which is below)
|
|
2357
|
+
*
|
|
2358
|
+
* @param model - The root Object3D of the character model
|
|
2359
|
+
* @param options - Configuration options
|
|
2360
|
+
* @returns FaceCenterResult with the calculated position
|
|
2361
|
+
*/
|
|
2362
|
+
declare function findFaceCenter(model: THREE.Object3D, options?: FindFaceCenterOptions): FaceCenterResult;
|
|
2363
|
+
/**
|
|
2364
|
+
* Get the forward direction of a model in world space.
|
|
2365
|
+
* Assumes the model's local forward is positive Z.
|
|
2366
|
+
*
|
|
2367
|
+
* @param model - The model to get forward direction for
|
|
2368
|
+
* @returns Normalized forward direction vector in world space
|
|
2369
|
+
*/
|
|
2370
|
+
declare function getModelForwardDirection(model: THREE.Object3D): THREE.Vector3;
|
|
2371
|
+
/**
|
|
2372
|
+
* Check if a character model is facing forward (toward camera at origin).
|
|
2373
|
+
* Uses head/eye bone positions to determine facing direction.
|
|
2374
|
+
*
|
|
2375
|
+
* @param model - The character model
|
|
2376
|
+
* @param eyeBoneNames - Names of eye bones to search for
|
|
2377
|
+
* @returns 'forward' if facing camera, 'backward' if facing away, 'unknown' if can't determine
|
|
2378
|
+
*/
|
|
2379
|
+
declare function detectFacingDirection(model: THREE.Object3D, eyeBoneNames?: {
|
|
2380
|
+
left: string[];
|
|
2381
|
+
right: string[];
|
|
2382
|
+
}): 'forward' | 'backward' | 'unknown';
|
|
2383
|
+
|
|
2384
|
+
export { type AUInfo, AU_INFO, AU_MIX_DEFAULTS, AU_TO_MORPHS, type AnalyzeModelOptions, type Animation, type AnimationActionHandle, type AnimationAnalysis, type AnimationClipInfo, type AnimationInfo, type AnimationPlayOptions, 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 CharacterConfig, type CharacterRegistry, type ClipHandle, type ClipOptions, type CompositeRotation, type CompositeRotationState, type CurvePoint, type CurvesMap, DEFAULT_HAIR_PHYSICS_CONFIG, type ExpandAnimation, type ExpandedRegionState, AU_MAPPING_CONFIG as 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$1 as 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, collectMorphMeshes, detectFacingDirection, extractFromGLTF, extractModelData, findFaceCenter, fuzzyNameMatch, generateMappingCorrections, getModelForwardDirection, hasLeftRightMorphs, isMixedAU, isPresetCompatible, resolveBoneName, resolveBoneNames, resolveFaceCenter, resolvePreset, resolvePresetWithOverrides, resolveProfile, suggestBestPreset, validateMappingConfig, validateMappings };
|