@lovelace_lol/embody 0.0.7 → 0.0.8
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 +541 -2394
- package/dist/index.d.ts +2 -100
- package/dist/index.js +7714 -12951
- package/dist/index.js.map +1 -1
- package/dist/three/annotations/DOMControls.d.ts +73 -0
- package/dist/three/annotations/DPthree.d.ts +113 -0
- package/dist/three/annotations/DPthree3DMarkers.d.ts +319 -0
- package/dist/three/annotations/DPthreeCameraController.d.ts +414 -0
- package/dist/three/annotations/DPthreeHTMLMarkers.d.ts +131 -0
- package/dist/three/annotations/DPthreeMarkers.d.ts +52 -0
- package/dist/three/annotations/adapter.d.ts +157 -0
- package/dist/three/annotations/animationFrameTaskScheduler.d.ts +18 -0
- package/dist/three/annotations/annotationAnchorModel.d.ts +16 -0
- package/dist/three/annotations/annotationCameraCore.d.ts +5 -0
- package/dist/three/annotations/boneResolutionProfile.d.ts +7 -0
- package/dist/three/annotations/index.d.ts +20 -0
- package/dist/three/annotations/loadFrame.d.ts +2 -0
- package/dist/three/annotations/markerSurfaceRaycast.d.ts +11 -0
- package/dist/three/annotations/markerVisibilityLifecycle.d.ts +21 -0
- package/dist/three/annotations/modelRenderPreparation.d.ts +6 -0
- package/dist/three/annotations/runtimeAnnotationPreview.d.ts +15 -0
- package/dist/three/annotations/runtimeAnnotationPreviewSide.d.ts +10 -0
- package/dist/three/annotations/runtimeAnnotationSidePreview.d.ts +5 -0
- package/dist/three/annotations/types.d.ts +262 -0
- package/dist/three/gaze.d.ts +122 -0
- package/dist/three/index.d.ts +147 -0
- package/dist/three/model.d.ts +31 -0
- package/dist/three/modelSkinning.d.ts +3 -0
- package/dist/three/reference-pose.d.ts +73 -0
- package/dist/three/scene.d.ts +253 -0
- package/dist/three/sceneRenderingBinding.d.ts +22 -0
- package/dist/three.js +7696 -8936
- package/dist/three.js.map +1 -1
- package/dist/wasm/README.md +551 -62
- package/dist/wasm/auto.d.ts +1 -0
- package/dist/wasm/embody_wasm.d.ts +388 -52
- package/dist/wasm/embody_wasm.js +1514 -119
- package/dist/wasm/embody_wasm_bg.wasm +0 -0
- package/dist/wasm/embody_wasm_bg.wasm.d.ts +115 -40
- package/dist/wasm/index.d.ts +126 -0
- package/dist/wasm.d.ts +1 -43
- package/dist/wasm.js +1043 -239
- package/dist/wasm.js.map +1 -1
- package/package.json +34 -39
- package/service-worker/character-asset-service-worker.js +295 -0
- package/dist/authoring/boneAuthoring.d.ts +0 -132
- package/dist/camera/annotationCameraAngles.d.ts +0 -28
- package/dist/camera/cameraRelativeGaze.d.ts +0 -15
- package/dist/camera/rustAnnotationCameraCore.d.ts +0 -136
- package/dist/cljs.cjs +0 -362
- package/dist/cljs.cjs.map +0 -1
- package/dist/cljs.d.ts +0 -5
- package/dist/cljs.js +0 -359
- package/dist/cljs.js.map +0 -1
- package/dist/core/TsClipCompiler.d.ts +0 -51
- package/dist/core/WasmRuntimeCore.d.ts +0 -41
- package/dist/core/compositeAxis.d.ts +0 -4
- package/dist/core/contracts/abi.d.ts +0 -70
- package/dist/core/contracts/clip.d.ts +0 -44
- package/dist/core/contracts/frame.d.ts +0 -27
- package/dist/core/contracts/host.d.ts +0 -28
- package/dist/core/contracts/ids.d.ts +0 -15
- package/dist/core/contracts/index.d.ts +0 -10
- package/dist/core/contracts/model.d.ts +0 -31
- package/dist/core/contracts/primitives.d.ts +0 -17
- package/dist/core/contracts/skeletonFit.d.ts +0 -41
- package/dist/core/index.d.ts +0 -6
- package/dist/core/types.d.ts +0 -472
- package/dist/core.cjs +0 -343
- package/dist/core.cjs.map +0 -1
- package/dist/core.js +0 -323
- package/dist/core.js.map +0 -1
- package/dist/engines/three/Embody.d.ts +0 -372
- package/dist/engines/three/ThreeAnimationRuntime.d.ts +0 -203
- package/dist/engines/three/ThreeClipAdapter.d.ts +0 -24
- package/dist/engines/three/ThreeFrameApplier.d.ts +0 -68
- package/dist/engines/three/ThreeModelInspector.d.ts +0 -54
- package/dist/engines/three/bakedClipPartitioning.d.ts +0 -16
- package/dist/engines/three/balanceUtils.d.ts +0 -3
- package/dist/engines/three/characterHost.d.ts +0 -80
- package/dist/engines/three/characterScene.d.ts +0 -98
- package/dist/engines/three/hair/HairPhysicsController.d.ts +0 -124
- package/dist/engines/three/index.d.ts +0 -20
- package/dist/engines/three/modelLoader.d.ts +0 -61
- package/dist/engines/three/profileTestScene.d.ts +0 -52
- package/dist/engines/three/types.d.ts +0 -20
- package/dist/hair/appearance.d.ts +0 -29
- package/dist/index.cjs +0 -14871
- package/dist/index.cjs.map +0 -1
- package/dist/interfaces/Animation.d.ts +0 -315
- package/dist/interfaces/EmbodyRuntime.d.ts +0 -90
- package/dist/interfaces/Hair.d.ts +0 -181
- package/dist/interfaces/HairPhysics.d.ts +0 -90
- package/dist/interfaces/index.d.ts +0 -11
- package/dist/mappings/extendPresetWithProfile.d.ts +0 -17
- package/dist/mappings/types.d.ts +0 -309
- package/dist/mappings/visemeSystem.d.ts +0 -26
- package/dist/physics/HairPhysics.d.ts +0 -86
- package/dist/physics/RustHairPhysics.d.ts +0 -18
- package/dist/physics/index.d.ts +0 -6
- package/dist/preset.cjs +0 -2005
- package/dist/preset.cjs.map +0 -1
- package/dist/preset.js +0 -2003
- package/dist/preset.js.map +0 -1
- package/dist/presets/bettaFish.d.ts +0 -328
- package/dist/presets/cc4.d.ts +0 -145
- package/dist/presets/index.d.ts +0 -30
- package/dist/presets/registry.d.ts +0 -57
- package/dist/profiles/resolveProfileConfig.d.ts +0 -36
- package/dist/profiles/rustResolvedProfile.d.ts +0 -31
- package/dist/profiles/types.d.ts +0 -118
- package/dist/regions/normalizeRegionTree.d.ts +0 -6
- package/dist/regions/regionMapping.d.ts +0 -17
- package/dist/regions/types.d.ts +0 -122
- package/dist/skeletonTemplates/humanoidSkeletonTemplates.d.ts +0 -32
- package/dist/test/setupWasm.d.ts +0 -1
- package/dist/three.cjs +0 -9550
- package/dist/three.cjs.map +0 -1
- package/dist/validation/analyzeModel.d.ts +0 -68
- package/dist/validation/extractModelData.d.ts +0 -90
- package/dist/validation/generateMappingCorrections.d.ts +0 -32
- package/dist/validation/geometryHelpers.d.ts +0 -66
- package/dist/validation/types.d.ts +0 -10
- package/dist/validation/validateMappings.d.ts +0 -77
- package/dist/wasm.cjs +0 -302
- package/dist/wasm.cjs.map +0 -1
- package/dist/wasmTypes.d.ts +0 -123
package/dist/wasm/README.md
CHANGED
|
@@ -1,72 +1,561 @@
|
|
|
1
|
-
# Embody
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Skeleton fitting, fit metadata, and automatic skin binding should follow the
|
|
15
|
-
same descriptor boundary. Rust can own host-neutral math over mesh descriptors,
|
|
16
|
-
template skeleton descriptors, rest-pose transforms, bone envelopes, topology,
|
|
17
|
-
inverse bind data, normals, scale, and manual fit adjustments. Host adapters
|
|
18
|
-
must still own real mesh, material, GLB, skinning, and engine-object mutation.
|
|
19
|
-
The TypeScript core contract exposes `TemplateSkeletonFitMetadata` so hosts can
|
|
20
|
-
persist template id, source character id, fit transform, height/spans, status,
|
|
21
|
-
vertical axis/anchor, confidence, and manual adjustments separately from actual
|
|
22
|
-
skin weights. Both TypeScript and Rust/Wasm expose helpers that compose a
|
|
23
|
-
solved fit transform with a user's manual scale and xyz offset.
|
|
24
|
-
|
|
25
|
-
Automatic skin binding is not a per-frame Wasm target by default. It should be
|
|
26
|
-
treated as research/offline-capable work until fixtures prove acceptable
|
|
27
|
-
deformation quality for clothing, hands, face, hair, non-standard poses, and
|
|
28
|
-
other hard cases. The runtime package should expose stable metadata and helper
|
|
29
|
-
math first, then only move weight generation into Rust/Wasm if benchmarks and
|
|
30
|
-
visual tests justify doing it in the browser.
|
|
31
|
-
|
|
32
|
-
`validate_generated_template_fit_metadata(template_id, source_character_id,
|
|
33
|
-
metadata)` validates persisted generated-template fit metadata without treating
|
|
34
|
-
it as imported rig or skinning data. The packed metadata layout is:
|
|
1
|
+
# Embody
|
|
2
|
+
|
|
3
|
+
Explicit profiles may be empty: `RuntimeCore.configure_with_profile('{}', modelJson)`
|
|
4
|
+
loads a static or not-yet-mapped model without selecting an embedded preset.
|
|
5
|
+
Missing mappings remain an authoring/validation concern; malformed JSON still
|
|
6
|
+
fails configuration. Existing mapped profiles retain their runtime behavior.
|
|
7
|
+
|
|
8
|
+
Embody is a Rust/Wasm character animation core for facial action units,
|
|
9
|
+
visemes, bone controls, baked animation clips, and hair motion. Three.js is a
|
|
10
|
+
host adapter, not the runtime implementation.
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
35
13
|
|
|
36
14
|
```text
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
15
|
+
src/ Rust source (the Wasm crate) — hair, clips, profiles, runtime
|
|
16
|
+
assets/presets/ Embedded typed character profiles
|
|
17
|
+
assets/templates/ Embedded humanoid skeleton templates
|
|
18
|
+
three/ Thin Three.js inspection/application adapter only
|
|
19
|
+
wasm/ Generated-Wasm JS loader + ABI constants
|
|
20
|
+
index.ts Package re-exports only
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
There is no parallel TypeScript hair/physics/runtime core. Hair idle, impulse,
|
|
24
|
+
gravity curves and the spring solver live in `src/hair_curves.rs` /
|
|
25
|
+
`src/hair.rs` and are exported through Wasm. Hosts schedule the resulting
|
|
26
|
+
ClipIR on their mixer; they must not reimplement hair sampling in TS.
|
|
27
|
+
|
|
28
|
+
The Rust core owns:
|
|
29
|
+
|
|
30
|
+
- preset and profile parsing, typed merge rules, validation, and corrections
|
|
31
|
+
- AU, viseme, morph, bone, continuum, and live control state
|
|
32
|
+
- compiling AU/viseme/named-morph snippets into concrete ClipIR
|
|
33
|
+
(`morphTarget` / `boneTransform` tracks) for the host mixer to schedule
|
|
34
|
+
- mesh-category routing and material profile data
|
|
35
|
+
- hair curves, hair physics, and appearance normalization
|
|
36
|
+
- annotation camera/marker math and humanoid template fitting
|
|
37
|
+
- profile-aware screen-space gaze geometry for eye/head AU trajectories
|
|
38
|
+
- renderer-neutral model analysis and packed live frame generation
|
|
39
|
+
|
|
40
|
+
The host animation library (Three `AnimationMixer`, Unity Animator, etc.) owns
|
|
41
|
+
clip playback, lerping, blending, looping, seeking, and crossfades. Rust must
|
|
42
|
+
not sample or lerp clips on the hot path.
|
|
43
|
+
|
|
44
|
+
An AU animation drives its profile's mapped bones and morphs through the same
|
|
45
|
+
Rust evaluation used by live AU controls. For example, speech can send an AU
|
|
46
|
+
103 curve and separate viseme shape curves; the profile decides which bones
|
|
47
|
+
and morphs AU 103 moves. Hosts must not replace that AU with a hardcoded jaw
|
|
48
|
+
rotation or read `auToBones` to construct their own bone tracks. When LipSync
|
|
49
|
+
already supplies a jaw AU curve, its existing `autoVisemeJaw: false` option
|
|
50
|
+
keeps automatic viseme jaw synthesis from replacing that curve; it does not
|
|
51
|
+
disable the AU's mapped morphs or the viseme shapes.
|
|
52
|
+
|
|
53
|
+
The TypeScript adapter is intentionally limited to operations that require
|
|
54
|
+
Three.js objects: scene traversal, ClipIR ↔ `AnimationClip` conversion, frame
|
|
55
|
+
application, material writes, model loading/disposal, and default scene
|
|
56
|
+
construction. The annotation adapter also owns camera controls, marker scene
|
|
57
|
+
objects, labels, selection, and their browser lifecycle; applications provide
|
|
58
|
+
the scene/camera/DOM inputs and render controls through the public API.
|
|
59
|
+
|
|
60
|
+
`ThreeFrameApplier.applyMeshMaterialConfigs(model, profile.meshes)` replays named
|
|
61
|
+
mesh visibility and material overrides when a host binds or replaces a profile.
|
|
62
|
+
It applies explicit `visible: false` and `visible: true` even without a material
|
|
63
|
+
entry, and restores render order, opacity, transparency, depth flags and blending
|
|
64
|
+
from `material`. Omitted fields leave the model's current values unchanged.
|
|
65
|
+
Polymer calls this adapter during model binding and profile replacement; hosts
|
|
66
|
+
do not need a separate mesh-visibility replay loop.
|
|
67
|
+
|
|
68
|
+
### Annotation runtime
|
|
69
|
+
|
|
70
|
+
`DPthreeCameraController` and the `DPthree` convenience facade from
|
|
71
|
+
`@lovelace_lol/embody/three` own the complete camera and annotation runtime.
|
|
72
|
+
`DPthree3DMarkers`, `DPthreeHTMLMarkers`, and the legacy `DPthreeMarkers` adapter
|
|
73
|
+
are available for hosts that need a marker-only layer. Rust owns camera
|
|
74
|
+
framing/flights/orbits, viewport clipping, curves, visibility factors and
|
|
75
|
+
endpoint separation. The Three adapter owns native model inspection,
|
|
76
|
+
raycasting, labels, DOM controls, marker expansion/solo/style state and disposal.
|
|
77
|
+
|
|
78
|
+
Applications pass `AnnotationCharacterConfig`; storage, agency, editor and
|
|
79
|
+
character-asset metadata remain host concerns. `loadRegions` defaults to
|
|
80
|
+
`resolveAnnotationCharacterConfig`, which resolves annotation and AU mapping
|
|
81
|
+
fields from an Embody preset without serializing unrelated host metadata.
|
|
82
|
+
An optional `resolveCharacterConfig` callback supports host profile intake.
|
|
83
|
+
`prepareRegionsAndMarkersForReveal` accepts an already-resolved profile and
|
|
84
|
+
prepares native surface queries while the model pose is still stable.
|
|
85
|
+
Initial queries yield to a browser paint between meshes once a 4 ms slice is
|
|
86
|
+
spent. Each native mesh raycast is indivisible; a large mesh can still cause a
|
|
87
|
+
frame drop, and slicing does not reduce total raycast CPU time. Interactive
|
|
88
|
+
annotation changes retain synchronous queries so a marker samples one pose.
|
|
89
|
+
Replacement, clearing and disposal cancel stale loads before scene commits.
|
|
90
|
+
|
|
91
|
+
For first reveal, call `controller.prepareModelForRender(model, { signal })`
|
|
92
|
+
from the existing `beforeReveal` callback (or directly before attaching the
|
|
93
|
+
model). It compiles the model's visible surfaces, then uses the controller's
|
|
94
|
+
existing render loop for one complete scene preparation draw. No host warmup
|
|
95
|
+
queue, shadow proxies or texture preparation loop is needed. The promise rejects
|
|
96
|
+
on failure or cancellation; retain borrowed model/scene/renderer resources until
|
|
97
|
+
it settles, even after `controller.dispose()`. This prepares visible variants,
|
|
98
|
+
not every future hidden material or animation state. See the
|
|
99
|
+
[model readiness contract](docs/SCENE_READINESS.md#model-readiness).
|
|
100
|
+
|
|
101
|
+
`createMarkerVisibilityLifecycle` owns automatic reveal/hide timers;
|
|
102
|
+
`createRuntimeAnnotationPreviewLifecycle` owns temporary authoring previews.
|
|
103
|
+
React hosts forward user events and subscribe to controller state. They do not
|
|
104
|
+
need local copies of the camera, marker, placement or timer algorithms.
|
|
105
|
+
The convenience facade delegates to one controller-owned marker layer.
|
|
106
|
+
|
|
107
|
+
## Runtime Use
|
|
108
|
+
|
|
109
|
+
Embody is an ES module package. Use `import` / dynamic `import()`; CommonJS
|
|
110
|
+
`require()` is not a supported runtime API. Package subpaths remain resolvable
|
|
111
|
+
through Node for tooling that only needs to locate an exported file.
|
|
112
|
+
|
|
113
|
+
Initialize the Wasm module before constructing a runtime:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { initEmbodyCore } from '@lovelace_lol/embody/wasm';
|
|
117
|
+
import {
|
|
118
|
+
AnimationMixer,
|
|
119
|
+
} from 'three';
|
|
120
|
+
import {
|
|
121
|
+
ThreeFrameApplier,
|
|
122
|
+
ThreeModelInspector,
|
|
123
|
+
createAnimationClipFromClipIR,
|
|
124
|
+
} from '@lovelace_lol/embody/three';
|
|
125
|
+
|
|
126
|
+
const wasm = await initEmbodyCore();
|
|
127
|
+
const inspector = new ThreeModelInspector();
|
|
128
|
+
const applier = new ThreeFrameApplier();
|
|
129
|
+
const inspection = inspector.inspectModel(model, { profile });
|
|
130
|
+
const runtime = new wasm.RuntimeCore(0);
|
|
131
|
+
const mixer = new AnimationMixer(model);
|
|
132
|
+
|
|
133
|
+
applier.setBindings(inspection);
|
|
134
|
+
runtime.configure_with_preset(
|
|
135
|
+
'cc4',
|
|
136
|
+
JSON.stringify(profileOverrides ?? {}),
|
|
137
|
+
JSON.stringify(inspection.descriptor),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const clipIR = JSON.parse(runtime.build_clip(
|
|
141
|
+
'smile',
|
|
142
|
+
JSON.stringify(curves),
|
|
143
|
+
JSON.stringify({ intensityScale: 1 }),
|
|
144
|
+
));
|
|
145
|
+
const clip = createAnimationClipFromClipIR(clipIR, inspection);
|
|
146
|
+
mixer.clipAction(clip).play();
|
|
147
|
+
|
|
148
|
+
function update(dtSeconds: number) {
|
|
149
|
+
// Live AU/viseme packed frames, then host mixer owns clip lerp.
|
|
150
|
+
applier.applyPackedMorphFrameDelta(runtime.evaluate_morph_frame_delta());
|
|
151
|
+
applier.applyPackedBoneFrameDelta(runtime.evaluate_bone_frame_delta());
|
|
152
|
+
mixer.update(dtSeconds);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Application-facing JavaScript APIs belong in the host package. Polymer owns
|
|
157
|
+
the CLJS character host used by LoomLarge and calls the Wasm exports directly.
|
|
158
|
+
|
|
159
|
+
### Direct viseme snapshots
|
|
160
|
+
|
|
161
|
+
`RuntimeCore.get_viseme(index)` and `get_viseme_jaw_scale(index)` read the
|
|
162
|
+
canonical direct viseme controls without changing a frame or sampling a host
|
|
163
|
+
mixer. Unset, cleared, or out-of-range slots return `0` and `1` respectively.
|
|
164
|
+
Hosts can snapshot these values before a temporary mouth-shape preview and
|
|
165
|
+
restore them with the existing setters; a zero value releases its live override.
|
|
166
|
+
|
|
167
|
+
### Preparing a default scene
|
|
168
|
+
|
|
169
|
+
`createDefaultCharacterSceneRuntime(container, { rendering: { preference: 'auto' } })`
|
|
170
|
+
returns a stable scene, camera, lighting controller and `rendering` settings
|
|
171
|
+
controller immediately. `ready` reports the initial attempt; failure retains the
|
|
172
|
+
controller for recovery through `setSettings`. `getSettings`, `getSnapshot` and
|
|
173
|
+
`subscribe` expose the requested preference, actual backend, status and errors.
|
|
174
|
+
Live changes prepare and replace only backend resources, preserving the character,
|
|
175
|
+
scene, camera, lighting and marker state. `DPthreeCameraController({ rendering,
|
|
176
|
+
scene, camera, domElement: container })` retargets its existing loop and controls;
|
|
177
|
+
hosts persist settings and display status without recreating the scene.
|
|
178
|
+
|
|
179
|
+
Auto tries native WebGPU then falls back only on acquisition/initialization
|
|
180
|
+
failure. Explicit WebGPU remains strict; material/preparation errors retain the
|
|
181
|
+
previous usable renderer. Device loss is observable and recoverable through the
|
|
182
|
+
same controller. `acquireRenderer()` leases protect captures and model mutations;
|
|
183
|
+
release them before requesting a switch. `releaseSceneResources()` drains rendering
|
|
184
|
+
before detaching and freeing borrowed resources, including after device loss or
|
|
185
|
+
scene disposal. Await `runtime.dispose()` before freeing resources on scene teardown.
|
|
186
|
+
|
|
187
|
+
The existing async factory remains available and rejects after cleanup on initial
|
|
188
|
+
failure. Its successful handles expose the same controller and dynamic backend /
|
|
189
|
+
renderer getters. The synchronous factory stays WebGL-specific. See the owning
|
|
190
|
+
contract for latest-request handling, capture/model leases and teardown ordering.
|
|
191
|
+
|
|
192
|
+
Three runtime and types are checked at 0.184.0, with a >=0.184.0 runtime peer.
|
|
193
|
+
Initial scene readiness does not prepare subsequently added models or establish
|
|
194
|
+
first-frame/visual parity. See [renderer selection and scene readiness](docs/SCENE_READINESS.md)
|
|
195
|
+
for ownership, version rationale and remaining real-browser acceptance.
|
|
196
|
+
|
|
197
|
+
### Bilateral snippet channels
|
|
198
|
+
|
|
199
|
+
AU balance uses the character's left/right: `-1` drives the left side,
|
|
200
|
+
`0` drives both, and `1` drives the right. `RuntimeCore.get_au_balance(id)`
|
|
201
|
+
returns the stored live balance, clamped by the existing setters to [-1, 1],
|
|
202
|
+
with neutral zero for an unset id or after `clear()`. Hosts can save it alongside
|
|
203
|
+
`get_au(id)` when serializing a manual pose.
|
|
204
|
+
|
|
205
|
+
Legacy `build_clip` curve maps use
|
|
206
|
+
`options.balanceMap[auId]`, falling back to `options.balance` and then zero.
|
|
207
|
+
For `build_typed_clip`, an AU target's explicit `balance` takes precedence:
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
const channels = [
|
|
211
|
+
{ target: { type: 'au', id: 43, balance: -1 }, keyframes: leftWink },
|
|
212
|
+
{ target: { type: 'au', id: 43, balance: 1 }, keyframes: rightWink },
|
|
213
|
+
];
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Each channel retains its own keyframe times and intensity scale. Typed AU and
|
|
217
|
+
viseme ids belong to separate namespaces, regardless of `snippetCategory`.
|
|
218
|
+
Duplicate typed channels for the same AU or viseme and concrete morph target
|
|
219
|
+
combine by maximum, including intersections between authored linear curves.
|
|
220
|
+
Independent endpoint-balanced channels keep their own inherited or explicit
|
|
221
|
+
starts; an inactive opposite-side contribution cannot replace an active
|
|
222
|
+
channel's anchor. Composite bone rotations take the maximum AU contribution
|
|
223
|
+
after each channel's balance. Legacy curves and distinct semantic ids that
|
|
224
|
+
share a morph retain their existing separate tracks and host blending.
|
|
225
|
+
|
|
226
|
+
A duplicate typed group with multiple enabled contributions to the same morph
|
|
227
|
+
cannot also contain an inherited start: compilation rejects that combination
|
|
228
|
+
with an error identifying the AU/viseme and destination. Author one curve per
|
|
229
|
+
side or explicit starting values. Previously, duplicate ids silently discarded
|
|
230
|
+
all but the last channel. Inheritance on supported morph tracks is captured
|
|
231
|
+
when the Three clip is constructed. Hosts must construct it again to capture
|
|
232
|
+
a fresh pose; this does not change cached host replay or generated bone
|
|
233
|
+
inheritance behavior.
|
|
234
|
+
|
|
235
|
+
### Gaze geometry contract
|
|
236
|
+
|
|
237
|
+
`solve_profile_screen_space_gaze` and `solve_profile_viewer_space_gaze` return
|
|
238
|
+
14 floats: combined target XY, eye target XY, head target XY, total yaw/pitch,
|
|
239
|
+
camera yaw/pitch, eye-to-camera distance, and viewer world XYZ. XY outputs are
|
|
240
|
+
signed AU intensities in [-1, 1]; positive Y is up and horizontal direction is
|
|
241
|
+
subject-relative. Angular values are degrees.
|
|
242
|
+
|
|
243
|
+
Pass current world camera position/quaternion, character eye midpoint, and
|
|
244
|
+
model quaternion. The viewer solver takes normalized image XY (+Y up) and
|
|
245
|
+
depth behind the virtual camera; depth and all positions must share scene
|
|
246
|
+
units. Convert physical webcam estimates before calling and supply the actual
|
|
247
|
+
source FOV/aspect. Zero viewer XY extends the character-eye-to-camera bearing,
|
|
248
|
+
so the character looks at the viewer even when scene framing puts its eyes
|
|
249
|
+
away from image center. Offsets retain the camera's right/up axes; when the
|
|
250
|
+
camera coincides with the eyes, its local +Z supplies the fallback bearing.
|
|
251
|
+
The depth guard is 0.2–10 scene units. Mouse and webcam can share this contract
|
|
252
|
+
when they use the same calibration.
|
|
253
|
+
|
|
254
|
+
Ordinary head following participates toward the common target from calibrated
|
|
255
|
+
model-neutral +Z: `head_follow_fraction` is a share of the target yaw/pitch,
|
|
256
|
+
defaulting to 0.35 for a non-finite value. Zero prefers neutral, one follows the
|
|
257
|
+
full target, and eye overflow can recruit more head movement within its limits.
|
|
258
|
+
The explicit `lock_head_to_camera` flag instead prefers the camera bearing while
|
|
259
|
+
eyes can reach; a zero follow fraction alone does not select that policy. When
|
|
260
|
+
eyes are disabled the head carries the target regardless of follow or lock.
|
|
261
|
+
This removes the previous implicit camera bias, which could point the head
|
|
262
|
+
opposite the selected target when the camera was off-center. Hosts that intend
|
|
263
|
+
camera-facing behavior must request the explicit viewer-solver lock; Polymer's
|
|
264
|
+
`headFollowEyes: false` maps to zero participation, not an implicit lock.
|
|
265
|
+
Eye angles are solved in the
|
|
266
|
+
rotated head frame, using yaw then pitch to match runtime composition. Limits
|
|
267
|
+
and AU normalization are directional and include each binding's scale. Missing
|
|
268
|
+
or morph-only mappings contribute no inferred angular capacity.
|
|
269
|
+
|
|
270
|
+
The profile must describe the active rig with calibrated semantic axes:
|
|
271
|
+
model +Z is forward, +Y is up. This ABI does not include the skeleton's rest
|
|
272
|
+
frames or optical axes, per-eye origins, current animated head pose, or
|
|
273
|
+
webcam-to-display calibration. It solves an endpoint in the assumed calibrated
|
|
274
|
+
basis; it does not provide binocular convergence or compensate intermediate
|
|
275
|
+
head motion. Shared eye commands use the smaller mapped eye capacity; unequal
|
|
276
|
+
left/right ranges need separate calibration. Hosts must compose the requested
|
|
277
|
+
rotations without averaging independent yaw/pitch clips together.
|
|
278
|
+
|
|
279
|
+
For import-time inspection, `captureModelReferencePose` provides an explicit,
|
|
280
|
+
immutable transform and morph reference that hosts can reuse after playback.
|
|
281
|
+
`extendModelReferencePose` explicitly adds appended skeletons while preserving
|
|
282
|
+
the captured parent transforms. It does not infer a bind pose or perform retargeting. See
|
|
283
|
+
[Animation reference poses](docs/ANIMATION_REFERENCE_POSES.md) for the contract,
|
|
284
|
+
step/linear clip conversion, and remaining import work.
|
|
285
|
+
|
|
286
|
+
## Presets And Profiles
|
|
287
|
+
|
|
288
|
+
### Full humanoid skeleton and body authoring
|
|
289
|
+
|
|
290
|
+
`humanoid.getSpecification` exposes all 55 VRM 1.0 anatomical roles and their
|
|
291
|
+
parent/required rules. CC4 maps all 55 to distinct authored bones and supplies
|
|
292
|
+
58 body controls for fingers, wrists, shoulders, feet, whole body motion, the
|
|
293
|
+
spine, head, eyes and jaw. `profile.setHumanoidRoleBinding` edits or clears a role and
|
|
294
|
+
retargets its existing actuators. The runtime keeps the same bone/morph evaluator;
|
|
295
|
+
other rigs must author their own calibrated axes/ranges and optional muscle
|
|
296
|
+
morph targets. See [Body controls](docs/body-controls.md) for migration, validation
|
|
297
|
+
and role-assignment contracts.
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
`cc4` and `fish` are embedded in the Wasm binary. `skeletal` is accepted as a
|
|
301
|
+
host-level alias for `fish`; exact custom profiles use
|
|
302
|
+
`RuntimeCore.configure_with_profile` and are never silently merged with CC4.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const ids = wasm.list_presets();
|
|
306
|
+
const cc4 = JSON.parse(wasm.get_preset_json('cc4'));
|
|
307
|
+
const merged = JSON.parse(wasm.merge_embedded_preset('cc4', JSON.stringify(overrides)));
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Host-neutral profile authoring operations use one JSON request entry point:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
const result = JSON.parse(wasm.embody_request(JSON.stringify({
|
|
314
|
+
op: 'bone.applyAxisUpdate',
|
|
315
|
+
payload: { profile, boneName: 'CC_Base_Head', axis: 'yaw', update },
|
|
316
|
+
})));
|
|
50
317
|
```
|
|
51
318
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
319
|
+
This boundary keeps ClojureScript and other hosts independent of Rust struct
|
|
320
|
+
layouts while ensuring profile semantics execute in Rust.
|
|
321
|
+
|
|
322
|
+
Speech articulation data belongs to the rig preset. The CC4 profile keeps two
|
|
323
|
+
ordered tables beside its canonical `visemeSlots`: `visemeJawAmounts` stores
|
|
324
|
+
the jaw opening for each viseme index, and `visemeTongueTargets` stores the AU
|
|
325
|
+
target map for that same index. Empty tongue maps mean that viseme has no
|
|
326
|
+
tongue overlay. Hosts pass these resolved tables to their lip-sync planner;
|
|
327
|
+
they should not recreate rig-specific jaw or tongue values in application code.
|
|
328
|
+
The live runtime also compiles both tables: `setViseme` applies the viseme morph,
|
|
329
|
+
the mapped jaw opening, and the mapped tongue AUs from the same preset entry.
|
|
330
|
+
|
|
331
|
+
## Model Analysis
|
|
332
|
+
|
|
333
|
+
Three.js models are reduced to a renderer-neutral descriptor by
|
|
334
|
+
`ThreeModelInspector`. Rust consumes that descriptor for extraction and
|
|
335
|
+
validation:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
const clips = serializeAnimationClips(model, animations, inspection);
|
|
339
|
+
const report = JSON.parse(wasm.analyze_model_descriptor(
|
|
340
|
+
JSON.stringify(inspection.descriptor),
|
|
341
|
+
JSON.stringify(clips),
|
|
342
|
+
JSON.stringify(profile),
|
|
343
|
+
JSON.stringify({ suggestCorrections: true }),
|
|
344
|
+
));
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Pose-aware focus
|
|
348
|
+
|
|
349
|
+
`ThreeGazeFocus` from `@lovelace_lol/embody/three` applies a focus constraint
|
|
350
|
+
inside an animation runtime. It has no clock or target-selection policy:
|
|
351
|
+
call `restore()` before evaluating the base animation, then `apply(request,
|
|
352
|
+
controls)` after it. Restoration compares quaternion orientations independently
|
|
353
|
+
of Float32 sample norms, preserving the exact authored sample and any newer
|
|
354
|
+
authored orientation. Repeated applications between mixer ticks do not accumulate
|
|
355
|
+
the constraint's previous correction. Controls are the current positions of existing motor
|
|
356
|
+
tracks. The optional sampled `controls.headIntensity` and `controls.eyeIntensity`
|
|
357
|
+
override the matching request fields, so the host can animate configuration
|
|
358
|
+
changes on its existing motor tracks. Both mean contribution in [0, 1]: zero
|
|
359
|
+
preserves the evaluated base, one applies the full solved excursion, and legacy
|
|
360
|
+
values above one normalize to one. Intermediate eye contribution proportionally
|
|
361
|
+
reduces the correction; it does not shrink anatomical limits. Reduced eye
|
|
362
|
+
contribution deliberately leaves focus error. These controls do not amplify
|
|
363
|
+
input displacement or control response speed.
|
|
364
|
+
|
|
365
|
+
During travel, the remaining sampled bearing rotates the requested finite
|
|
366
|
+
target. At the endpoint that rotation becomes identity, reaching the exact
|
|
367
|
+
world target continuously. There is no settled-threshold switch between targets
|
|
368
|
+
constructed from the authored and tracking-shifted eye origins; a larger head
|
|
369
|
+
turn must not introduce a final eye snap.
|
|
370
|
+
|
|
371
|
+
Pass `{ referencePose }` as the constructor's third argument, reusing the
|
|
372
|
+
reference captured at import. Without it, construction captures the current
|
|
373
|
+
pose, so callers must construct before playback. The head solves its bounded
|
|
374
|
+
gaze excursion in that reference hierarchy under the model's current scene
|
|
375
|
+
transform, then composes the excursion onto the evaluated authored head pose.
|
|
376
|
+
Head and neck gestures retain their motion instead of being cancelled or
|
|
377
|
+
reversed by tracking. Each eye then solves toward the same finite target from
|
|
378
|
+
its own origin in the final animated hierarchy. Full eye contribution retains
|
|
379
|
+
binocular focus wherever the combined authored motion and gaze are reachable;
|
|
380
|
+
otherwise diagnostics report the remaining angular error and limits. Authored
|
|
381
|
+
gestures are not suppressed to force a target into range. The signed limits
|
|
382
|
+
bound the tracking excursion; they do not clamp the final authored pose plus
|
|
383
|
+
that excursion against a neutral-reference joint range. Authored motion can
|
|
384
|
+
therefore take the combined pose beyond that range or the eyes' reach.
|
|
385
|
+
|
|
386
|
+
`readControlState({worldTarget, headIntensity, eyeIntensity})` provides initial
|
|
387
|
+
motor bearings without changing the rig. It inverts existing focus contributions
|
|
388
|
+
against their saved base, and seeds zero added head excursion when enabling on
|
|
389
|
+
a purely authored pose. When replacing legacy AU controls, also provide the
|
|
390
|
+
evaluated local `headBaseQuaternion` and/or `eyeBaseQuaternions` (a map keyed by
|
|
391
|
+
bone name), with those legacy controls excluded. `headSeedLimited` and
|
|
392
|
+
`eyeSeedLimited` report poses that cannot be represented by the bounded motor
|
|
393
|
+
bearings. Parallel legacy eyes cannot both be preserved exactly by a shared
|
|
394
|
+
finite target; inverse eye contribution avoids double attenuation but does not
|
|
395
|
+
remove that convergence residual. This helper supplies no handoff transition
|
|
396
|
+
clock; the host owns activation policy and movement timing.
|
|
56
397
|
|
|
57
|
-
|
|
58
|
-
|
|
398
|
+
The optional `profile.gazeCalibration` supplies bone-local
|
|
399
|
+
`head`, `leftEye`, and `rightEye` optical axes and `modelUnitsPerMeter`.
|
|
400
|
+
Without explicit optical axes, signed yaw/pitch bindings determine the optical
|
|
401
|
+
frame. Unresolvable joints remain untouched and are reported in diagnostics.
|
|
59
402
|
|
|
60
|
-
|
|
403
|
+
`solve_profile_viewer_space_gaze_scaled` adds `world_units_per_meter` as its
|
|
404
|
+
last argument. Multiply the authored units-per-meter calibration by the
|
|
405
|
+
presentation's uniform root scale. The original viewer solver keeps its
|
|
406
|
+
existing scale-one behavior. Neither solver infers physical webcam/display
|
|
407
|
+
placement from face landmarks.
|
|
61
408
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
409
|
+
`solve_profile_tracking_gaze` maps normalized display-right/up controls to the
|
|
410
|
+
profile's full signed head AU rotation range. Pass `head_follow_fraction = 1`
|
|
411
|
+
for full participation: on an aligned display axis, an input endpoint of +/-1
|
|
412
|
+
requests the same head rotation as the corresponding AU at intensity 1. Binding
|
|
413
|
+
scale and asymmetric `maxDegrees` remain authoritative. Camera distance and FOV
|
|
414
|
+
do not reduce this range. Intermediate inputs interpolate between the bounded
|
|
415
|
+
camera bearing at zero and the corresponding signed endpoint; camera roll
|
|
416
|
+
rotates the display axes into the model frame. With head tracking disabled,
|
|
417
|
+
the target spans the eye AU range instead.
|
|
418
|
+
|
|
419
|
+
The signature is `(profile_json, tracking_target, camera_position,
|
|
420
|
+
camera_quaternion, gaze_origin, model_quaternion, eyes_enabled, head_enabled,
|
|
421
|
+
head_follow_fraction, lock_head_to_camera, world_units_per_meter)`. It returns
|
|
422
|
+
the same 14-float layout as the existing physical projection solvers. Target Z
|
|
423
|
+
is depth in meters, clamped to 0.2–10 before scene scaling; it sets a finite
|
|
424
|
+
binocular focus distance without attenuating the angular range. X/Y clamp to
|
|
425
|
+
[-1, 1], nonfinite coordinates become zero, invalid positive depth/scale use
|
|
426
|
+
0.8/1 respectively, and absent AU bindings supply no angular capacity.
|
|
427
|
+
|
|
428
|
+
This is an input-range mapping, not a physical reconstruction of the viewer's
|
|
429
|
+
position. The existing screen/viewer projection APIs retain their contracts.
|
|
430
|
+
Consumers must select this entry point and request full head participation to
|
|
431
|
+
use it; adding the export alone does not change LoomLarge tracking. Eye focus
|
|
432
|
+
still uses the resulting finite target and reports residual error when reduced
|
|
433
|
+
strength, authored motion, or rig limits make that target unreachable.
|
|
434
|
+
|
|
435
|
+
## npm releases and PR previews
|
|
436
|
+
|
|
437
|
+
Production consumers install an exact published `@lovelace_lol/embody` version
|
|
438
|
+
from public npm without read credentials and commit their lockfile. The GitHub
|
|
439
|
+
source repository stays private. A stable release contains the compiled JS,
|
|
440
|
+
TypeScript declarations, and Wasm; consumers do not build Embody from Git.
|
|
441
|
+
|
|
442
|
+
The `Verify and publish package` workflow publishes a stable npm release after
|
|
443
|
+
a push to `main`, or a manual run on `main` with the `ref` input left empty.
|
|
444
|
+
It chooses the next patch version, records the source commit in `gitHead`, and
|
|
445
|
+
builds and validates once. Stable version selection reads public metadata from
|
|
446
|
+
`registry.npmjs.org` anonymously; redirects and failed lookups stop the release.
|
|
447
|
+
Metadata reads never transmit publishing credentials. The separate
|
|
448
|
+
publish job first requires `npm access get status` to report the existing package
|
|
449
|
+
as public, then uses the `npm` environment's `NPM_KEY` to publish the packed
|
|
450
|
+
output with public access and creates its Git tag and GitHub release. Stable
|
|
451
|
+
releases are serialized through version selection and publication. Provenance
|
|
452
|
+
remains disabled for the private source repository. Retrying a published commit
|
|
453
|
+
reuses its existing version, and an older unpublished commit cannot replace a
|
|
454
|
+
newer source release.
|
|
455
|
+
|
|
456
|
+
PRs, including drafts, manual runs with an explicit `ref`, and `publish-pkg-pr-new`
|
|
457
|
+
repository dispatches run full verification and publish immutable `pkg.pr.new`
|
|
458
|
+
previews for testing linked upstream changes. An explicit ref remains a preview
|
|
459
|
+
request even when it names `main`; it does not publish to npm. Manual and
|
|
460
|
+
repository dispatch requests may include `upstream_pr` to identify their linked PR.
|
|
461
|
+
|
|
462
|
+
After publication, the producing workflow uploads a JSON notification record
|
|
463
|
+
without a dispatch credential. The separate `Notify package publication`
|
|
464
|
+
`workflow_run` receiver checks out `main`, treats the artifact only as data, and
|
|
465
|
+
validates the successful run attempt, repository, workflow, and source commit.
|
|
466
|
+
Stable records must identify that run's main commit; linked previews must match
|
|
467
|
+
the current open PR head targeting main. The receiver then sends
|
|
468
|
+
`upstream-package-published` to `meekmachine/LoomLarge` using
|
|
469
|
+
`CROSS_REPO_CI_TOKEN` from the `release-coordination` environment. That token must
|
|
470
|
+
not be stored as a repository secret or exposed to a PR build/helper.
|
|
471
|
+
|
|
472
|
+
Stable notifications contain the exact source SHA and npm version. Preview
|
|
473
|
+
notifications contain the full-SHA URL and upstream PR number, or `null` for an
|
|
474
|
+
unlinked manual preview. The receiver checks the canonical preview URL once and
|
|
475
|
+
fails visibly if it is unavailable. LoomLarge owns downstream dependency updates;
|
|
476
|
+
Embody does not edit Polymer or application branches. Missing credentials and
|
|
477
|
+
failed notifications fail the receiver visibly. Fork PRs do not send
|
|
478
|
+
notifications or receive npm/dispatch secrets.
|
|
479
|
+
|
|
480
|
+
GitHub activates the receiver after its workflow is present on `main`. The
|
|
481
|
+
release-stack PR's first preview remains usable by its immutable URL before
|
|
482
|
+
that bootstrap merge; subsequent successful publications notify automatically.
|
|
483
|
+
|
|
484
|
+
Before enabling stable releases, the existing npm package must have public
|
|
485
|
+
visibility and `NPM_KEY` must permit publishing it. No npm read token is required.
|
|
486
|
+
Configure both the `npm` and `release-coordination` GitHub
|
|
487
|
+
environments to allow deployments from `main` only. Store `CROSS_REPO_CI_TOKEN`
|
|
488
|
+
only in `release-coordination`, with permission to dispatch to LoomLarge. These
|
|
489
|
+
account, environment protection, and secret settings are prerequisites; source
|
|
490
|
+
changes do not configure them. Existing versions are never republished or
|
|
491
|
+
overwritten.
|
|
492
|
+
|
|
493
|
+
## Development
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
npm ci
|
|
65
497
|
npm run build
|
|
66
|
-
|
|
498
|
+
npm test
|
|
499
|
+
npm run test:package
|
|
500
|
+
npm run check:generated
|
|
67
501
|
```
|
|
68
502
|
|
|
69
|
-
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
|
|
503
|
+
`npm run build` creates the JS adapter bundles, declarations, generated
|
|
504
|
+
wasm-bindgen glue, and `.wasm` binary in `dist/`. Generated output is not source
|
|
505
|
+
and is not committed. Package checks consume that existing build, so run the
|
|
506
|
+
build once before `npm run test:package`.
|
|
507
|
+
|
|
508
|
+
The public Wasm loader returns `EmbodyCore`, derived directly from wasm-bindgen's
|
|
509
|
+
generated declarations, including `RuntimeCore` constructors, methods, and
|
|
510
|
+
numeric helpers. Instance types can also be imported with `import type` from
|
|
511
|
+
`@lovelace_lol/embody/wasm`. Rust exports remain the source of truth for these
|
|
512
|
+
signatures; JSON payload contents are still encoded as strings.
|
|
513
|
+
|
|
514
|
+
Run the build before `npm test` or `npm run typecheck` in a clean checkout, and
|
|
515
|
+
rebuild after changing Rust exports. The build generates the Wasm bindings before
|
|
516
|
+
TypeScript declarations. Neither typechecking nor package verification rebuilds
|
|
517
|
+
the package; CI checks and publishes that same build.
|
|
518
|
+
|
|
519
|
+
## License
|
|
520
|
+
|
|
521
|
+
MIT. See [LICENSE](LICENSE) and [AUTHORS.md](AUTHORS.md).
|
|
522
|
+
|
|
523
|
+
### Semantic poses and generated motion
|
|
524
|
+
|
|
525
|
+
`RuntimeCore.get_semantic_pose_catalog_json()` returns the configured profile's
|
|
526
|
+
motion identifiers, direction labels, VRM roles, resolved bone/morph names and
|
|
527
|
+
per-direction model support and available sides. VRM names anatomy; profile motion IDs name authored
|
|
528
|
+
actions. Hosts should send only supported directions to generation prompts,
|
|
529
|
+
along with their bounds: intensity and morph strength `0..1`, bilateral balance
|
|
530
|
+
`-1` character-left, `0` both, `1` character-right. Amounts are normalized authored
|
|
531
|
+
motion, not radians or world-space rotation.
|
|
532
|
+
|
|
533
|
+
`capture_semantic_pose_json()` returns `{version:1,controls:[{controlId,positive,
|
|
534
|
+
negative?}]}`. Each direction stores intensity, balance and morphStrength
|
|
535
|
+
independently, preserving opposed action values. These are manual runtime
|
|
536
|
+
controls; capturing an arbitrary mixer/baked pose would require pose inference.
|
|
537
|
+
`validate_semantic_pose_json()` fills omitted settings without mutation.
|
|
538
|
+
`apply_semantic_pose_json()` validates the complete input before replacing Body
|
|
539
|
+
actions, preserving unrelated facial state. Either direction may be omitted;
|
|
540
|
+
a control requires at least one. Known missing outputs remain portable to partial
|
|
541
|
+
rigs; unknown IDs, duplicate actions and invalid bounds fail. Applying a semantic pose releases prior direct overrides on its exact mapped
|
|
542
|
+
mesh/target IDs, preserving unrelated facial overrides and same-named targets on
|
|
543
|
+
unmapped meshes.
|
|
544
|
+
|
|
545
|
+
`build_semantic_clip(name, animationJson, optionsJson)` compiles
|
|
546
|
+
`{version:1,durationSeconds,tracks:[{controlId,direction,balance?,morphStrength?,
|
|
547
|
+
keyframes:[{time,intensity}]}]}` through the same AU/bone/morph evaluator.
|
|
548
|
+
Direction is `positive` or `negative`; time is seconds. Unsupported directions,
|
|
549
|
+
unknown controls, duplicate actions and non-increasing/out-of-range keys fail.
|
|
550
|
+
`options.faceCurves` accepts additional non-Body AU curves so face and body compile
|
|
551
|
+
together, including overlapping mapped bones. Compilation preserves live state;
|
|
552
|
+
per-track morph strength changes only the compiled output. The host mixer owns
|
|
553
|
+
playback and interpolation, as with existing snippets.
|
|
554
|
+
|
|
555
|
+
Catalog `availableSides` identifies actual mapped left/right/center outputs.
|
|
556
|
+
Generated animations reject a negative balance without a left output or positive
|
|
557
|
+
balance without a right output; balance zero may drive the remaining side of a
|
|
558
|
+
partial rig. Stored static poses retain their portability across missing outputs.
|
|
559
|
+
`evaluate_semantic_body_bone_frame()` returns exact Body-owned properties including
|
|
560
|
+
neutral values, allowing an explicit semantic pose/reset to replace old raw bone
|
|
561
|
+
edits without touching unrelated properties.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index';
|