@linxai/3d-shared 0.1.0
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/package.json +41 -0
- package/src/components/SceneBuilder.tsx +34 -0
- package/src/components/SceneContent.tsx +63 -0
- package/src/contexts/LoadingContext.tsx +46 -0
- package/src/hooks/useAnimationControl.ts +145 -0
- package/src/hooks/useAnimationLoader.ts +149 -0
- package/src/hooks/useAnimationStateMachine.ts +229 -0
- package/src/hooks/useCharacterController.ts +203 -0
- package/src/hooks/useModelLoader.ts +115 -0
- package/src/hooks/useMorphControl.ts +46 -0
- package/src/hooks/useProceduralAnimations.ts +91 -0
- package/src/hooks/useTalkingControl.ts +109 -0
- package/src/index.ts +32 -0
- package/src/internal.ts +52 -0
- package/src/types/character.ts +136 -0
- package/src/types/index.ts +197 -0
- package/src/utils/characterUtils.ts +156 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import type { SceneConfig, OrbitControlsConfig, ModelLoadConfig, AnimationStatesConfig } from './index'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Character 接口
|
|
6
|
+
* 只暴露最核心的 API,极简设计
|
|
7
|
+
*/
|
|
8
|
+
export interface Character {
|
|
9
|
+
/** 加载的模型对象 */
|
|
10
|
+
model: any
|
|
11
|
+
|
|
12
|
+
/** 设置动画状态(如 'idle', 'speaking', 'dancing') */
|
|
13
|
+
setState: (state: string, mood?: string) => void
|
|
14
|
+
|
|
15
|
+
/** 获取当前状态和情绪 */
|
|
16
|
+
getState: () => { state: string; mood: string }
|
|
17
|
+
|
|
18
|
+
/** 开始说话(可选指定情绪) */
|
|
19
|
+
startSpeak: (emotion?: string) => void
|
|
20
|
+
|
|
21
|
+
/** 停止说话 */
|
|
22
|
+
stopSpeak: () => void
|
|
23
|
+
|
|
24
|
+
/** 追加字幕文本(支持流式,仅 web 平台) */
|
|
25
|
+
appendSubtitle?: (text: string) => void
|
|
26
|
+
|
|
27
|
+
/** 替换字幕文本(仅 web 平台) */
|
|
28
|
+
setSubtitle?: (text: string) => void
|
|
29
|
+
|
|
30
|
+
/** 清空字幕(仅 web 平台) */
|
|
31
|
+
clearSubtitle?: () => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 内部 Character 接口
|
|
36
|
+
* 包含所有底层 API,仅供内部使用
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export interface CharacterInternal extends Character {
|
|
40
|
+
/** 动画剪辑列表(内部使用) */
|
|
41
|
+
clips: any[] | null
|
|
42
|
+
/** 根据 ID 获取动画剪辑(内部使用) */
|
|
43
|
+
getClip: (id: string) => any | null
|
|
44
|
+
/** 动画 Map (id -> clip)(内部使用) */
|
|
45
|
+
animationsMap: Map<string, any>
|
|
46
|
+
/** AnimationMixer 实例(内部使用) */
|
|
47
|
+
mixer: any | null
|
|
48
|
+
|
|
49
|
+
// 底层动画控制(内部使用)
|
|
50
|
+
play: (animationId: string, options?: { loop?: boolean; fadeIn?: number; fadeOut?: number }) => void
|
|
51
|
+
stop: (fadeOut?: number) => void
|
|
52
|
+
pause: () => void
|
|
53
|
+
resume: () => void
|
|
54
|
+
getCurrentAnimation: () => string | null
|
|
55
|
+
isPlaying: () => boolean
|
|
56
|
+
isSpeaking: () => boolean
|
|
57
|
+
|
|
58
|
+
// 情绪和表情控制(内部使用)
|
|
59
|
+
setMood: (mood: string) => void
|
|
60
|
+
applyEmotion: (emotion: string) => void
|
|
61
|
+
setMorphWeight: (morphName: string, value: number) => void
|
|
62
|
+
setDualMorph: (baseName: string, value: number) => void
|
|
63
|
+
|
|
64
|
+
// 程序化动画开关(内部使用)
|
|
65
|
+
setHeadSwayEnabled: (enabled: boolean) => void
|
|
66
|
+
setBlinkingEnabled: (enabled: boolean) => void
|
|
67
|
+
setLipSyncEnabled: (enabled: boolean) => void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 覆盖层配置(UI 相关)
|
|
72
|
+
* 字幕通过 Character.appendSubtitle/setSubtitle/clearSubtitle 控制
|
|
73
|
+
*/
|
|
74
|
+
export interface OverlayConfig {
|
|
75
|
+
/** 是否显示浮层 */
|
|
76
|
+
show?: boolean
|
|
77
|
+
/** 是否显示说话按钮 */
|
|
78
|
+
showSpeakButton?: boolean
|
|
79
|
+
/** 是否正在加载/连接 */
|
|
80
|
+
isLoading?: boolean
|
|
81
|
+
/** 是否已连接 */
|
|
82
|
+
isConnected?: boolean
|
|
83
|
+
/** 按钮点击回调 */
|
|
84
|
+
onButtonClick?: () => void
|
|
85
|
+
/** 字幕样式 */
|
|
86
|
+
subtitleStyle?: any
|
|
87
|
+
/** 浮层容器样式 */
|
|
88
|
+
style?: any
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* CharacterView Props 接口
|
|
93
|
+
* 平台包需要实现这个接口
|
|
94
|
+
*/
|
|
95
|
+
export interface CharacterViewProps {
|
|
96
|
+
// 核心配置(必需)
|
|
97
|
+
/** 模型配置 */
|
|
98
|
+
model: ModelLoadConfig
|
|
99
|
+
/** 动画状态机配置 */
|
|
100
|
+
animationStates?: AnimationStatesConfig
|
|
101
|
+
|
|
102
|
+
// 初始状态
|
|
103
|
+
/** 初始状态(默认 'idle') */
|
|
104
|
+
initialState?: string
|
|
105
|
+
/** 初始情绪(默认 'neutral') */
|
|
106
|
+
initialMood?: string
|
|
107
|
+
/** 是否启用欢迎动画 */
|
|
108
|
+
enableWelcome?: boolean
|
|
109
|
+
|
|
110
|
+
// 场景配置
|
|
111
|
+
/** 场景配置(灯光、相机等) */
|
|
112
|
+
sceneConfig?: SceneConfig
|
|
113
|
+
/** 轨道控制器配置 */
|
|
114
|
+
controlsConfig?: OrbitControlsConfig
|
|
115
|
+
|
|
116
|
+
// UI 配置
|
|
117
|
+
/** 覆盖层配置(字幕、按钮等) */
|
|
118
|
+
overlay?: OverlayConfig
|
|
119
|
+
/** Canvas 样式 */
|
|
120
|
+
style?: any
|
|
121
|
+
/** Canvas className */
|
|
122
|
+
className?: string
|
|
123
|
+
|
|
124
|
+
// 扩展内容
|
|
125
|
+
/** 额外的 3D 场景内容 */
|
|
126
|
+
children?: ReactNode
|
|
127
|
+
|
|
128
|
+
// 回调
|
|
129
|
+
/** 角色加载完成 */
|
|
130
|
+
onReady?: (character: Character) => void
|
|
131
|
+
/** 状态变化 */
|
|
132
|
+
onStateChange?: (state: string, mood: string) => void
|
|
133
|
+
/** 错误处理 */
|
|
134
|
+
onError?: (error: Error) => void
|
|
135
|
+
}
|
|
136
|
+
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 光源配置
|
|
3
|
+
*/
|
|
4
|
+
export interface LightConfig {
|
|
5
|
+
/** 环境光强度 */
|
|
6
|
+
ambientIntensity?: number
|
|
7
|
+
/** 点光源配置 */
|
|
8
|
+
pointLight?: {
|
|
9
|
+
position?: [number, number, number]
|
|
10
|
+
intensity?: number
|
|
11
|
+
} | null
|
|
12
|
+
/** 方向光配置 */
|
|
13
|
+
directionalLight?: {
|
|
14
|
+
position?: [number, number, number]
|
|
15
|
+
intensity?: number
|
|
16
|
+
} | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GridHelper 配置
|
|
21
|
+
*/
|
|
22
|
+
export interface GridHelperConfig {
|
|
23
|
+
/** 是否显示网格 */
|
|
24
|
+
visible?: boolean
|
|
25
|
+
/** 网格大小 */
|
|
26
|
+
size?: number
|
|
27
|
+
/** 网格分割数 */
|
|
28
|
+
divisions?: number
|
|
29
|
+
/** 中心线颜色 */
|
|
30
|
+
centerLineColor?: string
|
|
31
|
+
/** 网格线颜色 */
|
|
32
|
+
gridColor?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 3D 场景配置
|
|
37
|
+
*/
|
|
38
|
+
export interface SceneConfig {
|
|
39
|
+
/** 背景颜色 */
|
|
40
|
+
backgroundColor?: string
|
|
41
|
+
/** 环境光强度(已废弃,使用 lights.ambientIntensity) */
|
|
42
|
+
ambientLightIntensity?: number
|
|
43
|
+
/** 光源配置 */
|
|
44
|
+
lights?: LightConfig
|
|
45
|
+
/** 相机位置 */
|
|
46
|
+
cameraPosition?: [number, number, number]
|
|
47
|
+
/** 相机视野角度 */
|
|
48
|
+
fov?: number
|
|
49
|
+
/** GridHelper 配置 */
|
|
50
|
+
gridHelper?: GridHelperConfig
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* OrbitControls 配置
|
|
55
|
+
*/
|
|
56
|
+
export interface OrbitControlsConfig {
|
|
57
|
+
/** 是否启用阻尼 */
|
|
58
|
+
enableDamping?: boolean
|
|
59
|
+
/** 阻尼系数 */
|
|
60
|
+
dampingFactor?: number
|
|
61
|
+
/** 是否允许旋转 */
|
|
62
|
+
enableRotate?: boolean
|
|
63
|
+
/** 是否允许缩放 */
|
|
64
|
+
enableZoom?: boolean
|
|
65
|
+
/** 是否允许平移 */
|
|
66
|
+
enablePan?: boolean
|
|
67
|
+
/** 最小距离 */
|
|
68
|
+
minDistance?: number
|
|
69
|
+
/** 最大距离 */
|
|
70
|
+
maxDistance?: number
|
|
71
|
+
/** 最小极角 */
|
|
72
|
+
minPolarAngle?: number
|
|
73
|
+
/** 最大极角 */
|
|
74
|
+
maxPolarAngle?: number
|
|
75
|
+
/** 控制器目标点(摄像机看向的位置) */
|
|
76
|
+
target?: [number, number, number]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 动画配置
|
|
81
|
+
*/
|
|
82
|
+
export interface AnimationConfig {
|
|
83
|
+
/** 动画 ID(用于重命名) */
|
|
84
|
+
id: string
|
|
85
|
+
/** 动画文件路径 (Web: URL string, Native: require() 结果或 Asset URI string) */
|
|
86
|
+
path: string | number
|
|
87
|
+
/** 动画描述(可选) */
|
|
88
|
+
description?: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 动画状态机配置
|
|
93
|
+
*/
|
|
94
|
+
export interface AnimationStateConfig {
|
|
95
|
+
/**
|
|
96
|
+
* 状态下的动画列表
|
|
97
|
+
* State 决定播放哪些动画,Mood 决定面部表情
|
|
98
|
+
*/
|
|
99
|
+
animations: string[]
|
|
100
|
+
/** UI 显示标签(可选) */
|
|
101
|
+
label?: string
|
|
102
|
+
/** UI 显示图标(可选) */
|
|
103
|
+
icon?: string
|
|
104
|
+
/** 状态配置 */
|
|
105
|
+
config?: {
|
|
106
|
+
/** 是否循环播放 */
|
|
107
|
+
loop?: boolean
|
|
108
|
+
/** 持续时间(毫秒),用于自动切换到下一个动画 */
|
|
109
|
+
duration?: number
|
|
110
|
+
/** 淡入时间(秒) */
|
|
111
|
+
fadeIn?: number
|
|
112
|
+
/** 淡出时间(秒) */
|
|
113
|
+
fadeOut?: number
|
|
114
|
+
/** 是否随机播放(从动画列表中) */
|
|
115
|
+
random?: boolean
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 动画状态机总配置
|
|
121
|
+
*/
|
|
122
|
+
export interface AnimationStatesConfig {
|
|
123
|
+
[state: string]: AnimationStateConfig
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 模型加载配置
|
|
128
|
+
*/
|
|
129
|
+
export interface ModelLoadConfig {
|
|
130
|
+
/** 模型路径 (Web: URL string, Native: require() 结果或 Asset URI string) */
|
|
131
|
+
path: string | number
|
|
132
|
+
/** 缩放比例 */
|
|
133
|
+
scale?: number | [number, number, number]
|
|
134
|
+
/** 位置偏移 */
|
|
135
|
+
position?: [number, number, number]
|
|
136
|
+
/** 旋转角度 */
|
|
137
|
+
rotation?: [number, number, number]
|
|
138
|
+
/** 是否启用阴影 */
|
|
139
|
+
castShadow?: boolean
|
|
140
|
+
/** 是否接收阴影 */
|
|
141
|
+
receiveShadow?: boolean
|
|
142
|
+
/** 动画配置列表 */
|
|
143
|
+
animations?: AnimationConfig[]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 模型实例配置
|
|
148
|
+
*/
|
|
149
|
+
export interface ModelInstance {
|
|
150
|
+
/** 模型 ID */
|
|
151
|
+
id: string
|
|
152
|
+
/** 模型配置 */
|
|
153
|
+
config: ModelLoadConfig
|
|
154
|
+
/** 是否可见 */
|
|
155
|
+
visible?: boolean
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 3D 场景状态
|
|
160
|
+
*/
|
|
161
|
+
export interface SceneState {
|
|
162
|
+
/** 是否已加载 */
|
|
163
|
+
loaded: boolean
|
|
164
|
+
/** 加载进度 (0-1) */
|
|
165
|
+
progress?: number
|
|
166
|
+
/** 错误信息 */
|
|
167
|
+
error?: Error | null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 头部微动参数
|
|
172
|
+
*/
|
|
173
|
+
export interface HeadSwayParams {
|
|
174
|
+
/** 摆动速度 */
|
|
175
|
+
speed: number
|
|
176
|
+
/** 摆动范围(弧度) */
|
|
177
|
+
range: number
|
|
178
|
+
/** 俯仰偏移(弧度) */
|
|
179
|
+
pitchOffset: number
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 情绪表情配置
|
|
184
|
+
*/
|
|
185
|
+
export interface EmotionConfig {
|
|
186
|
+
/** 情绪名称 */
|
|
187
|
+
emotion: string
|
|
188
|
+
/** 头部微动参数 */
|
|
189
|
+
headSway?: HeadSwayParams
|
|
190
|
+
/** Morph targets 配置 */
|
|
191
|
+
morphs?: {
|
|
192
|
+
[morphName: string]: number
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Character 接口(从独立文件导出)
|
|
197
|
+
export type { Character, CharacterViewProps, CharacterInternal, OverlayConfig } from './character'
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { Object3D, Bone, SkinnedMesh } from 'three'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 查找模型中的头部骨骼(支持标准 Humanoid 骨骼命名)
|
|
5
|
+
*/
|
|
6
|
+
export function findHeadBone(model: any): Bone | null {
|
|
7
|
+
if (!model?.scene) return null
|
|
8
|
+
|
|
9
|
+
const headBoneNames = ['Head', 'head', 'mixamorig:Head', 'mixamorigHead']
|
|
10
|
+
|
|
11
|
+
let headBone: Bone | null = null
|
|
12
|
+
model.scene.traverse((child: any) => {
|
|
13
|
+
if (child.isBone && headBoneNames.includes(child.name)) {
|
|
14
|
+
headBone = child as Bone
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return headBone
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 查找模型中所有包含 morph targets 的 SkinnedMesh
|
|
23
|
+
*/
|
|
24
|
+
export function findMorphTargetMeshes(model: any): SkinnedMesh[] {
|
|
25
|
+
if (!model?.scene) return []
|
|
26
|
+
|
|
27
|
+
const meshes: SkinnedMesh[] = []
|
|
28
|
+
model.scene.traverse((child: any) => {
|
|
29
|
+
if (child.isSkinnedMesh && child.morphTargetDictionary && child.morphTargetInfluences) {
|
|
30
|
+
meshes.push(child as SkinnedMesh)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return meshes
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 设置单个 Morph Target 的权重
|
|
39
|
+
*/
|
|
40
|
+
export function setMorphWeight(
|
|
41
|
+
meshes: SkinnedMesh[],
|
|
42
|
+
morphName: string,
|
|
43
|
+
value: number
|
|
44
|
+
): void {
|
|
45
|
+
meshes.forEach(mesh => {
|
|
46
|
+
const index = mesh.morphTargetDictionary?.[morphName]
|
|
47
|
+
if (index !== undefined && mesh.morphTargetInfluences) {
|
|
48
|
+
mesh.morphTargetInfluences[index] = Math.max(0, Math.min(1, value))
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 设置双侧 Morph Target(ARKit 标准,如 eyeBlinkLeft/eyeBlinkRight)
|
|
55
|
+
*/
|
|
56
|
+
export function setDualMorph(
|
|
57
|
+
meshes: SkinnedMesh[],
|
|
58
|
+
baseName: string,
|
|
59
|
+
value: number
|
|
60
|
+
): void {
|
|
61
|
+
const leftName = `${baseName}Left`
|
|
62
|
+
const rightName = `${baseName}Right`
|
|
63
|
+
|
|
64
|
+
setMorphWeight(meshes, leftName, value)
|
|
65
|
+
setMorphWeight(meshes, rightName, value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 重置所有 lip-sync 相关的 morph targets
|
|
70
|
+
*/
|
|
71
|
+
export function resetLipSyncMorphs(meshes: SkinnedMesh[]): void {
|
|
72
|
+
// 基础嘴部 morphs
|
|
73
|
+
setMorphWeight(meshes, 'jawOpen', 0)
|
|
74
|
+
setDualMorph(meshes, 'mouthFunnel', 0)
|
|
75
|
+
|
|
76
|
+
// 其他嘴部 morphs
|
|
77
|
+
const mouthMorphs = [
|
|
78
|
+
'mouthClose',
|
|
79
|
+
'mouthPucker',
|
|
80
|
+
'mouthLeft',
|
|
81
|
+
'mouthRight',
|
|
82
|
+
'mouthRollLower',
|
|
83
|
+
'mouthRollUpper',
|
|
84
|
+
'mouthShrugLower',
|
|
85
|
+
'mouthShrugUpper',
|
|
86
|
+
'mouthStretch',
|
|
87
|
+
'mouthPress',
|
|
88
|
+
'mouthDimple',
|
|
89
|
+
'mouthLowerDown',
|
|
90
|
+
'mouthUpperUp',
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
mouthMorphs.forEach(morphName => {
|
|
94
|
+
setMorphWeight(meshes, morphName, 0)
|
|
95
|
+
setDualMorph(meshes, morphName, 0)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// 重置眨眼
|
|
99
|
+
setDualMorph(meshes, 'eyeBlink', 0)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 获取情绪对应的头部微动参数
|
|
104
|
+
*/
|
|
105
|
+
export function getEmotionHeadSwayParams(emotion: string) {
|
|
106
|
+
const D2R = Math.PI / 180
|
|
107
|
+
|
|
108
|
+
switch (emotion) {
|
|
109
|
+
case 'happy':
|
|
110
|
+
return { speed: 1.8, range: 7 * D2R, pitchOffset: -5 * D2R }
|
|
111
|
+
case 'sad':
|
|
112
|
+
return { speed: 0.6, range: 3 * D2R, pitchOffset: 8 * D2R }
|
|
113
|
+
case 'excited':
|
|
114
|
+
return { speed: 2.2, range: 9 * D2R, pitchOffset: -3 * D2R }
|
|
115
|
+
case 'neutral':
|
|
116
|
+
default:
|
|
117
|
+
return { speed: 1.0, range: 5 * D2R, pitchOffset: 0 }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 应用情绪表情 (ARKit morph targets)
|
|
123
|
+
*/
|
|
124
|
+
export function applyEmotionMorphs(
|
|
125
|
+
meshes: SkinnedMesh[],
|
|
126
|
+
emotion: string
|
|
127
|
+
): void {
|
|
128
|
+
// 重置基础表情
|
|
129
|
+
const morphsToReset = ['mouthSmile', 'mouthFrown', 'cheekSquint', 'eyeSquint', 'browDown']
|
|
130
|
+
morphsToReset.forEach(key => setDualMorph(meshes, key, 0))
|
|
131
|
+
setMorphWeight(meshes, 'browInnerUp', 0)
|
|
132
|
+
|
|
133
|
+
// 应用情绪表情
|
|
134
|
+
switch (emotion) {
|
|
135
|
+
case 'happy':
|
|
136
|
+
setDualMorph(meshes, 'mouthSmile', 0.7)
|
|
137
|
+
setDualMorph(meshes, 'cheekSquint', 0.5)
|
|
138
|
+
setDualMorph(meshes, 'eyeSquint', 0.2)
|
|
139
|
+
break
|
|
140
|
+
case 'sad':
|
|
141
|
+
setDualMorph(meshes, 'mouthFrown', 0.6)
|
|
142
|
+
setMorphWeight(meshes, 'browInnerUp', 0.7)
|
|
143
|
+
break
|
|
144
|
+
case 'excited':
|
|
145
|
+
setDualMorph(meshes, 'mouthSmile', 0.9)
|
|
146
|
+
setDualMorph(meshes, 'cheekSquint', 0.7)
|
|
147
|
+
setDualMorph(meshes, 'eyeSquint', 0.3)
|
|
148
|
+
setMorphWeight(meshes, 'browInnerUp', 0.4)
|
|
149
|
+
break
|
|
150
|
+
case 'neutral':
|
|
151
|
+
default:
|
|
152
|
+
setDualMorph(meshes, 'mouthSmile', 0.1)
|
|
153
|
+
break
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"strict": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"moduleResolution": "bundler",
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist"]
|
|
22
|
+
}
|
|
23
|
+
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['src/index.ts', 'src/internal.ts'],
|
|
5
|
+
format: ['cjs', 'esm'],
|
|
6
|
+
dts: true,
|
|
7
|
+
external: ['react', 'react-native', 'three', '@react-three/fiber', '@react-three/drei'],
|
|
8
|
+
// 使用 React 17+ 的新 JSX runtime
|
|
9
|
+
esbuildOptions(options) {
|
|
10
|
+
options.jsx = 'automatic'
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|