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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { OculusViseme } from './visemes';
|
|
2
|
+
export type VisemeTier = 'oculus' | 'arkit' | 'minimal' | 'none';
|
|
3
|
+
/** @deprecated Use VisemeTier. */
|
|
4
|
+
export type VísemeTier = VisemeTier;
|
|
5
|
+
export interface MorphCoverage {
|
|
6
|
+
/** All morph target names found across all meshes in the scene. */
|
|
7
|
+
all: string[];
|
|
8
|
+
/** Oculus viseme morph targets present (subset of OCULUS_VISEMES). */
|
|
9
|
+
oculusVisemes: OculusViseme[];
|
|
10
|
+
/** Whether the full Oculus set (all 15) is present. */
|
|
11
|
+
hasFullOculusSet: boolean;
|
|
12
|
+
/** ARKit blend shape names found (jawOpen, mouthFunnel, etc.). */
|
|
13
|
+
arkitShapes: string[];
|
|
14
|
+
/** Whether a meaningful ARKit set is present (≥10 mouth shapes). */
|
|
15
|
+
hasArkitSet: boolean;
|
|
16
|
+
/** Best lip-sync strategy available for this model. */
|
|
17
|
+
visemeTier: VisemeTier;
|
|
18
|
+
}
|
|
19
|
+
export interface SkeletonInfo {
|
|
20
|
+
/** Whether a skeleton (SkinnedMesh) was found. */
|
|
21
|
+
hasRig: boolean;
|
|
22
|
+
/** All bone names found in the scene. */
|
|
23
|
+
bones: string[];
|
|
24
|
+
/** Whether the standard humanoid bones are present (hips → spine → head). */
|
|
25
|
+
isHumanoid: boolean;
|
|
26
|
+
/** Missing standard humanoid bones (if any). */
|
|
27
|
+
missingHumanoidBones: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface MeshStats {
|
|
30
|
+
/** Total triangle count across all meshes. */
|
|
31
|
+
triangles: number;
|
|
32
|
+
/** Number of distinct mesh primitives. */
|
|
33
|
+
meshCount: number;
|
|
34
|
+
/** Estimated LOD tier based on triangle count. */
|
|
35
|
+
lodTier: 'high' | 'medium' | 'low' | 'very-low';
|
|
36
|
+
}
|
|
37
|
+
export interface AvatarSchemaReport {
|
|
38
|
+
morphs: MorphCoverage;
|
|
39
|
+
skeleton: SkeletonInfo;
|
|
40
|
+
mesh: MeshStats;
|
|
41
|
+
/** Whether this model is ready for full-quality talking-head rendering. */
|
|
42
|
+
isReadyForLipSync: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Minimal scene shape needed by the GLB schema walker. */
|
|
45
|
+
interface AvatarSchemaScene {
|
|
46
|
+
traverse: (cb: (obj: AvatarSchemaObject) => void) => void;
|
|
47
|
+
}
|
|
48
|
+
/** Minimal traversed object shape used to avoid depending on runtime Three.js classes. */
|
|
49
|
+
interface AvatarSchemaObject {
|
|
50
|
+
isSkinnedMesh?: boolean;
|
|
51
|
+
isBone?: boolean;
|
|
52
|
+
isMesh?: boolean;
|
|
53
|
+
name?: string;
|
|
54
|
+
skeleton?: {
|
|
55
|
+
bones?: Array<{
|
|
56
|
+
name?: string;
|
|
57
|
+
}>;
|
|
58
|
+
};
|
|
59
|
+
morphTargetDictionary?: Record<string, unknown>;
|
|
60
|
+
geometry?: {
|
|
61
|
+
index?: {
|
|
62
|
+
count: number;
|
|
63
|
+
};
|
|
64
|
+
attributes?: {
|
|
65
|
+
position?: {
|
|
66
|
+
count: number;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Scan a loaded Three.js Object3D scene and return a structured compatibility
|
|
73
|
+
* report for lip-sync, skeleton, and mesh quality.
|
|
74
|
+
*
|
|
75
|
+
* @param scene - The root Object3D from a GLTFLoader result (gltf.scene)
|
|
76
|
+
*/
|
|
77
|
+
export declare function walkAvatarSchema(scene: AvatarSchemaScene): AvatarSchemaReport;
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/avatar/schema.ts
|
|
3
|
+
// GLB schema walker — scan any loaded Three.js scene and report morph target
|
|
4
|
+
// coverage, skeleton bones, viseme tier, and LOD stats.
|
|
5
|
+
//
|
|
6
|
+
// Use this to gate rendering decisions ("does this model need the ARKit remap?"),
|
|
7
|
+
// power the avatar creator's "compatibility report", and feed the Unity/Unreal
|
|
8
|
+
// SDK manifests.
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.walkAvatarSchema = walkAvatarSchema;
|
|
11
|
+
const visemes_1 = require("./visemes");
|
|
12
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
13
|
+
// Minimum humanoid bones expected on any rigged avatar.
|
|
14
|
+
const REQUIRED_HUMANOID_BONES = [
|
|
15
|
+
'Hips', 'Spine', 'Head', 'Neck',
|
|
16
|
+
'LeftUpperArm', 'RightUpperArm',
|
|
17
|
+
'LeftUpperLeg', 'RightUpperLeg',
|
|
18
|
+
];
|
|
19
|
+
// ARKit mouth shapes that indicate a real ARKit rig (not just jawOpen).
|
|
20
|
+
const ARKIT_MOUTH_SHAPES = [
|
|
21
|
+
'jawOpen', 'mouthFunnel', 'mouthPucker', 'mouthSmileLeft', 'mouthSmileRight',
|
|
22
|
+
'mouthFrownLeft', 'mouthFrownRight', 'mouthStretchLeft', 'mouthStretchRight',
|
|
23
|
+
'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',
|
|
24
|
+
'mouthLowerDownLeft', 'mouthLowerDownRight', 'mouthUpperUpLeft', 'mouthUpperUpRight',
|
|
25
|
+
'mouthClose', 'mouthDimpleLeft', 'mouthDimpleRight', 'mouthPressLeft', 'mouthPressRight',
|
|
26
|
+
];
|
|
27
|
+
// ─── Walker ──────────────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Scan a loaded Three.js Object3D scene and return a structured compatibility
|
|
30
|
+
* report for lip-sync, skeleton, and mesh quality.
|
|
31
|
+
*
|
|
32
|
+
* @param scene - The root Object3D from a GLTFLoader result (gltf.scene)
|
|
33
|
+
*/
|
|
34
|
+
function walkAvatarSchema(scene) {
|
|
35
|
+
const allMorphNames = new Set();
|
|
36
|
+
const boneNames = new Set();
|
|
37
|
+
let hasRig = false;
|
|
38
|
+
let totalTris = 0;
|
|
39
|
+
let meshCount = 0;
|
|
40
|
+
scene.traverse((obj) => {
|
|
41
|
+
// Collect bone names from SkinnedMesh skeletons
|
|
42
|
+
if (obj.isSkinnedMesh) {
|
|
43
|
+
hasRig = true;
|
|
44
|
+
if (obj.skeleton?.bones) {
|
|
45
|
+
for (const bone of obj.skeleton.bones) {
|
|
46
|
+
if (bone.name)
|
|
47
|
+
boneNames.add(bone.name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Also collect from Bone objects directly
|
|
52
|
+
if (obj.isBone && obj.name) {
|
|
53
|
+
boneNames.add(obj.name);
|
|
54
|
+
}
|
|
55
|
+
// Collect morph targets and mesh stats
|
|
56
|
+
if (obj.isMesh && obj.morphTargetDictionary) {
|
|
57
|
+
for (const name of Object.keys(obj.morphTargetDictionary)) {
|
|
58
|
+
allMorphNames.add(name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (obj.isMesh && obj.geometry) {
|
|
62
|
+
meshCount++;
|
|
63
|
+
const geo = obj.geometry;
|
|
64
|
+
if (geo.index) {
|
|
65
|
+
totalTris += geo.index.count / 3;
|
|
66
|
+
}
|
|
67
|
+
else if (geo.attributes?.position) {
|
|
68
|
+
totalTris += geo.attributes.position.count / 3;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// ── Morph analysis ───────────────────────────────────────────────────────
|
|
73
|
+
const allMorphList = Array.from(allMorphNames);
|
|
74
|
+
const allLower = allMorphList.map(n => n.toLowerCase());
|
|
75
|
+
// Oculus visemes: look for viseme_PP, viseme_aa, etc.
|
|
76
|
+
const oculusFound = [];
|
|
77
|
+
for (const v of visemes_1.OCULUS_VISEMES) {
|
|
78
|
+
const target = `viseme_${v}`.toLowerCase();
|
|
79
|
+
if (allLower.some(n => n === target || n === v.toLowerCase())) {
|
|
80
|
+
oculusFound.push(v);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const hasFullOculusSet = oculusFound.length === visemes_1.OCULUS_VISEMES.length;
|
|
84
|
+
// ARKit shapes
|
|
85
|
+
const arkitFound = ARKIT_MOUTH_SHAPES.filter(shape => allLower.some(n => n === shape.toLowerCase()));
|
|
86
|
+
const hasArkitSet = arkitFound.length >= 10;
|
|
87
|
+
// Minimal: just jawOpen or mouthOpen
|
|
88
|
+
const hasMinimal = allLower.some(n => n === 'jawopen' || n === 'mouthopen' || n === 'viseme_aa');
|
|
89
|
+
let visemeTier;
|
|
90
|
+
if (hasFullOculusSet)
|
|
91
|
+
visemeTier = 'oculus';
|
|
92
|
+
else if (hasArkitSet)
|
|
93
|
+
visemeTier = 'arkit';
|
|
94
|
+
else if (hasMinimal)
|
|
95
|
+
visemeTier = 'minimal';
|
|
96
|
+
else
|
|
97
|
+
visemeTier = 'none';
|
|
98
|
+
// ── Skeleton analysis ────────────────────────────────────────────────────
|
|
99
|
+
const bonesLower = Array.from(boneNames).map(b => b.toLowerCase());
|
|
100
|
+
const missingHumanoidBones = REQUIRED_HUMANOID_BONES.filter(required => !bonesLower.some(b => b.includes(required.toLowerCase())));
|
|
101
|
+
const isHumanoid = hasRig && missingHumanoidBones.length === 0;
|
|
102
|
+
// ── Mesh stats ───────────────────────────────────────────────────────────
|
|
103
|
+
let lodTier;
|
|
104
|
+
if (totalTris > 70000)
|
|
105
|
+
lodTier = 'high';
|
|
106
|
+
else if (totalTris > 30000)
|
|
107
|
+
lodTier = 'medium';
|
|
108
|
+
else if (totalTris > 8000)
|
|
109
|
+
lodTier = 'low';
|
|
110
|
+
else
|
|
111
|
+
lodTier = 'very-low';
|
|
112
|
+
return {
|
|
113
|
+
morphs: {
|
|
114
|
+
all: allMorphList,
|
|
115
|
+
oculusVisemes: oculusFound,
|
|
116
|
+
hasFullOculusSet,
|
|
117
|
+
arkitShapes: arkitFound,
|
|
118
|
+
hasArkitSet,
|
|
119
|
+
visemeTier,
|
|
120
|
+
},
|
|
121
|
+
skeleton: {
|
|
122
|
+
hasRig,
|
|
123
|
+
bones: Array.from(boneNames),
|
|
124
|
+
isHumanoid,
|
|
125
|
+
missingHumanoidBones,
|
|
126
|
+
},
|
|
127
|
+
mesh: {
|
|
128
|
+
triangles: Math.round(totalTris),
|
|
129
|
+
meshCount,
|
|
130
|
+
lodTier,
|
|
131
|
+
},
|
|
132
|
+
isReadyForLipSync: visemeTier === 'oculus' || visemeTier === 'arkit',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* Standard viseme keys supported by the avatar (Oculus set).
|
|
3
3
|
*/
|
|
4
4
|
export type OculusViseme = 'sil' | 'PP' | 'FF' | 'TH' | 'DD' | 'kk' | 'CH' | 'SS' | 'nn' | 'RR' | 'aa' | 'ee' | 'ih' | 'oh' | 'ou';
|
|
5
|
+
export type RhubarbViseme = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'X';
|
|
5
6
|
/** Rhubarb mouth shape cue (Preston Blair set: A-H, X) */
|
|
6
7
|
export interface VisemeCue {
|
|
7
8
|
startMs: number;
|
|
8
9
|
endMs: number;
|
|
9
|
-
viseme:
|
|
10
|
+
viseme: RhubarbViseme;
|
|
10
11
|
weight?: number;
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
@@ -31,3 +32,48 @@ export interface AgentVisemePayload {
|
|
|
31
32
|
cues: VisemeCue[];
|
|
32
33
|
}
|
|
33
34
|
export declare const OCULUS_VISEMES: OculusViseme[];
|
|
35
|
+
export declare const RHUBARB_VISEMES: RhubarbViseme[];
|
|
36
|
+
export declare const OCULUS_MORPH_TARGET_NAMES: Record<OculusViseme, string>;
|
|
37
|
+
export type OculusVisemeWeights = Partial<Record<OculusViseme, number>>;
|
|
38
|
+
export declare const RHUBARB_TO_OCULUS: Record<RhubarbViseme, OculusVisemeWeights>;
|
|
39
|
+
export type ArkitBlendShape = 'jawOpen' | 'jawForward' | 'jawLeft' | 'jawRight' | 'mouthClose' | 'mouthFunnel' | 'mouthPucker' | 'mouthLeft' | 'mouthRight' | 'mouthSmileLeft' | 'mouthSmileRight' | 'mouthFrownLeft' | 'mouthFrownRight' | 'mouthDimpleLeft' | 'mouthDimpleRight' | 'mouthStretchLeft' | 'mouthStretchRight' | 'mouthRollLower' | 'mouthRollUpper' | 'mouthShrugLower' | 'mouthShrugUpper' | 'mouthPressLeft' | 'mouthPressRight' | 'mouthLowerDownLeft' | 'mouthLowerDownRight' | 'mouthUpperUpLeft' | 'mouthUpperUpRight' | 'tongueOut';
|
|
40
|
+
/**
|
|
41
|
+
* Each Oculus viseme expressed as a weighted combination of ARKit blend shapes.
|
|
42
|
+
* Weights are in 0–1 range. Apply by multiplying each ARKit shape's influence.
|
|
43
|
+
*
|
|
44
|
+
* Sources:
|
|
45
|
+
* - OVR LipSync viseme reference (phonetic articulation targets)
|
|
46
|
+
* - Empirical tuning against RPM and face-squeeze avatars
|
|
47
|
+
*/
|
|
48
|
+
export declare const ARKIT_TO_OCULUS: Record<OculusViseme, Partial<Record<ArkitBlendShape, number>>>;
|
|
49
|
+
export type EngineVisemeMappingArtifact = {
|
|
50
|
+
schemaVersion: 1;
|
|
51
|
+
rhubarbVisemes: RhubarbViseme[];
|
|
52
|
+
oculusVisemes: OculusViseme[];
|
|
53
|
+
oculusMorphTargetNames: Record<OculusViseme, string>;
|
|
54
|
+
rhubarbToOculus: Record<RhubarbViseme, OculusVisemeWeights>;
|
|
55
|
+
oculusToArkit: Record<OculusViseme, Partial<Record<ArkitBlendShape, number>>>;
|
|
56
|
+
};
|
|
57
|
+
export declare const ENGINE_VISEME_MAPPING: EngineVisemeMappingArtifact;
|
|
58
|
+
export declare function oculusWeightsForRhubarbCue(cue: Pick<VisemeCue, 'viseme' | 'weight'>): Record<OculusViseme, number>;
|
|
59
|
+
/**
|
|
60
|
+
* Remap a set of ARKit blend shape weights to Oculus viseme weights.
|
|
61
|
+
*
|
|
62
|
+
* Given current ARKit influences (e.g. from face tracking or morph target state),
|
|
63
|
+
* returns the equivalent Oculus viseme weights. Each viseme's output weight is the
|
|
64
|
+
* weighted average of its constituent ARKit shapes, clamped to 0–1.
|
|
65
|
+
*
|
|
66
|
+
* Use cases:
|
|
67
|
+
* - Runtime: drive Oculus viseme morphs from ARKit face tracking data
|
|
68
|
+
* - Bake-time: compute Oculus morph target deltas from ARKit shape keys in a GLB
|
|
69
|
+
*/
|
|
70
|
+
export declare function remapArkitToOculus(arkitWeights: Partial<Record<ArkitBlendShape, number>>): Record<OculusViseme, number>;
|
|
71
|
+
/**
|
|
72
|
+
* For a given Oculus viseme, return the ARKit blend shape weights that produce it.
|
|
73
|
+
* This is the "forward" direction — useful when baking Oculus morph targets into
|
|
74
|
+
* a GLB that only has ARKit shapes.
|
|
75
|
+
*
|
|
76
|
+
* Returns the raw recipe weights (not normalized). To bake into a morph target,
|
|
77
|
+
* set each ARKit shape to the returned weight and snapshot the mesh delta.
|
|
78
|
+
*/
|
|
79
|
+
export declare function getArkitWeightsForViseme(viseme: OculusViseme): Partial<Record<ArkitBlendShape, number>>;
|
|
@@ -1,6 +1,119 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OCULUS_VISEMES = void 0;
|
|
3
|
+
exports.ENGINE_VISEME_MAPPING = exports.ARKIT_TO_OCULUS = exports.RHUBARB_TO_OCULUS = exports.OCULUS_MORPH_TARGET_NAMES = exports.RHUBARB_VISEMES = exports.OCULUS_VISEMES = void 0;
|
|
4
|
+
exports.oculusWeightsForRhubarbCue = oculusWeightsForRhubarbCue;
|
|
5
|
+
exports.remapArkitToOculus = remapArkitToOculus;
|
|
6
|
+
exports.getArkitWeightsForViseme = getArkitWeightsForViseme;
|
|
4
7
|
exports.OCULUS_VISEMES = [
|
|
5
8
|
'sil', 'PP', 'FF', 'TH', 'DD', 'kk', 'CH', 'SS', 'nn', 'RR', 'aa', 'ee', 'ih', 'oh', 'ou',
|
|
6
9
|
];
|
|
10
|
+
exports.RHUBARB_VISEMES = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'X'];
|
|
11
|
+
exports.OCULUS_MORPH_TARGET_NAMES = {
|
|
12
|
+
sil: 'viseme_sil',
|
|
13
|
+
PP: 'viseme_PP',
|
|
14
|
+
FF: 'viseme_FF',
|
|
15
|
+
TH: 'viseme_TH',
|
|
16
|
+
DD: 'viseme_DD',
|
|
17
|
+
kk: 'viseme_kk',
|
|
18
|
+
CH: 'viseme_CH',
|
|
19
|
+
SS: 'viseme_SS',
|
|
20
|
+
nn: 'viseme_nn',
|
|
21
|
+
RR: 'viseme_RR',
|
|
22
|
+
aa: 'viseme_aa',
|
|
23
|
+
ee: 'viseme_ee',
|
|
24
|
+
ih: 'viseme_ih',
|
|
25
|
+
oh: 'viseme_oh',
|
|
26
|
+
ou: 'viseme_ou',
|
|
27
|
+
};
|
|
28
|
+
exports.RHUBARB_TO_OCULUS = {
|
|
29
|
+
A: { aa: 0.72 },
|
|
30
|
+
B: { PP: 0.85 },
|
|
31
|
+
C: { ih: 0.68 },
|
|
32
|
+
D: { FF: 0.78 },
|
|
33
|
+
E: { ee: 0.72 },
|
|
34
|
+
F: { oh: 0.62 },
|
|
35
|
+
G: { ou: 0.58 },
|
|
36
|
+
H: { nn: 0.72 },
|
|
37
|
+
X: { sil: 1 },
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Each Oculus viseme expressed as a weighted combination of ARKit blend shapes.
|
|
41
|
+
* Weights are in 0–1 range. Apply by multiplying each ARKit shape's influence.
|
|
42
|
+
*
|
|
43
|
+
* Sources:
|
|
44
|
+
* - OVR LipSync viseme reference (phonetic articulation targets)
|
|
45
|
+
* - Empirical tuning against RPM and face-squeeze avatars
|
|
46
|
+
*/
|
|
47
|
+
exports.ARKIT_TO_OCULUS = {
|
|
48
|
+
sil: { mouthClose: 0.1 },
|
|
49
|
+
PP: { mouthClose: 0.8, mouthPucker: 0.3 },
|
|
50
|
+
FF: { mouthLowerDownLeft: 0.5, mouthLowerDownRight: 0.5, mouthRollLower: 0.4 },
|
|
51
|
+
TH: { tongueOut: 0.6, jawOpen: 0.15, mouthLowerDownLeft: 0.2, mouthLowerDownRight: 0.2 },
|
|
52
|
+
DD: { mouthShrugUpper: 0.5, jawOpen: 0.2, mouthUpperUpLeft: 0.3, mouthUpperUpRight: 0.3 },
|
|
53
|
+
kk: { jawOpen: 0.25, mouthStretchLeft: 0.4, mouthStretchRight: 0.4, mouthShrugUpper: 0.2 },
|
|
54
|
+
CH: { mouthStretchLeft: 0.5, mouthStretchRight: 0.5, jawOpen: 0.3, mouthFunnel: 0.2 },
|
|
55
|
+
SS: { mouthStretchLeft: 0.35, mouthStretchRight: 0.35, mouthClose: 0.3 },
|
|
56
|
+
nn: { jawOpen: 0.15, mouthDimpleLeft: 0.4, mouthDimpleRight: 0.4, mouthShrugLower: 0.2 },
|
|
57
|
+
RR: { mouthPucker: 0.4, mouthFunnel: 0.3, jawOpen: 0.15, mouthRollLower: 0.2 },
|
|
58
|
+
aa: { jawOpen: 0.7, mouthLowerDownLeft: 0.4, mouthLowerDownRight: 0.4 },
|
|
59
|
+
ee: { mouthSmileLeft: 0.6, mouthSmileRight: 0.6, jawOpen: 0.2 },
|
|
60
|
+
ih: { mouthSmileLeft: 0.4, mouthSmileRight: 0.4, jawOpen: 0.1 },
|
|
61
|
+
oh: { jawOpen: 0.4, mouthFunnel: 0.8 },
|
|
62
|
+
ou: { mouthPucker: 0.9, mouthRollLower: 0.3 },
|
|
63
|
+
};
|
|
64
|
+
exports.ENGINE_VISEME_MAPPING = {
|
|
65
|
+
schemaVersion: 1,
|
|
66
|
+
rhubarbVisemes: exports.RHUBARB_VISEMES,
|
|
67
|
+
oculusVisemes: exports.OCULUS_VISEMES,
|
|
68
|
+
oculusMorphTargetNames: exports.OCULUS_MORPH_TARGET_NAMES,
|
|
69
|
+
rhubarbToOculus: exports.RHUBARB_TO_OCULUS,
|
|
70
|
+
oculusToArkit: exports.ARKIT_TO_OCULUS,
|
|
71
|
+
};
|
|
72
|
+
function oculusWeightsForRhubarbCue(cue) {
|
|
73
|
+
const weight = Math.max(0, Math.min(1, cue.weight ?? 1));
|
|
74
|
+
const result = Object.fromEntries(exports.OCULUS_VISEMES.map((viseme) => [viseme, 0]));
|
|
75
|
+
const mapping = exports.RHUBARB_TO_OCULUS[cue.viseme];
|
|
76
|
+
for (const [viseme, value] of Object.entries(mapping)) {
|
|
77
|
+
result[viseme] = Math.max(0, Math.min(1, value * weight));
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Remap a set of ARKit blend shape weights to Oculus viseme weights.
|
|
83
|
+
*
|
|
84
|
+
* Given current ARKit influences (e.g. from face tracking or morph target state),
|
|
85
|
+
* returns the equivalent Oculus viseme weights. Each viseme's output weight is the
|
|
86
|
+
* weighted average of its constituent ARKit shapes, clamped to 0–1.
|
|
87
|
+
*
|
|
88
|
+
* Use cases:
|
|
89
|
+
* - Runtime: drive Oculus viseme morphs from ARKit face tracking data
|
|
90
|
+
* - Bake-time: compute Oculus morph target deltas from ARKit shape keys in a GLB
|
|
91
|
+
*/
|
|
92
|
+
function remapArkitToOculus(arkitWeights) {
|
|
93
|
+
const result = {};
|
|
94
|
+
for (const viseme of exports.OCULUS_VISEMES) {
|
|
95
|
+
const recipe = exports.ARKIT_TO_OCULUS[viseme];
|
|
96
|
+
let sum = 0;
|
|
97
|
+
let coeffSum = 0;
|
|
98
|
+
for (const [shape, coeff] of Object.entries(recipe)) {
|
|
99
|
+
const input = arkitWeights[shape] ?? 0;
|
|
100
|
+
sum += input * coeff;
|
|
101
|
+
coeffSum += coeff;
|
|
102
|
+
}
|
|
103
|
+
// Normalize by total coefficient weight so recipes with more shapes
|
|
104
|
+
// don't produce disproportionately high output
|
|
105
|
+
result[viseme] = coeffSum > 0 ? Math.min(1, Math.max(0, sum / coeffSum)) : 0;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* For a given Oculus viseme, return the ARKit blend shape weights that produce it.
|
|
111
|
+
* This is the "forward" direction — useful when baking Oculus morph targets into
|
|
112
|
+
* a GLB that only has ARKit shapes.
|
|
113
|
+
*
|
|
114
|
+
* Returns the raw recipe weights (not normalized). To bake into a morph target,
|
|
115
|
+
* set each ARKit shape to the returned weight and snapshot the mesh delta.
|
|
116
|
+
*/
|
|
117
|
+
function getArkitWeightsForViseme(viseme) {
|
|
118
|
+
return { ...exports.ARKIT_TO_OCULUS[viseme] };
|
|
119
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AvatarCanvas =
|
|
3
|
+
exports.AvatarCanvas = AvatarCanvas;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
// @ts-nocheck
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- R3F pointer events and Three.js scene internals are intentionally dynamic in this editor bridge. */
|
|
6
7
|
const drei_1 = require("@react-three/drei");
|
|
7
8
|
const fiber_1 = require("@react-three/fiber");
|
|
8
9
|
const react_1 = require("react");
|
|
@@ -10,6 +11,96 @@ const AvatarCanvasErrorBoundary_1 = require("./AvatarCanvasErrorBoundary");
|
|
|
10
11
|
const AvatarModel_1 = require("./AvatarModel");
|
|
11
12
|
const RigidAccessory_1 = require("./RigidAccessory");
|
|
12
13
|
const SkinnedClothing_1 = require("./SkinnedClothing");
|
|
14
|
+
function DirectTransform({ activeAssetId, placements, onPlacementChange }) {
|
|
15
|
+
const { gl } = (0, fiber_1.useThree)();
|
|
16
|
+
(0, react_1.useEffect)(() => {
|
|
17
|
+
const canvas = gl.domElement;
|
|
18
|
+
let dragging = false;
|
|
19
|
+
let lastX = 0;
|
|
20
|
+
let lastY = 0;
|
|
21
|
+
function isModified(e) {
|
|
22
|
+
return e.shiftKey || e.ctrlKey || e.metaKey;
|
|
23
|
+
}
|
|
24
|
+
function onPointerDown(e) {
|
|
25
|
+
if (!activeAssetId || !isModified(e))
|
|
26
|
+
return;
|
|
27
|
+
e.stopPropagation();
|
|
28
|
+
canvas.setPointerCapture(e.pointerId);
|
|
29
|
+
dragging = true;
|
|
30
|
+
lastX = e.clientX;
|
|
31
|
+
lastY = e.clientY;
|
|
32
|
+
}
|
|
33
|
+
function onPointerMove(e) {
|
|
34
|
+
if (!dragging || !activeAssetId || !onPlacementChange)
|
|
35
|
+
return;
|
|
36
|
+
const rect = canvas.getBoundingClientRect();
|
|
37
|
+
// Normalise deltas to canvas size so sensitivity is viewport-independent
|
|
38
|
+
const dx = (e.clientX - lastX) / rect.width;
|
|
39
|
+
const dy = (e.clientY - lastY) / rect.height;
|
|
40
|
+
lastX = e.clientX;
|
|
41
|
+
lastY = e.clientY;
|
|
42
|
+
const p = placements[activeAssetId];
|
|
43
|
+
if (!p)
|
|
44
|
+
return;
|
|
45
|
+
const TRANSLATE_SPEED = 2.0;
|
|
46
|
+
const ROTATE_SPEED = Math.PI * 2;
|
|
47
|
+
if (e.shiftKey && (e.altKey || e.metaKey)) {
|
|
48
|
+
// Shift+Alt/Meta drag → Y axis (lift/lower)
|
|
49
|
+
onPlacementChange(activeAssetId, {
|
|
50
|
+
...p,
|
|
51
|
+
position: [p.position[0], p.position[1] - dy * TRANSLATE_SPEED, p.position[2]],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else if (e.shiftKey) {
|
|
55
|
+
// Shift drag → XZ plane (slide left/right/forward/back)
|
|
56
|
+
onPlacementChange(activeAssetId, {
|
|
57
|
+
...p,
|
|
58
|
+
position: [
|
|
59
|
+
p.position[0] + dx * TRANSLATE_SPEED,
|
|
60
|
+
p.position[1],
|
|
61
|
+
p.position[2] + dy * TRANSLATE_SPEED,
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else if (e.ctrlKey || e.metaKey) {
|
|
66
|
+
// Ctrl/Cmd drag → Y-axis rotation (yaw)
|
|
67
|
+
onPlacementChange(activeAssetId, {
|
|
68
|
+
...p,
|
|
69
|
+
rotation: [p.rotation[0], p.rotation[1] + dx * ROTATE_SPEED, p.rotation[2]],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function onPointerUp(e) {
|
|
74
|
+
if (dragging)
|
|
75
|
+
canvas.releasePointerCapture(e.pointerId);
|
|
76
|
+
dragging = false;
|
|
77
|
+
}
|
|
78
|
+
function onWheel(e) {
|
|
79
|
+
if (!e.shiftKey || !activeAssetId || !onPlacementChange)
|
|
80
|
+
return;
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
const p = placements[activeAssetId];
|
|
83
|
+
if (!p)
|
|
84
|
+
return;
|
|
85
|
+
const factor = e.deltaY > 0 ? 0.95 : 1.05;
|
|
86
|
+
onPlacementChange(activeAssetId, {
|
|
87
|
+
...p,
|
|
88
|
+
scale: Math.max(0.01, (p.scale ?? 1) * factor),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
canvas.addEventListener('pointerdown', onPointerDown);
|
|
92
|
+
canvas.addEventListener('pointermove', onPointerMove);
|
|
93
|
+
canvas.addEventListener('pointerup', onPointerUp);
|
|
94
|
+
canvas.addEventListener('wheel', onWheel, { passive: false });
|
|
95
|
+
return () => {
|
|
96
|
+
canvas.removeEventListener('pointerdown', onPointerDown);
|
|
97
|
+
canvas.removeEventListener('pointermove', onPointerMove);
|
|
98
|
+
canvas.removeEventListener('pointerup', onPointerUp);
|
|
99
|
+
canvas.removeEventListener('wheel', onWheel);
|
|
100
|
+
};
|
|
101
|
+
}, [activeAssetId, placements, onPlacementChange, gl]);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
13
104
|
/** Captures the R3F scene object (which contains everything) and passes it up. */
|
|
14
105
|
function SceneRefCapture({ onSceneRef }) {
|
|
15
106
|
const { scene } = (0, fiber_1.useThree)();
|
|
@@ -45,7 +136,7 @@ function AvatarCanvas({ avatarUrl, equipped = [], placements = {}, editingAssetI
|
|
|
45
136
|
]
|
|
46
137
|
.filter(Boolean)
|
|
47
138
|
.join(' ');
|
|
48
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: wrapperClass, style: style, children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 opacity-[0.04] pointer-events-none", style: { backgroundImage: 'linear-gradient(rgba(255,255,255,0.3) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,0.3) 1px,transparent 1px)', backgroundSize: '60px 60px' } }), avatarUrl ? ((0, jsx_runtime_1.jsx)(AvatarCanvasErrorBoundary_1.AvatarCanvasErrorBoundary, { children: (0, jsx_runtime_1.jsx)(fiber_1.Canvas, { ref: canvasRef, camera: { position: cameraPosition, fov: 45 }, style: { width: '100%', height: '100%' }, children: (0, jsx_runtime_1.jsxs)(react_1.Suspense, { fallback: null, children: [onSceneRef && (0, jsx_runtime_1.jsx)(SceneRefCapture, { onSceneRef: onSceneRef }), (0, jsx_runtime_1.jsx)("ambientLight", { intensity: 0.6 }), (0, jsx_runtime_1.jsx)("directionalLight", { position: [5, 5, 5], intensity: 0.8 }), (0, jsx_runtime_1.jsx)(drei_1.Environment, { preset: "studio" }), (0, jsx_runtime_1.jsx)(drei_1.OrbitControls, { target: cameraTarget, minDistance: 1, maxDistance: 10, enablePan: false, makeDefault: true,
|
|
139
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: wrapperClass, style: style, children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 opacity-[0.04] pointer-events-none", style: { backgroundImage: 'linear-gradient(rgba(255,255,255,0.3) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,0.3) 1px,transparent 1px)', backgroundSize: '60px 60px' } }), avatarUrl ? ((0, jsx_runtime_1.jsx)(AvatarCanvasErrorBoundary_1.AvatarCanvasErrorBoundary, { children: (0, jsx_runtime_1.jsx)(fiber_1.Canvas, { ref: canvasRef, camera: { position: cameraPosition, fov: 45 }, style: { width: '100%', height: '100%' }, children: (0, jsx_runtime_1.jsxs)(react_1.Suspense, { fallback: null, children: [onSceneRef && (0, jsx_runtime_1.jsx)(SceneRefCapture, { onSceneRef: onSceneRef }), (0, jsx_runtime_1.jsx)("ambientLight", { intensity: 0.6 }), (0, jsx_runtime_1.jsx)("directionalLight", { position: [5, 5, 5], intensity: 0.8 }), (0, jsx_runtime_1.jsx)(drei_1.Environment, { preset: "studio" }), (0, jsx_runtime_1.jsx)(drei_1.OrbitControls, { target: cameraTarget, minDistance: 1, maxDistance: 10, enablePan: false, makeDefault: true }), (0, jsx_runtime_1.jsx)(drei_1.GizmoHelper, { alignment: "bottom-right", margin: [60, 60], children: (0, jsx_runtime_1.jsx)(drei_1.GizmoViewport, { axisColors: ['#e24444', '#44cc44', '#4488ff'], labelColor: "white" }) }), (0, jsx_runtime_1.jsx)(DirectTransform, { activeAssetId: editingId, placements: placements, onPlacementChange: onPlacementChange }), (0, jsx_runtime_1.jsx)(AvatarModel_1.AvatarModel, { url: avatarUrl, scale: 1, onSkeletonReady: handleSkeletonReady, onBoundsReady: handleBoundsReady }), equippedItems.map((asset) => {
|
|
49
140
|
if (!asset)
|
|
50
141
|
return null;
|
|
51
142
|
const llmPlacement = placements[asset.id];
|
|
@@ -68,4 +159,3 @@ function AvatarCanvas({ avatarUrl, equipped = [], placements = {}, editingAssetI
|
|
|
68
159
|
return null;
|
|
69
160
|
})] }) }) })) : ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center justify-center h-full text-muted-foreground", children: (0, jsx_runtime_1.jsx)("p", { children: "Select a base avatar to get started" }) }))] }));
|
|
70
161
|
}
|
|
71
|
-
exports.AvatarCanvas = AvatarCanvas;
|
|
@@ -15,21 +15,32 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.AvatarEditor =
|
|
36
|
+
exports.AvatarEditor = AvatarEditor;
|
|
27
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
28
38
|
/**
|
|
29
39
|
* Native avatar placement editor.
|
|
30
40
|
* Uses WgpuAvatar (R3F + expo-gl) as the render surface and Three.js raycasting
|
|
31
41
|
* for gesture-driven accessory placement. Replaces the old FilamentEditor.
|
|
32
42
|
*/
|
|
43
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- React Native gesture events and Three.js raycast payloads do not expose complete structural types here. */
|
|
33
44
|
const react_1 = require("react");
|
|
34
45
|
const react_native_1 = require("react-native");
|
|
35
46
|
const THREE = __importStar(require("three"));
|
|
@@ -86,7 +97,6 @@ function AvatarEditor({ avatarUrl, activeAssetId = null, onPlacementChange, styl
|
|
|
86
97
|
}, [activeAssetId, avatarScene, onPlacementChange]);
|
|
87
98
|
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, style], onLayout: (e) => { layoutRef.current = e.nativeEvent.layout; }, children: [(0, jsx_runtime_1.jsx)(wgpu_1.WgpuAvatar, { avatarUrl: avatarUrl, style: react_native_1.StyleSheet.absoluteFill, onSceneReady: handleSceneReady }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: react_native_1.StyleSheet.absoluteFill, onTouchEnd: handleTap, pointerEvents: activeAssetId ? 'auto' : 'none' })] }));
|
|
88
99
|
}
|
|
89
|
-
exports.AvatarEditor = AvatarEditor;
|
|
90
100
|
exports.default = AvatarEditor;
|
|
91
101
|
const styles = react_native_1.StyleSheet.create({
|
|
92
102
|
container: { flex: 1, overflow: 'hidden' },
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AvatarModel =
|
|
3
|
+
exports.AvatarModel = AvatarModel;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
// @ts-nocheck
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- R3F GLTF skeleton data is passed through from untyped loader internals. */
|
|
6
7
|
const drei_1 = require("@react-three/drei");
|
|
7
8
|
const fiber_1 = require("@react-three/fiber");
|
|
8
9
|
const react_1 = require("react");
|
|
@@ -40,4 +41,3 @@ function AvatarModel({ url, scale = 1, onSkeletonReady, onBoundsReady, }) {
|
|
|
40
41
|
});
|
|
41
42
|
return (0, jsx_runtime_1.jsx)("primitive", { ref: groupRef, object: scene, scale: scale });
|
|
42
43
|
}
|
|
43
|
-
exports.AvatarModel = AvatarModel;
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* Drop-in replacement: same FaceSqueezeEditorProps interface.
|
|
11
11
|
* The .web.tsx stub is shared — import from FaceSqueezeEditor.web.tsx.
|
|
12
12
|
*/
|
|
13
|
+
import '../wgpu/blobShim';
|
|
13
14
|
export declare const FACE_SQUEEZE_LOCAL_MODULE: number;
|
|
14
15
|
export interface FaceSqueezeEditorProps {
|
|
15
16
|
onClose: () => void;
|
|
17
|
+
avatarModule?: unknown;
|
|
16
18
|
}
|
|
17
|
-
export declare function FaceSqueezeEditor({ onClose }: FaceSqueezeEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function FaceSqueezeEditor({ onClose, avatarModule, }: FaceSqueezeEditorProps): import("react/jsx-runtime").JSX.Element;
|