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.
- package/dist/TalkingHead.web.js +7 -17
- package/dist/api/studioApi.js +26 -25
- package/dist/appearance/apply.d.ts +6 -0
- package/dist/appearance/apply.js +72 -7
- package/dist/appearance/index.d.ts +1 -1
- package/dist/appearance/index.js +2 -1
- package/dist/appearance/matchers.js +2 -1
- package/dist/appearance/schema.d.ts +1 -0
- package/dist/appearance/schema.js +3 -1
- package/dist/appearance/sharedState.d.ts +11 -0
- package/dist/appearance/sharedState.js +13 -0
- package/dist/editor/AvatarCanvas.d.ts +1 -0
- package/dist/editor/AvatarCanvas.js +3 -2
- package/dist/editor/AvatarModel.d.ts +1 -3
- package/dist/editor/AvatarModel.js +13 -5
- package/dist/editor/RigidAccessory.js +2 -1
- package/dist/editor/SkinnedClothing.js +9 -18
- package/dist/filament/FilamentAvatar.js +20 -42
- package/dist/filament/editor/FilamentEditor.js +12 -21
- package/dist/filament/editor/FilamentEditor.web.js +2 -1
- package/dist/filament/editor/PrecisionPanel.js +8 -18
- package/dist/filament/editor/boneSnap.js +12 -22
- package/dist/filament/editor/studioTheme.js +2 -2
- package/dist/filament/useAuthedFilamentUri.js +9 -18
- package/dist/html.js +2 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -3
- package/dist/sketchfab/api.js +5 -4
- package/dist/sketchfab/useSketchfabSearch.js +2 -1
- package/dist/tts/useDirectVisemeStream.js +2 -1
- package/dist/tts/useMotionMarkers.d.ts +1 -0
- package/dist/tts/useMotionMarkers.js +2 -1
- package/dist/utils/avatarUtils.js +3 -2
- package/dist/utils/faceLandmarkerToShapeWeights.d.ts +44 -0
- package/dist/utils/faceLandmarkerToShapeWeights.js +112 -0
- package/dist/voice/convertToWav.js +2 -1
- package/dist/voice/useAudioPlayer.js +2 -1
- package/dist/voice/useAudioRecording.js +2 -1
- package/dist/wardrobe/useAvatarWardrobeHydration.js +2 -1
- package/package.json +15 -3
package/dist/TalkingHead.web.js
CHANGED
|
@@ -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) ||
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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");
|
package/dist/api/studioApi.js
CHANGED
|
@@ -1,30 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.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 {};
|
package/dist/appearance/apply.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.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
|
-
//
|
|
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
|
-
|
|
38
|
-
|
|
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';
|
package/dist/appearance/index.js
CHANGED
|
@@ -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 =
|
|
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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.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,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.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,
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
33
|
-
}
|
|
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 =
|
|
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) ||
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 =
|
|
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;
|