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,29 @@
|
|
|
1
|
+
export { default as ColorSwatch } from './ColorSwatch';
|
|
2
|
+
export type { ColorSwatchProps } from './ColorSwatch';
|
|
3
|
+
export { StudioSurfaceCard, default as StudioSurfaceCardDefault } from './StudioSurfaceCard';
|
|
4
|
+
export { StudioEmptyState } from './StudioEmptyState';
|
|
5
|
+
export { StudioSectionHeader } from './StudioSectionHeader';
|
|
6
|
+
export { StudioFloatingAction } from './StudioFloatingAction';
|
|
7
|
+
export { StudioBrowseHeader } from './StudioBrowseHeader';
|
|
8
|
+
export { default as AppearancePanel } from './AppearancePanel';
|
|
9
|
+
export type { AppearancePanelProps } from './AppearancePanel';
|
|
10
|
+
export { default as AvatarGrid } from './AvatarGrid';
|
|
11
|
+
export type { AvatarGridItem, AvatarGridProps } from './AvatarGrid';
|
|
12
|
+
export { DetailsPanel } from './DetailsPanel';
|
|
13
|
+
export { default as AccessoryPanel } from './AccessoryPanel';
|
|
14
|
+
export type { AccessoryPanelProps } from './AccessoryPanel';
|
|
15
|
+
export { default as VoicePanel } from './VoicePanel';
|
|
16
|
+
export { default as CreateVoiceProfileSheet } from './CreateVoiceProfileSheet';
|
|
17
|
+
export { FilamentEditor } from './FilamentEditor';
|
|
18
|
+
export type { FilamentEditorProps } from './FilamentEditor';
|
|
19
|
+
export { PrecisionPanel } from './PrecisionPanel';
|
|
20
|
+
export { SketchfabModelCard } from './SketchfabModelCard';
|
|
21
|
+
export { AccessoryBrowserScreen } from './AccessoryBrowserScreen';
|
|
22
|
+
export type { AccessoryBrowserScreenProps } from './AccessoryBrowserScreen';
|
|
23
|
+
export { AvatarCreatorScreen } from './AvatarCreatorScreen';
|
|
24
|
+
export type { AvatarCreatorScreenProps } from './AvatarCreatorScreen';
|
|
25
|
+
export { AvatarEditorScreen } from './AvatarEditorScreen';
|
|
26
|
+
export type { AvatarEditorScreenProps } from './AvatarEditorScreen';
|
|
27
|
+
export { PublicGalleryScreen } from './PublicGalleryScreen';
|
|
28
|
+
export type { PublicGalleryScreenProps } from './PublicGalleryScreen';
|
|
29
|
+
export { BLURHASH, MAX_ASSET_SIZE, RESULTS_PER_PAGE } from './constants';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RESULTS_PER_PAGE = exports.MAX_ASSET_SIZE = exports.BLURHASH = exports.PublicGalleryScreen = exports.AvatarEditorScreen = exports.AvatarCreatorScreen = exports.AccessoryBrowserScreen = exports.SketchfabModelCard = exports.PrecisionPanel = exports.FilamentEditor = exports.CreateVoiceProfileSheet = exports.VoicePanel = exports.AccessoryPanel = exports.DetailsPanel = exports.AvatarGrid = exports.AppearancePanel = exports.StudioBrowseHeader = exports.StudioFloatingAction = exports.StudioSectionHeader = exports.StudioEmptyState = exports.StudioSurfaceCardDefault = exports.StudioSurfaceCard = exports.ColorSwatch = void 0;
|
|
7
|
+
// UI primitives
|
|
8
|
+
var ColorSwatch_1 = require("./ColorSwatch");
|
|
9
|
+
Object.defineProperty(exports, "ColorSwatch", { enumerable: true, get: function () { return __importDefault(ColorSwatch_1).default; } });
|
|
10
|
+
var StudioSurfaceCard_1 = require("./StudioSurfaceCard");
|
|
11
|
+
Object.defineProperty(exports, "StudioSurfaceCard", { enumerable: true, get: function () { return StudioSurfaceCard_1.StudioSurfaceCard; } });
|
|
12
|
+
Object.defineProperty(exports, "StudioSurfaceCardDefault", { enumerable: true, get: function () { return __importDefault(StudioSurfaceCard_1).default; } });
|
|
13
|
+
var StudioEmptyState_1 = require("./StudioEmptyState");
|
|
14
|
+
Object.defineProperty(exports, "StudioEmptyState", { enumerable: true, get: function () { return StudioEmptyState_1.StudioEmptyState; } });
|
|
15
|
+
var StudioSectionHeader_1 = require("./StudioSectionHeader");
|
|
16
|
+
Object.defineProperty(exports, "StudioSectionHeader", { enumerable: true, get: function () { return StudioSectionHeader_1.StudioSectionHeader; } });
|
|
17
|
+
var StudioFloatingAction_1 = require("./StudioFloatingAction");
|
|
18
|
+
Object.defineProperty(exports, "StudioFloatingAction", { enumerable: true, get: function () { return StudioFloatingAction_1.StudioFloatingAction; } });
|
|
19
|
+
var StudioBrowseHeader_1 = require("./StudioBrowseHeader");
|
|
20
|
+
Object.defineProperty(exports, "StudioBrowseHeader", { enumerable: true, get: function () { return StudioBrowseHeader_1.StudioBrowseHeader; } });
|
|
21
|
+
// Editor panels
|
|
22
|
+
var AppearancePanel_1 = require("./AppearancePanel");
|
|
23
|
+
Object.defineProperty(exports, "AppearancePanel", { enumerable: true, get: function () { return __importDefault(AppearancePanel_1).default; } });
|
|
24
|
+
var AvatarGrid_1 = require("./AvatarGrid");
|
|
25
|
+
Object.defineProperty(exports, "AvatarGrid", { enumerable: true, get: function () { return __importDefault(AvatarGrid_1).default; } });
|
|
26
|
+
var DetailsPanel_1 = require("./DetailsPanel");
|
|
27
|
+
Object.defineProperty(exports, "DetailsPanel", { enumerable: true, get: function () { return DetailsPanel_1.DetailsPanel; } });
|
|
28
|
+
var AccessoryPanel_1 = require("./AccessoryPanel");
|
|
29
|
+
Object.defineProperty(exports, "AccessoryPanel", { enumerable: true, get: function () { return __importDefault(AccessoryPanel_1).default; } });
|
|
30
|
+
var VoicePanel_1 = require("./VoicePanel");
|
|
31
|
+
Object.defineProperty(exports, "VoicePanel", { enumerable: true, get: function () { return __importDefault(VoicePanel_1).default; } });
|
|
32
|
+
var CreateVoiceProfileSheet_1 = require("./CreateVoiceProfileSheet");
|
|
33
|
+
Object.defineProperty(exports, "CreateVoiceProfileSheet", { enumerable: true, get: function () { return __importDefault(CreateVoiceProfileSheet_1).default; } });
|
|
34
|
+
var FilamentEditor_1 = require("./FilamentEditor");
|
|
35
|
+
Object.defineProperty(exports, "FilamentEditor", { enumerable: true, get: function () { return FilamentEditor_1.FilamentEditor; } });
|
|
36
|
+
var PrecisionPanel_1 = require("./PrecisionPanel");
|
|
37
|
+
Object.defineProperty(exports, "PrecisionPanel", { enumerable: true, get: function () { return PrecisionPanel_1.PrecisionPanel; } });
|
|
38
|
+
// Sketchfab shared
|
|
39
|
+
var SketchfabModelCard_1 = require("./SketchfabModelCard");
|
|
40
|
+
Object.defineProperty(exports, "SketchfabModelCard", { enumerable: true, get: function () { return SketchfabModelCard_1.SketchfabModelCard; } });
|
|
41
|
+
// Screen-level components (navigation via props/callbacks)
|
|
42
|
+
var AccessoryBrowserScreen_1 = require("./AccessoryBrowserScreen");
|
|
43
|
+
Object.defineProperty(exports, "AccessoryBrowserScreen", { enumerable: true, get: function () { return AccessoryBrowserScreen_1.AccessoryBrowserScreen; } });
|
|
44
|
+
var AvatarCreatorScreen_1 = require("./AvatarCreatorScreen");
|
|
45
|
+
Object.defineProperty(exports, "AvatarCreatorScreen", { enumerable: true, get: function () { return AvatarCreatorScreen_1.AvatarCreatorScreen; } });
|
|
46
|
+
var AvatarEditorScreen_1 = require("./AvatarEditorScreen");
|
|
47
|
+
Object.defineProperty(exports, "AvatarEditorScreen", { enumerable: true, get: function () { return AvatarEditorScreen_1.AvatarEditorScreen; } });
|
|
48
|
+
var PublicGalleryScreen_1 = require("./PublicGalleryScreen");
|
|
49
|
+
Object.defineProperty(exports, "PublicGalleryScreen", { enumerable: true, get: function () { return PublicGalleryScreen_1.PublicGalleryScreen; } });
|
|
50
|
+
// Constants
|
|
51
|
+
var constants_1 = require("./constants");
|
|
52
|
+
Object.defineProperty(exports, "BLURHASH", { enumerable: true, get: function () { return constants_1.BLURHASH; } });
|
|
53
|
+
Object.defineProperty(exports, "MAX_ASSET_SIZE", { enumerable: true, get: function () { return constants_1.MAX_ASSET_SIZE; } });
|
|
54
|
+
Object.defineProperty(exports, "RESULTS_PER_PAGE", { enumerable: true, get: function () { return constants_1.RESULTS_PER_PAGE; } });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SketchfabModel } from '../sketchfab';
|
|
2
|
+
import type { AvatarCapabilities } from '../core/avatar/avatarCapabilities';
|
|
3
|
+
/** Per-model inspection state. A model's capabilities never change, so once a
|
|
4
|
+
* uid is 'done'/'unknown' it stays cached for the whole session. */
|
|
5
|
+
export type CapabilityEntry = {
|
|
6
|
+
status: 'pending';
|
|
7
|
+
} | {
|
|
8
|
+
status: 'unknown';
|
|
9
|
+
} | {
|
|
10
|
+
status: 'done';
|
|
11
|
+
cap: AvatarCapabilities;
|
|
12
|
+
};
|
|
13
|
+
export interface UseSketchfabCapabilitiesOptions {
|
|
14
|
+
models: SketchfabModel[];
|
|
15
|
+
apiKey: string;
|
|
16
|
+
/** How many leading results to pre-inspect. */
|
|
17
|
+
topN?: number;
|
|
18
|
+
/** Max concurrent range-inspections. */
|
|
19
|
+
concurrency?: number;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare const DEFAULT_CAPABILITY_TOP_N = 12;
|
|
23
|
+
/**
|
|
24
|
+
* Background-inspects the top-N search results (range-reads each GLB's JSON
|
|
25
|
+
* chunk) so the picker can rank/badge known-good avatars before import.
|
|
26
|
+
*
|
|
27
|
+
* Results are cached per uid for the session and survive query changes. The
|
|
28
|
+
* effect keys on the *set* of leading uids — appending pages (load-more) does
|
|
29
|
+
* not re-run it, so in-flight inspections are never thrashed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useSketchfabCapabilities({ models, apiKey, topN, concurrency, enabled, }: UseSketchfabCapabilitiesOptions): Map<string, CapabilityEntry>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_CAPABILITY_TOP_N = void 0;
|
|
4
|
+
exports.useSketchfabCapabilities = useSketchfabCapabilities;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const sketchfab_1 = require("../sketchfab");
|
|
7
|
+
exports.DEFAULT_CAPABILITY_TOP_N = 12;
|
|
8
|
+
const DEFAULT_CONCURRENCY = 3;
|
|
9
|
+
/**
|
|
10
|
+
* Background-inspects the top-N search results (range-reads each GLB's JSON
|
|
11
|
+
* chunk) so the picker can rank/badge known-good avatars before import.
|
|
12
|
+
*
|
|
13
|
+
* Results are cached per uid for the session and survive query changes. The
|
|
14
|
+
* effect keys on the *set* of leading uids — appending pages (load-more) does
|
|
15
|
+
* not re-run it, so in-flight inspections are never thrashed.
|
|
16
|
+
*/
|
|
17
|
+
function useSketchfabCapabilities({ models, apiKey, topN = exports.DEFAULT_CAPABILITY_TOP_N, concurrency = DEFAULT_CONCURRENCY, enabled = true, }) {
|
|
18
|
+
// Durable per-uid cache; the snapshot is a cheap clone published on mutation
|
|
19
|
+
// so consumers re-render.
|
|
20
|
+
const cacheRef = (0, react_1.useRef)(new Map());
|
|
21
|
+
const [snapshot, setSnapshot] = (0, react_1.useState)(() => new Map());
|
|
22
|
+
const publish = (0, react_1.useCallback)(() => {
|
|
23
|
+
setSnapshot(new Map(cacheRef.current));
|
|
24
|
+
}, []);
|
|
25
|
+
// Mirror the latest props into a ref (in an effect, never during render) so
|
|
26
|
+
// the inspection effect can read them without re-running on every list
|
|
27
|
+
// change — it should only re-run when the leading uid set changes.
|
|
28
|
+
const latest = (0, react_1.useRef)({ models, apiKey, topN, concurrency });
|
|
29
|
+
(0, react_1.useEffect)(() => {
|
|
30
|
+
latest.current = { models, apiKey, topN, concurrency };
|
|
31
|
+
});
|
|
32
|
+
const topKey = enabled && apiKey
|
|
33
|
+
? models.slice(0, topN).map((m) => m.uid).join(',')
|
|
34
|
+
: '';
|
|
35
|
+
(0, react_1.useEffect)(() => {
|
|
36
|
+
if (!topKey)
|
|
37
|
+
return;
|
|
38
|
+
const { models, apiKey, topN, concurrency } = latest.current;
|
|
39
|
+
// The cache Map identity is stable for the hook's lifetime; capture it so
|
|
40
|
+
// the cleanup closure doesn't read a (lint-flagged) ref in cleanup.
|
|
41
|
+
const cache = cacheRef.current;
|
|
42
|
+
const targets = models
|
|
43
|
+
.slice(0, topN)
|
|
44
|
+
.map((m) => m.uid)
|
|
45
|
+
.filter((uid) => !cache.has(uid));
|
|
46
|
+
if (targets.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
const ac = new AbortController();
|
|
49
|
+
let cancelled = false;
|
|
50
|
+
for (const uid of targets)
|
|
51
|
+
cache.set(uid, { status: 'pending' });
|
|
52
|
+
publish();
|
|
53
|
+
const queue = [...targets];
|
|
54
|
+
const worker = async () => {
|
|
55
|
+
while (!cancelled && queue.length > 0) {
|
|
56
|
+
const uid = queue.shift();
|
|
57
|
+
const cap = await (0, sketchfab_1.fetchSketchfabCapabilities)(uid, apiKey, ac.signal);
|
|
58
|
+
if (cancelled)
|
|
59
|
+
return;
|
|
60
|
+
cache.set(uid, cap ? { status: 'done', cap } : { status: 'unknown' });
|
|
61
|
+
publish();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const lanes = Math.min(Math.max(1, concurrency), queue.length);
|
|
65
|
+
void Promise.all(Array.from({ length: lanes }, () => worker()));
|
|
66
|
+
return () => {
|
|
67
|
+
cancelled = true;
|
|
68
|
+
ac.abort();
|
|
69
|
+
// Drop entries still pending so a later run (or re-appearance) retries.
|
|
70
|
+
let changed = false;
|
|
71
|
+
for (const uid of targets) {
|
|
72
|
+
if (cache.get(uid)?.status === 'pending') {
|
|
73
|
+
cache.delete(uid);
|
|
74
|
+
changed = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (changed)
|
|
78
|
+
publish();
|
|
79
|
+
};
|
|
80
|
+
}, [topKey, publish]);
|
|
81
|
+
return snapshot;
|
|
82
|
+
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useDirectVisemeStream = useDirectVisemeStream;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
-
const fetch_1 = require("expo/fetch");
|
|
6
5
|
// How long to keep retrying a stream before giving up (ms).
|
|
7
6
|
const STREAM_RETRY_BUDGET_MS = 3000;
|
|
8
7
|
// Initial retry delay; doubles each attempt up to MAX_RETRY_DELAY_MS.
|
|
9
8
|
const INITIAL_RETRY_DELAY_MS = 150;
|
|
10
9
|
const MAX_RETRY_DELAY_MS = 1000;
|
|
10
|
+
const shouldLogDebugMessages = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
|
|
11
11
|
/**
|
|
12
12
|
* Opens a direct SSE connection to the TTS server to receive viseme data,
|
|
13
13
|
* bypassing the agent data channel relay.
|
|
@@ -38,12 +38,12 @@ function useDirectVisemeStream(onVisemes) {
|
|
|
38
38
|
// Strip trailing /v1 if present so we don't double it
|
|
39
39
|
const base = ttsBaseUrl.replace(/\/v1\/?$/, "");
|
|
40
40
|
const url = `${base}/v1/audio/visemes/${encodeURIComponent(requestId)}/stream`;
|
|
41
|
-
(async () => {
|
|
41
|
+
void (async () => {
|
|
42
42
|
const startedAt = Date.now();
|
|
43
43
|
let retryDelay = INITIAL_RETRY_DELAY_MS;
|
|
44
44
|
while (!signal.aborted) {
|
|
45
45
|
try {
|
|
46
|
-
const response = await
|
|
46
|
+
const response = await fetch(url, {
|
|
47
47
|
headers: { Accept: "text/event-stream" },
|
|
48
48
|
signal,
|
|
49
49
|
});
|
|
@@ -67,11 +67,14 @@ function useDirectVisemeStream(onVisemes) {
|
|
|
67
67
|
onVisemesRef.current(payload);
|
|
68
68
|
});
|
|
69
69
|
// Stream ended cleanly — done.
|
|
70
|
-
|
|
70
|
+
if (shouldLogDebugMessages) {
|
|
71
|
+
console.log("[VisemeSSE] stream ended", { requestId });
|
|
72
|
+
}
|
|
71
73
|
return;
|
|
72
74
|
}
|
|
73
75
|
catch (err) {
|
|
74
|
-
|
|
76
|
+
const isAbortError = err instanceof Error && err.name === "AbortError";
|
|
77
|
+
if (isAbortError || signal.aborted)
|
|
75
78
|
return;
|
|
76
79
|
const elapsed = Date.now() - startedAt;
|
|
77
80
|
if (elapsed >= STREAM_RETRY_BUDGET_MS) {
|
|
@@ -119,11 +122,13 @@ async function readSseStream(reader, signal, onVisemes) {
|
|
|
119
122
|
const jsonText = dataLines.join("\n");
|
|
120
123
|
try {
|
|
121
124
|
const payload = JSON.parse(jsonText);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
if (shouldLogDebugMessages) {
|
|
126
|
+
console.log("[VisemeSSE] received", {
|
|
127
|
+
requestId: payload.requestId,
|
|
128
|
+
cues: Array.isArray(payload.cues) ? payload.cues.length : 0,
|
|
129
|
+
durationMs: payload.durationMs ?? null,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
127
132
|
onVisemes(payload);
|
|
128
133
|
}
|
|
129
134
|
catch (parseErr) {
|
|
@@ -1,20 +1,105 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.resolveLocalAssetUri = resolveLocalAssetUri;
|
|
4
37
|
exports.resolveLocalAssetUrl = resolveLocalAssetUrl;
|
|
5
38
|
const expo_asset_1 = require("expo-asset");
|
|
6
39
|
const react_native_1 = require("react-native");
|
|
7
40
|
const expo_file_system_1 = require("expo-file-system");
|
|
41
|
+
const LegacyFileSystem = __importStar(require("expo-file-system/legacy"));
|
|
42
|
+
/**
|
|
43
|
+
* Narrows dynamic require() results to the Expo asset module shapes accepted by Asset.fromModule.
|
|
44
|
+
*/
|
|
45
|
+
function isExpoAssetModule(module) {
|
|
46
|
+
if (typeof module === 'number' || typeof module === 'string') {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return (typeof module === 'object' &&
|
|
50
|
+
module !== null &&
|
|
51
|
+
'uri' in module &&
|
|
52
|
+
typeof module.uri === 'string' &&
|
|
53
|
+
'width' in module &&
|
|
54
|
+
typeof module.width === 'number' &&
|
|
55
|
+
'height' in module &&
|
|
56
|
+
typeof module.height === 'number');
|
|
57
|
+
}
|
|
58
|
+
function inferAssetFileName(uri) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = new URL(uri);
|
|
61
|
+
const unstablePath = parsed.searchParams.get('unstable_path') ?? '';
|
|
62
|
+
const hash = parsed.searchParams.get('hash');
|
|
63
|
+
const sourcePath = unstablePath || parsed.pathname;
|
|
64
|
+
const extMatch = sourcePath.match(/\.([a-z0-9]+)$/i);
|
|
65
|
+
const ext = extMatch?.[1] ?? 'bin';
|
|
66
|
+
return `${hash ?? 'asset'}.${ext}`;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return 'asset.bin';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function resolveAssetUriWithFallback(asset) {
|
|
73
|
+
try {
|
|
74
|
+
await asset.downloadAsync();
|
|
75
|
+
return asset.localUri ?? asset.uri ?? null;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const remoteUri = asset.uri;
|
|
79
|
+
if (!remoteUri || (!remoteUri.startsWith('http://') && !remoteUri.startsWith('https://'))) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
if (!LegacyFileSystem.cacheDirectory) {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
const dir = `${LegacyFileSystem.cacheDirectory}expo-asset-fallback/`;
|
|
86
|
+
const fileUri = `${dir}${inferAssetFileName(remoteUri)}`;
|
|
87
|
+
await LegacyFileSystem.makeDirectoryAsync(dir, { intermediates: true });
|
|
88
|
+
const downloaded = await LegacyFileSystem.downloadAsync(remoteUri, fileUri);
|
|
89
|
+
return downloaded.uri;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
8
92
|
/**
|
|
9
93
|
* Resolves a local Expo asset module into a file:// URI for use with
|
|
10
94
|
* WgpuAvatar model loading.
|
|
11
95
|
*/
|
|
12
96
|
async function resolveLocalAssetUri(module) {
|
|
13
97
|
try {
|
|
98
|
+
if (!isExpoAssetModule(module)) {
|
|
99
|
+
throw new TypeError('Expected an Expo asset module id, URI, or asset descriptor.');
|
|
100
|
+
}
|
|
14
101
|
const asset = expo_asset_1.Asset.fromModule(module);
|
|
15
|
-
await asset
|
|
16
|
-
const uri = asset.localUri ?? asset.uri;
|
|
17
|
-
return uri ?? null;
|
|
102
|
+
return await resolveAssetUriWithFallback(asset);
|
|
18
103
|
}
|
|
19
104
|
catch (e) {
|
|
20
105
|
console.error('[AssetUtils] Failed to resolve asset:', e);
|
|
@@ -29,9 +114,11 @@ async function resolveLocalAssetUri(module) {
|
|
|
29
114
|
*/
|
|
30
115
|
async function resolveLocalAssetUrl(module) {
|
|
31
116
|
try {
|
|
117
|
+
if (!isExpoAssetModule(module)) {
|
|
118
|
+
throw new TypeError('Expected an Expo asset module id, URI, or asset descriptor.');
|
|
119
|
+
}
|
|
32
120
|
const asset = expo_asset_1.Asset.fromModule(module);
|
|
33
|
-
await asset
|
|
34
|
-
const uri = asset.localUri ?? asset.uri;
|
|
121
|
+
const uri = await resolveAssetUriWithFallback(asset);
|
|
35
122
|
if (!uri)
|
|
36
123
|
return null;
|
|
37
124
|
if (react_native_1.Platform.OS === 'web') {
|
|
@@ -57,10 +57,8 @@ async function getLandmarker() {
|
|
|
57
57
|
if (_landmarkerPromise)
|
|
58
58
|
return _landmarkerPromise;
|
|
59
59
|
_landmarkerPromise = (async () => {
|
|
60
|
-
// Dynamic import
|
|
61
|
-
//
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
63
|
-
// @ts-ignore
|
|
60
|
+
// Dynamic import keeps the MediaPipe WASM/runtime out of bundles that never
|
|
61
|
+
// call this utility.
|
|
64
62
|
const vision = await Promise.resolve().then(() => __importStar(require(
|
|
65
63
|
/* webpackChunkName: "mediapipe-tasks-vision" */
|
|
66
64
|
'@mediapipe/tasks-vision')));
|
|
@@ -14,8 +14,12 @@ function useAudioPlayer({ onError, } = {}) {
|
|
|
14
14
|
setIsPlaying(false);
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
|
-
audioRef.current.play()
|
|
18
|
-
|
|
17
|
+
void audioRef.current.play()
|
|
18
|
+
.then(() => setIsPlaying(true))
|
|
19
|
+
.catch((error) => {
|
|
20
|
+
const err = error instanceof Error ? error : new Error('Failed to play audio file');
|
|
21
|
+
onError?.(err);
|
|
22
|
+
});
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
else {
|
|
@@ -42,8 +46,17 @@ function useAudioPlayer({ onError, } = {}) {
|
|
|
42
46
|
}
|
|
43
47
|
audioRef.current = null;
|
|
44
48
|
});
|
|
45
|
-
audio.play()
|
|
46
|
-
|
|
49
|
+
void audio.play()
|
|
50
|
+
.then(() => setIsPlaying(true))
|
|
51
|
+
.catch((error) => {
|
|
52
|
+
const err = error instanceof Error ? error : new Error('Failed to play audio file');
|
|
53
|
+
if (onError) {
|
|
54
|
+
onError(err);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.error('[useAudioPlayer] Playback error:', err.message);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
47
60
|
}
|
|
48
61
|
};
|
|
49
62
|
const cleanup = () => {
|
|
@@ -16,11 +16,12 @@ async function fetchFirstSampleUrl(voiceId) {
|
|
|
16
16
|
return `${_baseUrl}/samples/${samples[0].id}`;
|
|
17
17
|
}
|
|
18
18
|
function useVoicePreview() {
|
|
19
|
+
const [emptyPlayer] = (0, react_1.useState)(() => (0, expo_audio_1.createAudioPlayer)(null));
|
|
19
20
|
const playerRef = (0, react_1.useRef)(null);
|
|
20
21
|
const [player, setPlayer] = (0, react_1.useState)(null);
|
|
21
22
|
const [activeVoiceId, setActiveVoiceId] = (0, react_1.useState)(null);
|
|
22
23
|
const [loadingVoiceId, setLoadingVoiceId] = (0, react_1.useState)(null);
|
|
23
|
-
const status = (0, expo_audio_1.useAudioPlayerStatus)(player);
|
|
24
|
+
const status = (0, expo_audio_1.useAudioPlayerStatus)(player ?? emptyPlayer);
|
|
24
25
|
(0, react_1.useEffect)(() => {
|
|
25
26
|
if (status?.didJustFinish) {
|
|
26
27
|
setActiveVoiceId(null);
|
|
@@ -75,7 +76,8 @@ function useVoicePreview() {
|
|
|
75
76
|
playerRef.current?.pause();
|
|
76
77
|
playerRef.current?.remove();
|
|
77
78
|
playerRef.current = null;
|
|
79
|
+
emptyPlayer.remove();
|
|
78
80
|
};
|
|
79
|
-
}, []);
|
|
81
|
+
}, [emptyPlayer]);
|
|
80
82
|
return { activeVoiceId, loadingVoiceId, previewVoice, stopPreview };
|
|
81
83
|
}
|
package/dist/wardrobe/index.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export * from './wardrobeStore';
|
|
|
2
2
|
export { useAvatarWardrobeHydration } from './useAvatarWardrobeHydration';
|
|
3
3
|
export { useStudioAvatar } from './useStudioAvatar';
|
|
4
4
|
export type { UseStudioAvatarOptions, UseStudioAvatarResult } from './useStudioAvatar';
|
|
5
|
+
export { useAccessoryGestures, applyDragTranslate, applyPinchScale, applyRotation, } from './useAccessoryGestures';
|
package/dist/wardrobe/index.js
CHANGED
|
@@ -14,9 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.useStudioAvatar = exports.useAvatarWardrobeHydration = void 0;
|
|
17
|
+
exports.applyRotation = exports.applyPinchScale = exports.applyDragTranslate = exports.useAccessoryGestures = exports.useStudioAvatar = exports.useAvatarWardrobeHydration = void 0;
|
|
18
18
|
__exportStar(require("./wardrobeStore"), exports);
|
|
19
19
|
var useAvatarWardrobeHydration_1 = require("./useAvatarWardrobeHydration");
|
|
20
20
|
Object.defineProperty(exports, "useAvatarWardrobeHydration", { enumerable: true, get: function () { return useAvatarWardrobeHydration_1.useAvatarWardrobeHydration; } });
|
|
21
21
|
var useStudioAvatar_1 = require("./useStudioAvatar");
|
|
22
22
|
Object.defineProperty(exports, "useStudioAvatar", { enumerable: true, get: function () { return useStudioAvatar_1.useStudioAvatar; } });
|
|
23
|
+
var useAccessoryGestures_1 = require("./useAccessoryGestures");
|
|
24
|
+
Object.defineProperty(exports, "useAccessoryGestures", { enumerable: true, get: function () { return useAccessoryGestures_1.useAccessoryGestures; } });
|
|
25
|
+
Object.defineProperty(exports, "applyDragTranslate", { enumerable: true, get: function () { return useAccessoryGestures_1.applyDragTranslate; } });
|
|
26
|
+
Object.defineProperty(exports, "applyPinchScale", { enumerable: true, get: function () { return useAccessoryGestures_1.applyPinchScale; } });
|
|
27
|
+
Object.defineProperty(exports, "applyRotation", { enumerable: true, get: function () { return useAccessoryGestures_1.applyRotation; } });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type AssetPlacement } from './wardrobeStore';
|
|
2
|
+
/**
|
|
3
|
+
* Translate a position by screen-space drag deltas.
|
|
4
|
+
* Z is intentionally locked — drag stays in the XY plane to avoid depth
|
|
5
|
+
* artifacts from single-finger gestures.
|
|
6
|
+
*/
|
|
7
|
+
export declare function applyDragTranslate(startPos: [number, number, number], translationX: number, translationY: number, sensitivity?: number): [number, number, number];
|
|
8
|
+
/**
|
|
9
|
+
* Scale a [x, y, z] triple by a pinch factor, clamped to [0.1, 10].
|
|
10
|
+
*/
|
|
11
|
+
export declare function applyPinchScale(startScale: [number, number, number], scaleFactor: number): [number, number, number];
|
|
12
|
+
/**
|
|
13
|
+
* Accumulate Euler rotation from gesture deltas (radians).
|
|
14
|
+
* deltaX maps to Y-axis (yaw), deltaY maps to X-axis (pitch).
|
|
15
|
+
*/
|
|
16
|
+
export declare function applyRotation(startRot: [number, number, number], deltaX: number, deltaY: number): [number, number, number];
|
|
17
|
+
export declare function useAccessoryGestures(): {
|
|
18
|
+
commitTransform: (id: string, placement: AssetPlacement) => void;
|
|
19
|
+
selectAccessory: (id: string) => Promise<void>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
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.applyDragTranslate = applyDragTranslate;
|
|
37
|
+
exports.applyPinchScale = applyPinchScale;
|
|
38
|
+
exports.applyRotation = applyRotation;
|
|
39
|
+
exports.useAccessoryGestures = useAccessoryGestures;
|
|
40
|
+
const Haptics = __importStar(require("expo-haptics"));
|
|
41
|
+
const wardrobeStore_1 = require("./wardrobeStore");
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Pure math helpers — 'worklet' annotated so they can run on the UI thread
|
|
44
|
+
// via react-native-reanimated gesture handlers.
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
/**
|
|
47
|
+
* Translate a position by screen-space drag deltas.
|
|
48
|
+
* Z is intentionally locked — drag stays in the XY plane to avoid depth
|
|
49
|
+
* artifacts from single-finger gestures.
|
|
50
|
+
*/
|
|
51
|
+
function applyDragTranslate(startPos, translationX, translationY, sensitivity = 0.01) {
|
|
52
|
+
return [
|
|
53
|
+
startPos[0] + translationX * sensitivity,
|
|
54
|
+
startPos[1] - translationY * sensitivity,
|
|
55
|
+
startPos[2],
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Scale a [x, y, z] triple by a pinch factor, clamped to [0.1, 10].
|
|
60
|
+
*/
|
|
61
|
+
function applyPinchScale(startScale, scaleFactor) {
|
|
62
|
+
const clamp = (v) => Math.max(0.1, Math.min(v, 10));
|
|
63
|
+
return [
|
|
64
|
+
clamp(startScale[0] * scaleFactor),
|
|
65
|
+
clamp(startScale[1] * scaleFactor),
|
|
66
|
+
clamp(startScale[2] * scaleFactor),
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Accumulate Euler rotation from gesture deltas (radians).
|
|
71
|
+
* deltaX maps to Y-axis (yaw), deltaY maps to X-axis (pitch).
|
|
72
|
+
*/
|
|
73
|
+
function applyRotation(startRot, deltaX, deltaY) {
|
|
74
|
+
return [
|
|
75
|
+
startRot[0] + deltaY,
|
|
76
|
+
startRot[1] + deltaX,
|
|
77
|
+
startRot[2],
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Hook
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
function useAccessoryGestures() {
|
|
84
|
+
const setActiveAsset = (0, wardrobeStore_1.useWardrobeStore)((state) => state.setActiveAsset);
|
|
85
|
+
const setPlacement = (0, wardrobeStore_1.useWardrobeStore)((state) => state.setPlacement);
|
|
86
|
+
const commitTransform = (id, placement) => {
|
|
87
|
+
setPlacement(id, placement);
|
|
88
|
+
};
|
|
89
|
+
const selectAccessory = async (id) => {
|
|
90
|
+
setActiveAsset(id);
|
|
91
|
+
await Haptics.selectionAsync();
|
|
92
|
+
};
|
|
93
|
+
return { commitTransform, selectAccessory };
|
|
94
|
+
}
|
|
@@ -6,9 +6,13 @@ const studioApi_1 = require("../api/studioApi");
|
|
|
6
6
|
const wardrobeStore_1 = require("./wardrobeStore");
|
|
7
7
|
function useAvatarWardrobeHydration({ avatarId, accessories, }) {
|
|
8
8
|
const hydrateFromApi = (0, wardrobeStore_1.useWardrobeStore)((s) => s.hydrateFromApi);
|
|
9
|
+
const accessoriesRef = (0, react_1.useRef)(accessories ?? []);
|
|
9
10
|
const accessorySignature = (0, react_1.useMemo)(() => JSON.stringify(accessories ?? []), [accessories]);
|
|
10
11
|
(0, react_1.useEffect)(() => {
|
|
11
|
-
|
|
12
|
+
accessoriesRef.current = accessories ?? [];
|
|
13
|
+
}, [accessories]);
|
|
14
|
+
(0, react_1.useEffect)(() => {
|
|
15
|
+
const nextAccessories = accessoriesRef.current;
|
|
12
16
|
if (!avatarId || nextAccessories.length === 0) {
|
|
13
17
|
hydrateFromApi([], {});
|
|
14
18
|
return;
|
|
@@ -25,10 +29,12 @@ function useAvatarWardrobeHydration({ avatarId, accessories, }) {
|
|
|
25
29
|
hydrateFromApi(nextAccessories, lookup);
|
|
26
30
|
})
|
|
27
31
|
.catch((err) => {
|
|
32
|
+
if (cancelled)
|
|
33
|
+
return;
|
|
28
34
|
console.error('[useAvatarWardrobeHydration] Failed to hydrate wardrobe:', err);
|
|
29
35
|
});
|
|
30
36
|
return () => {
|
|
31
37
|
cancelled = true;
|
|
32
38
|
};
|
|
33
|
-
}, [accessorySignature, avatarId, hydrateFromApi
|
|
39
|
+
}, [accessorySignature, avatarId, hydrateFromApi]);
|
|
34
40
|
}
|