metaverse-avatar 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/ASSET_LICENSES.md +122 -0
- package/Avatar.js +860 -0
- package/LICENSE.md +21 -0
- package/README.md +407 -0
- package/anims/UAL1_Standard.glb +0 -0
- package/anims/UAL2_Standard.glb +0 -0
- package/anims/pirouette.bvh +867 -0
- package/attachments.js +388 -0
- package/avatarManager.js +110 -0
- package/blink.js +58 -0
- package/bvh.js +110 -0
- package/gltfAnim.js +271 -0
- package/index.js +61 -0
- package/licenses/AGPL-3.0.txt +661 -0
- package/models/body.glb +0 -0
- package/models/eyes.glb +0 -0
- package/models/feet.glb +0 -0
- package/models/hands.glb +0 -0
- package/models/head.glb +0 -0
- package/models/textures/android_face.png +0 -0
- package/models/textures/android_face_ao.jpg +0 -0
- package/models/textures/android_face_metallic.jpg +0 -0
- package/models/textures/android_face_normal.jpg +0 -0
- package/models/textures/android_face_roughness.jpg +0 -0
- package/models/textures/android_lower.png +0 -0
- package/models/textures/android_lower_ao.jpg +0 -0
- package/models/textures/android_lower_metallic.jpg +0 -0
- package/models/textures/android_lower_normal.jpg +0 -0
- package/models/textures/android_lower_roughness.jpg +0 -0
- package/models/textures/android_upper.png +0 -0
- package/models/textures/android_upper_ao.jpg +0 -0
- package/models/textures/android_upper_metallic.jpg +0 -0
- package/models/textures/android_upper_normal.jpg +0 -0
- package/models/textures/android_upper_roughness.jpg +0 -0
- package/models/textures/blue_eyes.png +0 -0
- package/models/textures/layers/cute_pants.png +0 -0
- package/models/textures/layers/cute_shirt.png +0 -0
- package/nipple.js +218 -0
- package/package.json +48 -0
- package/pbr.js +225 -0
- package/physics.js +313 -0
- package/skeleton.js +70 -0
- package/sliders.js +590 -0
- package/speech.js +130 -0
- package/visemes.js +66 -0
- package/voice.js +75 -0
package/sliders.js
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
// Shape sliders for the Ruth2 fitted-mesh body.
|
|
2
|
+
//
|
|
3
|
+
// How fitted-mesh shape sliders work: collision-volume bones are scaled and
|
|
4
|
+
// offset to deform the skin. In upstream viewers that mechanism reads
|
|
5
|
+
// avatar_lad.xml, where
|
|
6
|
+
// each visual parameter drives some combination of (a) morph targets baked
|
|
7
|
+
// into the system avatar mesh and (b) scale/offset transforms on skeleton
|
|
8
|
+
// bones — in particular the invisible "collision volume" bones (BELLY, BUTT,
|
|
9
|
+
// CHEST, LEFT_PEC, ...). Mesh bodies like Ruth2 can't use the system morphs,
|
|
10
|
+
// so fitted-mesh bodies are weighted to the collision volumes and respond to
|
|
11
|
+
// the bone-driven subset of sliders. We reproduce that idea here: every
|
|
12
|
+
// slider multiplies the rest scale (and optionally offsets the rest
|
|
13
|
+
// position) of a set of bones.
|
|
14
|
+
//
|
|
15
|
+
// Each effect: { bones: [...], scale: [x,y,z amounts], offset: [x,y,z meters],
|
|
16
|
+
// rot: [x,y,z radians] }. Applied as: boneScale = rest * (1 + amount * t),
|
|
17
|
+
// bonePos = rest + offset * t, boneRot = rest * euler(rot * t), with t in
|
|
18
|
+
// [-1, 1]. NOTE: the rig is Z-up inside the armature (Blender convention)
|
|
19
|
+
// and the avatar faces +X, so Z is "up", Y is left/right, X is front/back.
|
|
20
|
+
|
|
21
|
+
// Bento finger bone name helpers: mHand<Finger><Joint><Side>
|
|
22
|
+
const FINGERS = ['Index', 'Middle', 'Ring', 'Pinky'];
|
|
23
|
+
const fingerBones = (side, fingers = FINGERS, joints = [1, 2, 3]) =>
|
|
24
|
+
fingers.flatMap((f) => joints.map((j) => `mHand${f}${j}${side}`));
|
|
25
|
+
|
|
26
|
+
// Lip bone trios (left / centre / right), upper and lower — independently
|
|
27
|
+
// addressable, so the top and bottom lips can be shaped separately. Plus the
|
|
28
|
+
// two mouth corners. In the armature frame: X = front/back (protrusion),
|
|
29
|
+
// Y = left/right, Z = up/down.
|
|
30
|
+
const UPPER_LIP = ['mFaceLipUpperLeft', 'mFaceLipUpperCenter', 'mFaceLipUpperRight'];
|
|
31
|
+
const LOWER_LIP = ['mFaceLipLowerLeft', 'mFaceLipLowerCenter', 'mFaceLipLowerRight'];
|
|
32
|
+
const LIP_CORNERS = ['mFaceLipCornerLeft', 'mFaceLipCornerRight'];
|
|
33
|
+
const ALL_LIP = [...UPPER_LIP, ...LOWER_LIP];
|
|
34
|
+
const MOUTH_ALL = [...ALL_LIP, ...LIP_CORNERS]; // every lip bone, for whole-mouth moves
|
|
35
|
+
|
|
36
|
+
export const SLIDERS = [
|
|
37
|
+
// ---- Body ----
|
|
38
|
+
{ id: 'height', group: 'Body', label: 'Height', special: 'height' },
|
|
39
|
+
{
|
|
40
|
+
id: 'thickness', group: 'Body', label: 'Body Thickness',
|
|
41
|
+
effects: [{ bones: ['PELVIS', 'BELLY', 'CHEST', 'UPPER_BACK', 'LOWER_BACK', 'NECK', 'BUTT'], scale: [0.22, 0.22, 0] },
|
|
42
|
+
{ bones: ['L_CLAVICLE', 'R_CLAVICLE'], scale: [0, 0.22, 0] }],
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// ---- Head ----
|
|
46
|
+
// (the grafted Ruth2 v4 head: face bones under the synthesized mHead)
|
|
47
|
+
{
|
|
48
|
+
id: 'head_size', group: 'Head', label: 'Head Size',
|
|
49
|
+
effects: [{ bones: ['mHead'], scale: [0.25, 0.25, 0.25] }],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
// Overall face elongation (mHead Z = vertical). Long faces (oval/diamond)
|
|
53
|
+
// vs short faces (round/square) — combine with the width controls below.
|
|
54
|
+
id: 'face_length', group: 'Head', label: 'Face Length',
|
|
55
|
+
effects: [{ bones: ['mHead'], scale: [0, 0, 0.2] }],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
// Overall face width (mHead Y = lateral). Broad vs narrow whole head.
|
|
59
|
+
id: 'face_width', group: 'Head', label: 'Face Width',
|
|
60
|
+
effects: [{ bones: ['mHead'], scale: [0, 0.18, 0] }],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
// Width at the forehead/temple level — the top of the face-shape triangle.
|
|
64
|
+
// Wide forehead + narrow jaw = heart shape.
|
|
65
|
+
id: 'forehead_width', group: 'Head', label: 'Forehead Width',
|
|
66
|
+
effects: [
|
|
67
|
+
{ bones: ['mFaceForeheadLeft'], offset: [0, 0.007, 0] },
|
|
68
|
+
{ bones: ['mFaceForeheadRight'], offset: [0, -0.007, 0] },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
// Width at the cheekbone level — the middle of the face-shape triangle.
|
|
73
|
+
// Wide here with narrow forehead + jaw = diamond shape.
|
|
74
|
+
id: 'cheek_width', group: 'Head', label: 'Cheekbone Width',
|
|
75
|
+
effects: [
|
|
76
|
+
{ bones: ['mFaceCheekUpperLeft'], offset: [0, 0.007, 0] },
|
|
77
|
+
{ bones: ['mFaceCheekUpperRight'], offset: [0, -0.007, 0] },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'ear_size', group: 'Head', label: 'Ear Size',
|
|
82
|
+
effects: [{ bones: ['mFaceEar1Left', 'mFaceEar1Right'], scale: [0.6, 0.6, 0.6] }],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
// Ear protrusion: push the whole ear laterally outward (Y) so it stands off
|
|
86
|
+
// the head; negative tucks it flat against the skull.
|
|
87
|
+
id: 'ear_angle', group: 'Head', label: 'Ear Protrusion',
|
|
88
|
+
effects: [
|
|
89
|
+
{ bones: ['mFaceEar1Left', 'mFaceEar2Left'], offset: [0, 0.006, 0] },
|
|
90
|
+
{ bones: ['mFaceEar1Right', 'mFaceEar2Right'], offset: [0, -0.006, 0] },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
// Ear length/height (Z). Scales the base bone; the tip bone inherits it, so
|
|
95
|
+
// the whole ear stretches vertically.
|
|
96
|
+
id: 'ear_length', group: 'Head', label: 'Ear Length',
|
|
97
|
+
effects: [{ bones: ['mFaceEar1Left', 'mFaceEar1Right'], scale: [0, 0, 0.5] }],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
// Pointed / elf ears: extend the tip bone upward (Z) while narrowing its
|
|
101
|
+
// cross-section (X depth + Y width) so it tapers to a point, plus raise and
|
|
102
|
+
// flare it outward. Negative gives a rounder, blunter tip.
|
|
103
|
+
id: 'ear_point', group: 'Head', label: 'Ear Point',
|
|
104
|
+
effects: [
|
|
105
|
+
{ bones: ['mFaceEar2Left', 'mFaceEar2Right'], scale: [-0.35, -0.35, 0.7] },
|
|
106
|
+
{ bones: ['mFaceEar2Left'], offset: [0, 0.004, 0.009] },
|
|
107
|
+
{ bones: ['mFaceEar2Right'], offset: [0, -0.004, 0.009] },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'nose_size', group: 'Head', label: 'Nose Size',
|
|
112
|
+
effects: [{ bones: ['mFaceNoseLeft', 'mFaceNoseRight', 'mFaceNoseCenter', 'mFaceNoseBase', 'mFaceNoseBridge'], scale: [0.5, 0.5, 0.5] }],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'nose_width', group: 'Head', label: 'Nose Width',
|
|
116
|
+
effects: [
|
|
117
|
+
{ bones: ['mFaceNoseLeft'], offset: [0, 0.006, 0] },
|
|
118
|
+
{ bones: ['mFaceNoseRight'], offset: [0, -0.006, 0] },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
// Bridge prominence: push the bridge forward (+X) for a higher/Roman nose,
|
|
123
|
+
// pull it back (-X) for a flatter profile.
|
|
124
|
+
id: 'nose_bridge', group: 'Head', label: 'Nose Bridge',
|
|
125
|
+
effects: [{ bones: ['mFaceNoseBridge'], offset: [0.006, 0, 0] }],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
// Tip projection: how far the nose tip sticks out from the face (+X).
|
|
129
|
+
id: 'nose_tip', group: 'Head', label: 'Nose Tip',
|
|
130
|
+
effects: [{ bones: ['mFaceNoseCenter'], offset: [0.008, 0, 0] }],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
// Tip tilt: raise (+Z, upturned) or lower (-Z, drooping) the tip.
|
|
134
|
+
id: 'nose_tilt', group: 'Head', label: 'Nose Tilt',
|
|
135
|
+
effects: [{ bones: ['mFaceNoseCenter'], offset: [0, 0, 0.006] }],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'jaw_width', group: 'Head', label: 'Jaw Width',
|
|
139
|
+
effects: [{ bones: ['mFaceJaw'], scale: [0, 0.35, 0] }],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'chin_depth', group: 'Head', label: 'Chin Depth',
|
|
143
|
+
effects: [{ bones: ['mFaceChin'], offset: [0.012, 0, 0] }],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'cheek_fullness', group: 'Head', label: 'Cheek Fullness',
|
|
147
|
+
effects: [{ bones: ['mFaceCheekLowerLeft', 'mFaceCheekLowerRight', 'mFaceCheekUpperLeft', 'mFaceCheekUpperRight'], scale: [0.5, 0.5, 0.5] }],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
// High cheekbones: push the upper cheeks forward (+X) and up (+Z) for a more
|
|
151
|
+
// defined, sculpted look.
|
|
152
|
+
id: 'cheekbones', group: 'Head', label: 'Cheekbones',
|
|
153
|
+
effects: [{ bones: ['mFaceCheekUpperLeft', 'mFaceCheekUpperRight'], offset: [0.006, 0, 0.004] }],
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
// Gaunt/hollow lower cheeks: positive shrinks them in for a sunken look,
|
|
157
|
+
// negative fills them out (chubby).
|
|
158
|
+
id: 'cheek_hollow', group: 'Head', label: 'Cheek Hollows',
|
|
159
|
+
effects: [{ bones: ['mFaceCheekLowerLeft', 'mFaceCheekLowerRight'], scale: [-0.4, -0.4, -0.4] }],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'eye_size', group: 'Head', label: 'Eye Size',
|
|
163
|
+
effects: [{ bones: ['mEyeLeft', 'mEyeRight'], scale: [0.3, 0.3, 0.3] }],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'eye_spacing', group: 'Head', label: 'Eye Spacing',
|
|
167
|
+
effects: [
|
|
168
|
+
{ bones: ['mEyeLeft'], offset: [0, 0.005, 0] },
|
|
169
|
+
{ bones: ['mEyeRight'], offset: [0, -0.005, 0] },
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
// Brow ridge projection: push the brows forward (+X) and down (-Z) for a
|
|
174
|
+
// heavy, low "hunter" brow; negative recedes them for a smoother forehead.
|
|
175
|
+
id: 'brow_ridge', group: 'Head', label: 'Brow Ridge',
|
|
176
|
+
effects: [{
|
|
177
|
+
bones: ['mFaceEyebrowInnerLeft', 'mFaceEyebrowCenterLeft', 'mFaceEyebrowOuterLeft',
|
|
178
|
+
'mFaceEyebrowInnerRight', 'mFaceEyebrowCenterRight', 'mFaceEyebrowOuterRight'],
|
|
179
|
+
offset: [0.007, 0, -0.003],
|
|
180
|
+
}],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
// Raise/lower the whole brow line (Z). Positive = higher brows.
|
|
184
|
+
id: 'brow_height', group: 'Head', label: 'Brow Height',
|
|
185
|
+
effects: [{
|
|
186
|
+
bones: ['mFaceEyebrowInnerLeft', 'mFaceEyebrowCenterLeft', 'mFaceEyebrowOuterLeft',
|
|
187
|
+
'mFaceEyebrowInnerRight', 'mFaceEyebrowCenterRight', 'mFaceEyebrowOuterRight'],
|
|
188
|
+
offset: [0, 0, 0.006],
|
|
189
|
+
}],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
// Brow angle: positive lifts the outer brow and drops the inner for an
|
|
193
|
+
// arched/upswept look; negative gives a flat or sloping brow.
|
|
194
|
+
id: 'brow_tilt', group: 'Head', label: 'Brow Angle',
|
|
195
|
+
effects: [
|
|
196
|
+
{ bones: ['mFaceEyebrowInnerLeft', 'mFaceEyebrowInnerRight'], offset: [0, 0, -0.004] },
|
|
197
|
+
{ bones: ['mFaceEyebrowOuterLeft', 'mFaceEyebrowOuterRight'], offset: [0, 0, 0.004] },
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
// Eye slant / canthal tilt: drop the inner eye corners (-Z) for an upward
|
|
202
|
+
// almond slant; positive = upslant, negative = downturned.
|
|
203
|
+
id: 'eye_slant', group: 'Head', label: 'Eye Slant',
|
|
204
|
+
effects: [{ bones: ['mFaceEyecornerInnerLeft', 'mFaceEyecornerInnerRight'], offset: [0, 0, -0.004] }],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
// Palpebral aperture — resting eye openness, both eyes. eye_closed shows
|
|
208
|
+
// +Y rotation shuts the upper lid, so negative here narrows to a slit/squint
|
|
209
|
+
// and positive widens the eyes (upper lid up, lower lid down).
|
|
210
|
+
id: 'eye_opening', group: 'Head', label: 'Eye Opening',
|
|
211
|
+
effects: [
|
|
212
|
+
{ bones: ['mFaceEyeLidUpperLeft', 'mFaceEyeLidUpperRight'], rot: [0, -0.4, 0] },
|
|
213
|
+
{ bones: ['mFaceEyeLidLowerLeft', 'mFaceEyeLidLowerRight'], rot: [0, 0.15, 0] },
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
// Upper-lid fullness — approximates monolid vs double-lid. A true
|
|
218
|
+
// supratarsal crease needs a morph/normal map (bones can't carve a fold);
|
|
219
|
+
// this puffs the upper lid forward (+X) and down (-Z) for a full, smooth
|
|
220
|
+
// monolid look, or recedes it (negative) for a deeper-set, creased eye.
|
|
221
|
+
id: 'lid_fullness', group: 'Head', label: 'Upper Lid Fullness',
|
|
222
|
+
effects: [{ bones: ['mFaceEyeLidUpperLeft', 'mFaceEyeLidUpperRight'], offset: [0.003, 0, -0.002] }],
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: 'eye_closed_l', group: 'Head', label: 'Left Eye Closed',
|
|
226
|
+
effects: [
|
|
227
|
+
{ bones: ['mFaceEyeLidUpperLeft'], rot: [0, 0.55, 0] },
|
|
228
|
+
{ bones: ['mFaceEyeLidLowerLeft'], rot: [0, -0.15, 0] },
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 'eye_closed_r', group: 'Head', label: 'Right Eye Closed',
|
|
233
|
+
effects: [
|
|
234
|
+
{ bones: ['mFaceEyeLidUpperRight'], rot: [0, 0.55, 0] },
|
|
235
|
+
{ bones: ['mFaceEyeLidLowerRight'], rot: [0, -0.15, 0] },
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// ---- Mouth (jaw + tongue) ----
|
|
240
|
+
{
|
|
241
|
+
id: 'mouth_open', group: 'Mouth', label: 'Mouth Open',
|
|
242
|
+
effects: [{ bones: ['mFaceJaw'], rot: [0, 0.45, 0] }],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: 'tongue_out', group: 'Mouth', label: 'Tongue Out',
|
|
246
|
+
effects: [
|
|
247
|
+
{ bones: ['mFaceTongueBase'], offset: [0.02, 0, 0] },
|
|
248
|
+
{ bones: ['mFaceTongueTip'], offset: [0.015, 0, 0] },
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// ---- Lips ----
|
|
253
|
+
// A character-creator-style lip rig: overall size + position, independent
|
|
254
|
+
// upper/lower fullness and protrusion, pucker, cupid's bow, smile, and corner
|
|
255
|
+
// shaping. Each maps to a distinct DOF on the lip / corner bones.
|
|
256
|
+
{
|
|
257
|
+
id: 'lip_size', group: 'Lips', label: 'Lip Size (overall)',
|
|
258
|
+
effects: [{ bones: MOUTH_ALL, scale: [0.45, 0.45, 0.45] }],
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'mouth_width', group: 'Lips', label: 'Mouth Width',
|
|
262
|
+
effects: [
|
|
263
|
+
{ bones: ['mFaceLipCornerLeft'], offset: [0, 0.008, 0] },
|
|
264
|
+
{ bones: ['mFaceLipCornerRight'], offset: [0, -0.008, 0] },
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
// Slide the whole mouth up or down the face.
|
|
269
|
+
id: 'mouth_raise', group: 'Lips', label: 'Mouth Up / Down',
|
|
270
|
+
effects: [{ bones: MOUTH_ALL, offset: [0, 0, 0.006] }],
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
// Slide the whole mouth forward or back (set into / off the face).
|
|
274
|
+
id: 'mouth_depth', group: 'Lips', label: 'Mouth Forward / Back',
|
|
275
|
+
effects: [{ bones: MOUTH_ALL, offset: [0.006, 0, 0] }],
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
// Vertical fullness (height) of the TOP lip only.
|
|
279
|
+
id: 'upper_lip_fullness', group: 'Lips', label: 'Upper Lip Fullness',
|
|
280
|
+
effects: [{ bones: UPPER_LIP, scale: [0, 0, 0.6] }],
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
// Vertical fullness (height) of the BOTTOM lip only — thin-top/full-bottom etc.
|
|
284
|
+
id: 'lower_lip_fullness', group: 'Lips', label: 'Lower Lip Fullness',
|
|
285
|
+
effects: [{ bones: LOWER_LIP, scale: [0, 0, 0.6] }],
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
// Roll the top lip out (forward) or in.
|
|
289
|
+
id: 'upper_lip_protrude', group: 'Lips', label: 'Upper Lip Protrusion',
|
|
290
|
+
effects: [{ bones: UPPER_LIP, offset: [0.005, 0, 0] }],
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
// Roll the bottom lip out (forward) or in.
|
|
294
|
+
id: 'lower_lip_protrude', group: 'Lips', label: 'Lower Lip Protrusion',
|
|
295
|
+
effects: [{ bones: LOWER_LIP, offset: [0.005, 0, 0] }],
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
// Kiss/pucker: push both lips forward and draw the corners inward.
|
|
299
|
+
id: 'lip_pucker', group: 'Lips', label: 'Lip Pucker',
|
|
300
|
+
effects: [
|
|
301
|
+
{ bones: ALL_LIP, offset: [0.006, 0, 0] },
|
|
302
|
+
{ bones: ['mFaceLipCornerLeft'], offset: [0.004, -0.006, 0] },
|
|
303
|
+
{ bones: ['mFaceLipCornerRight'], offset: [0.004, 0.006, 0] },
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
// Shape the upper-lip centre peak (height + protrusion) on its own.
|
|
308
|
+
id: 'cupids_bow', group: 'Lips', label: "Cupid's Bow",
|
|
309
|
+
effects: [{ bones: ['mFaceLipUpperCenter'], offset: [0.004, 0, 0.004] }],
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
// Smile / frown: lift the corners UP and pull them OUT (not forward).
|
|
313
|
+
id: 'smile', group: 'Lips', label: 'Smile / Frown',
|
|
314
|
+
effects: [
|
|
315
|
+
{ bones: ['mFaceLipCornerLeft'], offset: [0, 0.004, 0.006] },
|
|
316
|
+
{ bones: ['mFaceLipCornerRight'], offset: [0, -0.004, 0.006] },
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
// Push the corners forward/out or tuck them back into the cheeks (dimple).
|
|
321
|
+
id: 'lip_corner_depth', group: 'Lips', label: 'Lip Corner Depth',
|
|
322
|
+
effects: [{ bones: LIP_CORNERS, offset: [0.005, 0, 0] }],
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
// ---- Torso ----
|
|
326
|
+
{
|
|
327
|
+
id: 'torso_length', group: 'Torso', label: 'Torso Length',
|
|
328
|
+
effects: [{ bones: ['mTorso'], scale: [0, 0, 0.25] }],
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: 'shoulders', group: 'Torso', label: 'Shoulders',
|
|
332
|
+
effects: [
|
|
333
|
+
{ bones: ['mChest'], scale: [0.1, 0, 0] },
|
|
334
|
+
{ bones: ['L_CLAVICLE', 'R_CLAVICLE'], scale: [0.35, 0.15, 0.15] },
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: 'breast_size', group: 'Torso', label: 'Breast Size',
|
|
339
|
+
effects: [{ bones: ['LEFT_PEC', 'RIGHT_PEC'], scale: [0.55, 0.55, 0.55] }],
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
// Spacing along the lateral axis (Y). Pec locals share orientation, so the
|
|
343
|
+
// two sides need opposite signs to move apart/together. Positive = apart.
|
|
344
|
+
id: 'breast_spacing', group: 'Torso', label: 'Breast Spacing',
|
|
345
|
+
effects: [
|
|
346
|
+
{ bones: ['LEFT_PEC'], offset: [0, 0.025, 0] },
|
|
347
|
+
{ bones: ['RIGHT_PEC'], offset: [0, -0.025, 0] },
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
// Lift: raise the pec volume (+Z is up for these bones) without resizing it.
|
|
352
|
+
id: 'breast_lift', group: 'Torso', label: 'Breast Lift',
|
|
353
|
+
effects: [{ bones: ['LEFT_PEC', 'RIGHT_PEC'], offset: [0, 0, 0.02] }],
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
// Nipple protrusion — a vertex morph, not a bone (there is no nipple bone;
|
|
357
|
+
// the breast is skinned to the PEC volumes). + extends the tip forward,
|
|
358
|
+
// - pulls it in / flattens. See nipple.js.
|
|
359
|
+
id: 'nipple', group: 'Torso', label: 'Nipple', special: 'nipple',
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
// Mirrors SL's "Chest Male No Pecs" (param 685): translates pecs inward
|
|
363
|
+
// toward the spine to flatten the chest. Positive = flatter (masculine).
|
|
364
|
+
id: 'pec_flatten', group: 'Torso', label: 'Chest Flatten',
|
|
365
|
+
effects: [{ bones: ['LEFT_PEC', 'RIGHT_PEC'], scale: [-0.85, -0.85, -0.85], offset: [-0.05, 0, 0] }],
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: 'belly_size', group: 'Torso', label: 'Belly Size',
|
|
369
|
+
effects: [{ bones: ['BELLY'], scale: [0.55, 0.8, 0.45] }],
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
// Forward-biased belly bulge: depth-dominant scale plus a +X (anterior)
|
|
373
|
+
// offset so the volume grows forward instead of symmetrically into the spine.
|
|
374
|
+
id: 'belly_distend', group: 'Torso', label: 'Belly Distension',
|
|
375
|
+
effects: [{ bones: ['BELLY'], scale: [0.9, 0.25, 0.2], offset: [0.04, 0, 0] }],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
// Raise/lower the belly volume vertically (-Z is up, matching the pec bones)
|
|
379
|
+
// without resizing it. Positive = higher.
|
|
380
|
+
id: 'belly_lift', group: 'Torso', label: 'Belly Lift',
|
|
381
|
+
effects: [{ bones: ['BELLY'], offset: [0, 0, -0.03] }],
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
// Cinch/widen the midsection on both cross-section axes (X depth + Y width),
|
|
385
|
+
// leaving bone length (Z) alone. Negative = cinched waist (hourglass).
|
|
386
|
+
id: 'waist', group: 'Torso', label: 'Waist',
|
|
387
|
+
effects: [{ bones: ['LOWER_BACK', 'BELLY'], scale: [0.3, 0.3, 0] }],
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: 'torso_muscles', group: 'Torso', label: 'Torso Muscles',
|
|
391
|
+
effects: [{ bones: ['UPPER_BACK', 'LOWER_BACK'], scale: [0.35, 0.35, 0.35] }],
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
id: 'neck_length', group: 'Torso', label: 'Neck Length',
|
|
395
|
+
effects: [{ bones: ['mNeck'], scale: [0, 0, 0.45] }],
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: 'neck_thickness', group: 'Torso', label: 'Neck Thickness',
|
|
399
|
+
effects: [{ bones: ['NECK'], scale: [0.35, 0.35, 0] }],
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
// ---- Arms ----
|
|
403
|
+
{
|
|
404
|
+
id: 'arm_length', group: 'Arms', label: 'Arm Length',
|
|
405
|
+
effects: [{ bones: ['mShoulderLeft', 'mShoulderRight', 'mElbowLeft', 'mElbowRight'], scale: [0.16, 0, 0] }],
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
id: 'bicep_size', group: 'Arms', label: 'Bicep Size',
|
|
409
|
+
effects: [{ bones: ['L_UPPER_ARM', 'R_UPPER_ARM'], scale: [0, 0.45, 0.45] }],
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: 'forearm_size', group: 'Arms', label: 'Forearm Size',
|
|
413
|
+
effects: [{ bones: ['L_LOWER_ARM', 'R_LOWER_ARM'], scale: [0, 0.45, 0.45] }],
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: 'hand_size', group: 'Arms', label: 'Hand Size',
|
|
417
|
+
effects: [{ bones: ['mWristLeft', 'mWristRight'], scale: [0.25, 0.25, 0.25] }],
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
// Broaden + thicken the palm on its cross-section axes (Y/Z) without
|
|
421
|
+
// lengthening (X = wrist→fingers). Square, chunky palm = masculine hands.
|
|
422
|
+
id: 'palm_width', group: 'Arms', label: 'Palm Width',
|
|
423
|
+
effects: [{ bones: ['mWristLeft', 'mWristRight'], scale: [0, 0.4, 0.4] }],
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// ---- Fingers (Bento — upstream rigs only expose Hand Size; finger posing is
|
|
427
|
+
// normally done with Bento animations, but the bones are all here) ----
|
|
428
|
+
{
|
|
429
|
+
id: 'finger_length', group: 'Fingers', label: 'Finger Length',
|
|
430
|
+
effects: [{ bones: [...fingerBones('Left', [...FINGERS, 'Thumb'], [1]), ...fingerBones('Right', [...FINGERS, 'Thumb'], [1])], scale: [0, 0.3, 0] }],
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: 'finger_thickness', group: 'Fingers', label: 'Finger Thickness',
|
|
434
|
+
effects: [{ bones: [...fingerBones('Left', [...FINGERS, 'Thumb'], [1]), ...fingerBones('Right', [...FINGERS, 'Thumb'], [1])], scale: [0.4, 0, 0.4] }],
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: 'fist_l', group: 'Left Hand', label: 'Fist Curl',
|
|
438
|
+
effects: [{ bones: fingerBones('Left'), rot: [-0.5, 0, 0] }],
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: 'spread_l', group: 'Left Hand', label: 'Finger Spread',
|
|
442
|
+
effects: [
|
|
443
|
+
{ bones: fingerBones('Left', ['Index'], [1]), rot: [0, 0, -0.18] },
|
|
444
|
+
{ bones: fingerBones('Left', ['Ring'], [1]), rot: [0, 0, 0.12] },
|
|
445
|
+
{ bones: fingerBones('Left', ['Pinky'], [1]), rot: [0, 0, 0.25] },
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: 'thumb_l', group: 'Left Hand', label: 'Thumb Curl',
|
|
450
|
+
effects: [{ bones: fingerBones('Left', ['Thumb']), rot: [0, 0, -0.35] }],
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
id: 'fist_r', group: 'Right Hand', label: 'Fist Curl',
|
|
454
|
+
effects: [{ bones: fingerBones('Right'), rot: [0.5, 0, 0] }],
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
id: 'spread_r', group: 'Right Hand', label: 'Finger Spread',
|
|
458
|
+
effects: [
|
|
459
|
+
{ bones: fingerBones('Right', ['Index'], [1]), rot: [0, 0, 0.18] },
|
|
460
|
+
{ bones: fingerBones('Right', ['Ring'], [1]), rot: [0, 0, -0.12] },
|
|
461
|
+
{ bones: fingerBones('Right', ['Pinky'], [1]), rot: [0, 0, -0.25] },
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: 'thumb_r', group: 'Right Hand', label: 'Thumb Curl',
|
|
466
|
+
effects: [{ bones: fingerBones('Right', ['Thumb']), rot: [0, 0, 0.35] }],
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
// ---- Legs ----
|
|
470
|
+
{
|
|
471
|
+
id: 'leg_length', group: 'Legs', label: 'Leg Length',
|
|
472
|
+
effects: [
|
|
473
|
+
{ bones: ['mHipLeft', 'mHipRight'], scale: [0, 0, 0.16] },
|
|
474
|
+
// Lift the pelvis so feet stay near the ground (upper+lower leg ~0.85 m).
|
|
475
|
+
{ bones: ['mPelvis'], offset: [0, 0, 0.85 * 0.16] },
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: 'thigh_muscles', group: 'Legs', label: 'Thigh Muscles',
|
|
480
|
+
effects: [{ bones: ['L_UPPER_LEG', 'R_UPPER_LEG'], scale: [0.45, 0.45, 0] }],
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
id: 'calf_muscles', group: 'Legs', label: 'Calf Muscles',
|
|
484
|
+
effects: [{ bones: ['L_LOWER_LEG', 'R_LOWER_LEG'], scale: [0.45, 0.45, 0] }],
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
// Posterior-biased: depth-dominant scale plus a -X (back) offset so the
|
|
488
|
+
// volume projects rearward instead of growing symmetrically into the pelvis.
|
|
489
|
+
id: 'butt_size', group: 'Legs', label: 'Butt Size',
|
|
490
|
+
effects: [{ bones: ['BUTT'], scale: [0.8, 0.45, 0.5], offset: [-0.03, 0, 0] }],
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
// Lift/perk rather than enlarge: raise the volume (+Z) without scaling it.
|
|
494
|
+
id: 'butt_lift', group: 'Legs', label: 'Butt Lift',
|
|
495
|
+
effects: [{ bones: ['BUTT'], offset: [0, 0, 0.025] }],
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
id: 'hip_width', group: 'Legs', label: 'Hip Width',
|
|
499
|
+
effects: [
|
|
500
|
+
{ bones: ['PELVIS'], scale: [0.3, 0, 0] },
|
|
501
|
+
{ bones: ['mHipLeft'], offset: [0.018, 0, 0] },
|
|
502
|
+
{ bones: ['mHipRight'], offset: [-0.018, 0, 0] },
|
|
503
|
+
],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: 'foot_size', group: 'Legs', label: 'Foot Size',
|
|
507
|
+
effects: [{ bones: ['mAnkleLeft', 'mAnkleRight'], scale: [0.3, 0.3, 0.3] }],
|
|
508
|
+
},
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
// Turn a { sliderId: t } state object into per-bone adjustments:
|
|
512
|
+
// Map<boneName, { scale: [sx,sy,sz] multipliers, offset: [x,y,z] }>
|
|
513
|
+
export function computeBoneAdjustments(state) {
|
|
514
|
+
const adj = new Map();
|
|
515
|
+
const get = (bone) => {
|
|
516
|
+
if (!adj.has(bone)) adj.set(bone, { scale: [1, 1, 1], offset: [0, 0, 0], rot: [0, 0, 0] });
|
|
517
|
+
return adj.get(bone);
|
|
518
|
+
};
|
|
519
|
+
for (const slider of SLIDERS) {
|
|
520
|
+
if (!slider.effects) continue;
|
|
521
|
+
const t = state[slider.id] ?? 0;
|
|
522
|
+
// No t === 0 skip: every slider-referenced bone always gets an entry so
|
|
523
|
+
// returning a slider to zero restores the bone's rest transform.
|
|
524
|
+
for (const fx of slider.effects) {
|
|
525
|
+
for (const bone of fx.bones) {
|
|
526
|
+
const a = get(bone);
|
|
527
|
+
if (fx.scale) for (let i = 0; i < 3; i++) a.scale[i] *= 1 + fx.scale[i] * t;
|
|
528
|
+
if (fx.offset) for (let i = 0; i < 3; i++) a.offset[i] += fx.offset[i] * t;
|
|
529
|
+
if (fx.rot) for (let i = 0; i < 3; i++) a.rot[i] += fx.rot[i] * t;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return adj;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ---- sex-based body presets -------------------------------------------------
|
|
537
|
+
// t values (slider range [-1, 1]) for body-group sliders. Head and mouth
|
|
538
|
+
// sliders are left at zero (sex-neutral); the male preset broadens the hands
|
|
539
|
+
// (palm width + finger thickness) for a more masculine silhouette.
|
|
540
|
+
// Derived from the SL Ruth2 binding shape and canonical SL male param defaults.
|
|
541
|
+
export const SEX_PRESETS = {
|
|
542
|
+
female: {
|
|
543
|
+
height: 0,
|
|
544
|
+
thickness: 0,
|
|
545
|
+
shoulders: 0,
|
|
546
|
+
breast_size: 0,
|
|
547
|
+
pec_flatten: 0,
|
|
548
|
+
nipple: -0.5, // default woman: nipple pulled in (slider -50)
|
|
549
|
+
belly_size: 0,
|
|
550
|
+
torso_muscles: 0,
|
|
551
|
+
neck_length: 0,
|
|
552
|
+
neck_thickness: 0,
|
|
553
|
+
arm_length: 0,
|
|
554
|
+
bicep_size: 0,
|
|
555
|
+
forearm_size: 0,
|
|
556
|
+
hand_size: 0,
|
|
557
|
+
palm_width: 0,
|
|
558
|
+
finger_thickness: 0,
|
|
559
|
+
leg_length: 0,
|
|
560
|
+
thigh_muscles: 0,
|
|
561
|
+
calf_muscles: 0,
|
|
562
|
+
butt_size: 0,
|
|
563
|
+
hip_width: 0,
|
|
564
|
+
foot_size: 0,
|
|
565
|
+
},
|
|
566
|
+
male: {
|
|
567
|
+
height: 0,
|
|
568
|
+
thickness: 0.3, // broader torso
|
|
569
|
+
shoulders: 0.5, // wider shoulders
|
|
570
|
+
breast_size: -1, // shrink pecs fully (→ 45%)
|
|
571
|
+
pec_flatten: 1, // push pecs inward + shrink further (→ ~7% total)
|
|
572
|
+
nipple: 0, // neutral nipple protrusion
|
|
573
|
+
belly_size: 0.1, // slightly more belly
|
|
574
|
+
torso_muscles: 0.8, // broader back (V-taper)
|
|
575
|
+
neck_length: 0,
|
|
576
|
+
neck_thickness: 0.5, // thicker neck
|
|
577
|
+
arm_length: 0,
|
|
578
|
+
bicep_size: 0.5, // thicker upper arms
|
|
579
|
+
forearm_size: 0.5, // thicker forearms
|
|
580
|
+
hand_size: 0.3, // slightly larger hands
|
|
581
|
+
palm_width: 0.5, // broad, square palms
|
|
582
|
+
finger_thickness: 0.4, // thicker fingers
|
|
583
|
+
leg_length: 0,
|
|
584
|
+
thigh_muscles: 0.4, // thicker thighs
|
|
585
|
+
calf_muscles: 0.4, // thicker calves
|
|
586
|
+
butt_size: -0.3, // less pronounced butt
|
|
587
|
+
hip_width: -0.5, // narrower hips
|
|
588
|
+
foot_size: 0.1, // slightly larger feet
|
|
589
|
+
},
|
|
590
|
+
};
|