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,27 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
export declare const HUMANOID_BONES: readonly ["Hips", "Spine", "Spine1", "Spine2", "Neck", "Head", "LeftShoulder", "LeftArm", "LeftForeArm", "LeftHand", "RightShoulder", "RightArm", "RightForeArm", "RightHand", "LeftUpLeg", "LeftLeg", "LeftFoot", "LeftToeBase", "RightUpLeg", "RightLeg", "RightFoot", "RightToeBase"];
|
|
3
|
+
export type HumanoidBone = (typeof HUMANOID_BONES)[number];
|
|
4
|
+
export interface BoneSnapResult {
|
|
5
|
+
bone: string;
|
|
6
|
+
position: THREE.Vector3;
|
|
7
|
+
distance: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Given an avatar scene and a world-space position, finds the nearest skeleton
|
|
11
|
+
* bone whose name matches a known humanoid bone name.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findNearestBone(avatarScene: THREE.Object3D, worldPos: THREE.Vector3, maxDistance?: number): BoneSnapResult | null;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the world-space position of a named bone in the avatar scene.
|
|
16
|
+
* Returns the scene origin if the bone is not found.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getWorldPositionForBone(avatarScene: THREE.Object3D, boneName: string): THREE.Vector3;
|
|
19
|
+
export declare const getWorldPositionForPlacement: typeof getWorldPositionForBone;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the nearest known humanoid bone to a placement point.
|
|
22
|
+
*/
|
|
23
|
+
export declare function snapPlacementToNearestBone(avatarScene: THREE.Object3D, worldPos: THREE.Vector3): BoneSnapResult | null;
|
|
24
|
+
/**
|
|
25
|
+
* Web-safe snap hook. Native builds use the haptic version from boneSnap.ts.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useBoneSnap(avatarScene: THREE.Object3D | null): (worldPos: THREE.Vector3) => BoneSnapResult | null;
|
|
@@ -0,0 +1,99 @@
|
|
|
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.getWorldPositionForPlacement = exports.HUMANOID_BONES = void 0;
|
|
37
|
+
exports.findNearestBone = findNearestBone;
|
|
38
|
+
exports.getWorldPositionForBone = getWorldPositionForBone;
|
|
39
|
+
exports.snapPlacementToNearestBone = snapPlacementToNearestBone;
|
|
40
|
+
exports.useBoneSnap = useBoneSnap;
|
|
41
|
+
const react_1 = require("react");
|
|
42
|
+
const THREE = __importStar(require("three"));
|
|
43
|
+
exports.HUMANOID_BONES = [
|
|
44
|
+
'Hips', 'Spine', 'Spine1', 'Spine2', 'Neck', 'Head',
|
|
45
|
+
'LeftShoulder', 'LeftArm', 'LeftForeArm', 'LeftHand',
|
|
46
|
+
'RightShoulder', 'RightArm', 'RightForeArm', 'RightHand',
|
|
47
|
+
'LeftUpLeg', 'LeftLeg', 'LeftFoot', 'LeftToeBase',
|
|
48
|
+
'RightUpLeg', 'RightLeg', 'RightFoot', 'RightToeBase',
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Given an avatar scene and a world-space position, finds the nearest skeleton
|
|
52
|
+
* bone whose name matches a known humanoid bone name.
|
|
53
|
+
*/
|
|
54
|
+
function findNearestBone(avatarScene, worldPos, maxDistance = 0.35) {
|
|
55
|
+
let best = null;
|
|
56
|
+
const boneWorldPos = new THREE.Vector3();
|
|
57
|
+
avatarScene.traverse((obj) => {
|
|
58
|
+
if (!(obj instanceof THREE.Bone))
|
|
59
|
+
return;
|
|
60
|
+
if (!exports.HUMANOID_BONES.includes(obj.name))
|
|
61
|
+
return;
|
|
62
|
+
obj.getWorldPosition(boneWorldPos);
|
|
63
|
+
const dist = worldPos.distanceTo(boneWorldPos);
|
|
64
|
+
if (dist < maxDistance && (!best || dist < best.distance)) {
|
|
65
|
+
best = { bone: obj.name, position: boneWorldPos.clone(), distance: dist };
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return best;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns the world-space position of a named bone in the avatar scene.
|
|
72
|
+
* Returns the scene origin if the bone is not found.
|
|
73
|
+
*/
|
|
74
|
+
function getWorldPositionForBone(avatarScene, boneName) {
|
|
75
|
+
const result = new THREE.Vector3();
|
|
76
|
+
avatarScene.traverse((obj) => {
|
|
77
|
+
if (obj instanceof THREE.Bone && obj.name === boneName) {
|
|
78
|
+
obj.getWorldPosition(result);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
exports.getWorldPositionForPlacement = getWorldPositionForBone;
|
|
84
|
+
/**
|
|
85
|
+
* Returns the nearest known humanoid bone to a placement point.
|
|
86
|
+
*/
|
|
87
|
+
function snapPlacementToNearestBone(avatarScene, worldPos) {
|
|
88
|
+
return findNearestBone(avatarScene, worldPos);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Web-safe snap hook. Native builds use the haptic version from boneSnap.ts.
|
|
92
|
+
*/
|
|
93
|
+
function useBoneSnap(avatarScene) {
|
|
94
|
+
return (0, react_1.useCallback)((worldPos) => {
|
|
95
|
+
if (!avatarScene)
|
|
96
|
+
return null;
|
|
97
|
+
return findNearestBone(avatarScene, worldPos);
|
|
98
|
+
}, [avatarScene]);
|
|
99
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { AvatarCanvas } from './AvatarCanvas';
|
|
2
|
+
export { AvatarEditor } from './AvatarEditor';
|
|
3
|
+
export { AvatarModel } from './AvatarModel';
|
|
4
|
+
export { RigidAccessory } from './RigidAccessory';
|
|
5
|
+
export { SkinnedClothing } from './SkinnedClothing';
|
|
6
|
+
export { FaceSqueezeEditor, FACE_SQUEEZE_LOCAL_MODULE } from './FaceSqueezeEditor.web';
|
|
7
|
+
export { studioTheme, withAlpha } from './studioTheme';
|
|
8
|
+
export { useBoneSnap, findNearestBone, getWorldPositionForBone, getWorldPositionForPlacement, snapPlacementToNearestBone, HUMANOID_BONES, } from './boneSnap.web';
|
|
9
|
+
export type { AvatarEditorProps, AssetPlacement, EquippedAsset, FaceSqueezeEditorProps } from './types';
|
|
10
|
+
export type { BoneSnapResult, HumanoidBone } from './boneSnap.web';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HUMANOID_BONES = exports.snapPlacementToNearestBone = exports.getWorldPositionForPlacement = exports.getWorldPositionForBone = exports.findNearestBone = exports.useBoneSnap = exports.withAlpha = exports.studioTheme = exports.FACE_SQUEEZE_LOCAL_MODULE = exports.FaceSqueezeEditor = exports.SkinnedClothing = exports.RigidAccessory = exports.AvatarModel = exports.AvatarEditor = exports.AvatarCanvas = void 0;
|
|
4
|
+
var AvatarCanvas_1 = require("./AvatarCanvas");
|
|
5
|
+
Object.defineProperty(exports, "AvatarCanvas", { enumerable: true, get: function () { return AvatarCanvas_1.AvatarCanvas; } });
|
|
6
|
+
var AvatarEditor_1 = require("./AvatarEditor");
|
|
7
|
+
Object.defineProperty(exports, "AvatarEditor", { enumerable: true, get: function () { return AvatarEditor_1.AvatarEditor; } });
|
|
8
|
+
var AvatarModel_1 = require("./AvatarModel");
|
|
9
|
+
Object.defineProperty(exports, "AvatarModel", { enumerable: true, get: function () { return AvatarModel_1.AvatarModel; } });
|
|
10
|
+
var RigidAccessory_1 = require("./RigidAccessory");
|
|
11
|
+
Object.defineProperty(exports, "RigidAccessory", { enumerable: true, get: function () { return RigidAccessory_1.RigidAccessory; } });
|
|
12
|
+
var SkinnedClothing_1 = require("./SkinnedClothing");
|
|
13
|
+
Object.defineProperty(exports, "SkinnedClothing", { enumerable: true, get: function () { return SkinnedClothing_1.SkinnedClothing; } });
|
|
14
|
+
var FaceSqueezeEditor_web_1 = require("./FaceSqueezeEditor.web");
|
|
15
|
+
Object.defineProperty(exports, "FaceSqueezeEditor", { enumerable: true, get: function () { return FaceSqueezeEditor_web_1.FaceSqueezeEditor; } });
|
|
16
|
+
Object.defineProperty(exports, "FACE_SQUEEZE_LOCAL_MODULE", { enumerable: true, get: function () { return FaceSqueezeEditor_web_1.FACE_SQUEEZE_LOCAL_MODULE; } });
|
|
17
|
+
var studioTheme_1 = require("./studioTheme");
|
|
18
|
+
Object.defineProperty(exports, "studioTheme", { enumerable: true, get: function () { return studioTheme_1.studioTheme; } });
|
|
19
|
+
Object.defineProperty(exports, "withAlpha", { enumerable: true, get: function () { return studioTheme_1.withAlpha; } });
|
|
20
|
+
var boneSnap_web_1 = require("./boneSnap.web");
|
|
21
|
+
Object.defineProperty(exports, "useBoneSnap", { enumerable: true, get: function () { return boneSnap_web_1.useBoneSnap; } });
|
|
22
|
+
Object.defineProperty(exports, "findNearestBone", { enumerable: true, get: function () { return boneSnap_web_1.findNearestBone; } });
|
|
23
|
+
Object.defineProperty(exports, "getWorldPositionForBone", { enumerable: true, get: function () { return boneSnap_web_1.getWorldPositionForBone; } });
|
|
24
|
+
Object.defineProperty(exports, "getWorldPositionForPlacement", { enumerable: true, get: function () { return boneSnap_web_1.getWorldPositionForPlacement; } });
|
|
25
|
+
Object.defineProperty(exports, "snapPlacementToNearestBone", { enumerable: true, get: function () { return boneSnap_web_1.snapPlacementToNearestBone; } });
|
|
26
|
+
Object.defineProperty(exports, "HUMANOID_BONES", { enumerable: true, get: function () { return boneSnap_web_1.HUMANOID_BONES; } });
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
export declare const studioTheme: {
|
|
2
2
|
readonly colors: {
|
|
3
|
-
readonly background: "#
|
|
4
|
-
readonly backgroundAlt: "#
|
|
5
|
-
readonly surface: "#
|
|
6
|
-
readonly surfaceRaised: "#
|
|
7
|
-
readonly surfaceSoft: "#
|
|
8
|
-
readonly border: "#
|
|
9
|
-
readonly borderStrong: "#
|
|
10
|
-
readonly textPrimary: "#
|
|
11
|
-
readonly textSecondary: "#
|
|
12
|
-
readonly textMuted: "#
|
|
13
|
-
readonly accent: "#
|
|
14
|
-
readonly accentStrong: "#
|
|
15
|
-
readonly accentMuted: "rgba(
|
|
3
|
+
readonly background: "#0d1018";
|
|
4
|
+
readonly backgroundAlt: "#13151C";
|
|
5
|
+
readonly surface: "#1a1e29";
|
|
6
|
+
readonly surfaceRaised: "#222a3a";
|
|
7
|
+
readonly surfaceSoft: "#282B36";
|
|
8
|
+
readonly border: "#363b4a";
|
|
9
|
+
readonly borderStrong: "#424655";
|
|
10
|
+
readonly textPrimary: "#FAF7F0";
|
|
11
|
+
readonly textSecondary: "#C8CCD9";
|
|
12
|
+
readonly textMuted: "#6B7080";
|
|
13
|
+
readonly accent: "#4F5BD5";
|
|
14
|
+
readonly accentStrong: "#6673D6";
|
|
15
|
+
readonly accentMuted: "rgba(79, 91, 213, 0.16)";
|
|
16
16
|
readonly success: "#69c08a";
|
|
17
|
-
readonly danger: "#
|
|
17
|
+
readonly danger: "#E85C48";
|
|
18
18
|
readonly shadow: "rgba(0, 0, 0, 0.32)";
|
|
19
19
|
readonly haze: "rgba(255, 255, 255, 0.05)";
|
|
20
20
|
};
|
|
@@ -2,23 +2,26 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.studioTheme = void 0;
|
|
4
4
|
exports.withAlpha = withAlpha;
|
|
5
|
+
// Self-contained theme. Palette is inspired by @sitebay/theme (slate dark
|
|
6
|
+
// surfaces + indigo accent + parchment text) but intentionally NOT a hard
|
|
7
|
+
// dependency — keeps this package standalone/publishable.
|
|
5
8
|
exports.studioTheme = {
|
|
6
9
|
colors: {
|
|
7
|
-
background: '#
|
|
8
|
-
backgroundAlt: '#
|
|
9
|
-
surface: '#
|
|
10
|
-
surfaceRaised: '#
|
|
11
|
-
surfaceSoft: '#
|
|
12
|
-
border: '#
|
|
13
|
-
borderStrong: '#
|
|
14
|
-
textPrimary: '#
|
|
15
|
-
textSecondary: '#
|
|
16
|
-
textMuted: '#
|
|
17
|
-
accent: '#
|
|
18
|
-
accentStrong: '#
|
|
19
|
-
accentMuted: 'rgba(
|
|
10
|
+
background: '#0d1018',
|
|
11
|
+
backgroundAlt: '#13151C',
|
|
12
|
+
surface: '#1a1e29',
|
|
13
|
+
surfaceRaised: '#222a3a',
|
|
14
|
+
surfaceSoft: '#282B36',
|
|
15
|
+
border: '#363b4a',
|
|
16
|
+
borderStrong: '#424655',
|
|
17
|
+
textPrimary: '#FAF7F0',
|
|
18
|
+
textSecondary: '#C8CCD9',
|
|
19
|
+
textMuted: '#6B7080',
|
|
20
|
+
accent: '#4F5BD5',
|
|
21
|
+
accentStrong: '#6673D6',
|
|
22
|
+
accentMuted: 'rgba(79, 91, 213, 0.16)',
|
|
20
23
|
success: '#69c08a',
|
|
21
|
-
danger: '#
|
|
24
|
+
danger: '#E85C48',
|
|
22
25
|
shadow: 'rgba(0, 0, 0, 0.32)',
|
|
23
26
|
haze: 'rgba(255, 255, 255, 0.05)',
|
|
24
27
|
},
|
package/dist/editor/types.d.ts
CHANGED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAccessoriesScript = buildAccessoriesScript;
|
|
4
|
+
/**
|
|
5
|
+
* Generated avatar WebView accessories script section.
|
|
6
|
+
*
|
|
7
|
+
* Kept as a string builder so html.ts can compose the final document without
|
|
8
|
+
* owning every browser-side subsystem inline.
|
|
9
|
+
*/
|
|
10
|
+
function buildAccessoriesScript() {
|
|
11
|
+
return `let THREE_REF = null;
|
|
12
|
+
let gltfLoaderInstance = null;
|
|
13
|
+
const currentAccessories = {};
|
|
14
|
+
let pendingAccessoriesList = [];
|
|
15
|
+
|
|
16
|
+
function disposeHierarchy(node) {
|
|
17
|
+
if (!node) return;
|
|
18
|
+
node.traverse((child) => {
|
|
19
|
+
if (child.isMesh) {
|
|
20
|
+
if (child.geometry) child.geometry.dispose();
|
|
21
|
+
if (child.material) {
|
|
22
|
+
const materials = Array.isArray(child.material) ? child.material : [child.material];
|
|
23
|
+
materials.forEach(mat => {
|
|
24
|
+
if (mat.map) mat.map.dispose();
|
|
25
|
+
if (mat.lightMap) mat.lightMap.dispose();
|
|
26
|
+
if (mat.bumpMap) mat.bumpMap.dispose();
|
|
27
|
+
if (mat.normalMap) mat.normalMap.dispose();
|
|
28
|
+
if (mat.specularMap) mat.specularMap.dispose();
|
|
29
|
+
if (mat.envMap) mat.envMap.dispose();
|
|
30
|
+
mat.dispose();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function ensureThree() {
|
|
38
|
+
if (!THREE_REF) {
|
|
39
|
+
THREE_REF = await import('three');
|
|
40
|
+
const { GLTFLoader } = await import('three/addons/loaders/GLTFLoader.js');
|
|
41
|
+
const { DRACOLoader } = await import('three/addons/loaders/DRACOLoader.js');
|
|
42
|
+
gltfLoaderInstance = new GLTFLoader();
|
|
43
|
+
const dracoLoader = new DRACOLoader();
|
|
44
|
+
dracoLoader.setDecoderPath(DRACO_URL);
|
|
45
|
+
gltfLoaderInstance.setDRACOLoader(dracoLoader);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function applyAccessories(accessoriesList) {
|
|
50
|
+
log('[ACC] applyAccessories called with ' + accessoriesList.length + ' items');
|
|
51
|
+
pendingAccessoriesList = accessoriesList;
|
|
52
|
+
await ensureThree();
|
|
53
|
+
const root = (head && head.armature) ? head.armature : staticModel;
|
|
54
|
+
log('[ACC] root=' + (root ? root.constructor.name + '/' + root.name : 'NULL') + ' head=' + !!head + ' head.armature=' + !!(head && head.armature) + ' staticModel=' + !!staticModel);
|
|
55
|
+
if (!root) { log('[ACC] ABORT: no root'); return; }
|
|
56
|
+
|
|
57
|
+
const boneNames = [];
|
|
58
|
+
root.traverse((child) => { if (child.isBone) boneNames.push(child.name); });
|
|
59
|
+
log('[ACC] Bones found: ' + boneNames.join(', '));
|
|
60
|
+
|
|
61
|
+
const newAccessoryIds = new Set(accessoriesList.map(a => a.id));
|
|
62
|
+
for (const id in currentAccessories) {
|
|
63
|
+
if (!newAccessoryIds.has(id)) {
|
|
64
|
+
const acc = currentAccessories[id];
|
|
65
|
+
if (acc.model) {
|
|
66
|
+
if (acc.model.parent) acc.model.parent.remove(acc.model);
|
|
67
|
+
disposeHierarchy(acc.model);
|
|
68
|
+
}
|
|
69
|
+
delete currentAccessories[id];
|
|
70
|
+
log('[ACC] Removed old accessory: ' + id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const accData of accessoriesList) {
|
|
75
|
+
log('[ACC] Processing: id=' + accData.id + ' url=' + accData.url + ' bone=' + accData.bone);
|
|
76
|
+
const existing = currentAccessories[accData.id];
|
|
77
|
+
|
|
78
|
+
if (!existing || existing.url !== accData.url) {
|
|
79
|
+
if (existing && existing.model) {
|
|
80
|
+
if (existing.model.parent) existing.model.parent.remove(existing.model);
|
|
81
|
+
disposeHierarchy(existing.model);
|
|
82
|
+
}
|
|
83
|
+
currentAccessories[accData.id] = { ...accData, model: null, isLoading: true, latestData: accData };
|
|
84
|
+
log('[ACC] Starting GLB load: ' + accData.url);
|
|
85
|
+
(async () => {
|
|
86
|
+
try {
|
|
87
|
+
const loadedUrl = await loadWithAuth(accData.url);
|
|
88
|
+
gltfLoaderInstance.load(loadedUrl, (gltf) => {
|
|
89
|
+
if (loadedUrl.startsWith('blob:')) URL.revokeObjectURL(loadedUrl);
|
|
90
|
+
log('[ACC] GLB loaded OK for ' + accData.id);
|
|
91
|
+
const model = gltf.scene;
|
|
92
|
+
const latestData = (currentAccessories[accData.id] && currentAccessories[accData.id].latestData) || accData;
|
|
93
|
+
let targetBone = null;
|
|
94
|
+
let prefixCandidate = null;
|
|
95
|
+
root.traverse((child) => {
|
|
96
|
+
if (child.isBone && child.name === latestData.bone) targetBone = child;
|
|
97
|
+
else if (!prefixCandidate && child.name.replace(/_\\d+$/, '') === latestData.bone) prefixCandidate = child;
|
|
98
|
+
});
|
|
99
|
+
if (!targetBone && prefixCandidate) {
|
|
100
|
+
targetBone = prefixCandidate;
|
|
101
|
+
log('[ACC] Prefix match bone: ' + targetBone.name);
|
|
102
|
+
}
|
|
103
|
+
model.traverse((child) => { if (child.isMesh) child.frustumCulled = false; });
|
|
104
|
+
if (!targetBone) { log('[ACC] Bone not found: ' + latestData.bone + '. Using root.'); targetBone = root; }
|
|
105
|
+
else log('[ACC] Found bone: ' + targetBone.name);
|
|
106
|
+
const modelScale = latestData.scale !== undefined ? latestData.scale : 1.0;
|
|
107
|
+
model.scale.set(modelScale, modelScale, modelScale);
|
|
108
|
+
model.position.set(
|
|
109
|
+
latestData.position ? latestData.position[0] : 0,
|
|
110
|
+
latestData.position ? latestData.position[1] : 0,
|
|
111
|
+
latestData.position ? latestData.position[2] : 0
|
|
112
|
+
);
|
|
113
|
+
if (latestData.rotation) model.rotation.set(...latestData.rotation);
|
|
114
|
+
if (!currentAccessories[accData.id] || currentAccessories[accData.id].url !== accData.url) {
|
|
115
|
+
log('[ACC] Aborting: ' + accData.id + ' changed while loading.');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
targetBone.add(model);
|
|
119
|
+
log('[ACC] Attached to ' + targetBone.name);
|
|
120
|
+
currentAccessories[accData.id].model = model;
|
|
121
|
+
currentAccessories[accData.id].isLoading = false;
|
|
122
|
+
}, () => {}, (err) => {
|
|
123
|
+
if (loadedUrl.startsWith('blob:')) URL.revokeObjectURL(loadedUrl);
|
|
124
|
+
log('[ACC] FAILED: ' + accData.id + ': ' + err.message);
|
|
125
|
+
if (currentAccessories[accData.id]) currentAccessories[accData.id].isLoading = false;
|
|
126
|
+
});
|
|
127
|
+
} catch (authErr) {
|
|
128
|
+
log('[ACC] Auth fetch failed ' + accData.id + ': ' + authErr.message);
|
|
129
|
+
if (currentAccessories[accData.id]) currentAccessories[accData.id].isLoading = false;
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
} else if (existing && existing.isLoading) {
|
|
133
|
+
existing.latestData = accData;
|
|
134
|
+
} else if (existing && existing.model) {
|
|
135
|
+
const model = existing.model;
|
|
136
|
+
const accScale = accData.scale !== undefined ? accData.scale : 1.0;
|
|
137
|
+
model.scale.set(accScale, accScale, accScale);
|
|
138
|
+
model.position.set(
|
|
139
|
+
accData.position ? accData.position[0] : 0,
|
|
140
|
+
accData.position ? accData.position[1] : 0,
|
|
141
|
+
accData.position ? accData.position[2] : 0
|
|
142
|
+
);
|
|
143
|
+
if (accData.rotation) model.rotation.set(...accData.rotation);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildMotionScript(): string;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildMotionScript = buildMotionScript;
|
|
4
|
+
/**
|
|
5
|
+
* Generated avatar WebView motion script section.
|
|
6
|
+
*
|
|
7
|
+
* Kept as a string builder so html.ts can compose the final document without
|
|
8
|
+
* owning every browser-side subsystem inline. The motion definitions and bone
|
|
9
|
+
* search map are serialized from the shared, framework-free source of truth in
|
|
10
|
+
* src/core/avatar/motion.ts so the WebView and the native WGPU path can never
|
|
11
|
+
* drift apart. The math below mirrors src/core/avatar/motionRuntime.ts.
|
|
12
|
+
*/
|
|
13
|
+
const motion_1 = require("../core/avatar/motion");
|
|
14
|
+
function buildMotionScript() {
|
|
15
|
+
return `/* ── MotionEngine ────────────────────────────────────────────────────────── */
|
|
16
|
+
/* Quaternion math helpers (no Three.js dep required) */
|
|
17
|
+
const QM = {
|
|
18
|
+
fromAxisAngle(ax, ay, az, angle) {
|
|
19
|
+
const s = Math.sin(angle / 2);
|
|
20
|
+
return { x: ax*s, y: ay*s, z: az*s, w: Math.cos(angle/2) };
|
|
21
|
+
},
|
|
22
|
+
multiply(a, b) {
|
|
23
|
+
return {
|
|
24
|
+
x: a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y,
|
|
25
|
+
y: a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x,
|
|
26
|
+
z: a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w,
|
|
27
|
+
w: a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
applyTo(bone, q) {
|
|
31
|
+
bone.quaternion.set(q.x, q.y, q.z, q.w);
|
|
32
|
+
},
|
|
33
|
+
copyFrom(bone) {
|
|
34
|
+
const q = bone.quaternion;
|
|
35
|
+
return { x: q.x, y: q.y, z: q.z, w: q.w };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/* Motion definitions + bone search map — serialized from the shared TS source
|
|
40
|
+
of truth (src/core/avatar/motion.ts) at build time. */
|
|
41
|
+
const MOTION_DEFS = ${JSON.stringify(motion_1.MOTION_DEFS)};
|
|
42
|
+
const BONE_SEARCH = ${JSON.stringify(motion_1.MOTION_BONE_SEARCH)};
|
|
43
|
+
|
|
44
|
+
/* Runtime state */
|
|
45
|
+
let motionActive = false;
|
|
46
|
+
let motionKey = null;
|
|
47
|
+
let motionStartTime = null;
|
|
48
|
+
let motionBones = {};
|
|
49
|
+
let motionRestQuats = {};
|
|
50
|
+
let motionSmileTargets = [];
|
|
51
|
+
let motionBeatTimer = null;
|
|
52
|
+
let motionBeatIndex = 0;
|
|
53
|
+
let motionAutoReturnTimer = null;
|
|
54
|
+
|
|
55
|
+
/* Bone scan: norm() strips punctuation and lowercases, so RPM names like
|
|
56
|
+
LeftShoulder, RightArm, LeftForeArm etc. all match correctly. */
|
|
57
|
+
function motionScanBones(root) {
|
|
58
|
+
motionBones = {}; motionRestQuats = {};
|
|
59
|
+
if (!root) return;
|
|
60
|
+
const norm = s => s.toLowerCase().replace(/[_\\s\\-\\.]/g, '');
|
|
61
|
+
root.traverse(child => {
|
|
62
|
+
if (!child.isBone) return;
|
|
63
|
+
const lk = norm(child.name);
|
|
64
|
+
for (const [key, kws] of Object.entries(BONE_SEARCH)) {
|
|
65
|
+
if (motionBones[key]) continue;
|
|
66
|
+
if (kws.some(kw => lk.includes(norm(kw)))) {
|
|
67
|
+
motionBones[key] = child;
|
|
68
|
+
motionRestQuats[key] = QM.copyFrom(child);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
const found = Object.keys(motionBones);
|
|
73
|
+
log('MotionEngine: ' + (found.length === 0
|
|
74
|
+
? 'No rigged bones detected — motion engine needs a humanoid skeleton.'
|
|
75
|
+
: found.length + ' bones mapped: ' + found.join(', ') + '.'));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function motionScanSmile(root) {
|
|
79
|
+
motionSmileTargets = [];
|
|
80
|
+
if (!root) return;
|
|
81
|
+
root.traverse(child => {
|
|
82
|
+
if (!child.isMesh || !child.morphTargetDictionary) return;
|
|
83
|
+
Object.keys(child.morphTargetDictionary).forEach(name => {
|
|
84
|
+
const lk = name.toLowerCase();
|
|
85
|
+
if (lk.includes('smile') || lk.includes('happy') || lk.includes('joy') || lk.includes('mouthsmile')) {
|
|
86
|
+
motionSmileTargets.push({ mesh: child, index: child.morphTargetDictionary[name] });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function motionInitFromRoot(root) {
|
|
93
|
+
motionScanBones(root);
|
|
94
|
+
motionScanSmile(root);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function motionSetSmile(v) {
|
|
98
|
+
motionSmileTargets.forEach(t => { t.mesh.morphTargetInfluences[t.index] = v; });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* BPM beat indicator — no-ops in WebView (no beat UI) */
|
|
102
|
+
function motionStartBeat(bpm) {
|
|
103
|
+
clearInterval(motionBeatTimer);
|
|
104
|
+
motionBeatIndex = 0;
|
|
105
|
+
/* beat UI elements not present in WebView — intentional no-op */
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function motionStopBeat() {
|
|
109
|
+
clearInterval(motionBeatTimer);
|
|
110
|
+
motionBeatTimer = null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ── applyMotionBones ────────────────────────────────────────
|
|
114
|
+
Called every frame from inside the render loop
|
|
115
|
+
(fallback: before renderer.render; TH: inside head.opt.update).
|
|
116
|
+
Runs AFTER TH has written its own bones so our values are
|
|
117
|
+
the last thing written before the draw call.
|
|
118
|
+
─────────────────────────────────────────────────────────── */
|
|
119
|
+
function applyMotionBones() {
|
|
120
|
+
if (!motionActive) return;
|
|
121
|
+
const def = MOTION_DEFS[motionKey];
|
|
122
|
+
if (!def) return;
|
|
123
|
+
const tSec = (performance.now() - motionStartTime) / 1000;
|
|
124
|
+
|
|
125
|
+
for (const [boneName, oscillators] of Object.entries(def.bones)) {
|
|
126
|
+
const bone = motionBones[boneName];
|
|
127
|
+
if (!bone) continue;
|
|
128
|
+
const rest = motionRestQuats[boneName];
|
|
129
|
+
let q = { ...rest };
|
|
130
|
+
|
|
131
|
+
for (const osc of oscillators) {
|
|
132
|
+
const angle = osc.freq === 0
|
|
133
|
+
? osc.amp
|
|
134
|
+
: osc.amp * Math.sin(2 * Math.PI * osc.freq * tSec + osc.phase);
|
|
135
|
+
q = QM.multiply(q, QM.fromAxisAngle(osc.ax, osc.ay, osc.az, angle));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
bone.quaternion.set(q.x, q.y, q.z, q.w);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
window.playMotion = function(key) {
|
|
143
|
+
const def = MOTION_DEFS[key];
|
|
144
|
+
if (!def) { log('MotionEngine: unknown motion key: ' + key); return false; }
|
|
145
|
+
const root = (head && head.armature) ? head.armature : staticModel;
|
|
146
|
+
if (!root) { log('MotionEngine: no model loaded'); return false; }
|
|
147
|
+
|
|
148
|
+
if (Object.keys(motionBones).length === 0) motionInitFromRoot(root);
|
|
149
|
+
|
|
150
|
+
window.stopMotion(false);
|
|
151
|
+
|
|
152
|
+
motionKey = key;
|
|
153
|
+
motionActive = true;
|
|
154
|
+
motionStartTime = performance.now();
|
|
155
|
+
|
|
156
|
+
motionStartBeat(def.bpm);
|
|
157
|
+
motionSetSmile(def.smile);
|
|
158
|
+
log('MotionEngine: playing ' + def.label + ' @ ' + def.bpm + ' BPM');
|
|
159
|
+
|
|
160
|
+
/* Pose-like motions (attack/defend) auto-revert to rest so combat loops
|
|
161
|
+
return to guard without an explicit stop message. */
|
|
162
|
+
if (def.autoReturnMs > 0) {
|
|
163
|
+
motionAutoReturnTimer = setTimeout(function() {
|
|
164
|
+
if (motionKey === key) window.stopMotion(true);
|
|
165
|
+
}, def.autoReturnMs);
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
window.stopMotion = function(restore) {
|
|
171
|
+
if (restore === undefined) restore = true;
|
|
172
|
+
if (motionAutoReturnTimer) { clearTimeout(motionAutoReturnTimer); motionAutoReturnTimer = null; }
|
|
173
|
+
motionActive = false;
|
|
174
|
+
motionStopBeat();
|
|
175
|
+
|
|
176
|
+
if (restore) {
|
|
177
|
+
for (const [key, bone] of Object.entries(motionBones)) {
|
|
178
|
+
const rest = motionRestQuats[key];
|
|
179
|
+
if (rest) bone.quaternion.set(rest.x, rest.y, rest.z, rest.w);
|
|
180
|
+
}
|
|
181
|
+
motionSetSmile(0);
|
|
182
|
+
motionKey = null;
|
|
183
|
+
log('MotionEngine: stopped');
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
/* ── End MotionEngine ─────────────────────────────────────── */
|
|
187
|
+
|
|
188
|
+
`;
|
|
189
|
+
}
|