talking-head-studio 0.4.11 → 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 +279 -193
- package/dist/TalkingHead.d.ts +28 -3
- package/dist/TalkingHead.js +21 -2
- package/dist/TalkingHead.web.d.ts +31 -4
- package/dist/TalkingHead.web.js +11 -1
- 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 +16 -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/backends/gaussian.js +6 -4
- 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 +33 -5
- package/dist/core/avatar/visemes.d.ts +16 -1
- package/dist/core/avatar/visemes.js +48 -1
- package/dist/editor/AvatarCanvas.js +92 -1
- package/dist/editor/AvatarEditor.native.js +1 -0
- package/dist/editor/AvatarModel.js +1 -0
- package/dist/editor/FaceSqueezeEditor.d.ts +3 -1
- package/dist/editor/FaceSqueezeEditor.js +176 -112
- package/dist/editor/FaceSqueezeEditor.web.d.ts +3 -1
- package/dist/editor/FaceSqueezeEditor.web.js +30 -28
- package/dist/editor/RigidAccessory.js +17 -2
- package/dist/editor/SkinnedClothing.js +1 -0
- package/dist/editor/boneLockedDrag.d.ts +11 -0
- package/dist/editor/boneLockedDrag.js +68 -0
- 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 +17 -14
- 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 +55 -732
- package/dist/index.d.ts +7 -3
- package/dist/index.js +17 -1
- package/dist/index.web.d.ts +18 -1
- package/dist/index.web.js +36 -3
- package/dist/sketchfab/api.js +1 -0
- 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/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.js +15 -10
- package/dist/utils/avatarUtils.js +92 -5
- package/dist/utils/faceLandmarkerToShapeWeights.js +2 -4
- package/dist/voice/useAudioPlayer.js +17 -4
- package/dist/voice/useVoicePreview.js +4 -2
- package/dist/wardrobe/index.d.ts +1 -0
- package/dist/wardrobe/index.js +6 -1
- package/dist/wardrobe/useAccessoryGestures.d.ts +20 -0
- package/dist/wardrobe/useAccessoryGestures.js +94 -0
- package/dist/wardrobe/useAvatarWardrobeHydration.js +8 -2
- package/dist/wardrobe/useStudioAvatar.js +11 -2
- 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 +296 -39
- 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 +4 -2
- package/dist/wgpu/useNativeGLTF.d.ts +7 -0
- package/dist/wgpu/useNativeGLTF.js +36 -0
- package/package.json +97 -31
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* patchThreeForRN — patches Three.js loaders for React Native compatibility.
|
|
4
|
+
*
|
|
5
|
+
* Problem: GLTFLoader (three-stdlib) calls URL.createObjectURL(blob) when
|
|
6
|
+
* extracting embedded textures from a GLB's buffer views. React Native's
|
|
7
|
+
* Hermes / JSC runtime does not implement URL.createObjectURL, so every
|
|
8
|
+
* texture inside the GLB fails with "Cannot create URL for blob!".
|
|
9
|
+
*
|
|
10
|
+
* Fix strategy (3 layers):
|
|
11
|
+
*
|
|
12
|
+
* Layer 1 — URL.createObjectURL polyfill
|
|
13
|
+
* Instead of throwing, store the Blob in a module-level Map under a
|
|
14
|
+
* synthetic "rnblob://<id>" key and return that key as the "URL".
|
|
15
|
+
* URL.revokeObjectURL deletes the entry.
|
|
16
|
+
*
|
|
17
|
+
* Layer 2 — THREE.ImageLoader patch
|
|
18
|
+
* When ImageLoader.load() receives an "rnblob://" URL, retrieve the
|
|
19
|
+
* stored Blob, read it as base64 via FileReader, build a "data:" URI,
|
|
20
|
+
* then set it as img.src so expo-gl can decode it natively.
|
|
21
|
+
*
|
|
22
|
+
* Layer 3 — THREE.TextureLoader / THREE.FileLoader safety net
|
|
23
|
+
* Same interception for any direct TextureLoader usage and for
|
|
24
|
+
* FileLoader which is also used internally by GLTFLoader.
|
|
25
|
+
*
|
|
26
|
+
* Call installThreeRNPatches() once at module load time, before any
|
|
27
|
+
* useGLTF / GLTFLoader usage. Pass debug=true for verbose logging.
|
|
28
|
+
*/
|
|
29
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- Three.js loader monkey patches must touch dynamic private fields across RN runtimes. */
|
|
30
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
33
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
34
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(o, k2, desc);
|
|
37
|
+
}) : (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
o[k2] = m[k];
|
|
40
|
+
}));
|
|
41
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
42
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
43
|
+
}) : function(o, v) {
|
|
44
|
+
o["default"] = v;
|
|
45
|
+
});
|
|
46
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
47
|
+
var ownKeys = function(o) {
|
|
48
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
49
|
+
var ar = [];
|
|
50
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
51
|
+
return ar;
|
|
52
|
+
};
|
|
53
|
+
return ownKeys(o);
|
|
54
|
+
};
|
|
55
|
+
return function (mod) {
|
|
56
|
+
if (mod && mod.__esModule) return mod;
|
|
57
|
+
var result = {};
|
|
58
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
59
|
+
__setModuleDefault(result, mod);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
63
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
64
|
+
exports.installThreeRNPatches = installThreeRNPatches;
|
|
65
|
+
const react_native_1 = require("react-native");
|
|
66
|
+
const base64_js_1 = require("base64-js");
|
|
67
|
+
const THREE = __importStar(require("three"));
|
|
68
|
+
const blobShim_1 = require("./blobShim");
|
|
69
|
+
let installed = false;
|
|
70
|
+
// blobStore is only used if something still produces rnblob:// URLs (shouldn't happen
|
|
71
|
+
// after blobShim returns data: URIs directly, but kept as safety net)
|
|
72
|
+
const blobStore = new Map();
|
|
73
|
+
/** Re-patch URL.createObjectURL on the current globalThis.URL.
|
|
74
|
+
* Must run every time installThreeRNPatches() is called because libraries like
|
|
75
|
+
* @livekit/react-native call setupURLPolyfill() which replaces globalThis.URL
|
|
76
|
+
* with a new whatwg-url object whose createObjectURL always throws
|
|
77
|
+
* "Cannot create URL for blob!". */
|
|
78
|
+
function repatchUrlCreateObjectURL() {
|
|
79
|
+
const URLObj = globalThis.URL;
|
|
80
|
+
if (!URLObj)
|
|
81
|
+
return;
|
|
82
|
+
if (typeof URLObj.createObjectURL === 'function' &&
|
|
83
|
+
URLObj.createObjectURL.__blobShim) {
|
|
84
|
+
return; // already our patch — no-op
|
|
85
|
+
}
|
|
86
|
+
const patchedCreateObjectURL = (blob) => {
|
|
87
|
+
const bytes = blobShim_1.blobBytesStore.get(blob);
|
|
88
|
+
if (bytes) {
|
|
89
|
+
const mime = blob.type || 'application/octet-stream';
|
|
90
|
+
const b64 = (0, base64_js_1.fromByteArray)(bytes);
|
|
91
|
+
return `data:${mime};base64,${b64}`;
|
|
92
|
+
}
|
|
93
|
+
throw new Error('[BlobShim] createObjectURL: no bytes stashed for this Blob');
|
|
94
|
+
};
|
|
95
|
+
patchedCreateObjectURL.__blobShim = true;
|
|
96
|
+
URLObj.createObjectURL = patchedCreateObjectURL;
|
|
97
|
+
URLObj.revokeObjectURL = (_url) => { };
|
|
98
|
+
}
|
|
99
|
+
function isInlineImageUrl(url) {
|
|
100
|
+
return url.startsWith('rnblob://') || url.startsWith('data:image/');
|
|
101
|
+
}
|
|
102
|
+
function getMimeTypeFromDataUri(dataUri) {
|
|
103
|
+
const mimeMatch = dataUri.match(/^data:([^;,]+)[;,]/i);
|
|
104
|
+
return mimeMatch?.[1] ?? 'image/png';
|
|
105
|
+
}
|
|
106
|
+
function decodeDataUriBytes(dataUri) {
|
|
107
|
+
const marker = ';base64,';
|
|
108
|
+
const markerIndex = dataUri.indexOf(marker);
|
|
109
|
+
if (markerIndex < 0) {
|
|
110
|
+
throw new Error('[ThreeRNPatch] Unsupported data URI encoding');
|
|
111
|
+
}
|
|
112
|
+
const base64 = dataUri.slice(markerIndex + marker.length);
|
|
113
|
+
return (0, base64_js_1.toByteArray)(base64);
|
|
114
|
+
}
|
|
115
|
+
async function bytesFromBlob(blob) {
|
|
116
|
+
if (typeof blob.arrayBuffer === 'function') {
|
|
117
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
118
|
+
}
|
|
119
|
+
const dataUri = await new Promise((resolve, reject) => {
|
|
120
|
+
const reader = new FileReader();
|
|
121
|
+
reader.onload = () => resolve(String(reader.result));
|
|
122
|
+
reader.onerror = () => reject(new Error('[ThreeRNPatch] FileReader failed while decoding blob'));
|
|
123
|
+
reader.readAsDataURL(blob);
|
|
124
|
+
});
|
|
125
|
+
return decodeDataUriBytes(dataUri);
|
|
126
|
+
}
|
|
127
|
+
function installThreeRNPatches(debug = false) {
|
|
128
|
+
if (react_native_1.Platform.OS === 'web')
|
|
129
|
+
return;
|
|
130
|
+
const log = (...args) => {
|
|
131
|
+
if (debug)
|
|
132
|
+
console.log('[ThreeRNPatch]', ...args);
|
|
133
|
+
};
|
|
134
|
+
// Layer 1: Re-patch URL.createObjectURL on the *current* globalThis.URL.
|
|
135
|
+
// This must run every call (not guarded by `installed`) because libraries like
|
|
136
|
+
// @livekit/react-native call setupURLPolyfill() which replaces globalThis.URL
|
|
137
|
+
// with a new whatwg-url object whose createObjectURL always throws.
|
|
138
|
+
// We re-apply our override so GLTFLoader's `const URL = self.URL` gets our patch.
|
|
139
|
+
repatchUrlCreateObjectURL();
|
|
140
|
+
log('URL.createObjectURL re-patched on current globalThis.URL');
|
|
141
|
+
if (installed)
|
|
142
|
+
return;
|
|
143
|
+
installed = true;
|
|
144
|
+
log('Installing Three.js RN patches…');
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
// Layer 2: THREE.ImageLoader patch — patch prototype to avoid ESM live-binding restrictions
|
|
147
|
+
// three.webgpu.js is an ESM file; its named exports are getter-only live bindings
|
|
148
|
+
// from the consumer's perspective, so assigning THREE.ImageLoader = X throws.
|
|
149
|
+
// Patching the prototype achieves the same effect without touching the namespace.
|
|
150
|
+
// -----------------------------------------------------------------------
|
|
151
|
+
const origImageLoaderLoad = THREE.ImageLoader.prototype.load;
|
|
152
|
+
THREE.ImageLoader.prototype.load = function patchedImageLoad(url, onLoad, onProgress, onError) {
|
|
153
|
+
if (!url || !isInlineImageUrl(url)) {
|
|
154
|
+
return origImageLoaderLoad.call(this, url, onLoad, onProgress, onError);
|
|
155
|
+
}
|
|
156
|
+
if (url.startsWith('data:image/')) {
|
|
157
|
+
const img = _makeImageFromDataUri(url, getMimeTypeFromDataUri(url));
|
|
158
|
+
onLoad?.(img);
|
|
159
|
+
return img;
|
|
160
|
+
}
|
|
161
|
+
const blob = blobStore.get(url);
|
|
162
|
+
if (!blob) {
|
|
163
|
+
log(`ImageLoader: no blob found for ${url} — returning placeholder`);
|
|
164
|
+
const placeholder = _makePlaceholder();
|
|
165
|
+
onLoad?.(placeholder);
|
|
166
|
+
return placeholder;
|
|
167
|
+
}
|
|
168
|
+
log(`ImageLoader: reading blob ${url} size=${blob.size} type=${blob.type}`);
|
|
169
|
+
const reader = new FileReader();
|
|
170
|
+
reader.onload = () => {
|
|
171
|
+
const dataUri = reader.result;
|
|
172
|
+
log(`ImageLoader: blob → data URI (${dataUri.length} chars)`);
|
|
173
|
+
const img = _makeImageFromDataUri(dataUri, blob.type || 'image/png');
|
|
174
|
+
onLoad?.(img);
|
|
175
|
+
};
|
|
176
|
+
reader.onerror = () => {
|
|
177
|
+
const err = new Error(`[ThreeRNPatch] FileReader failed for ${url}`);
|
|
178
|
+
log(`ImageLoader: FileReader error for ${url}`);
|
|
179
|
+
onError?.(err);
|
|
180
|
+
};
|
|
181
|
+
reader.readAsDataURL(blob);
|
|
182
|
+
return _makePlaceholder();
|
|
183
|
+
};
|
|
184
|
+
log('Patched THREE.ImageLoader.prototype.load');
|
|
185
|
+
// -----------------------------------------------------------------------
|
|
186
|
+
// Layer 3: THREE.TextureLoader safety net — same prototype-patch approach
|
|
187
|
+
// -----------------------------------------------------------------------
|
|
188
|
+
const origTextureLoaderLoad = THREE.TextureLoader.prototype.load;
|
|
189
|
+
THREE.TextureLoader.prototype.load = function patchedTextureLoad(url, onLoad, onProgress, onError) {
|
|
190
|
+
if (!url || !isInlineImageUrl(url)) {
|
|
191
|
+
return origTextureLoaderLoad.call(this, url, onLoad, onProgress, onError);
|
|
192
|
+
}
|
|
193
|
+
if (url.startsWith('data:image/')) {
|
|
194
|
+
const tex = new THREE.Texture();
|
|
195
|
+
tex.image = _makeImageFromDataUri(url, getMimeTypeFromDataUri(url));
|
|
196
|
+
tex.needsUpdate = true;
|
|
197
|
+
onLoad?.(tex);
|
|
198
|
+
return tex;
|
|
199
|
+
}
|
|
200
|
+
const blob = blobStore.get(url);
|
|
201
|
+
if (!blob) {
|
|
202
|
+
log(`TextureLoader: no blob for ${url} — returning empty texture`);
|
|
203
|
+
const tex = new THREE.Texture();
|
|
204
|
+
setTimeout(() => onLoad?.(tex), 0);
|
|
205
|
+
return tex;
|
|
206
|
+
}
|
|
207
|
+
log(`TextureLoader: reading blob ${url} size=${blob.size}`);
|
|
208
|
+
const tex = new THREE.Texture();
|
|
209
|
+
const reader = new FileReader();
|
|
210
|
+
reader.onload = () => {
|
|
211
|
+
const dataUri = reader.result;
|
|
212
|
+
const img = _makeImageFromDataUri(dataUri, blob.type || 'image/png');
|
|
213
|
+
tex.image = img;
|
|
214
|
+
tex.needsUpdate = true;
|
|
215
|
+
onLoad?.(tex);
|
|
216
|
+
};
|
|
217
|
+
reader.onerror = () => onError?.(new ErrorEvent('error'));
|
|
218
|
+
reader.readAsDataURL(blob);
|
|
219
|
+
return tex;
|
|
220
|
+
};
|
|
221
|
+
log('Patched THREE.TextureLoader.prototype.load');
|
|
222
|
+
const ImageBitmapLoader = THREE.ImageBitmapLoader;
|
|
223
|
+
const origImageBitmapLoad = ImageBitmapLoader?.prototype?.load;
|
|
224
|
+
if (origImageBitmapLoad) {
|
|
225
|
+
ImageBitmapLoader.prototype.load = function patchedImageBitmapLoad(url, onLoad, onProgress, onError) {
|
|
226
|
+
if (!url || !isInlineImageUrl(url)) {
|
|
227
|
+
return origImageBitmapLoad.call(this, url, onLoad, onProgress, onError);
|
|
228
|
+
}
|
|
229
|
+
const loadInlineBitmap = async () => {
|
|
230
|
+
try {
|
|
231
|
+
const bytes = url.startsWith('data:image/')
|
|
232
|
+
? decodeDataUriBytes(url)
|
|
233
|
+
: await (async () => {
|
|
234
|
+
const blob = blobStore.get(url);
|
|
235
|
+
if (!blob)
|
|
236
|
+
return null;
|
|
237
|
+
return await bytesFromBlob(blob);
|
|
238
|
+
})();
|
|
239
|
+
if (!bytes) {
|
|
240
|
+
throw new Error(`[ThreeRNPatch] No blob found for ${url}`);
|
|
241
|
+
}
|
|
242
|
+
if (typeof createImageBitmap !== 'function') {
|
|
243
|
+
throw new Error('[ThreeRNPatch] createImageBitmap is unavailable');
|
|
244
|
+
}
|
|
245
|
+
const imageBitmap = await createImageBitmap(bytes);
|
|
246
|
+
onLoad?.(imageBitmap);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
void loadInlineBitmap();
|
|
253
|
+
return undefined;
|
|
254
|
+
};
|
|
255
|
+
log('Patched THREE.ImageBitmapLoader.prototype.load');
|
|
256
|
+
}
|
|
257
|
+
log('Three.js RN patches installed — embedded GLB textures will now load via FileReader+dataURI');
|
|
258
|
+
}
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// Helpers
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
/**
|
|
263
|
+
* Build a minimal HTMLImageElement-like object from a data URI.
|
|
264
|
+
* expo-gl / react-three-fiber / @react-three/fiber/native accept this shape
|
|
265
|
+
* for texture.image on native (it gets handed to gl.texImage2D via Expo's NativeImageLoader).
|
|
266
|
+
*/
|
|
267
|
+
function _makeImageFromDataUri(dataUri, _mimeType) {
|
|
268
|
+
return {
|
|
269
|
+
src: dataUri,
|
|
270
|
+
width: 0, // Three.js reads these after decoding; 0 is fine pre-decode
|
|
271
|
+
height: 0,
|
|
272
|
+
complete: false,
|
|
273
|
+
naturalWidth: 0,
|
|
274
|
+
naturalHeight: 0,
|
|
275
|
+
crossOrigin: '',
|
|
276
|
+
// expo-gl NativeImageLoader checks for 'uri' as well
|
|
277
|
+
uri: dataUri,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/** 1×1 transparent pixel stand-in (used when we have no blob data at all). */
|
|
281
|
+
function _makePlaceholder() {
|
|
282
|
+
return {
|
|
283
|
+
src: '',
|
|
284
|
+
width: 1,
|
|
285
|
+
height: 1,
|
|
286
|
+
complete: true,
|
|
287
|
+
naturalWidth: 1,
|
|
288
|
+
naturalHeight: 1,
|
|
289
|
+
crossOrigin: '',
|
|
290
|
+
uri: '',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.computeObjectPlacement = computeObjectPlacement;
|
|
37
|
+
const THREE = __importStar(require("three"));
|
|
38
|
+
function computeObjectPlacement(object, target, desiredMaxDimension = 1.5) {
|
|
39
|
+
const box = new THREE.Box3().setFromObject(object);
|
|
40
|
+
if (box.isEmpty())
|
|
41
|
+
return null;
|
|
42
|
+
const size = box.getSize(new THREE.Vector3());
|
|
43
|
+
const maxDimension = Math.max(size.x, size.y, size.z);
|
|
44
|
+
if (!Number.isFinite(maxDimension) || maxDimension <= 0)
|
|
45
|
+
return null;
|
|
46
|
+
const scale = desiredMaxDimension / maxDimension;
|
|
47
|
+
const center = box.getCenter(new THREE.Vector3());
|
|
48
|
+
const position = target.clone().sub(center.multiplyScalar(scale));
|
|
49
|
+
return { position, scale };
|
|
50
|
+
}
|
|
@@ -61,7 +61,7 @@ function useAuthedModelUri(remoteUrl) {
|
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
let cancelled = false;
|
|
64
|
-
(async () => {
|
|
64
|
+
void (async () => {
|
|
65
65
|
try {
|
|
66
66
|
const token = await (0, studioApi_1.getToken)();
|
|
67
67
|
// Strip query params (access_token changes per session) so the same GLB is cached across sessions
|
|
@@ -93,7 +93,9 @@ function useAuthedModelUri(remoteUrl) {
|
|
|
93
93
|
});
|
|
94
94
|
// Verify we got a valid GLB — check size and magic bytes ("glTF")
|
|
95
95
|
const downloaded = await FileSystem.getInfoAsync(dlResult.uri);
|
|
96
|
-
const downloadedSize = downloaded.size
|
|
96
|
+
const downloadedSize = 'size' in downloaded && typeof downloaded.size === 'number'
|
|
97
|
+
? downloaded.size
|
|
98
|
+
: undefined;
|
|
97
99
|
if (!downloaded.exists || !downloadedSize || downloadedSize < 1000) {
|
|
98
100
|
console.error('[useAuthedModelUri] Download too small (' + (downloadedSize ?? 0) + 'B), likely an error response');
|
|
99
101
|
failedUrls.add(remoteUrl);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useNativeGLTF = useNativeGLTF;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const loadGLTFFromUri_1 = require("./loadGLTFFromUri");
|
|
6
|
+
const gltfPromiseCache = new Map();
|
|
7
|
+
function getOrLoad(uri) {
|
|
8
|
+
const cached = gltfPromiseCache.get(uri);
|
|
9
|
+
if (cached)
|
|
10
|
+
return cached;
|
|
11
|
+
const promise = (0, loadGLTFFromUri_1.loadGLTFFromUri)(uri);
|
|
12
|
+
gltfPromiseCache.set(uri, promise);
|
|
13
|
+
return promise;
|
|
14
|
+
}
|
|
15
|
+
function useNativeGLTF(uri) {
|
|
16
|
+
const [state, setState] = (0, react_1.useState)({ gltf: null, error: null });
|
|
17
|
+
(0, react_1.useEffect)(() => {
|
|
18
|
+
if (!uri) {
|
|
19
|
+
setState({ gltf: null, error: null });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
let cancelled = false;
|
|
23
|
+
setState({ gltf: null, error: null });
|
|
24
|
+
getOrLoad(uri).then((gltf) => {
|
|
25
|
+
if (!cancelled)
|
|
26
|
+
setState({ gltf, error: null });
|
|
27
|
+
}, (error) => {
|
|
28
|
+
if (!cancelled)
|
|
29
|
+
setState({ gltf: null, error });
|
|
30
|
+
});
|
|
31
|
+
return () => {
|
|
32
|
+
cancelled = true;
|
|
33
|
+
};
|
|
34
|
+
}, [uri]);
|
|
35
|
+
return state;
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "talking-head-studio",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"description": "Cross-platform 3D avatar component for React Native & web — lip-sync, gestures, accessories, and LLM integration. Powered by TalkingHead + Three.js.",
|
|
5
5
|
"main": "dist/index.web.js",
|
|
6
6
|
"browser": "dist/index.web.js",
|
|
7
7
|
"react-native": "dist/index.js",
|
|
8
|
-
"types": "dist/index.
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
9
|
"source": "src/index.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
12
|
"react-native": "./dist/index.js",
|
|
13
|
-
"types": "./dist/index.
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
14
|
"default": "./dist/index.web.js"
|
|
15
15
|
},
|
|
16
16
|
"./editor": {
|
|
17
17
|
"types": "./dist/editor/index.d.ts",
|
|
18
|
+
"browser": "./dist/editor/index.web.js",
|
|
18
19
|
"default": "./dist/editor/index.js"
|
|
19
20
|
},
|
|
20
21
|
"./voice": {
|
|
21
22
|
"types": "./dist/voice/index.d.ts",
|
|
22
23
|
"default": "./dist/voice/index.js"
|
|
23
24
|
},
|
|
25
|
+
"./contract": {
|
|
26
|
+
"types": "./dist/contract.d.ts",
|
|
27
|
+
"default": "./dist/contract.js"
|
|
28
|
+
},
|
|
24
29
|
"./appearance": {
|
|
25
30
|
"types": "./dist/appearance/index.d.ts",
|
|
26
31
|
"default": "./dist/appearance/index.js"
|
|
@@ -42,6 +47,14 @@
|
|
|
42
47
|
"types": "./dist/wgpu/index.d.ts",
|
|
43
48
|
"default": "./dist/wgpu/index.js"
|
|
44
49
|
},
|
|
50
|
+
"./studio": {
|
|
51
|
+
"types": "./dist/studio/index.d.ts",
|
|
52
|
+
"default": "./dist/studio/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./html": {
|
|
55
|
+
"types": "./dist/html.d.ts",
|
|
56
|
+
"default": "./dist/html.js"
|
|
57
|
+
},
|
|
45
58
|
"./dist/assets/*.glb": "./dist/assets/*.glb"
|
|
46
59
|
},
|
|
47
60
|
"files": [
|
|
@@ -49,12 +62,13 @@
|
|
|
49
62
|
],
|
|
50
63
|
"scripts": {
|
|
51
64
|
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
52
|
-
"build": "npm run clean && tsc --project tsconfig.json &&
|
|
65
|
+
"build": "npm run clean && tsc --project tsconfig.json && node tools/copy-build-assets.mjs",
|
|
53
66
|
"typecheck": "tsc --noEmit",
|
|
54
67
|
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
55
68
|
"format": "prettier --write 'src/**/*.{ts,tsx}'",
|
|
56
69
|
"test": "jest",
|
|
57
|
-
"
|
|
70
|
+
"test:metadata": "node --test 'tests/*.test.mjs'",
|
|
71
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm test -- --runInBand && npm run test:metadata && npm run build",
|
|
58
72
|
"build:creator": "vite build --config packages/avatar-creator/vite.config.ts"
|
|
59
73
|
},
|
|
60
74
|
"keywords": [
|
|
@@ -97,24 +111,44 @@
|
|
|
97
111
|
],
|
|
98
112
|
"sideEffects": false,
|
|
99
113
|
"dependencies": {
|
|
114
|
+
"@gltf-transform/core": "^4.3.0",
|
|
100
115
|
"@mediapipe/tasks-vision": "^0.10.34",
|
|
101
|
-
"
|
|
116
|
+
"base64-js": "^1.5.1",
|
|
102
117
|
"zustand": "^5.0.12"
|
|
103
118
|
},
|
|
104
119
|
"peerDependencies": {
|
|
120
|
+
"@expo/vector-icons": ">=14",
|
|
121
|
+
"@gorhom/bottom-sheet": ">=5",
|
|
105
122
|
"@react-three/drei": ">=9",
|
|
106
123
|
"@react-three/fiber": ">=8",
|
|
124
|
+
"@shopify/flash-list": ">=1",
|
|
107
125
|
"expo": ">=51",
|
|
108
126
|
"expo-asset": ">=10",
|
|
109
127
|
"expo-audio": ">=0.3",
|
|
128
|
+
"expo-blur": ">=13",
|
|
129
|
+
"expo-document-picker": ">=12",
|
|
110
130
|
"expo-file-system": ">=17",
|
|
131
|
+
"expo-haptics": ">=13",
|
|
132
|
+
"expo-image": ">=1",
|
|
133
|
+
"moti": ">=0.29",
|
|
111
134
|
"react": ">=18",
|
|
112
|
-
"react-native": ">=0.
|
|
135
|
+
"react-native": ">=0.81",
|
|
136
|
+
"react-native-reanimated": ">=3",
|
|
113
137
|
"react-native-webview": ">=13",
|
|
114
138
|
"react-native-wgpu": ">=0.1",
|
|
115
|
-
"three": ">=0.170"
|
|
139
|
+
"three": ">=0.170",
|
|
140
|
+
"three-stdlib": ">=2"
|
|
116
141
|
},
|
|
117
142
|
"peerDependenciesMeta": {
|
|
143
|
+
"@expo/vector-icons": {
|
|
144
|
+
"optional": true
|
|
145
|
+
},
|
|
146
|
+
"@gorhom/bottom-sheet": {
|
|
147
|
+
"optional": true
|
|
148
|
+
},
|
|
149
|
+
"@shopify/flash-list": {
|
|
150
|
+
"optional": true
|
|
151
|
+
},
|
|
118
152
|
"react-native": {
|
|
119
153
|
"optional": true
|
|
120
154
|
},
|
|
@@ -133,9 +167,24 @@
|
|
|
133
167
|
"expo-audio": {
|
|
134
168
|
"optional": true
|
|
135
169
|
},
|
|
170
|
+
"expo-blur": {
|
|
171
|
+
"optional": true
|
|
172
|
+
},
|
|
173
|
+
"expo-document-picker": {
|
|
174
|
+
"optional": true
|
|
175
|
+
},
|
|
136
176
|
"expo-file-system": {
|
|
137
177
|
"optional": true
|
|
138
178
|
},
|
|
179
|
+
"expo-haptics": {
|
|
180
|
+
"optional": true
|
|
181
|
+
},
|
|
182
|
+
"expo-image": {
|
|
183
|
+
"optional": true
|
|
184
|
+
},
|
|
185
|
+
"moti": {
|
|
186
|
+
"optional": true
|
|
187
|
+
},
|
|
139
188
|
"@react-three/fiber": {
|
|
140
189
|
"optional": true
|
|
141
190
|
},
|
|
@@ -144,49 +193,66 @@
|
|
|
144
193
|
},
|
|
145
194
|
"three": {
|
|
146
195
|
"optional": true
|
|
196
|
+
},
|
|
197
|
+
"three-stdlib": {
|
|
198
|
+
"optional": true
|
|
199
|
+
},
|
|
200
|
+
"react-native-reanimated": {
|
|
201
|
+
"optional": true
|
|
147
202
|
}
|
|
148
203
|
},
|
|
149
204
|
"devDependencies": {
|
|
150
205
|
"@babel/core": "^7.29.0",
|
|
151
|
-
"@babel/preset-env": "^7.29.
|
|
206
|
+
"@babel/preset-env": "^7.29.2",
|
|
152
207
|
"@babel/preset-typescript": "^7.28.5",
|
|
153
208
|
"@expo/vector-icons": "^15.1.1",
|
|
209
|
+
"@gorhom/bottom-sheet": "^5.2.8",
|
|
154
210
|
"@react-native/babel-preset": "^0.84.1",
|
|
155
|
-
"@react-three/drei": "^
|
|
156
|
-
"@react-three/fiber": "^
|
|
211
|
+
"@react-three/drei": "^10.7.7",
|
|
212
|
+
"@react-three/fiber": "^9.5.0",
|
|
213
|
+
"@shopify/flash-list": "2.0.2",
|
|
157
214
|
"@testing-library/react-native": "^13.3.3",
|
|
158
215
|
"@types/jest": "^30.0.0",
|
|
216
|
+
"@types/node": "^25.5.0",
|
|
159
217
|
"@types/react": "^19.2.14",
|
|
160
|
-
"@types/react-dom": "^19.
|
|
218
|
+
"@types/react-dom": "^19.2.3",
|
|
161
219
|
"@types/react-native": "^0.73.0",
|
|
162
|
-
"@types/three": "^0.
|
|
163
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
164
|
-
"@typescript-eslint/parser": "^8.
|
|
165
|
-
"@vitejs/plugin-react": "^
|
|
166
|
-
"babel-jest": "^30.
|
|
167
|
-
"eslint": "^9.39.
|
|
220
|
+
"@types/three": "^0.183.1",
|
|
221
|
+
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
222
|
+
"@typescript-eslint/parser": "^8.57.2",
|
|
223
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
224
|
+
"babel-jest": "^30.3.0",
|
|
225
|
+
"eslint": "^9.39.4",
|
|
168
226
|
"eslint-config-prettier": "^10.1.8",
|
|
169
227
|
"eslint-plugin-react": "^7.37.5",
|
|
170
228
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
171
229
|
"eslint-plugin-react-native": "^5.0.0",
|
|
172
|
-
"expo-
|
|
173
|
-
"expo-
|
|
230
|
+
"expo-asset": "55.0.13",
|
|
231
|
+
"expo-audio": "55.0.11",
|
|
232
|
+
"expo-blur": "55.0.12",
|
|
233
|
+
"expo-document-picker": "55.0.11",
|
|
234
|
+
"expo-file-system": "55.0.15",
|
|
235
|
+
"expo-haptics": "55.0.11",
|
|
236
|
+
"expo-image": "55.0.8",
|
|
174
237
|
"express": "^5.2.1",
|
|
175
|
-
"jest": "^30.
|
|
176
|
-
"jest-environment-jsdom": "^30.
|
|
238
|
+
"jest": "^30.3.0",
|
|
239
|
+
"jest-environment-jsdom": "^30.3.0",
|
|
177
240
|
"metro-react-native-babel-preset": "^0.77.0",
|
|
178
241
|
"moti": "^0.30.0",
|
|
179
|
-
"multer": "^2.1.
|
|
242
|
+
"multer": "^2.1.1",
|
|
180
243
|
"prettier": "^3.8.1",
|
|
181
|
-
"react": "
|
|
182
|
-
"react-dom": "
|
|
183
|
-
"react-native-gesture-handler": "
|
|
184
|
-
"react-native-reanimated": "
|
|
185
|
-
"react-native-webview": "
|
|
186
|
-
"react-
|
|
187
|
-
"
|
|
244
|
+
"react": "19.2.0",
|
|
245
|
+
"react-dom": "19.2.0",
|
|
246
|
+
"react-native-gesture-handler": "~2.30.0",
|
|
247
|
+
"react-native-reanimated": "4.2.1",
|
|
248
|
+
"react-native-webview": "13.16.0",
|
|
249
|
+
"react-native-wgpu": "0.5.11",
|
|
250
|
+
"react-native-worklets": "0.7.2",
|
|
251
|
+
"react-test-renderer": "19.2.0",
|
|
252
|
+
"three": "^0.183.2",
|
|
253
|
+
"three-stdlib": "^2.36.1",
|
|
188
254
|
"ts-jest": "^29.4.6",
|
|
189
|
-
"typescript": "^
|
|
255
|
+
"typescript": "^6.0.2",
|
|
190
256
|
"vite": "^8.0.3"
|
|
191
257
|
}
|
|
192
258
|
}
|