talking-head-studio 0.4.10 → 0.4.12
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 +299 -337
- package/dist/TalkingHead.d.ts +44 -28
- package/dist/TalkingHead.js +21 -2
- package/dist/TalkingHead.web.d.ts +37 -4
- package/dist/TalkingHead.web.js +28 -8
- package/dist/TalkingHeadVisualization.d.ts +22 -0
- package/dist/TalkingHeadVisualization.js +30 -10
- package/dist/api/studioApi.d.ts +12 -1
- package/dist/api/studioApi.js +41 -28
- package/dist/appearance/apply.js +2 -3
- package/dist/appearance/matchers.js +1 -2
- package/dist/appearance/schema.js +1 -2
- package/dist/contract.d.ts +14 -0
- package/dist/contract.js +30 -0
- package/dist/core/avatar/avatarCapabilities.d.ts +60 -0
- package/dist/core/avatar/avatarCapabilities.js +100 -0
- package/dist/core/avatar/backend.d.ts +130 -0
- package/dist/core/avatar/backend.js +4 -0
- package/dist/core/avatar/backends/gaussian.d.ts +49 -0
- package/dist/core/avatar/backends/gaussian.js +293 -0
- package/dist/core/avatar/backends/index.d.ts +3 -0
- package/dist/core/avatar/backends/index.js +7 -0
- package/dist/core/avatar/backends/morphTarget.d.ts +39 -0
- package/dist/core/avatar/backends/morphTarget.js +179 -0
- package/dist/core/avatar/faceControls.d.ts +40 -0
- package/dist/core/avatar/faceControls.js +138 -0
- package/dist/core/avatar/motion.d.ts +1713 -0
- package/dist/core/avatar/motion.js +550 -0
- package/dist/core/avatar/motionRuntime.d.ts +46 -0
- package/dist/core/avatar/motionRuntime.js +84 -0
- package/dist/core/avatar/schema.d.ts +78 -0
- package/dist/core/avatar/schema.js +134 -0
- package/dist/core/avatar/visemes.d.ts +47 -1
- package/dist/core/avatar/visemes.js +114 -1
- package/dist/editor/AvatarCanvas.js +93 -3
- package/dist/editor/AvatarEditor.native.js +19 -9
- package/dist/editor/AvatarModel.js +2 -2
- package/dist/editor/FaceSqueezeEditor.d.ts +3 -1
- package/dist/editor/FaceSqueezeEditor.js +195 -121
- package/dist/editor/FaceSqueezeEditor.web.d.ts +3 -1
- package/dist/editor/FaceSqueezeEditor.web.js +32 -30
- package/dist/editor/RigidAccessory.js +18 -4
- package/dist/editor/SkinnedClothing.js +19 -9
- package/dist/editor/boneLockedDrag.d.ts +11 -0
- package/dist/editor/boneLockedDrag.js +68 -0
- package/dist/editor/boneSnap.js +22 -12
- package/dist/editor/boneSnap.web.d.ts +27 -0
- package/dist/editor/boneSnap.web.js +99 -0
- package/dist/editor/index.web.d.ts +10 -0
- package/dist/editor/index.web.js +26 -0
- package/dist/editor/sounds/haha.wav +0 -0
- package/dist/editor/sounds/owie.wav +0 -0
- package/dist/editor/sounds/stop.wav +0 -0
- package/dist/editor/studioTheme.d.ts +14 -14
- package/dist/editor/studioTheme.js +19 -16
- package/dist/editor/types.d.ts +1 -0
- package/dist/html/accessories.d.ts +7 -0
- package/dist/html/accessories.js +149 -0
- package/dist/html/motion.d.ts +1 -0
- package/dist/html/motion.js +189 -0
- package/dist/html/visemes.d.ts +7 -0
- package/dist/html/visemes.js +348 -0
- package/dist/html.d.ts +1 -1
- package/dist/html.js +56 -734
- package/dist/index.d.ts +19 -1
- package/dist/index.js +44 -5
- package/dist/index.web.d.ts +18 -1
- package/dist/index.web.js +36 -3
- package/dist/platform/api/types.d.ts +10 -0
- package/dist/platform/api/types.js +2 -0
- package/dist/platform/marketplace/types.d.ts +32 -0
- package/dist/platform/marketplace/types.js +2 -0
- package/dist/platform/sdk/unity.d.ts +27 -0
- package/dist/platform/sdk/unity.js +2 -0
- package/dist/platform/sdk/unreal.d.ts +23 -0
- package/dist/platform/sdk/unreal.js +2 -0
- package/dist/platform/sdk/web.d.ts +16 -0
- package/dist/platform/sdk/web.js +2 -0
- package/dist/sketchfab/api.js +5 -5
- package/dist/sketchfab/glbInspect.d.ts +22 -0
- package/dist/sketchfab/glbInspect.js +58 -0
- package/dist/sketchfab/index.d.ts +3 -0
- package/dist/sketchfab/index.js +8 -1
- package/dist/sketchfab/inspectRemote.d.ts +13 -0
- package/dist/sketchfab/inspectRemote.js +77 -0
- package/dist/sketchfab/types.d.ts +10 -0
- package/dist/sketchfab/useSketchfabSearch.js +1 -2
- package/dist/studio/AccessoryBrowserScreen.d.ts +6 -0
- package/dist/studio/AccessoryBrowserScreen.js +626 -0
- package/dist/studio/AccessoryPanel.d.ts +10 -0
- package/dist/studio/AccessoryPanel.js +396 -0
- package/dist/studio/AppearancePanel.d.ts +9 -0
- package/dist/studio/AppearancePanel.js +77 -0
- package/dist/studio/AvatarCreatorScreen.d.ts +5 -0
- package/dist/studio/AvatarCreatorScreen.js +806 -0
- package/dist/studio/AvatarEditorScreen.d.ts +14 -0
- package/dist/studio/AvatarEditorScreen.js +510 -0
- package/dist/studio/AvatarGrid.d.ts +23 -0
- package/dist/studio/AvatarGrid.js +257 -0
- package/dist/studio/ColorSwatch.d.ts +8 -0
- package/dist/studio/ColorSwatch.js +100 -0
- package/dist/studio/CreateVoiceProfileSheet.d.ts +8 -0
- package/dist/studio/CreateVoiceProfileSheet.js +242 -0
- package/dist/studio/DetailsPanel.d.ts +15 -0
- package/dist/studio/DetailsPanel.js +239 -0
- package/dist/studio/FilamentEditor.d.ts +2 -0
- package/dist/studio/FilamentEditor.js +6 -0
- package/dist/studio/PrecisionPanel.d.ts +2 -0
- package/dist/studio/PrecisionPanel.js +7 -0
- package/dist/studio/PublicGalleryScreen.d.ts +5 -0
- package/dist/studio/PublicGalleryScreen.js +358 -0
- package/dist/studio/SketchfabModelCard.d.ts +20 -0
- package/dist/studio/SketchfabModelCard.js +104 -0
- package/dist/studio/StudioBrowseHeader.d.ts +9 -0
- package/dist/studio/StudioBrowseHeader.js +28 -0
- package/dist/studio/StudioEmptyState.d.ts +8 -0
- package/dist/studio/StudioEmptyState.js +29 -0
- package/dist/studio/StudioFloatingAction.d.ts +13 -0
- package/dist/studio/StudioFloatingAction.js +42 -0
- package/dist/studio/StudioSectionHeader.d.ts +7 -0
- package/dist/studio/StudioSectionHeader.js +27 -0
- package/dist/studio/StudioSurfaceCard.d.ts +8 -0
- package/dist/studio/StudioSurfaceCard.js +20 -0
- package/dist/studio/VoicePanel.d.ts +15 -0
- package/dist/studio/VoicePanel.js +305 -0
- package/dist/studio/constants.d.ts +3 -0
- package/dist/studio/constants.js +6 -0
- package/dist/studio/index.d.ts +29 -0
- package/dist/studio/index.js +54 -0
- package/dist/studio/useSketchfabCapabilities.d.ts +31 -0
- package/dist/studio/useSketchfabCapabilities.js +82 -0
- package/dist/tts/useDirectVisemeStream.d.ts +2 -6
- package/dist/tts/useDirectVisemeStream.js +16 -12
- package/dist/tts/useMotionMarkers.d.ts +0 -1
- package/dist/tts/useMotionMarkers.js +1 -2
- package/dist/utils/avatarUtils.js +94 -8
- package/dist/utils/faceLandmarkerToShapeWeights.js +21 -14
- package/dist/voice/convertToWav.js +1 -2
- package/dist/voice/index.d.ts +3 -0
- package/dist/voice/index.js +6 -1
- package/dist/voice/useAudioPlayer.js +18 -6
- package/dist/voice/useAudioRecording.js +1 -2
- package/dist/voice/useFaceControls.d.ts +14 -0
- package/dist/voice/useFaceControls.js +81 -0
- package/dist/voice/useVoicePreview.d.ts +7 -0
- package/dist/voice/useVoicePreview.js +83 -0
- package/dist/wardrobe/index.d.ts +3 -0
- package/dist/wardrobe/index.js +8 -1
- package/dist/wardrobe/useAccessoryGestures.d.ts +20 -0
- package/dist/wardrobe/useAccessoryGestures.js +94 -0
- package/dist/wardrobe/useAvatarWardrobeHydration.js +9 -4
- package/dist/wardrobe/useStudioAvatar.d.ts +29 -0
- package/dist/wardrobe/useStudioAvatar.js +186 -0
- package/dist/wardrobe/wardrobeStore.d.ts +2 -0
- package/dist/wardrobe/wardrobeStore.js +12 -2
- package/dist/wgpu/R3FWebGpuCanvas.d.ts +15 -0
- package/dist/wgpu/R3FWebGpuCanvas.js +176 -0
- package/dist/wgpu/WgpuAvatar.d.ts +26 -2
- package/dist/wgpu/WgpuAvatar.js +313 -46
- package/dist/wgpu/accessoryDefaults.d.ts +12 -0
- package/dist/wgpu/accessoryDefaults.js +19 -0
- package/dist/wgpu/blobShim.d.ts +2 -0
- package/dist/wgpu/blobShim.js +191 -0
- package/dist/wgpu/index.d.ts +1 -0
- package/dist/wgpu/index.js +4 -1
- package/dist/wgpu/loadGLTFFromUri.d.ts +2 -0
- package/dist/wgpu/loadGLTFFromUri.js +75 -0
- package/dist/wgpu/morphTables.js +21 -10
- package/dist/wgpu/motionState.d.ts +20 -0
- package/dist/wgpu/motionState.js +31 -0
- package/dist/wgpu/patchThreeForRN.d.ts +28 -0
- package/dist/wgpu/patchThreeForRN.js +292 -0
- package/dist/wgpu/scenePlacement.d.ts +5 -0
- package/dist/wgpu/scenePlacement.js +50 -0
- package/dist/wgpu/useAuthedModelUri.js +22 -11
- package/dist/wgpu/useNativeGLTF.d.ts +7 -0
- package/dist/wgpu/useNativeGLTF.js +36 -0
- package/package.json +102 -32
package/dist/appearance/apply.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.invalidateMorphCache = invalidateMorphCache;
|
|
4
|
+
exports.applyAppearanceToObject3D = applyAppearanceToObject3D;
|
|
4
5
|
const matchers_1 = require("./matchers");
|
|
5
6
|
const schema_1 = require("./schema");
|
|
6
7
|
function asMaterialArray(material) {
|
|
@@ -60,7 +61,6 @@ function getMorphCache(object3d) {
|
|
|
60
61
|
function invalidateMorphCache(object3d) {
|
|
61
62
|
morphCacheByScene.delete(object3d);
|
|
62
63
|
}
|
|
63
|
-
exports.invalidateMorphCache = invalidateMorphCache;
|
|
64
64
|
// ---------------------------------------------------------------------------
|
|
65
65
|
// Main apply function
|
|
66
66
|
// ---------------------------------------------------------------------------
|
|
@@ -121,4 +121,3 @@ function applyAppearanceToObject3D(object3d, appearance) {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
-
exports.applyAppearanceToObject3D = applyAppearanceToObject3D;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pickTargetForMaterialName =
|
|
3
|
+
exports.pickTargetForMaterialName = pickTargetForMaterialName;
|
|
4
4
|
function tokenizeMaterialName(name) {
|
|
5
5
|
return name
|
|
6
6
|
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
@@ -33,4 +33,3 @@ function pickTargetForMaterialName(name) {
|
|
|
33
33
|
}
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
|
-
exports.pickTargetForMaterialName = pickTargetForMaterialName;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeAppearance =
|
|
3
|
+
exports.normalizeAppearance = normalizeAppearance;
|
|
4
4
|
const HEX_COLOR_PATTERN = /^#(?:[\dA-Fa-f]{3}|[\dA-Fa-f]{6})$/;
|
|
5
5
|
function normalizeHexColor(value) {
|
|
6
6
|
if (!HEX_COLOR_PATTERN.test(value)) {
|
|
@@ -22,4 +22,3 @@ function normalizeAppearance(input) {
|
|
|
22
22
|
faceShapeWeights: input.faceShapeWeights,
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
exports.normalizeAppearance = normalizeAppearance;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable avatar contract entrypoint for SiteBay clients.
|
|
3
|
+
*
|
|
4
|
+
* Runtime packages should import avatar control, viseme, and backend surface
|
|
5
|
+
* types from `talking-head-studio/contract` instead of reaching into internal
|
|
6
|
+
* `src/core/avatar/*` paths.
|
|
7
|
+
*/
|
|
8
|
+
export type { AgentVisemePayload, ArkitBlendShape, EngineVisemeMappingArtifact, OculusViseme, OculusVisemeWeights, RhubarbViseme, VisemeCue, } from './core/avatar/visemes';
|
|
9
|
+
export { ARKIT_TO_OCULUS, ENGINE_VISEME_MAPPING, OCULUS_MORPH_TARGET_NAMES, OCULUS_VISEMES, RHUBARB_TO_OCULUS, RHUBARB_VISEMES, getArkitWeightsForViseme, oculusWeightsForRhubarbCue, remapArkitToOculus, } from './core/avatar/visemes';
|
|
10
|
+
export type { EyeGaze, ExpressionState, FaceControl, HeadPose, Viseme, } from './core/avatar/faceControls';
|
|
11
|
+
export { applyVisemeToExpression, createNeutralExpression, visemeToExpression, } from './core/avatar/faceControls';
|
|
12
|
+
export type { AvatarBackend, AvatarRenderTarget, CalibrationProfile, } from './core/avatar/backend';
|
|
13
|
+
export { MOTION_DEFS, MOTION_KEYS, isMotionKey, TALKINGHEAD_GESTURES, TALKINGHEAD_POSES, } from './core/avatar/motion';
|
|
14
|
+
export type { MotionKey, MotionDef, MotionDefWithBones, MotionBoneKey, MotionOscillator, TalkingHeadGesture, TalkingHeadPose, } from './core/avatar/motion';
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Stable avatar contract entrypoint for SiteBay clients.
|
|
4
|
+
*
|
|
5
|
+
* Runtime packages should import avatar control, viseme, and backend surface
|
|
6
|
+
* types from `talking-head-studio/contract` instead of reaching into internal
|
|
7
|
+
* `src/core/avatar/*` paths.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.TALKINGHEAD_POSES = exports.TALKINGHEAD_GESTURES = exports.isMotionKey = exports.MOTION_KEYS = exports.MOTION_DEFS = exports.visemeToExpression = exports.createNeutralExpression = exports.applyVisemeToExpression = exports.remapArkitToOculus = exports.oculusWeightsForRhubarbCue = exports.getArkitWeightsForViseme = exports.RHUBARB_VISEMES = exports.RHUBARB_TO_OCULUS = exports.OCULUS_VISEMES = exports.OCULUS_MORPH_TARGET_NAMES = exports.ENGINE_VISEME_MAPPING = exports.ARKIT_TO_OCULUS = void 0;
|
|
11
|
+
var visemes_1 = require("./core/avatar/visemes");
|
|
12
|
+
Object.defineProperty(exports, "ARKIT_TO_OCULUS", { enumerable: true, get: function () { return visemes_1.ARKIT_TO_OCULUS; } });
|
|
13
|
+
Object.defineProperty(exports, "ENGINE_VISEME_MAPPING", { enumerable: true, get: function () { return visemes_1.ENGINE_VISEME_MAPPING; } });
|
|
14
|
+
Object.defineProperty(exports, "OCULUS_MORPH_TARGET_NAMES", { enumerable: true, get: function () { return visemes_1.OCULUS_MORPH_TARGET_NAMES; } });
|
|
15
|
+
Object.defineProperty(exports, "OCULUS_VISEMES", { enumerable: true, get: function () { return visemes_1.OCULUS_VISEMES; } });
|
|
16
|
+
Object.defineProperty(exports, "RHUBARB_TO_OCULUS", { enumerable: true, get: function () { return visemes_1.RHUBARB_TO_OCULUS; } });
|
|
17
|
+
Object.defineProperty(exports, "RHUBARB_VISEMES", { enumerable: true, get: function () { return visemes_1.RHUBARB_VISEMES; } });
|
|
18
|
+
Object.defineProperty(exports, "getArkitWeightsForViseme", { enumerable: true, get: function () { return visemes_1.getArkitWeightsForViseme; } });
|
|
19
|
+
Object.defineProperty(exports, "oculusWeightsForRhubarbCue", { enumerable: true, get: function () { return visemes_1.oculusWeightsForRhubarbCue; } });
|
|
20
|
+
Object.defineProperty(exports, "remapArkitToOculus", { enumerable: true, get: function () { return visemes_1.remapArkitToOculus; } });
|
|
21
|
+
var faceControls_1 = require("./core/avatar/faceControls");
|
|
22
|
+
Object.defineProperty(exports, "applyVisemeToExpression", { enumerable: true, get: function () { return faceControls_1.applyVisemeToExpression; } });
|
|
23
|
+
Object.defineProperty(exports, "createNeutralExpression", { enumerable: true, get: function () { return faceControls_1.createNeutralExpression; } });
|
|
24
|
+
Object.defineProperty(exports, "visemeToExpression", { enumerable: true, get: function () { return faceControls_1.visemeToExpression; } });
|
|
25
|
+
var motion_1 = require("./core/avatar/motion");
|
|
26
|
+
Object.defineProperty(exports, "MOTION_DEFS", { enumerable: true, get: function () { return motion_1.MOTION_DEFS; } });
|
|
27
|
+
Object.defineProperty(exports, "MOTION_KEYS", { enumerable: true, get: function () { return motion_1.MOTION_KEYS; } });
|
|
28
|
+
Object.defineProperty(exports, "isMotionKey", { enumerable: true, get: function () { return motion_1.isMotionKey; } });
|
|
29
|
+
Object.defineProperty(exports, "TALKINGHEAD_GESTURES", { enumerable: true, get: function () { return motion_1.TALKINGHEAD_GESTURES; } });
|
|
30
|
+
Object.defineProperty(exports, "TALKINGHEAD_POSES", { enumerable: true, get: function () { return motion_1.TALKINGHEAD_POSES; } });
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type AvatarSchemaReport, type VisemeTier } from './schema';
|
|
2
|
+
import { type MotionBoneKey } from './motion';
|
|
3
|
+
export interface MotionCoverage {
|
|
4
|
+
/** Motion-engine bone keys this rig can drive (head, spine, arms, …). */
|
|
5
|
+
matched: MotionBoneKey[];
|
|
6
|
+
/** Motion-engine bone keys with no matching bone. */
|
|
7
|
+
missing: MotionBoneKey[];
|
|
8
|
+
/** Enough of the body is rigged for the procedural motion engine to read as
|
|
9
|
+
* "moving" (spine + head + at least one arm). */
|
|
10
|
+
ready: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface AvatarCapabilities {
|
|
13
|
+
/** Full schema report (visemes, skeleton, mesh) from walkAvatarSchema. */
|
|
14
|
+
schema: AvatarSchemaReport;
|
|
15
|
+
/** Best lip-sync strategy: oculus / arkit / minimal / none. */
|
|
16
|
+
lipSyncTier: VisemeTier;
|
|
17
|
+
/** Can this avatar lip-sync from a viseme schedule (morph-driven)? */
|
|
18
|
+
canLipSync: boolean;
|
|
19
|
+
/** Can the talking-head motion engine give this avatar body motion/gestures? */
|
|
20
|
+
canMove: boolean;
|
|
21
|
+
/** Procedural-motion bone coverage detail. */
|
|
22
|
+
motion: MotionCoverage;
|
|
23
|
+
/** Baked animation clip count (informational; the motion engine is procedural). */
|
|
24
|
+
animations: number;
|
|
25
|
+
}
|
|
26
|
+
type GltfJson = {
|
|
27
|
+
nodes?: {
|
|
28
|
+
name?: string;
|
|
29
|
+
}[];
|
|
30
|
+
meshes?: {
|
|
31
|
+
primitives?: {
|
|
32
|
+
indices?: number;
|
|
33
|
+
attributes?: Record<string, number>;
|
|
34
|
+
targets?: unknown[];
|
|
35
|
+
}[];
|
|
36
|
+
extras?: {
|
|
37
|
+
targetNames?: string[];
|
|
38
|
+
};
|
|
39
|
+
}[];
|
|
40
|
+
skins?: {
|
|
41
|
+
joints?: number[];
|
|
42
|
+
}[];
|
|
43
|
+
accessors?: {
|
|
44
|
+
count?: number;
|
|
45
|
+
}[];
|
|
46
|
+
animations?: unknown[];
|
|
47
|
+
};
|
|
48
|
+
/** Decode a .glb (binary) or .gltf (JSON) ArrayBuffer/Uint8Array into its glTF JSON. */
|
|
49
|
+
export declare function parseGlbJson(data: ArrayBuffer | Uint8Array): GltfJson;
|
|
50
|
+
/** Capability report from glTF JSON (the unit-testable core). */
|
|
51
|
+
export declare function inspectAvatarGltf(json: GltfJson): AvatarCapabilities;
|
|
52
|
+
/** Capability report from raw GLB/glTF bytes (browser: fetch(url).arrayBuffer()). */
|
|
53
|
+
export declare function inspectAvatarCapabilities(data: ArrayBuffer | Uint8Array): AvatarCapabilities;
|
|
54
|
+
/**
|
|
55
|
+
* Capability report from a GLB's raw JSON-chunk bytes — for the streaming
|
|
56
|
+
* import path that reads only the header + JSON chunk off disk (mirrors
|
|
57
|
+
* sketchfab's inspectGlbJsonChunk, but for full avatar capabilities).
|
|
58
|
+
*/
|
|
59
|
+
export declare function inspectAvatarJsonChunk(jsonBytes: Uint8Array): AvatarCapabilities;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/avatar/glbInspect.ts
|
|
3
|
+
// Pre-flight avatar capability check from raw GLB/glTF bytes — no Three.js, no
|
|
4
|
+
// WebGL load required. Reuses walkAvatarSchema() (the single source of truth for
|
|
5
|
+
// viseme tier + humanoid rig) by adapting the glTF JSON into the minimal scene
|
|
6
|
+
// that walker expects, then layers on motion-engine bone coverage.
|
|
7
|
+
//
|
|
8
|
+
// The avatar creator runs this when a user picks/uploads/URLs an avatar, so it
|
|
9
|
+
// can soft-warn ("this model can't lip-sync / won't gesture") before committing.
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.parseGlbJson = parseGlbJson;
|
|
12
|
+
exports.inspectAvatarGltf = inspectAvatarGltf;
|
|
13
|
+
exports.inspectAvatarCapabilities = inspectAvatarCapabilities;
|
|
14
|
+
exports.inspectAvatarJsonChunk = inspectAvatarJsonChunk;
|
|
15
|
+
const schema_1 = require("./schema");
|
|
16
|
+
const motion_1 = require("./motion");
|
|
17
|
+
/** Decode a .glb (binary) or .gltf (JSON) ArrayBuffer/Uint8Array into its glTF JSON. */
|
|
18
|
+
function parseGlbJson(data) {
|
|
19
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
20
|
+
// GLB: 'glTF' magic (0x46546C67), then 12-byte header + JSON chunk.
|
|
21
|
+
if (bytes.length >= 12 && bytes[0] === 0x67 && bytes[1] === 0x6c && bytes[2] === 0x54 && bytes[3] === 0x46) {
|
|
22
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
23
|
+
const jsonLen = view.getUint32(12, true); // first chunk length (JSON)
|
|
24
|
+
const jsonBytes = bytes.subarray(20, 20 + jsonLen);
|
|
25
|
+
return JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
26
|
+
}
|
|
27
|
+
// Otherwise assume .gltf JSON text.
|
|
28
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
29
|
+
}
|
|
30
|
+
const norm = (s) => s.toLowerCase().replace(/[_\s\-.]/g, '');
|
|
31
|
+
/** Capability report from glTF JSON (the unit-testable core). */
|
|
32
|
+
function inspectAvatarGltf(json) {
|
|
33
|
+
const nodes = json.nodes ?? [];
|
|
34
|
+
const meshes = json.meshes ?? [];
|
|
35
|
+
const skins = json.skins ?? [];
|
|
36
|
+
const accessors = json.accessors ?? [];
|
|
37
|
+
// Adapt the glTF JSON into the minimal { traverse } scene walkAvatarSchema wants.
|
|
38
|
+
const objects = [];
|
|
39
|
+
for (const skin of skins) {
|
|
40
|
+
const bones = (skin.joints ?? []).map((j) => ({ name: nodes[j]?.name }));
|
|
41
|
+
objects.push({ isSkinnedMesh: true, skeleton: { bones } });
|
|
42
|
+
for (const j of skin.joints ?? []) {
|
|
43
|
+
const nm = nodes[j]?.name;
|
|
44
|
+
if (nm)
|
|
45
|
+
objects.push({ isBone: true, name: nm });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const mesh of meshes) {
|
|
49
|
+
const targetNames = mesh.extras?.targetNames ?? [];
|
|
50
|
+
const dict = targetNames.length ? Object.fromEntries(targetNames.map((n, i) => [String(n), i])) : undefined;
|
|
51
|
+
let idxCount = 0;
|
|
52
|
+
let posCount = 0;
|
|
53
|
+
let hasIdx = false;
|
|
54
|
+
for (const prim of mesh.primitives ?? []) {
|
|
55
|
+
if (prim.indices != null && accessors[prim.indices]) {
|
|
56
|
+
idxCount += accessors[prim.indices].count ?? 0;
|
|
57
|
+
hasIdx = true;
|
|
58
|
+
}
|
|
59
|
+
const posIdx = prim.attributes?.POSITION;
|
|
60
|
+
if (posIdx != null && accessors[posIdx])
|
|
61
|
+
posCount += accessors[posIdx].count ?? 0;
|
|
62
|
+
}
|
|
63
|
+
objects.push({
|
|
64
|
+
isMesh: true,
|
|
65
|
+
...(dict ? { morphTargetDictionary: dict } : {}),
|
|
66
|
+
geometry: hasIdx ? { index: { count: idxCount } } : { attributes: { position: { count: posCount } } },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const schema = (0, schema_1.walkAvatarSchema)({ traverse: (cb) => objects.forEach((o) => cb(o)) });
|
|
70
|
+
// Motion-engine coverage: which logical bones the procedural engine can drive.
|
|
71
|
+
const bonesNorm = schema.skeleton.bones.map(norm);
|
|
72
|
+
const matched = [];
|
|
73
|
+
const missing = [];
|
|
74
|
+
for (const [key, kws] of Object.entries(motion_1.MOTION_BONE_SEARCH)) {
|
|
75
|
+
const hit = kws.some((kw) => bonesNorm.some((b) => b.includes(norm(kw))));
|
|
76
|
+
(hit ? matched : missing).push(key);
|
|
77
|
+
}
|
|
78
|
+
const has = (k) => matched.includes(k);
|
|
79
|
+
const ready = schema.skeleton.hasRig && has('spine') && has('head') && (has('leftArm') || has('rightArm'));
|
|
80
|
+
return {
|
|
81
|
+
schema,
|
|
82
|
+
lipSyncTier: schema.morphs.visemeTier,
|
|
83
|
+
canLipSync: schema.morphs.visemeTier !== 'none',
|
|
84
|
+
canMove: ready,
|
|
85
|
+
motion: { matched, missing, ready },
|
|
86
|
+
animations: (json.animations ?? []).length,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Capability report from raw GLB/glTF bytes (browser: fetch(url).arrayBuffer()). */
|
|
90
|
+
function inspectAvatarCapabilities(data) {
|
|
91
|
+
return inspectAvatarGltf(parseGlbJson(data));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Capability report from a GLB's raw JSON-chunk bytes — for the streaming
|
|
95
|
+
* import path that reads only the header + JSON chunk off disk (mirrors
|
|
96
|
+
* sketchfab's inspectGlbJsonChunk, but for full avatar capabilities).
|
|
97
|
+
*/
|
|
98
|
+
function inspectAvatarJsonChunk(jsonBytes) {
|
|
99
|
+
return inspectAvatarGltf(JSON.parse(new TextDecoder().decode(jsonBytes)));
|
|
100
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { FaceControl } from './faceControls';
|
|
2
|
+
/**
|
|
3
|
+
* A render target is intentionally abstract here; concrete backends can
|
|
4
|
+
* accept whatever they need (WebGL/WebGPU context, React Native surface,
|
|
5
|
+
* WebView, etc.) via their own config layer.
|
|
6
|
+
*/
|
|
7
|
+
export type AvatarRenderTarget = unknown;
|
|
8
|
+
/**
|
|
9
|
+
* Canonical interface that all avatar renderers implement.
|
|
10
|
+
*
|
|
11
|
+
* The idea is:
|
|
12
|
+
* - talking-head-studio produces a stream of FaceControl values
|
|
13
|
+
* (pose + ExpressionState) from TTS visemes, emotions, and motions.
|
|
14
|
+
* - Each AvatarBackend implementation adapts those controls to its
|
|
15
|
+
* own rendering mechanism (morph targets, FLAME coefficients,
|
|
16
|
+
* Gaussian splats, 2D sprites, etc.).
|
|
17
|
+
*/
|
|
18
|
+
export interface AvatarBackend {
|
|
19
|
+
/** Optional one-time initialization step (e.g. load meshes, shaders, splats). */
|
|
20
|
+
initialize?(): Promise<void> | void;
|
|
21
|
+
/** Configure the backend with a render target (canvas, surface, WebView, etc.). */
|
|
22
|
+
attach(target: AvatarRenderTarget): Promise<void> | void;
|
|
23
|
+
/**
|
|
24
|
+
* Update the current pose + expression state.
|
|
25
|
+
* This should be a cheap, per-frame call.
|
|
26
|
+
*/
|
|
27
|
+
setControl(control: FaceControl): void;
|
|
28
|
+
/** Render the current control state into the attached target. */
|
|
29
|
+
renderFrame(): void;
|
|
30
|
+
/** Clean up graphics resources when the avatar is no longer needed. */
|
|
31
|
+
dispose?(): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Per-avatar calibration profile, allowing each asset to respond to the
|
|
35
|
+
* canonical control space in its own way.
|
|
36
|
+
*
|
|
37
|
+
* This is intentionally lightweight and focused on scalar ranges and
|
|
38
|
+
* asymmetries instead of model-specific knobs. Backends can use these
|
|
39
|
+
* values to remap 0..1 sliders into whatever ranges their rigs expect.
|
|
40
|
+
*/
|
|
41
|
+
export type CalibrationProfile = {
|
|
42
|
+
/** Neutral pose + expression for this avatar. */
|
|
43
|
+
neutral: FaceControl;
|
|
44
|
+
/** Per-channel scalar ranges (0..1 logical → model space). */
|
|
45
|
+
ranges?: {
|
|
46
|
+
jawOpen?: {
|
|
47
|
+
min: number;
|
|
48
|
+
max: number;
|
|
49
|
+
};
|
|
50
|
+
mouthSmile?: {
|
|
51
|
+
min: number;
|
|
52
|
+
max: number;
|
|
53
|
+
};
|
|
54
|
+
mouthFunnel?: {
|
|
55
|
+
min: number;
|
|
56
|
+
max: number;
|
|
57
|
+
};
|
|
58
|
+
mouthPucker?: {
|
|
59
|
+
min: number;
|
|
60
|
+
max: number;
|
|
61
|
+
};
|
|
62
|
+
mouthWide?: {
|
|
63
|
+
min: number;
|
|
64
|
+
max: number;
|
|
65
|
+
};
|
|
66
|
+
upperLipRaise?: {
|
|
67
|
+
min: number;
|
|
68
|
+
max: number;
|
|
69
|
+
};
|
|
70
|
+
lowerLipDepress?: {
|
|
71
|
+
min: number;
|
|
72
|
+
max: number;
|
|
73
|
+
};
|
|
74
|
+
cheekRaise?: {
|
|
75
|
+
min: number;
|
|
76
|
+
max: number;
|
|
77
|
+
};
|
|
78
|
+
blinkLeft?: {
|
|
79
|
+
min: number;
|
|
80
|
+
max: number;
|
|
81
|
+
};
|
|
82
|
+
blinkRight?: {
|
|
83
|
+
min: number;
|
|
84
|
+
max: number;
|
|
85
|
+
};
|
|
86
|
+
browInnerUp?: {
|
|
87
|
+
min: number;
|
|
88
|
+
max: number;
|
|
89
|
+
};
|
|
90
|
+
browDownLeft?: {
|
|
91
|
+
min: number;
|
|
92
|
+
max: number;
|
|
93
|
+
};
|
|
94
|
+
browDownRight?: {
|
|
95
|
+
min: number;
|
|
96
|
+
max: number;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/** Simple asymmetry controls (e.g. left/right smile strength). */
|
|
100
|
+
asymmetry?: {
|
|
101
|
+
smileLeftBias?: number;
|
|
102
|
+
blinkLeftBias?: number;
|
|
103
|
+
};
|
|
104
|
+
/** Eye gaze range limits (default: full -1..1 on both axes). */
|
|
105
|
+
gazeLimits?: {
|
|
106
|
+
x?: {
|
|
107
|
+
min: number;
|
|
108
|
+
max: number;
|
|
109
|
+
};
|
|
110
|
+
y?: {
|
|
111
|
+
min: number;
|
|
112
|
+
max: number;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
/** Optional head pose limits to keep motion comfortable. */
|
|
116
|
+
poseLimits?: {
|
|
117
|
+
yaw?: {
|
|
118
|
+
min: number;
|
|
119
|
+
max: number;
|
|
120
|
+
};
|
|
121
|
+
pitch?: {
|
|
122
|
+
min: number;
|
|
123
|
+
max: number;
|
|
124
|
+
};
|
|
125
|
+
roll?: {
|
|
126
|
+
min: number;
|
|
127
|
+
max: number;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type * as THREE from 'three';
|
|
2
|
+
import type { AvatarBackend, AvatarRenderTarget, CalibrationProfile } from '../backend';
|
|
3
|
+
import type { FaceControl } from '../faceControls';
|
|
4
|
+
import type { AvatarSchemaReport } from '../schema';
|
|
5
|
+
export interface GaussianBackendConfig {
|
|
6
|
+
/** Three.js scene root (gltf.scene). */
|
|
7
|
+
scene: THREE.Object3D;
|
|
8
|
+
/** Schema report from walkAvatarSchema — used to log coverage and skip morphs. */
|
|
9
|
+
schemaReport: AvatarSchemaReport;
|
|
10
|
+
/** Optional calibration profile for gaze limits and expression scaling. */
|
|
11
|
+
calibration?: CalibrationProfile;
|
|
12
|
+
/**
|
|
13
|
+
* 'fast' — only jaw bone driven (best for real-time on low-end devices).
|
|
14
|
+
* 'full' — all FLAME bone coefficients applied (default).
|
|
15
|
+
*/
|
|
16
|
+
patchQuality?: 'fast' | 'full';
|
|
17
|
+
}
|
|
18
|
+
export declare class GaussianBackend implements AvatarBackend {
|
|
19
|
+
private scene;
|
|
20
|
+
private schemaReport;
|
|
21
|
+
private calibration;
|
|
22
|
+
private patchQuality;
|
|
23
|
+
/** Resolved skeleton bone map: canonical name → Three.js Bone */
|
|
24
|
+
private boneMap;
|
|
25
|
+
/** Pre-computed deformation patches, one per Oculus viseme. */
|
|
26
|
+
private patches;
|
|
27
|
+
/** Snapshot of bone rotations at attach() — restored on dispose(). */
|
|
28
|
+
private basePose;
|
|
29
|
+
/** Last control applied — used in renderFrame() to avoid redundant writes. */
|
|
30
|
+
private lastControl;
|
|
31
|
+
constructor(config: GaussianBackendConfig);
|
|
32
|
+
initialize(): void;
|
|
33
|
+
attach(_target: AvatarRenderTarget): void;
|
|
34
|
+
setControl(control: FaceControl): void;
|
|
35
|
+
renderFrame(): void;
|
|
36
|
+
dispose(): void;
|
|
37
|
+
private _buildBoneMap;
|
|
38
|
+
private _captureBasePose;
|
|
39
|
+
private _restoreBasePose;
|
|
40
|
+
private _buildPatches;
|
|
41
|
+
private _applyControl;
|
|
42
|
+
private _applyGaze;
|
|
43
|
+
/** Canonical bones resolved from the skeleton. Useful for debugging rig coverage. */
|
|
44
|
+
get resolvedBones(): string[];
|
|
45
|
+
/** Number of viseme patches with at least one resolved bone. */
|
|
46
|
+
get patchCoverage(): number;
|
|
47
|
+
/** The schema report this backend was initialized with. */
|
|
48
|
+
get schema(): AvatarSchemaReport;
|
|
49
|
+
}
|