talking-head-studio 0.3.6 → 0.3.7

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 (40) hide show
  1. package/dist/TalkingHead.web.js +7 -17
  2. package/dist/api/studioApi.js +26 -25
  3. package/dist/appearance/apply.d.ts +6 -0
  4. package/dist/appearance/apply.js +72 -7
  5. package/dist/appearance/index.d.ts +1 -1
  6. package/dist/appearance/index.js +2 -1
  7. package/dist/appearance/matchers.js +2 -1
  8. package/dist/appearance/schema.d.ts +1 -0
  9. package/dist/appearance/schema.js +3 -1
  10. package/dist/appearance/sharedState.d.ts +11 -0
  11. package/dist/appearance/sharedState.js +13 -0
  12. package/dist/editor/AvatarCanvas.d.ts +1 -0
  13. package/dist/editor/AvatarCanvas.js +3 -2
  14. package/dist/editor/AvatarModel.d.ts +1 -3
  15. package/dist/editor/AvatarModel.js +13 -5
  16. package/dist/editor/RigidAccessory.js +2 -1
  17. package/dist/editor/SkinnedClothing.js +9 -18
  18. package/dist/filament/FilamentAvatar.js +20 -42
  19. package/dist/filament/editor/FilamentEditor.js +12 -21
  20. package/dist/filament/editor/FilamentEditor.web.js +2 -1
  21. package/dist/filament/editor/PrecisionPanel.js +8 -18
  22. package/dist/filament/editor/boneSnap.js +12 -22
  23. package/dist/filament/editor/studioTheme.js +2 -2
  24. package/dist/filament/useAuthedFilamentUri.js +9 -18
  25. package/dist/html.js +2 -1
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.js +1 -3
  28. package/dist/sketchfab/api.js +5 -4
  29. package/dist/sketchfab/useSketchfabSearch.js +2 -1
  30. package/dist/tts/useDirectVisemeStream.js +2 -1
  31. package/dist/tts/useMotionMarkers.d.ts +1 -0
  32. package/dist/tts/useMotionMarkers.js +2 -1
  33. package/dist/utils/avatarUtils.js +3 -2
  34. package/dist/utils/faceLandmarkerToShapeWeights.d.ts +44 -0
  35. package/dist/utils/faceLandmarkerToShapeWeights.js +112 -0
  36. package/dist/voice/convertToWav.js +2 -1
  37. package/dist/voice/useAudioPlayer.js +2 -1
  38. package/dist/voice/useAudioRecording.js +2 -1
  39. package/dist/wardrobe/useAvatarWardrobeHydration.js +2 -1
  40. package/package.json +15 -3
@@ -15,23 +15,13 @@ 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
- 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
- })();
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
35
25
  Object.defineProperty(exports, "__esModule", { value: true });
36
26
  exports.TalkingHead = void 0;
37
27
  const jsx_runtime_1 = require("react/jsx-runtime");
@@ -1,30 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.configureAvatarApi = configureAvatarApi;
4
- exports.getToken = getToken;
5
- exports.studioFetch = studioFetch;
6
- exports.getMyAvatars = getMyAvatars;
7
- exports.getAvatar = getAvatar;
8
- exports.updateAvatar = updateAvatar;
9
- exports.deleteAvatar = deleteAvatar;
10
- exports.createAvatar = createAvatar;
11
- exports.getPublicAvatars = getPublicAvatars;
12
- exports.getVoiceProfileSamples = getVoiceProfileSamples;
13
- exports.getVoiceProfiles = getVoiceProfiles;
14
- exports.setDefaultVoice = setDefaultVoice;
15
- exports.removeDefaultVoice = removeDefaultVoice;
16
- exports.avatarFileUrl = avatarFileUrl;
17
- exports.avatarThumbnailUrl = avatarThumbnailUrl;
18
- exports.avatarAnimatedThumbnailUrl = avatarAnimatedThumbnailUrl;
19
- exports.thumbnailHeaders = thumbnailHeaders;
20
- exports.listAssets = listAssets;
21
- exports.getAsset = getAsset;
22
- exports.uploadAsset = uploadAsset;
23
- exports.deleteAsset = deleteAsset;
24
- exports.suggestPlacement = suggestPlacement;
25
- exports.assetFileUrl = assetFileUrl;
26
- exports.createVoiceProfile = createVoiceProfile;
27
- exports.uploadVoiceSample = uploadVoiceSample;
3
+ exports.uploadVoiceSample = exports.createVoiceProfile = exports.assetFileUrl = exports.suggestPlacement = exports.deleteAsset = exports.uploadAsset = exports.getAsset = exports.listAssets = exports.thumbnailHeaders = exports.avatarAnimatedThumbnailUrl = exports.avatarThumbnailUrl = exports.avatarFileUrl = exports.removeDefaultVoice = exports.setDefaultVoice = exports.getVoiceProfiles = exports.getVoiceProfileSamples = exports.getPublicAvatars = exports.createAvatar = exports.deleteAvatar = exports.updateAvatar = exports.getAvatar = exports.getMyAvatars = exports.studioFetch = exports.getToken = exports.configureAvatarApi = void 0;
28
4
  // ---------------------------------------------------------------------------
29
5
  // Injectable configuration
30
6
  // ---------------------------------------------------------------------------
@@ -35,9 +11,11 @@ function configureAvatarApi(opts) {
35
11
  _baseUrl = opts.baseUrl;
36
12
  _getToken = opts.getToken;
37
13
  }
14
+ exports.configureAvatarApi = configureAvatarApi;
38
15
  async function getToken() {
39
16
  return _getToken ? _getToken() : null;
40
17
  }
18
+ exports.getToken = getToken;
41
19
  // ---------------------------------------------------------------------------
42
20
  // Core fetch wrappers
43
21
  // ---------------------------------------------------------------------------
@@ -70,6 +48,7 @@ async function studioFetch(path, options) {
70
48
  }
71
49
  return res.json();
72
50
  }
51
+ exports.studioFetch = studioFetch;
73
52
  /** POST/PUT with FormData, sharing auth token logic. */
74
53
  async function studioFetchForm(path, method, formData) {
75
54
  const token = await getToken();
@@ -97,9 +76,11 @@ async function studioFetchForm(path, method, formData) {
97
76
  function getMyAvatars() {
98
77
  return studioFetch('/v1/avatars');
99
78
  }
79
+ exports.getMyAvatars = getMyAvatars;
100
80
  function getAvatar(id) {
101
81
  return studioFetch(`/v1/avatars/${id}`);
102
82
  }
83
+ exports.getAvatar = getAvatar;
103
84
  function updateAvatar(id, data) {
104
85
  return studioFetch(`/v1/avatars/${id}`, {
105
86
  method: 'PATCH',
@@ -107,9 +88,11 @@ function updateAvatar(id, data) {
107
88
  body: JSON.stringify(data),
108
89
  });
109
90
  }
91
+ exports.updateAvatar = updateAvatar;
110
92
  function deleteAvatar(id) {
111
93
  return studioFetch(`/v1/avatars/${id}`, { method: 'DELETE' });
112
94
  }
95
+ exports.deleteAvatar = deleteAvatar;
113
96
  async function createAvatar(fileUri, name, description) {
114
97
  const formData = new FormData();
115
98
  formData.append('file', {
@@ -123,18 +106,22 @@ async function createAvatar(fileUri, name, description) {
123
106
  }
124
107
  return studioFetchForm('/v1/avatars', 'POST', formData);
125
108
  }
109
+ exports.createAvatar = createAvatar;
126
110
  function getPublicAvatars() {
127
111
  return studioFetch('/v1/avatars/public');
128
112
  }
113
+ exports.getPublicAvatars = getPublicAvatars;
129
114
  // ---------------------------------------------------------------------------
130
115
  // Voice profile endpoints
131
116
  // ---------------------------------------------------------------------------
132
117
  function getVoiceProfileSamples(profileId) {
133
118
  return studioFetch(`/profiles/${profileId}/samples`);
134
119
  }
120
+ exports.getVoiceProfileSamples = getVoiceProfileSamples;
135
121
  function getVoiceProfiles() {
136
122
  return studioFetch('/profiles');
137
123
  }
124
+ exports.getVoiceProfiles = getVoiceProfiles;
138
125
  function setDefaultVoice(avatarId, profileId) {
139
126
  return studioFetch(`/v1/avatars/${avatarId}/default-voice`, {
140
127
  method: 'PUT',
@@ -142,11 +129,13 @@ function setDefaultVoice(avatarId, profileId) {
142
129
  body: JSON.stringify({ profile_id: profileId }),
143
130
  });
144
131
  }
132
+ exports.setDefaultVoice = setDefaultVoice;
145
133
  function removeDefaultVoice(avatarId) {
146
134
  return studioFetch(`/v1/avatars/${avatarId}/default-voice`, {
147
135
  method: 'DELETE',
148
136
  });
149
137
  }
138
+ exports.removeDefaultVoice = removeDefaultVoice;
150
139
  // ---------------------------------------------------------------------------
151
140
  // URL helpers
152
141
  // ---------------------------------------------------------------------------
@@ -154,23 +143,27 @@ function avatarFileUrl(avatar) {
154
143
  const base = `${_baseUrl}${avatar.url}`;
155
144
  return avatar.updated_at ? `${base}?v=${encodeURIComponent(avatar.updated_at)}` : base;
156
145
  }
146
+ exports.avatarFileUrl = avatarFileUrl;
157
147
  function avatarThumbnailUrl(avatar) {
158
148
  const url = avatar.thumbnail_url || avatar.animated_thumbnail_url;
159
149
  if (!url)
160
150
  return null;
161
151
  return `${_baseUrl}${url}`;
162
152
  }
153
+ exports.avatarThumbnailUrl = avatarThumbnailUrl;
163
154
  function avatarAnimatedThumbnailUrl(avatar) {
164
155
  if (!avatar.animated_thumbnail_url)
165
156
  return null;
166
157
  return `${_baseUrl}${avatar.animated_thumbnail_url}`;
167
158
  }
159
+ exports.avatarAnimatedThumbnailUrl = avatarAnimatedThumbnailUrl;
168
160
  async function thumbnailHeaders() {
169
161
  const token = await getToken();
170
162
  if (!token)
171
163
  return {};
172
164
  return { Authorization: `Bearer ${token}` };
173
165
  }
166
+ exports.thumbnailHeaders = thumbnailHeaders;
174
167
  // ---------------------------------------------------------------------------
175
168
  // Wearable Asset endpoints
176
169
  // ---------------------------------------------------------------------------
@@ -178,9 +171,11 @@ function listAssets(category) {
178
171
  const query = category ? `?category=${encodeURIComponent(category)}` : '';
179
172
  return studioFetch(`/v1/assets${query}`);
180
173
  }
174
+ exports.listAssets = listAssets;
181
175
  function getAsset(id) {
182
176
  return studioFetch(`/v1/assets/${id}`);
183
177
  }
178
+ exports.getAsset = getAsset;
184
179
  async function uploadAsset(fileUri, meta) {
185
180
  const formData = new FormData();
186
181
  formData.append('file', {
@@ -203,16 +198,20 @@ async function uploadAsset(fileUri, meta) {
203
198
  formData.append('hides_body_parts', JSON.stringify(meta.hides_body_parts));
204
199
  return studioFetchForm('/v1/assets', 'POST', formData);
205
200
  }
201
+ exports.uploadAsset = uploadAsset;
206
202
  function deleteAsset(id) {
207
203
  return studioFetch(`/v1/assets/${id}`, { method: 'DELETE' });
208
204
  }
205
+ exports.deleteAsset = deleteAsset;
209
206
  function suggestPlacement(assetId, avatarId) {
210
207
  return studioFetch(`/v1/assets/${assetId}/suggest-placement?avatar_id=${encodeURIComponent(avatarId)}`, { method: 'POST' });
211
208
  }
209
+ exports.suggestPlacement = suggestPlacement;
212
210
  function assetFileUrl(asset) {
213
211
  const base = `${_baseUrl}${asset.url}`;
214
212
  return asset.updated_at ? `${base}?v=${encodeURIComponent(asset.updated_at)}` : base;
215
213
  }
214
+ exports.assetFileUrl = assetFileUrl;
216
215
  async function createVoiceProfile(name, language = 'en') {
217
216
  return studioFetch('/profiles', {
218
217
  method: 'POST',
@@ -222,6 +221,7 @@ async function createVoiceProfile(name, language = 'en') {
222
221
  body: JSON.stringify({ name, language }),
223
222
  });
224
223
  }
224
+ exports.createVoiceProfile = createVoiceProfile;
225
225
  async function uploadVoiceSample(profileId, fileUri, fileName, referenceText) {
226
226
  const formData = new FormData();
227
227
  formData.append('reference_text', referenceText);
@@ -233,3 +233,4 @@ async function uploadVoiceSample(profileId, fileUri, fileName, referenceText) {
233
233
  });
234
234
  return studioFetchForm(`/profiles/${profileId}/samples`, 'POST', formData);
235
235
  }
236
+ exports.uploadVoiceSample = uploadVoiceSample;
@@ -2,5 +2,11 @@ import { type AvatarAppearance } from './schema';
2
2
  type Object3DLike = {
3
3
  traverse?: (visitor: (object: unknown) => void) => void;
4
4
  };
5
+ /**
6
+ * Invalidate the morph cache for a scene — call this when the avatar GLB
7
+ * is replaced (e.g. after a new model loads) so the next appearance update
8
+ * re-traverses the fresh mesh.
9
+ */
10
+ export declare function invalidateMorphCache(object3d: object): void;
5
11
  export declare function applyAppearanceToObject3D(object3d: Object3DLike, appearance: AvatarAppearance): void;
6
12
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.applyAppearanceToObject3D = applyAppearanceToObject3D;
3
+ exports.applyAppearanceToObject3D = exports.invalidateMorphCache = void 0;
4
4
  const matchers_1 = require("./matchers");
5
5
  const schema_1 = require("./schema");
6
6
  function asMaterialArray(material) {
@@ -12,13 +12,64 @@ function asMaterialArray(material) {
12
12
  function getVertexCount(mesh) {
13
13
  return mesh.geometry?.attributes?.position?.count ?? 0;
14
14
  }
15
+ const morphCacheByScene = new WeakMap();
16
+ function buildMorphCache(object3d) {
17
+ const cache = new Map();
18
+ object3d.traverse?.((object) => {
19
+ const mesh = object;
20
+ if (!mesh?.isMesh ||
21
+ !mesh.morphTargetDictionary ||
22
+ !mesh.morphTargetInfluences) {
23
+ return;
24
+ }
25
+ for (const [name, idx] of Object.entries(mesh.morphTargetDictionary)) {
26
+ // Index by both the full name AND the bare name after a colon prefix.
27
+ // RPM avatars use "Wolf3D_Head:browInnerUp" style names; we want to
28
+ // match lookups using just "browInnerUp" (the ARKit part).
29
+ const keys = new Set();
30
+ const fullKey = name.toLowerCase();
31
+ keys.add(fullKey);
32
+ const colonIdx = name.indexOf(':');
33
+ if (colonIdx !== -1) {
34
+ keys.add(name.slice(colonIdx + 1).toLowerCase());
35
+ }
36
+ for (const key of keys) {
37
+ if (!cache.has(key))
38
+ cache.set(key, []);
39
+ cache.get(key).push({ influences: mesh.morphTargetInfluences, idx });
40
+ }
41
+ }
42
+ });
43
+ return cache;
44
+ }
45
+ function getMorphCache(object3d) {
46
+ // object3d must be an object reference for WeakMap
47
+ const key = object3d;
48
+ let cache = morphCacheByScene.get(key);
49
+ if (!cache) {
50
+ cache = buildMorphCache(object3d);
51
+ morphCacheByScene.set(key, cache);
52
+ }
53
+ return cache;
54
+ }
55
+ /**
56
+ * Invalidate the morph cache for a scene — call this when the avatar GLB
57
+ * is replaced (e.g. after a new model loads) so the next appearance update
58
+ * re-traverses the fresh mesh.
59
+ */
60
+ function invalidateMorphCache(object3d) {
61
+ morphCacheByScene.delete(object3d);
62
+ }
63
+ exports.invalidateMorphCache = invalidateMorphCache;
64
+ // ---------------------------------------------------------------------------
65
+ // Main apply function
66
+ // ---------------------------------------------------------------------------
15
67
  function applyAppearanceToObject3D(object3d, appearance) {
16
68
  if (typeof object3d.traverse !== 'function') {
17
69
  return;
18
70
  }
19
71
  const normalizedAppearance = (0, schema_1.normalizeAppearance)(appearance);
20
- // First pass: apply name-matched colors and track whether skin was matched,
21
- // also track the largest mesh for fallback skin coloring.
72
+ // --- Color pass (traverse every time material refs may be shared) -------
22
73
  let skinMatched = false;
23
74
  let largestVertexCount = 0;
24
75
  let largestMeshMaterial = null;
@@ -34,8 +85,9 @@ function applyAppearanceToObject3D(object3d, appearance) {
34
85
  }
35
86
  const target = (0, matchers_1.pickTargetForMaterialName)(material.name);
36
87
  if (!target) {
37
- // Track the largest unmatched mesh as a skin fallback candidate
38
- if (vertexCount > largestVertexCount && material.color && typeof material.color.set === 'function') {
88
+ if (vertexCount > largestVertexCount &&
89
+ material.color &&
90
+ typeof material.color.set === 'function') {
39
91
  largestVertexCount = vertexCount;
40
92
  largestMeshMaterial = material;
41
93
  }
@@ -51,9 +103,22 @@ function applyAppearanceToObject3D(object3d, appearance) {
51
103
  material.color.set(color);
52
104
  }
53
105
  });
54
- // Second pass: if no skin material was found by name, apply skin color to
55
- // the largest mesh — skin is the largest organ after all.
56
106
  if (!skinMatched && largestMeshMaterial && normalizedAppearance.skinColor) {
57
107
  largestMeshMaterial.color.set(normalizedAppearance.skinColor);
58
108
  }
109
+ // --- Face shape pass (cached morph target lookup) -------------------------
110
+ const weights = normalizedAppearance.faceShapeWeights;
111
+ if (!weights || Object.keys(weights).length === 0) {
112
+ return;
113
+ }
114
+ const cache = getMorphCache(object3d);
115
+ for (const [name, weight] of Object.entries(weights)) {
116
+ const entries = cache.get(name.toLowerCase());
117
+ if (!entries)
118
+ continue;
119
+ for (const { influences, idx } of entries) {
120
+ influences[idx] = weight;
121
+ }
122
+ }
59
123
  }
124
+ exports.applyAppearanceToObject3D = applyAppearanceToObject3D;
@@ -1,4 +1,4 @@
1
- export { applyAppearanceToObject3D } from './apply';
1
+ export { applyAppearanceToObject3D, invalidateMorphCache } from './apply';
2
2
  export { pickTargetForMaterialName } from './matchers';
3
3
  export { type AvatarAppearance, normalizeAppearance } from './schema';
4
4
  export type { AppearanceTarget } from './matchers';
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizeAppearance = exports.pickTargetForMaterialName = exports.applyAppearanceToObject3D = void 0;
3
+ exports.normalizeAppearance = exports.pickTargetForMaterialName = exports.invalidateMorphCache = exports.applyAppearanceToObject3D = void 0;
4
4
  var apply_1 = require("./apply");
5
5
  Object.defineProperty(exports, "applyAppearanceToObject3D", { enumerable: true, get: function () { return apply_1.applyAppearanceToObject3D; } });
6
+ Object.defineProperty(exports, "invalidateMorphCache", { enumerable: true, get: function () { return apply_1.invalidateMorphCache; } });
6
7
  var matchers_1 = require("./matchers");
7
8
  Object.defineProperty(exports, "pickTargetForMaterialName", { enumerable: true, get: function () { return matchers_1.pickTargetForMaterialName; } });
8
9
  var schema_1 = require("./schema");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pickTargetForMaterialName = pickTargetForMaterialName;
3
+ exports.pickTargetForMaterialName = void 0;
4
4
  function tokenizeMaterialName(name) {
5
5
  return name
6
6
  .replace(/([a-z\d])([A-Z])/g, '$1 $2')
@@ -33,3 +33,4 @@ function pickTargetForMaterialName(name) {
33
33
  }
34
34
  return null;
35
35
  }
36
+ exports.pickTargetForMaterialName = pickTargetForMaterialName;
@@ -4,5 +4,6 @@ export interface AvatarAppearance {
4
4
  hairColor?: string;
5
5
  skinColor?: string;
6
6
  eyeColor?: string;
7
+ faceShapeWeights?: Record<string, number>;
7
8
  }
8
9
  export declare function normalizeAppearance(input: AvatarAppearance): AvatarAppearance;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizeAppearance = normalizeAppearance;
3
+ exports.normalizeAppearance = void 0;
4
4
  const HEX_COLOR_PATTERN = /^#(?:[\dA-Fa-f]{3}|[\dA-Fa-f]{6})$/;
5
5
  function normalizeHexColor(value) {
6
6
  if (!HEX_COLOR_PATTERN.test(value)) {
@@ -19,5 +19,7 @@ function normalizeAppearance(input) {
19
19
  hairColor: input.hairColor != null ? normalizeHexColor(input.hairColor) : undefined,
20
20
  skinColor: input.skinColor != null ? normalizeHexColor(input.skinColor) : undefined,
21
21
  eyeColor: input.eyeColor != null ? normalizeHexColor(input.eyeColor) : undefined,
22
+ faceShapeWeights: input.faceShapeWeights,
22
23
  };
23
24
  }
25
+ exports.normalizeAppearance = normalizeAppearance;
@@ -0,0 +1,11 @@
1
+ import type { AvatarAppearance } from './schema';
2
+ /**
3
+ * Module-level mutable ref shared between the outer React tree
4
+ * (CreatorApp) and the R3F Canvas (AvatarModel.useFrame).
5
+ *
6
+ * CreatorApp writes here on every appearance change.
7
+ * AvatarModel reads here every frame — no props-through-Canvas needed.
8
+ */
9
+ export declare const sharedAppearance: {
10
+ current: AvatarAppearance;
11
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sharedAppearance = void 0;
4
+ /**
5
+ * Module-level mutable ref shared between the outer React tree
6
+ * (CreatorApp) and the R3F Canvas (AvatarModel.useFrame).
7
+ *
8
+ * CreatorApp writes here on every appearance change.
9
+ * AvatarModel reads here every frame — no props-through-Canvas needed.
10
+ */
11
+ exports.sharedAppearance = {
12
+ current: { version: 1 },
13
+ };
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import type { AvatarAppearance } from '../appearance';
2
3
  import type { AssetPlacement, EquippedAsset } from './types';
3
4
  interface AvatarCanvasProps {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AvatarCanvas = AvatarCanvas;
3
+ exports.AvatarCanvas = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  // @ts-nocheck
6
6
  const drei_1 = require("@react-three/drei");
@@ -43,7 +43,7 @@ function AvatarCanvas({ avatarUrl, appearance, equipped = [], placements = {}, e
43
43
  ]
44
44
  .filter(Boolean)
45
45
  .join(' ');
46
- 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)(AvatarModel_1.AvatarModel, { url: avatarUrl, appearance: appearance, scale: 1, onSkeletonReady: handleSkeletonReady, onBoundsReady: handleBoundsReady }), equippedItems.map((asset) => {
46
+ 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, enabled: !editingAssetId }), (0, jsx_runtime_1.jsx)(AvatarModel_1.AvatarModel, { url: avatarUrl, scale: 1, onSkeletonReady: handleSkeletonReady, onBoundsReady: handleBoundsReady }), equippedItems.map((asset) => {
47
47
  if (!asset)
48
48
  return null;
49
49
  const llmPlacement = placements[asset.id];
@@ -67,3 +67,4 @@ function AvatarCanvas({ avatarUrl, appearance, equipped = [], placements = {}, e
67
67
  return null;
68
68
  })] }) }) })) : ((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" }) }))] }));
69
69
  }
70
+ exports.AvatarCanvas = AvatarCanvas;
@@ -1,11 +1,9 @@
1
1
  import { Vector3 } from 'three';
2
- import type { AvatarAppearance } from '../appearance';
3
2
  interface AvatarModelProps {
4
3
  url: string;
5
- appearance?: AvatarAppearance;
6
4
  scale?: number;
7
5
  onSkeletonReady?: (skeleton: any) => void;
8
6
  onBoundsReady?: (center: typeof Vector3.prototype, height: number) => void;
9
7
  }
10
- export declare function AvatarModel({ url, appearance, scale, onSkeletonReady, onBoundsReady, }: AvatarModelProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function AvatarModel({ url, scale, onSkeletonReady, onBoundsReady, }: AvatarModelProps): import("react/jsx-runtime").JSX.Element;
11
9
  export {};
@@ -1,18 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AvatarModel = AvatarModel;
3
+ exports.AvatarModel = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  // @ts-nocheck
6
6
  const drei_1 = require("@react-three/drei");
7
+ const fiber_1 = require("@react-three/fiber");
7
8
  const react_1 = require("react");
8
9
  const three_1 = require("three");
9
10
  const appearance_1 = require("../appearance");
10
- function AvatarModel({ url, appearance, scale = 1, onSkeletonReady, onBoundsReady, }) {
11
+ const sharedState_1 = require("../appearance/sharedState");
12
+ function AvatarModel({ url, scale = 1, onSkeletonReady, onBoundsReady, }) {
11
13
  const { scene } = (0, drei_1.useGLTF)(url);
12
14
  const groupRef = (0, react_1.useRef)(null);
15
+ // Scene load / change: invalidate morph cache + report skeleton / bounds
13
16
  (0, react_1.useEffect)(() => {
14
17
  if (!scene)
15
18
  return;
19
+ (0, appearance_1.invalidateMorphCache)(scene);
16
20
  scene.traverse((child) => {
17
21
  if (child.isSkinnedMesh && child.skeleton) {
18
22
  onSkeletonReady?.(child.skeleton);
@@ -26,10 +30,14 @@ function AvatarModel({ url, appearance, scale = 1, onSkeletonReady, onBoundsRead
26
30
  onBoundsReady(center, height);
27
31
  }
28
32
  }, [scene, onSkeletonReady, onBoundsReady]);
29
- (0, react_1.useEffect)(() => {
33
+ // Apply appearance every frame from the shared module-level ref.
34
+ // sharedAppearance.current is written by CreatorApp outside the Canvas —
35
+ // no props chain, no circular deps, always fresh on every frame.
36
+ (0, fiber_1.useFrame)(() => {
30
37
  if (!scene)
31
38
  return;
32
- (0, appearance_1.applyAppearanceToObject3D)(scene, appearance ?? { version: 1 });
33
- }, [scene, appearance]);
39
+ (0, appearance_1.applyAppearanceToObject3D)(scene, sharedState_1.sharedAppearance.current);
40
+ });
34
41
  return (0, jsx_runtime_1.jsx)("primitive", { ref: groupRef, object: scene, scale: scale });
35
42
  }
43
+ exports.AvatarModel = AvatarModel;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RigidAccessory = RigidAccessory;
3
+ exports.RigidAccessory = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  // @ts-nocheck
6
6
  const drei_1 = require("@react-three/drei");
@@ -76,3 +76,4 @@ function RigidAccessory({ assetId, url, avatarScene, attachBone, offsetPosition,
76
76
  }
77
77
  return (0, jsx_runtime_1.jsx)("primitive", { object: clone });
78
78
  }
79
+ exports.RigidAccessory = RigidAccessory;
@@ -15,25 +15,15 @@ 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
- 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
- })();
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
35
25
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.SkinnedClothing = SkinnedClothing;
26
+ exports.SkinnedClothing = void 0;
37
27
  const jsx_runtime_1 = require("react/jsx-runtime");
38
28
  // @ts-nocheck
39
29
  const drei_1 = require("@react-three/drei");
@@ -123,3 +113,4 @@ function SkinnedClothing({ url, avatarSkeleton }) {
123
113
  }, [scene, avatarSkeleton]);
124
114
  return (0, jsx_runtime_1.jsx)("group", { ref: groupRef });
125
115
  }
116
+ exports.SkinnedClothing = SkinnedClothing;