talking-head-studio 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +8 -7
  2. package/dist/TalkingHead.d.ts +0 -1
  3. package/dist/TalkingHead.js +31 -29
  4. package/dist/TalkingHead.web.d.ts +1 -3
  5. package/dist/TalkingHead.web.js +72 -43
  6. package/dist/appearance/apply.d.ts +0 -1
  7. package/dist/appearance/apply.js +8 -5
  8. package/dist/appearance/index.d.ts +0 -1
  9. package/dist/appearance/index.js +9 -3
  10. package/dist/appearance/matchers.d.ts +0 -1
  11. package/dist/appearance/matchers.js +4 -1
  12. package/dist/appearance/schema.d.ts +0 -1
  13. package/dist/appearance/schema.js +4 -1
  14. package/dist/editor/AvatarCanvas.d.ts +1 -2
  15. package/dist/editor/AvatarCanvas.js +44 -60
  16. package/dist/editor/AvatarCanvasErrorBoundary.d.ts +1 -2
  17. package/dist/editor/AvatarCanvasErrorBoundary.js +9 -14
  18. package/dist/editor/AvatarModel.d.ts +1 -2
  19. package/dist/editor/AvatarModel.js +17 -13
  20. package/dist/editor/RigidAccessory.d.ts +1 -2
  21. package/dist/editor/RigidAccessory.js +19 -17
  22. package/dist/editor/SkinnedClothing.d.ts +1 -2
  23. package/dist/editor/SkinnedClothing.js +46 -9
  24. package/dist/editor/index.d.ts +0 -1
  25. package/dist/editor/index.js +11 -4
  26. package/dist/editor/types.d.ts +0 -1
  27. package/dist/editor/types.js +2 -1
  28. package/dist/html.d.ts +0 -1
  29. package/dist/html.js +8 -5
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.js +20 -2
  32. package/dist/index.web.d.ts +0 -1
  33. package/dist/index.web.js +20 -2
  34. package/dist/sketchfab/api.d.ts +0 -1
  35. package/dist/sketchfab/api.js +10 -4
  36. package/dist/sketchfab/categories.d.ts +0 -1
  37. package/dist/sketchfab/categories.js +5 -2
  38. package/dist/sketchfab/index.d.ts +0 -1
  39. package/dist/sketchfab/index.js +13 -3
  40. package/dist/sketchfab/types.d.ts +0 -1
  41. package/dist/sketchfab/types.js +2 -1
  42. package/dist/sketchfab/useSketchfabSearch.d.ts +0 -1
  43. package/dist/sketchfab/useSketchfabSearch.js +20 -17
  44. package/dist/voice/convertToWav.d.ts +0 -1
  45. package/dist/voice/convertToWav.js +4 -1
  46. package/dist/voice/index.d.ts +0 -1
  47. package/dist/voice/index.js +9 -3
  48. package/dist/voice/useAudioPlayer.d.ts +0 -1
  49. package/dist/voice/useAudioPlayer.js +7 -4
  50. package/dist/voice/useAudioRecording.d.ts +0 -1
  51. package/dist/voice/useAudioRecording.js +20 -17
  52. package/package.json +10 -8
  53. package/dist/TalkingHead.d.ts.map +0 -1
  54. package/dist/TalkingHead.web.d.ts.map +0 -1
  55. package/dist/__tests__/TalkingHead.test.d.ts +0 -2
  56. package/dist/__tests__/TalkingHead.test.d.ts.map +0 -1
  57. package/dist/__tests__/TalkingHead.test.js +0 -23
  58. package/dist/__tests__/sketchfab.test.d.ts +0 -2
  59. package/dist/__tests__/sketchfab.test.d.ts.map +0 -1
  60. package/dist/__tests__/sketchfab.test.js +0 -21
  61. package/dist/appearance/apply.d.ts.map +0 -1
  62. package/dist/appearance/index.d.ts.map +0 -1
  63. package/dist/appearance/matchers.d.ts.map +0 -1
  64. package/dist/appearance/schema.d.ts.map +0 -1
  65. package/dist/editor/AvatarCanvas.d.ts.map +0 -1
  66. package/dist/editor/AvatarCanvasErrorBoundary.d.ts.map +0 -1
  67. package/dist/editor/AvatarModel.d.ts.map +0 -1
  68. package/dist/editor/RigidAccessory.d.ts.map +0 -1
  69. package/dist/editor/SkinnedClothing.d.ts.map +0 -1
  70. package/dist/editor/index.d.ts.map +0 -1
  71. package/dist/editor/types.d.ts.map +0 -1
  72. package/dist/html.d.ts.map +0 -1
  73. package/dist/index.d.ts.map +0 -1
  74. package/dist/index.web.d.ts.map +0 -1
  75. package/dist/sketchfab/api.d.ts.map +0 -1
  76. package/dist/sketchfab/categories.d.ts.map +0 -1
  77. package/dist/sketchfab/index.d.ts.map +0 -1
  78. package/dist/sketchfab/types.d.ts.map +0 -1
  79. package/dist/sketchfab/useSketchfabSearch.d.ts.map +0 -1
  80. package/dist/voice/convertToWav.d.ts.map +0 -1
  81. package/dist/voice/index.d.ts.map +0 -1
  82. package/dist/voice/useAudioPlayer.d.ts.map +0 -1
  83. package/dist/voice/useAudioRecording.d.ts.map +0 -1
  84. package/src/TalkingHead.tsx +0 -276
  85. package/src/TalkingHead.web.tsx +0 -220
  86. package/src/__tests__/TalkingHead.test.tsx +0 -32
  87. package/src/__tests__/sketchfab.test.ts +0 -24
  88. package/src/appearance/apply.ts +0 -94
  89. package/src/appearance/index.ts +0 -4
  90. package/src/appearance/matchers.ts +0 -43
  91. package/src/appearance/schema.ts +0 -35
  92. package/src/editor/AvatarCanvas.tsx +0 -167
  93. package/src/editor/AvatarCanvasErrorBoundary.tsx +0 -64
  94. package/src/editor/AvatarModel.tsx +0 -49
  95. package/src/editor/RigidAccessory.tsx +0 -130
  96. package/src/editor/SkinnedClothing.tsx +0 -114
  97. package/src/editor/index.ts +0 -5
  98. package/src/editor/r3f-shim.d.ts +0 -34
  99. package/src/editor/types.ts +0 -30
  100. package/src/html.ts +0 -678
  101. package/src/index.ts +0 -11
  102. package/src/index.web.ts +0 -8
  103. package/src/sketchfab/api.ts +0 -82
  104. package/src/sketchfab/categories.ts +0 -127
  105. package/src/sketchfab/index.ts +0 -6
  106. package/src/sketchfab/types.ts +0 -40
  107. package/src/sketchfab/useSketchfabSearch.ts +0 -110
  108. package/src/voice/convertToWav.ts +0 -87
  109. package/src/voice/index.ts +0 -7
  110. package/src/voice/useAudioPlayer.ts +0 -78
  111. package/src/voice/useAudioRecording.ts +0 -207
@@ -1,49 +0,0 @@
1
- // @ts-nocheck
2
- import { useGLTF } from '@react-three/drei';
3
- import { useEffect, useRef } from 'react';
4
- import { Box3, Vector3 } from 'three';
5
- import { applyAppearanceToObject3D } from '../appearance';
6
- import type { AvatarAppearance } from '../appearance';
7
-
8
- interface AvatarModelProps {
9
- url: string;
10
- appearance?: AvatarAppearance;
11
- scale?: number;
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- onSkeletonReady?: (skeleton: any) => void;
14
- onBoundsReady?: (center: typeof Vector3.prototype, height: number) => void;
15
- }
16
-
17
- export function AvatarModel({
18
- url,
19
- appearance,
20
- scale = 1,
21
- onSkeletonReady,
22
- onBoundsReady,
23
- }: AvatarModelProps) {
24
- const { scene } = useGLTF(url);
25
- const groupRef = useRef(null);
26
-
27
- useEffect(() => {
28
- if (!scene) return;
29
- scene.traverse((child: any) => {
30
- if (child.isSkinnedMesh && child.skeleton) {
31
- onSkeletonReady?.(child.skeleton);
32
- }
33
- });
34
- if (onBoundsReady) {
35
- const box = new Box3().setFromObject(scene);
36
- const center = new Vector3();
37
- box.getCenter(center);
38
- const height = box.max.y - box.min.y;
39
- onBoundsReady(center, height);
40
- }
41
- }, [scene, onSkeletonReady, onBoundsReady]);
42
-
43
- useEffect(() => {
44
- if (!scene) return;
45
- applyAppearanceToObject3D(scene, appearance ?? { version: 1 as const });
46
- }, [scene, appearance]);
47
-
48
- return <primitive ref={groupRef} object={scene} scale={scale} />;
49
- }
@@ -1,130 +0,0 @@
1
- // @ts-nocheck
2
- import { useGLTF, PivotControls } from '@react-three/drei';
3
- import { useEffect, useMemo, useState } from 'react';
4
- import { Box3, Vector3 } from 'three';
5
- import type { AssetPlacement } from './types';
6
-
7
- interface RigidAccessoryProps {
8
- assetId: string;
9
- url: string;
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- avatarScene: any;
12
- attachBone: string;
13
- offsetPosition?: [number, number, number];
14
- offsetRotation?: [number, number, number];
15
- scale?: number;
16
- isEditing?: boolean;
17
- onPlacementChange?: (assetId: string, placement: AssetPlacement) => void;
18
- }
19
-
20
- export function RigidAccessory({
21
- assetId,
22
- url,
23
- avatarScene,
24
- attachBone,
25
- offsetPosition,
26
- offsetRotation,
27
- scale = 1,
28
- isEditing = false,
29
- onPlacementChange,
30
- }: RigidAccessoryProps) {
31
- const { scene } = useGLTF(url);
32
-
33
- // Clone uniquely for this instance
34
- const clone = useMemo(() => scene.clone(true), [scene]);
35
- const [basePos, setBasePos] = useState<typeof Vector3.prototype | null>(null);
36
-
37
- useEffect(() => {
38
- if (!avatarScene) return;
39
-
40
- let targetBone: any = null;
41
- avatarScene.traverse((child: any) => {
42
- if (child.isBone && child.name === attachBone) {
43
- targetBone = child;
44
- }
45
- });
46
-
47
- if (!targetBone) return;
48
-
49
- const box = new Box3().setFromObject(clone);
50
- const meshMin = box.min;
51
- const meshCenter = new Vector3();
52
- box.getCenter(meshCenter);
53
-
54
- const boneWorldPos = new Vector3();
55
- targetBone.getWorldPosition(boneWorldPos);
56
-
57
- setBasePos(new Vector3(
58
- boneWorldPos.x - meshCenter.x,
59
- boneWorldPos.y - meshMin.y,
60
- boneWorldPos.z - meshCenter.z,
61
- ));
62
- }, [avatarScene, attachBone, clone]);
63
-
64
- // Apply transforms live
65
- useEffect(() => {
66
- if (!basePos) return;
67
-
68
- clone.position.copy(basePos);
69
-
70
- if (offsetPosition) {
71
- clone.position.x += offsetPosition[0];
72
- clone.position.y += offsetPosition[1];
73
- clone.position.z += offsetPosition[2];
74
- }
75
-
76
- if (offsetRotation) {
77
- clone.rotation.set(...offsetRotation);
78
- }
79
-
80
- if (scale !== undefined) {
81
- clone.scale.set(scale, scale, scale);
82
- }
83
- }, [basePos, offsetPosition, offsetRotation, scale, clone]);
84
-
85
- const handleDragEnd = () => {
86
- if (!basePos) return;
87
-
88
- // The PivotControls autoTransform updates the object directly
89
- const newOffsetPos: [number, number, number] = [
90
- clone.position.x - basePos.x,
91
- clone.position.y - basePos.y,
92
- clone.position.z - basePos.z,
93
- ];
94
-
95
- const newOffsetRot: [number, number, number] = [
96
- clone.rotation.x,
97
- clone.rotation.y,
98
- clone.rotation.z,
99
- ];
100
-
101
- const currentScale = clone.scale.x;
102
-
103
- onPlacementChange?.(assetId, {
104
- bone: attachBone,
105
- position: newOffsetPos,
106
- rotation: newOffsetRot,
107
- scale: currentScale,
108
- });
109
- };
110
-
111
- if (!basePos) return null;
112
-
113
- if (isEditing) {
114
- return (
115
- <PivotControls
116
- anchor={[0, 0, 0]}
117
- scale={100}
118
- fixed={true}
119
- depthTest={false}
120
- activeAxes={[true, true, true]}
121
- onDragEnd={handleDragEnd}
122
- autoTransform={true}
123
- >
124
- <primitive object={clone} />
125
- </PivotControls>
126
- );
127
- }
128
-
129
- return <primitive object={clone} />;
130
- }
@@ -1,114 +0,0 @@
1
- // @ts-nocheck
2
- import { useGLTF } from '@react-three/drei';
3
- import { useEffect, useRef } from 'react';
4
- import * as THREE from 'three';
5
- import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';
6
-
7
- interface SkinnedClothingProps {
8
- url: string;
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- avatarSkeleton: any;
11
- }
12
-
13
- /**
14
- * Rebind a skinned mesh's bones to the avatar skeleton by matching bone names.
15
- * This handles hair packs / clothing that share the same rig convention but
16
- * were exported as separate files with their own skeleton.
17
- */
18
- function rebindToAvatarSkeleton(mesh: THREE.SkinnedMesh, avatarSkeleton: THREE.Skeleton) {
19
- const avatarBonesByName = new Map<string, THREE.Bone>();
20
- avatarSkeleton.bones.forEach((bone: THREE.Bone) => avatarBonesByName.set(bone.name, bone));
21
-
22
- // Build a new bone array for this mesh by matching names
23
- const newBones: THREE.Bone[] = [];
24
- const oldBones = mesh.skeleton.bones;
25
- const boneIndexMap = new Map<number, number>(); // old index → new index
26
-
27
- oldBones.forEach((oldBone: THREE.Bone, oldIdx: number) => {
28
- const matched = avatarBonesByName.get(oldBone.name);
29
- if (matched) {
30
- boneIndexMap.set(oldIdx, newBones.length);
31
- newBones.push(matched);
32
- }
33
- });
34
-
35
- if (newBones.length === 0) {
36
- // No bone names matched at all — just bind directly and hope for the best
37
- mesh.bind(avatarSkeleton);
38
- return;
39
- }
40
-
41
- // Remap skinIndex buffer to point at the new bone indices
42
- const skinIndex = mesh.geometry.attributes.skinIndex;
43
- const remappedIndex = skinIndex.array.slice();
44
-
45
- for (let i = 0; i < remappedIndex.length; i++) {
46
- const oldIdx = remappedIndex[i];
47
- const newIdx = boneIndexMap.get(oldIdx);
48
- remappedIndex[i] = newIdx !== undefined ? newIdx : 0;
49
- }
50
-
51
- mesh.geometry.setAttribute(
52
- 'skinIndex',
53
- new THREE.Uint16BufferAttribute(remappedIndex, skinIndex.itemSize),
54
- );
55
-
56
- // Build inverse bind matrices for the matched bones
57
- const inverseBindMatrices: number[] = [];
58
- newBones.forEach((bone: THREE.Bone) => {
59
- const m = new THREE.Matrix4();
60
- // Use the avatar skeleton's inverse bind matrix if bone index aligns,
61
- // otherwise compute from world matrix
62
- const avatarIdx = avatarSkeleton.bones.indexOf(bone);
63
- if (avatarIdx >= 0 && avatarSkeleton.boneInverses[avatarIdx]) {
64
- m.copy(avatarSkeleton.boneInverses[avatarIdx]);
65
- } else {
66
- m.copy(bone.matrixWorld).invert();
67
- }
68
- inverseBindMatrices.push(...m.elements);
69
- });
70
-
71
- const newSkeleton = new THREE.Skeleton(
72
- newBones,
73
- newBones.map((_: THREE.Bone, i: number) => {
74
- const m = new THREE.Matrix4();
75
- m.fromArray(inverseBindMatrices, i * 16);
76
- return m;
77
- }),
78
- );
79
-
80
- mesh.bind(newSkeleton);
81
- }
82
-
83
- export function SkinnedClothing({ url, avatarSkeleton }: SkinnedClothingProps) {
84
- const { scene } = useGLTF(url);
85
- const groupRef = useRef<any>(null);
86
-
87
- useEffect(() => {
88
- const group = groupRef.current;
89
- if (!group || !scene || !avatarSkeleton) return;
90
-
91
- const clone = SkeletonUtils.clone(scene as any);
92
-
93
- (clone as any).traverse((child: any) => {
94
- if (child.isSkinnedMesh) {
95
- rebindToAvatarSkeleton(child as THREE.SkinnedMesh, avatarSkeleton);
96
- }
97
- });
98
-
99
- group.add(clone as any);
100
-
101
- return () => {
102
- group.remove(clone as any);
103
- (clone as any).traverse((child: any) => {
104
- if (child.geometry) child.geometry.dispose();
105
- if (child.material) {
106
- const materials = Array.isArray(child.material) ? child.material : [child.material];
107
- materials.forEach((m: any) => m.dispose());
108
- }
109
- });
110
- };
111
- }, [scene, avatarSkeleton]);
112
-
113
- return <group ref={groupRef} />;
114
- }
@@ -1,5 +0,0 @@
1
- export { AvatarCanvas } from './AvatarCanvas';
2
- export { AvatarModel } from './AvatarModel';
3
- export { RigidAccessory } from './RigidAccessory';
4
- export { SkinnedClothing } from './SkinnedClothing';
5
- export type { AvatarEditorProps, AssetPlacement, EquippedAsset } from './types';
@@ -1,34 +0,0 @@
1
- /**
2
- * Minimal JSX intrinsic shims for React Three Fiber elements.
3
- * These are provided at runtime by @react-three/fiber (a peer dependency).
4
- * This file prevents TS errors when the peer dep types are not installed.
5
- */
6
- import type React from 'react';
7
-
8
- declare module 'react' {
9
- namespace JSX {
10
- interface IntrinsicElements {
11
- // R3F primitives
12
- primitive: {
13
- object: any;
14
- ref?: React.Ref<any>;
15
- scale?: any;
16
- [key: string]: any;
17
- };
18
- group: {
19
- ref?: React.Ref<any>;
20
- [key: string]: any;
21
- };
22
- // Three.js light intrinsics
23
- ambientLight: {
24
- intensity?: number;
25
- [key: string]: any;
26
- };
27
- directionalLight: {
28
- position?: [number, number, number];
29
- intensity?: number;
30
- [key: string]: any;
31
- };
32
- }
33
- }
34
- }
@@ -1,30 +0,0 @@
1
- import type { AvatarAppearance } from '../appearance';
2
- import type React from 'react';
3
-
4
- export interface AssetPlacement {
5
- bone: string;
6
- position: [number, number, number];
7
- rotation: [number, number, number];
8
- scale?: number;
9
- }
10
-
11
- export interface EquippedAsset {
12
- id: string;
13
- url: string;
14
- type: 'rigid' | 'skinned';
15
- slot?: string;
16
- attach_bone?: string;
17
- offset_position?: [number, number, number];
18
- offset_rotation?: [number, number, number];
19
- }
20
-
21
- export interface AvatarEditorProps {
22
- avatarUrl: string;
23
- appearance?: AvatarAppearance;
24
- equipped?: EquippedAsset[];
25
- placements?: Record<string, AssetPlacement>;
26
- editingAssetId?: string | null;
27
- onPlacementChange?: (assetId: string, placement: AssetPlacement) => void;
28
- className?: string;
29
- style?: React.CSSProperties;
30
- }