talking-head-studio 0.4.0 → 0.4.2

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.
Files changed (42) hide show
  1. package/dist/TalkingHeadVisualization.d.ts +1 -1
  2. package/dist/TalkingHeadVisualization.js +13 -13
  3. package/dist/editor/AvatarCanvas.d.ts +3 -14
  4. package/dist/editor/AvatarCanvas.js +5 -4
  5. package/dist/editor/AvatarEditor.d.ts +1 -0
  6. package/dist/editor/AvatarEditor.js +6 -0
  7. package/dist/editor/AvatarEditor.native.d.ts +4 -0
  8. package/dist/editor/AvatarEditor.native.js +93 -0
  9. package/dist/editor/boneSnap.d.ts +25 -0
  10. package/dist/editor/boneSnap.js +93 -0
  11. package/dist/editor/index.d.ts +4 -0
  12. package/dist/editor/index.js +13 -1
  13. package/dist/editor/types.d.ts +18 -4
  14. package/dist/utils/avatarUtils.d.ts +2 -3
  15. package/dist/utils/avatarUtils.js +5 -6
  16. package/dist/wardrobe/wardrobeStore.d.ts +1 -1
  17. package/dist/wgpu/WgpuAvatar.d.ts +3 -0
  18. package/dist/wgpu/WgpuAvatar.js +55 -87
  19. package/dist/{filament → wgpu}/morphTables.js +1 -1
  20. package/dist/wgpu/useAuthedModelUri.d.ts +11 -0
  21. package/dist/{filament/useAuthedFilamentUri.js → wgpu/useAuthedModelUri.js} +9 -9
  22. package/package.json +2 -15
  23. package/dist/filament/FilamentAvatar.d.ts +0 -41
  24. package/dist/filament/FilamentAvatar.js +0 -755
  25. package/dist/filament/editor/FilamentEditor.d.ts +0 -16
  26. package/dist/filament/editor/FilamentEditor.js +0 -880
  27. package/dist/filament/editor/FilamentEditor.web.d.ts +0 -19
  28. package/dist/filament/editor/FilamentEditor.web.js +0 -58
  29. package/dist/filament/editor/PrecisionPanel.d.ts +0 -1
  30. package/dist/filament/editor/PrecisionPanel.js +0 -252
  31. package/dist/filament/editor/boneSnap.d.ts +0 -10
  32. package/dist/filament/editor/boneSnap.js +0 -97
  33. package/dist/filament/editor/index.d.ts +0 -5
  34. package/dist/filament/editor/index.js +0 -19
  35. package/dist/filament/index.d.ts +0 -6
  36. package/dist/filament/index.js +0 -24
  37. package/dist/filament/useAuthedFilamentUri.d.ts +0 -11
  38. /package/dist/{filament/editor → editor}/studioTheme.d.ts +0 -0
  39. /package/dist/{filament/editor → editor}/studioTheme.js +0 -0
  40. /package/dist/{filament → wgpu}/faceSqueezeAssets.d.ts +0 -0
  41. /package/dist/{filament → wgpu}/faceSqueezeAssets.js +0 -0
  42. /package/dist/{filament → wgpu}/morphTables.d.ts +0 -0
@@ -43,14 +43,26 @@ const react_native_1 = require("react-native");
43
43
  const THREE = __importStar(require("three"));
44
44
  const native_1 = require("@react-three/fiber/native");
45
45
  const native_2 = require("@react-three/drei/native");
46
- const morphTables_1 = require("../filament/morphTables");
47
- const useAuthedFilamentUri_1 = require("../filament/useAuthedFilamentUri");
46
+ const morphTables_1 = require("./morphTables");
47
+ const useAuthedModelUri_1 = require("./useAuthedModelUri");
48
48
  // ---------------------------------------------------------------------------
49
49
  // Camera defaults — match FilamentAvatar constants
50
50
  // ---------------------------------------------------------------------------
51
51
  const CAMERA_POSITION = [0, 1.52, 0.85];
52
52
  const CAMERA_TARGET = new THREE.Vector3(0, 1.52, 0);
53
- const CAMERA_FOV_FULL = 38; // rough Three.js FOV equiv to 50mm focal length
53
+ const CAMERA_FOV_FULL = 38;
54
+ // ---------------------------------------------------------------------------
55
+ // Scene/camera ref capture — runs inside Canvas so it can access R3F context
56
+ // ---------------------------------------------------------------------------
57
+ function SceneCapture({ onSceneReady }) {
58
+ const { scene, camera } = (0, native_1.useThree)();
59
+ (0, react_1.useEffect)(() => {
60
+ onSceneReady(scene, camera);
61
+ // Only fire once on mount — scene/camera identity is stable
62
+ // eslint-disable-next-line react-hooks/exhaustive-deps
63
+ }, []);
64
+ return null;
65
+ }
54
66
  function buildMorphIndex(mesh) {
55
67
  const map = new Map();
56
68
  const dict = mesh.morphTargetDictionary;
@@ -58,7 +70,7 @@ function buildMorphIndex(mesh) {
58
70
  return map;
59
71
  for (const [name, idx] of Object.entries(dict)) {
60
72
  map.set(name.toLowerCase(), idx);
61
- map.set(name, idx); // keep original casing too
73
+ map.set(name, idx);
62
74
  }
63
75
  return map;
64
76
  }
@@ -70,13 +82,12 @@ function resolveIndex(morphIndex, aliases) {
70
82
  }
71
83
  return undefined;
72
84
  }
73
- function AvatarScene({ uri, morphStateRef, fov, onReady, onError }) {
85
+ function AvatarScene({ uri, morphStateRef, fov, onReady, onError, onSceneReady }) {
74
86
  const { camera } = (0, native_1.useThree)();
75
87
  const gltf = (0, native_2.useGLTF)(uri);
76
88
  const headMeshRef = (0, react_1.useRef)(null);
77
89
  const morphIndexRef = (0, react_1.useRef)(new Map());
78
90
  const readyFiredRef = (0, react_1.useRef)(false);
79
- // Set up camera on mount
80
91
  (0, react_1.useEffect)(() => {
81
92
  if (!(camera instanceof THREE.PerspectiveCamera))
82
93
  return;
@@ -85,7 +96,6 @@ function AvatarScene({ uri, morphStateRef, fov, onReady, onError }) {
85
96
  camera.lookAt(CAMERA_TARGET);
86
97
  camera.updateProjectionMatrix();
87
98
  }, [camera, fov]);
88
- // Find the head/face mesh with morph targets after GLTF loads
89
99
  (0, react_1.useEffect)(() => {
90
100
  if (!gltf?.scene)
91
101
  return;
@@ -111,58 +121,43 @@ function AvatarScene({ uri, morphStateRef, fov, onReady, onError }) {
111
121
  onReady();
112
122
  }
113
123
  }, [gltf, onReady, onError]);
114
- // Per-frame morph weight application
115
124
  (0, native_1.useFrame)((_, delta) => {
116
125
  const mesh = headMeshRef.current;
117
126
  if (!mesh?.morphTargetInfluences)
118
127
  return;
119
128
  const state = morphStateRef.current;
120
- const alpha = Math.min(1, state.alpha * delta * 60); // normalize to 60fps
121
- // Merge mood base + viseme target
129
+ const alpha = Math.min(1, state.alpha * delta * 60);
122
130
  const combined = { ...state.moodBase };
123
131
  for (const [name, w] of Object.entries(state.visemeTarget)) {
124
132
  combined[name] = Math.max(combined[name] ?? 0, w);
125
133
  }
126
- // Smooth current toward combined
127
- const allNames = new Set([
128
- ...Object.keys(state.current),
129
- ...Object.keys(combined),
130
- ]);
134
+ const allNames = new Set([...Object.keys(state.current), ...Object.keys(combined)]);
131
135
  for (const name of allNames) {
132
136
  const target = combined[name] ?? 0;
133
137
  const cur = state.current[name] ?? 0;
134
138
  const next = cur + (target - cur) * alpha;
135
139
  state.current[name] = next;
136
140
  const idx = resolveIndex(morphIndexRef.current, [name]);
137
- if (idx !== undefined) {
141
+ if (idx !== undefined)
138
142
  mesh.morphTargetInfluences[idx] = next;
139
- }
140
143
  }
141
- // Decay viseme layer
142
144
  for (const name of Object.keys(state.visemeTarget)) {
143
- state.visemeTarget[name] *= Math.pow(0.1, delta); // ~10x decay/s
144
- if (state.visemeTarget[name] < 0.001) {
145
+ state.visemeTarget[name] *= Math.pow(0.1, delta);
146
+ if (state.visemeTarget[name] < 0.001)
145
147
  delete state.visemeTarget[name];
146
- }
147
148
  }
148
149
  });
149
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("ambientLight", { intensity: 0.6 }), (0, jsx_runtime_1.jsx)("directionalLight", { position: [1, 3, 2], intensity: 1.2, castShadow: false }), (0, jsx_runtime_1.jsx)("primitive", { object: gltf.scene })] }));
150
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [onSceneReady && (0, jsx_runtime_1.jsx)(SceneCapture, { onSceneReady: onSceneReady }), (0, jsx_runtime_1.jsx)("ambientLight", { intensity: 0.6 }), (0, jsx_runtime_1.jsx)("directionalLight", { position: [1, 3, 2], intensity: 1.2, castShadow: false }), (0, jsx_runtime_1.jsx)("primitive", { object: gltf.scene })] }));
150
151
  }
151
152
  // ---------------------------------------------------------------------------
152
- // Error boundary so GLTF load failures surface to onError
153
+ // Error boundary
153
154
  // ---------------------------------------------------------------------------
154
155
  class GltfErrorBoundary extends react_1.default.Component {
155
- constructor(props) {
156
- super(props);
157
- this.state = { hasError: false };
158
- }
156
+ constructor(props) { super(props); this.state = { hasError: false }; }
159
157
  static getDerivedStateFromError() { return { hasError: true }; }
160
158
  componentDidCatch(err) { this.props.onError(err.message); }
161
- render() {
162
- if (this.state.hasError)
163
- return null;
164
- return this.props.children;
165
- }
159
+ render() { if (this.state.hasError)
160
+ return null; return this.props.children; }
166
161
  }
167
162
  // ---------------------------------------------------------------------------
168
163
  // Viseme schedule player
@@ -172,41 +167,30 @@ function applyVisemeCue(morphState, visemeKey) {
172
167
  if (!aliases)
173
168
  return;
174
169
  const w = morphTables_1.VISEME_WEIGHTS[visemeKey] ?? morphTables_1.DEFAULT_VISEME_WEIGHT;
175
- for (const alias of aliases) {
170
+ for (const alias of aliases)
176
171
  morphState.visemeTarget[alias] = w;
177
- }
178
172
  }
179
173
  // ---------------------------------------------------------------------------
180
174
  // Main exported component
181
175
  // ---------------------------------------------------------------------------
182
- exports.WgpuAvatar = (0, react_1.forwardRef)(({ style, avatarUrl, focalLength, mood = 'neutral', onReady, onError, }, ref) => {
183
- // Resolve authenticated file URI (same logic as FilamentAvatar)
176
+ exports.WgpuAvatar = (0, react_1.forwardRef)(({ style, avatarUrl, focalLength, mood = 'neutral', onReady, onError, onSceneReady }, ref) => {
184
177
  const remoteUrl = avatarUrl && (avatarUrl.startsWith('http://') || avatarUrl.startsWith('https://'))
185
- ? avatarUrl
186
- : null;
187
- const fileResult = (0, useAuthedFilamentUri_1.useAuthedFilamentUri)(remoteUrl);
188
- // Lock-in URI on first resolve — never change mid-session
178
+ ? avatarUrl : null;
179
+ const fileResult = (0, useAuthedModelUri_1.useAuthedModelUri)(remoteUrl);
189
180
  const lockedUriRef = (0, react_1.useRef)(null);
190
181
  const currentUri = fileResult?.uri ?? null;
191
- if (lockedUriRef.current === null && currentUri) {
182
+ if (lockedUriRef.current === null && currentUri)
192
183
  lockedUriRef.current = currentUri;
193
- }
194
184
  const localUri = lockedUriRef.current;
195
185
  const fov = focalLength
196
186
  ? Math.round(2 * Math.atan(21.634 / focalLength) * (180 / Math.PI))
197
187
  : CAMERA_FOV_FULL;
198
- // Shared morph state — mutated directly in useFrame, never causes re-renders
199
188
  const morphStateRef = (0, react_1.useRef)({
200
- current: {},
201
- visemeTarget: {},
202
- moodBase: {},
203
- alpha: 0.18,
189
+ current: {}, visemeTarget: {}, moodBase: {}, alpha: 0.18,
204
190
  });
205
- // Update mood baseline when mood prop changes
206
191
  (0, react_1.useEffect)(() => {
207
192
  morphStateRef.current.moodBase = { ...(morphTables_1.MOOD_MORPHS[mood] ?? {}) };
208
193
  }, [mood]);
209
- // Pending viseme schedule
210
194
  const scheduleRef = (0, react_1.useRef)(null);
211
195
  const scheduleTimersRef = (0, react_1.useRef)([]);
212
196
  const [isReady, setIsReady] = (0, react_1.useState)(false);
@@ -215,21 +199,6 @@ exports.WgpuAvatar = (0, react_1.forwardRef)(({ style, avatarUrl, focalLength, m
215
199
  clearTimeout(t);
216
200
  scheduleTimersRef.current = [];
217
201
  }, []);
218
- const handleReady = (0, react_1.useCallback)(() => {
219
- setIsReady(true);
220
- onReady?.();
221
- // Flush any pending schedule
222
- if (scheduleRef.current) {
223
- const s = scheduleRef.current;
224
- scheduleRef.current = null;
225
- applySchedule(s);
226
- }
227
- // eslint-disable-next-line react-hooks/exhaustive-deps
228
- }, [onReady]);
229
- const handleError = (0, react_1.useCallback)((msg) => {
230
- console.warn('[WgpuAvatar] GLTF error:', msg);
231
- onError?.(msg);
232
- }, [onError]);
233
202
  const applySchedule = (0, react_1.useCallback)((schedule) => {
234
203
  clearScheduleTimers();
235
204
  const now = Date.now();
@@ -238,39 +207,41 @@ exports.WgpuAvatar = (0, react_1.forwardRef)(({ style, avatarUrl, focalLength, m
238
207
  for (const cue of schedule.cues) {
239
208
  const delay = cue.startMs - offset;
240
209
  if (delay < -200)
241
- continue; // already expired
242
- const rhubarbKey = cue.viseme;
243
- const visemeKey = morphTables_1.RHUBARB_TO_VISEME[rhubarbKey] ?? rhubarbKey;
210
+ continue;
211
+ const visemeKey = morphTables_1.RHUBARB_TO_VISEME[cue.viseme] ?? cue.viseme;
244
212
  const t = setTimeout(() => {
245
213
  applyVisemeCue(morphStateRef.current, visemeKey);
246
214
  }, Math.max(0, delay));
247
215
  scheduleTimersRef.current.push(t);
248
216
  }
249
217
  }, [clearScheduleTimers]);
250
- // Cleanup on unmount
218
+ const handleReady = (0, react_1.useCallback)(() => {
219
+ setIsReady(true);
220
+ onReady?.();
221
+ if (scheduleRef.current) {
222
+ const s = scheduleRef.current;
223
+ scheduleRef.current = null;
224
+ applySchedule(s);
225
+ }
226
+ // eslint-disable-next-line react-hooks/exhaustive-deps
227
+ }, [onReady]);
228
+ const handleError = (0, react_1.useCallback)((msg) => {
229
+ console.warn('[WgpuAvatar] GLTF error:', msg);
230
+ onError?.(msg);
231
+ }, [onError]);
251
232
  (0, react_1.useEffect)(() => () => clearScheduleTimers(), [clearScheduleTimers]);
252
233
  (0, react_1.useImperativeHandle)(ref, () => ({
253
- setMood: (m) => {
254
- morphStateRef.current.moodBase = { ...(morphTables_1.MOOD_MORPHS[m] ?? {}) };
255
- },
256
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
257
- sendAmplitude: (_amplitude) => {
258
- // Optional: could drive jaw open with amplitude
259
- // morphStateRef.current.visemeTarget['jawOpen'] = _amplitude * 0.3;
260
- },
234
+ setMood: (m) => { morphStateRef.current.moodBase = { ...(morphTables_1.MOOD_MORPHS[m] ?? {}) }; },
235
+ sendAmplitude: (_amplitude) => { },
261
236
  sendViseme: (viseme, weight) => {
262
237
  const aliases = morphTables_1.VISEME_MORPH_ALIASES[viseme];
263
238
  if (!aliases)
264
239
  return;
265
240
  const w = weight ?? morphTables_1.VISEME_WEIGHTS[viseme] ?? morphTables_1.DEFAULT_VISEME_WEIGHT;
266
- for (const alias of aliases) {
241
+ for (const alias of aliases)
267
242
  morphStateRef.current.visemeTarget[alias] = w;
268
- }
269
- },
270
- clearVisemes: () => {
271
- clearScheduleTimers();
272
- morphStateRef.current.visemeTarget = {};
273
243
  },
244
+ clearVisemes: () => { clearScheduleTimers(); morphStateRef.current.visemeTarget = {}; },
274
245
  scheduleVisemes: (schedule) => {
275
246
  if (!isReady) {
276
247
  scheduleRef.current = schedule;
@@ -279,12 +250,9 @@ exports.WgpuAvatar = (0, react_1.forwardRef)(({ style, avatarUrl, focalLength, m
279
250
  applySchedule(schedule);
280
251
  },
281
252
  }), [isReady, applySchedule, clearScheduleTimers]);
282
- if (!localUri) {
253
+ if (!localUri)
283
254
  return (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.placeholder, style] });
284
- }
285
- return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.container, style], children: (0, jsx_runtime_1.jsx)(native_1.Canvas, { style: react_native_1.StyleSheet.absoluteFill, camera: { fov, position: CAMERA_POSITION, near: 0.01, far: 100 }, gl: { antialias: true, alpha: false }, onCreated: ({ gl }) => {
286
- gl.setClearColor(new THREE.Color('#1a1a2e'));
287
- }, children: (0, jsx_runtime_1.jsx)(GltfErrorBoundary, { onError: handleError, children: (0, jsx_runtime_1.jsx)(AvatarScene, { uri: localUri, morphStateRef: morphStateRef, fov: fov, onReady: handleReady, onError: handleError }) }) }) }));
255
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.container, style], children: (0, jsx_runtime_1.jsx)(native_1.Canvas, { style: react_native_1.StyleSheet.absoluteFill, camera: { fov, position: CAMERA_POSITION, near: 0.01, far: 100 }, gl: { antialias: true, alpha: false }, onCreated: ({ gl }) => { gl.setClearColor(new THREE.Color('#1a1a2e')); }, children: (0, jsx_runtime_1.jsx)(GltfErrorBoundary, { onError: handleError, children: (0, jsx_runtime_1.jsx)(AvatarScene, { uri: localUri, morphStateRef: morphStateRef, fov: fov, onReady: handleReady, onError: handleError, onSceneReady: onSceneReady }) }) }) }));
288
256
  });
289
257
  exports.WgpuAvatar.displayName = 'WgpuAvatar';
290
258
  const styles = react_native_1.StyleSheet.create({
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  // ---------------------------------------------------------------------------
3
- // Morph target tables for Filament avatar viseme/mood rendering
3
+ // Morph target tables for avatar viseme/mood rendering
4
4
  // ---------------------------------------------------------------------------
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.MOOD_MORPHS = exports.DEFAULT_VISEME_WEIGHT = exports.VISEME_WEIGHTS = exports.VISEME_MORPH_ALIASES = exports.RHUBARB_TO_VISEME = void 0;
@@ -0,0 +1,11 @@
1
+ export type AuthedFileResult = {
2
+ uri: string;
3
+ size: number;
4
+ } | null;
5
+ /**
6
+ * Downloads a remote URL (with Bearer auth) to the local cache and returns a
7
+ * `file://` URI suitable for WgpuAvatar model source.
8
+ * The native model fetcher doesn't send auth headers, so we pre-fetch here.
9
+ * Also returns the file size in bytes for GPU memory budgeting.
10
+ */
11
+ export declare function useAuthedModelUri(remoteUrl: string | null): AuthedFileResult;
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.useAuthedFilamentUri = void 0;
26
+ exports.useAuthedModelUri = void 0;
27
27
  const react_1 = require("react");
28
28
  const FileSystem = __importStar(require("expo-file-system/legacy"));
29
29
  const studioApi_1 = require("../api/studioApi");
@@ -31,11 +31,11 @@ const studioApi_1 = require("../api/studioApi");
31
31
  const failedUrls = new Set();
32
32
  /**
33
33
  * Downloads a remote URL (with Bearer auth) to the local cache and returns a
34
- * `file://` URI suitable for react-native-filament's <Model> source prop.
35
- * The native Filament fetcher doesn't send auth headers, so we pre-fetch here.
34
+ * `file://` URI suitable for WgpuAvatar model source.
35
+ * The native model fetcher doesn't send auth headers, so we pre-fetch here.
36
36
  * Also returns the file size in bytes for GPU memory budgeting.
37
37
  */
38
- function useAuthedFilamentUri(remoteUrl) {
38
+ function useAuthedModelUri(remoteUrl) {
39
39
  const [result, setResult] = (0, react_1.useState)(null);
40
40
  (0, react_1.useEffect)(() => {
41
41
  if (!remoteUrl) {
@@ -57,7 +57,7 @@ function useAuthedFilamentUri(remoteUrl) {
57
57
  // Strip query params (access_token changes per session) so the same GLB is cached across sessions
58
58
  const urlWithoutQuery = remoteUrl.split('?')[0];
59
59
  const key = urlWithoutQuery.replace(/[^a-zA-Z0-9]/g, '_').slice(-100);
60
- const dir = `${FileSystem.cacheDirectory}filament/`;
60
+ const dir = `${FileSystem.cacheDirectory}model/`;
61
61
  // Append .glb so native loaders can identify the format
62
62
  const localPath = `${dir}${key}.glb`;
63
63
  await FileSystem.makeDirectoryAsync(dir, { intermediates: true });
@@ -85,7 +85,7 @@ function useAuthedFilamentUri(remoteUrl) {
85
85
  const downloaded = await FileSystem.getInfoAsync(dlResult.uri);
86
86
  const downloadedSize = downloaded.size;
87
87
  if (!downloaded.exists || !downloadedSize || downloadedSize < 1000) {
88
- console.error('[useAuthedFilamentUri] Download too small (' + (downloadedSize ?? 0) + 'B), likely an error response');
88
+ console.error('[useAuthedModelUri] Download too small (' + (downloadedSize ?? 0) + 'B), likely an error response');
89
89
  failedUrls.add(remoteUrl);
90
90
  await FileSystem.deleteAsync(dlResult.uri);
91
91
  return;
@@ -96,7 +96,7 @@ function useAuthedFilamentUri(remoteUrl) {
96
96
  position: 0,
97
97
  });
98
98
  if (dlHeader !== 'Z2xURg==') {
99
- console.error('[useAuthedFilamentUri] Downloaded file is not a valid GLB (bad magic bytes)');
99
+ console.error('[useAuthedModelUri] Downloaded file is not a valid GLB (bad magic bytes)');
100
100
  failedUrls.add(remoteUrl);
101
101
  await FileSystem.deleteAsync(dlResult.uri);
102
102
  return;
@@ -105,7 +105,7 @@ function useAuthedFilamentUri(remoteUrl) {
105
105
  setResult({ uri: dlResult.uri, size: downloadedSize });
106
106
  }
107
107
  catch (e) {
108
- console.error('[useAuthedFilamentUri] Failed to download:', remoteUrl.slice(-60), e);
108
+ console.error('[useAuthedModelUri] Failed to download:', remoteUrl.slice(-60), e);
109
109
  }
110
110
  })();
111
111
  return () => {
@@ -114,4 +114,4 @@ function useAuthedFilamentUri(remoteUrl) {
114
114
  }, [remoteUrl]);
115
115
  return result;
116
116
  }
117
- exports.useAuthedFilamentUri = useAuthedFilamentUri;
117
+ exports.useAuthedModelUri = useAuthedModelUri;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talking-head-studio",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",
@@ -37,16 +37,6 @@
37
37
  "types": "./dist/wardrobe/index.d.ts",
38
38
  "default": "./dist/wardrobe/index.js"
39
39
  },
40
- "./filament": {
41
- "react-native": "./dist/filament/index.js",
42
- "types": "./dist/filament/index.d.ts",
43
- "default": "./dist/filament/index.js"
44
- },
45
- "./filament/editor": {
46
- "react-native": "./dist/filament/editor/index.js",
47
- "types": "./dist/filament/editor/index.d.ts",
48
- "default": "./dist/filament/editor/index.js"
49
- },
50
40
  "./wgpu": {
51
41
  "react-native": "./dist/wgpu/index.js",
52
42
  "types": "./dist/wgpu/index.d.ts",
@@ -104,6 +94,7 @@
104
94
  "sideEffects": false,
105
95
  "dependencies": {
106
96
  "@mediapipe/tasks-vision": "^0.10.34",
97
+ "expo-gl": "^55.0.10",
107
98
  "zustand": "^5.0.12"
108
99
  },
109
100
  "peerDependencies": {
@@ -114,7 +105,6 @@
114
105
  "expo-file-system": ">=17",
115
106
  "react": ">=18",
116
107
  "react-native": ">=0.73",
117
- "react-native-filament": ">=1",
118
108
  "react-native-webview": ">=13",
119
109
  "react-native-wgpu": ">=0.1",
120
110
  "three": ">=0.170"
@@ -126,9 +116,6 @@
126
116
  "react-native-webview": {
127
117
  "optional": true
128
118
  },
129
- "react-native-filament": {
130
- "optional": true
131
- },
132
119
  "react-native-wgpu": {
133
120
  "optional": true
134
121
  },
@@ -1,41 +0,0 @@
1
- /**
2
- * FilamentAvatar — native Filament-based avatar renderer.
3
- *
4
- * Replaces the TalkingHead WebView on iOS/Android with direct Filament
5
- * morph target writes — no WebView bridge, no JS→native→JS roundtrip,
6
- * visemes applied at 60fps in React Native JS.
7
- *
8
- * Implements the full TalkingHeadRef interface so it can be swapped in
9
- * transparently for the WebView renderer.
10
- */
11
- import React from 'react';
12
- import { ViewStyle, StyleProp } from 'react-native';
13
- import type { TalkingHeadViseme, TalkingHeadVisemeSchedule, TalkingHeadAccessory, TalkingHeadMood } from '../TalkingHead';
14
- export declare const CAMERA_FOCAL_FULL = 50;
15
- export declare const CAMERA_FOCAL_PIP = 85;
16
- export interface FilamentAvatarRef {
17
- sendViseme: (viseme: TalkingHeadViseme, weight?: number) => void;
18
- scheduleVisemes: (schedule: TalkingHeadVisemeSchedule) => void;
19
- clearVisemes: () => void;
20
- sendAmplitude: (amplitude: number) => void;
21
- setMood: (mood: TalkingHeadMood) => void;
22
- setHairColor: (color: string) => void;
23
- setSkinColor: (color: string) => void;
24
- setEyeColor: (color: string) => void;
25
- setAccessories: (accessories: TalkingHeadAccessory[]) => void;
26
- }
27
- interface FilamentAvatarProps {
28
- style?: StyleProp<ViewStyle>;
29
- avatarUrl: string | null;
30
- aspect?: number;
31
- focalLength?: number;
32
- mood?: TalkingHeadMood;
33
- hairColor?: string;
34
- skinColor?: string;
35
- eyeColor?: string;
36
- accessories?: TalkingHeadAccessory[];
37
- onReady?: () => void;
38
- onError?: (message: string) => void;
39
- }
40
- export declare const FilamentAvatar: React.ForwardRefExoticComponent<FilamentAvatarProps & React.RefAttributes<FilamentAvatarRef>>;
41
- export {};