tosijs-3d 0.11.3 → 0.11.4

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 (42) hide show
  1. package/CHANGELOG.md +5 -5
  2. package/changelog/v0.11.4.md +18 -0
  3. package/dist/character-builder.d.ts +33 -0
  4. package/dist/character-builder.d.ts.map +1 -0
  5. package/dist/character-builder.js +318 -0
  6. package/dist/character-builder.js.map +1 -0
  7. package/dist/hair-fit.d.ts +21 -0
  8. package/dist/hair-fit.d.ts.map +1 -0
  9. package/dist/hair-fit.js +169 -0
  10. package/dist/hair-fit.js.map +1 -0
  11. package/dist/human-face.d.ts +60 -0
  12. package/dist/human-face.d.ts.map +1 -0
  13. package/dist/human-face.js +348 -0
  14. package/dist/human-face.js.map +1 -0
  15. package/dist/human-hair.d.ts +63 -0
  16. package/dist/human-hair.d.ts.map +1 -0
  17. package/dist/human-hair.js +246 -0
  18. package/dist/human-hair.js.map +1 -0
  19. package/dist/human-morph.d.ts +41 -0
  20. package/dist/human-morph.d.ts.map +1 -0
  21. package/dist/human-morph.js +222 -0
  22. package/dist/human-morph.js.map +1 -0
  23. package/dist/human-pack.d.ts +79 -0
  24. package/dist/human-pack.d.ts.map +1 -0
  25. package/dist/human-pack.js +120 -0
  26. package/dist/human-pack.js.map +1 -0
  27. package/dist/human-rig.d.ts +37 -0
  28. package/dist/human-rig.d.ts.map +1 -0
  29. package/dist/human-rig.js +171 -0
  30. package/dist/human-rig.js.map +1 -0
  31. package/dist/index.d.ts +16 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +8 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/retarget.d.ts.map +1 -1
  36. package/dist/retarget.js +9 -1
  37. package/dist/retarget.js.map +1 -1
  38. package/dist/skin-tone.d.ts +32 -0
  39. package/dist/skin-tone.d.ts.map +1 -0
  40. package/dist/skin-tone.js +190 -0
  41. package/dist/skin-tone.js.map +1 -0
  42. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -12,16 +12,16 @@ Upgrading across a minor? Start with [Migration](/Migration/).
12
12
 
13
13
  ## Unreleased
14
14
 
15
- ## 0.11.3
15
+ ## 0.11.4
16
16
 
17
- The latest release. The biped eases between animations instead of cutting
18
- (`blendSeconds`, a quarter of a second by default), R3 toggles its weapon, XR
19
- thumbsticks get a deadzone so the aim no longer drifts, and the sun's shadow
20
- bias defaults clear acne stripes. [Read 0.11.3](/v0.11.3/).
17
+ The latest release. A character creator on MakeHuman's CC0 body: shape a
18
+ person with sliders, give them skin, eyes and hair, and watch them walk with
19
+ UAL's clips. [Read 0.11.4](/v0.11.4/).
21
20
 
22
21
  ## Every version
23
22
 
24
23
  <!-- toc -->
24
+ - [0.11.4](/v0.11.4/)
25
25
  - [0.11.3](/v0.11.3/)
26
26
  - [0.11.2](/v0.11.2/)
27
27
  - [0.11.1](/v0.11.1/)
@@ -0,0 +1,18 @@
1
+ # 0.11.4
2
+
3
+ <!--{ "parent": "CHANGELOG", "order": 9988996 }-->
4
+
5
+ ## Added
6
+
7
+ - **A character creator, on MakeHuman's CC0 body** (CHARACTER-DESIGN.md). Try it on the [character builder](/character-builder/) page: a person walking while you reshape them. The data is a **human pack** on the CDN (`makehuman/hm08/`), loaded with `loadHumanPack`.
8
+ - **Body**: `macroWeights` / `morphHuman` turn gender, age, muscle, weight, height, proportions and an ancestry mix into a body, in about a millisecond.
9
+ - **Skeleton**: `humanRig` places UAL's 65 joints from the body's own joint cubes, and bone heat binds the skin once per base, so UAL's clips play on any body shape (`buildRig`, `retargetClip`). `humanBody` draws a body unrigged.
10
+ - **Skin**: one dial from very fair to very dark through the Monk Skin Tone Scale, plus redness (`skinMaterial`, `skinAlbedo`); lips tinted from it over the pack's `lips` region, no UVs needed (`lipTint`, `lipAlbedo`).
11
+ - **Eyes**: `humanFace` adds eyes and eyelashes that fit every body (they are the base mesh's own helper vertices); the iris is drawn in code with a colour dial (`eyeTexture`, `irisColor`); `eyeGaze` aims the eyes at a world point with small saccades.
12
+ - **Hair**: Quaternius' CC0 hairstyles, beards and eyebrows, modelled for another head, fitted to any body (`loadHairLibrary`, `headScalp`, `humanHair`); tinted by `hairColor` (black to platinum, plus red). The pure fit is `fitToScalp` / `conformOutside`.
13
+
14
+ ## Fixed
15
+
16
+ - **Retargeting no longer lines a character's collarbones up with the clip's.** A collarbone's rest direction is the body's shape, like the spine's; where two rigs differed (UAL's runs back to the shoulder, MakeHuman's lies flat), every clip shrugged the shoulders.
17
+
18
+ Size: barrel (min, packages external) 1266.7 KB raw / 433.9 KB gz, +1.67% on 0.11.3 (the character creator).
@@ -0,0 +1,33 @@
1
+ import * as BABYLON from '@babylonjs/core';
2
+ import type { HumanPack } from './human-pack.js';
3
+ import type { HumanSkin } from './human-rig.js';
4
+ export interface HumanBody {
5
+ /** Move, turn and scale the body by this. */
6
+ root: BABYLON.TransformNode;
7
+ mesh: BABYLON.Mesh;
8
+ /** Reshape the body from new positions (xyz per vertex, the pack's frame). */
9
+ update(positions: Float32Array): void;
10
+ dispose(): void;
11
+ }
12
+ export declare function humanBody(scene: BABYLON.Scene, pack: HumanPack, name?: string): HumanBody;
13
+ export interface SkinMaterial extends BABYLON.PBRMaterial {
14
+ /** Re-colour: tone 0 (very fair) to 1 (very dark), redness 0.5 neutral. */
15
+ setSkin(tone: number, redness?: number): void;
16
+ }
17
+ /**
18
+ * A skin material from [skin-tone](/skin-tone/)'s dials: dielectric, fairly
19
+ * rough, the albedo the tone gives. One flat colour for now: lips, nails and
20
+ * a texture's detail need the mesh's UVs, which the pack does not carry yet.
21
+ */
22
+ export declare function skinMaterial(scene: BABYLON.Scene, options?: {
23
+ tone?: number;
24
+ redness?: number;
25
+ name?: string;
26
+ }): SkinMaterial;
27
+ /**
28
+ * Per-vertex colours (RGBA, the skin's vertices) that turn a skin material's
29
+ * colour into the lips' where `lips` (a pack region, every vertex) says so.
30
+ * They multiply the material's albedo, so they are the ratio lip / skin.
31
+ */
32
+ export declare function lipTint(skin: HumanSkin, lips: Float32Array, tone: number, redness?: number, into?: Float32Array): Float32Array;
33
+ //# sourceMappingURL=character-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"character-builder.d.ts","sourceRoot":"","sources":["../src/character-builder.ts"],"names":[],"mappings":"AA8NA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,IAAI,EAAE,OAAO,CAAC,aAAa,CAAA;IAC3B,IAAI,EAAE,OAAO,CAAC,IAAI,CAAA;IAClB,8EAA8E;IAC9E,MAAM,CAAC,SAAS,EAAE,YAAY,GAAG,IAAI,CAAA;IACrC,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,IAAI,EAAE,SAAS,EACf,IAAI,SAAU,GACb,SAAS,CAwDX;AAED,MAAM,WAAW,YAAa,SAAQ,OAAO,CAAC,WAAW;IACvD,2EAA2E;IAC3E,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,YAAY,CAWd;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,SAAM,EACb,IAAI,CAAC,EAAE,YAAY,GAClB,YAAY,CAgBd"}
@@ -0,0 +1,318 @@
1
+ /*#
2
+ # character builder
3
+
4
+ **Make a person, and watch them walk.** The first steps of the character
5
+ creator (CHARACTER-DESIGN.md): MakeHuman's CC0 body
6
+ ([human-pack](/human-pack/)), reshaped by sliders for gender, age, muscle,
7
+ weight, height and ancestry ([human-morph](/human-morph/)), with UAL's
8
+ skeleton placed from its own joint cubes ([human-rig](/human-rig/)) so it
9
+ plays UAL's clips at any shape.
10
+
11
+ ## Demo
12
+
13
+ Open the settings (the owl): the body sliders reshape it
14
+ while it walks, since every body carries its own joints. The skin weights are
15
+ bone heat's, worked out from the mesh once when the page loads. The eyes
16
+ ([human-face](/human-face/)) follow the camera, and the hair
17
+ ([human-hair](/human-hair/)) is Quaternius' CC0 hairstyles, made for another
18
+ head and fitted to this one.
19
+
20
+ ```js
21
+ import { b3d, b3dSun, b3dLight, slider3d, select3d, toggle3d, label3d, assetUrl, loadHumanPack, macroWeights, morphHuman, DEFAULT_HUMAN_BODY, humanRig, humanSkin, skinMaterial, lipTint, humanFace, eyeGaze, eyeTexture, eyeMaterial, lashMaterial, loadHairLibrary, headScalp, hairMaterial, humanHair, skinSegments, boneHeat, buildRig, readSkeleton, retargetClip } from 'tosijs-3d'
22
+ import { tosi, elements } from 'tosijs'
23
+ const { div } = elements
24
+
25
+ const status = div({ class: 'status' }, 'loading the animations…')
26
+ const { walker } = tosi({ walker: { ...DEFAULT_HUMAN_BODY, gender: 1, clip: 'Walk_Loop', tone: 0.3, redness: 0.5, iris: 0.2, hair: 'Hair_SimpleParted', beard: 'none', brows: 'Eyebrows_Regular', hairLight: 0.25, hairRed: 0.15, paused: false } })
27
+ let mat = null
28
+ let paint = () => {}
29
+ let eyes = null
30
+ let dress = () => {}
31
+ // Filled when the hair library loads.
32
+ const styles = { hair: ['none'], beard: ['none'], brows: ['none'] }
33
+ const clips = ['Idle_Loop', 'Walk_Loop', 'Jog_Fwd_Loop', 'Sprint_Loop', 'Dance_Loop', 'Crouch_Fwd_Loop']
34
+ let rebuild = () => {}
35
+
36
+ const slider = (label, key) =>
37
+ slider3d({ label, value: walker[key], min: 0, max: 1, step: 0.01 })
38
+
39
+ preview.append(
40
+ b3d(
41
+ {
42
+ clearColor: '#2a3340',
43
+ panelTitle: 'Walker',
44
+ scenePanel: () => [
45
+ select3d({ label: 'clip', value: walker.clip, options: clips }),
46
+ toggle3d({ label: 'pause', value: walker.paused }),
47
+ slider('gender', 'gender'),
48
+ slider('age', 'age'),
49
+ slider('muscle', 'muscle'),
50
+ slider('weight', 'weight'),
51
+ slider('height', 'height'),
52
+ label3d({ text: 'Skin' }),
53
+ slider3d({ label: 'tone', value: walker.tone, min: 0, max: 1, step: 0.01 }),
54
+ slider3d({ label: 'redness', value: walker.redness, min: 0, max: 1, step: 0.01 }),
55
+ slider3d({ label: 'eyes', value: walker.iris, min: 0, max: 1, step: 0.01 }),
56
+ label3d({ text: 'Hair' }),
57
+ select3d({ label: 'hair', value: walker.hair, options: styles.hair }),
58
+ select3d({ label: 'beard', value: walker.beard, options: styles.beard }),
59
+ select3d({ label: 'brows', value: walker.brows, options: styles.brows }),
60
+ slider3d({ label: 'light', value: walker.hairLight, min: 0, max: 1, step: 0.01 }),
61
+ slider3d({ label: 'red', value: walker.hairRed, min: 0, max: 1, step: 0.01 }),
62
+ label3d({ text: 'Ancestry' }),
63
+ slider('african', 'african'),
64
+ slider('asian', 'asian'),
65
+ slider('caucasian', 'caucasian'),
66
+ ],
67
+ async sceneCreated(el, BABYLON) {
68
+ const scene = el.scene
69
+ const cam = new BABYLON.ArcRotateCamera('c', Math.PI / 2 - 0.5, 1.35, 4, new BABYLON.Vector3(0, 0.9, 0), scene)
70
+ cam.minZ = 0.05
71
+ cam.attachControl(el.querySelector('canvas'), true)
72
+ el.setActiveCamera(cam)
73
+ BABYLON.MeshBuilder.CreateGround('g', { width: 8, height: 8 }, scene)
74
+
75
+ // UAL's mannequin is only the clips' source: it stays hidden.
76
+ const ual = await BABYLON.LoadAssetContainerAsync(assetUrl('quaternius/UAL1_core.glb', 2), scene)
77
+ status.textContent = 'loading the body…'
78
+ ual.addAllToScene()
79
+ for (const g of ual.animationGroups) g.stop()
80
+ const ualRoot = ual.meshes.find((m) => m.name === '__root__')
81
+ const source = readSkeleton(ual.skeletons[0], ualRoot)
82
+ ualRoot.setEnabled(false)
83
+
84
+ const pack = await loadHumanPack(assetUrl('makehuman/hm08/hm08.json', 4)).catch((e) => {
85
+ status.textContent = `could not load the body: ${e.message}`
86
+ throw e
87
+ })
88
+ // Quaternius' hairstyles, made for its own head, fitted to each body.
89
+ const hairLib = await loadHairLibrary(scene, assetUrl('quaternius/libraries/hairstyles.glb'))
90
+ const adult = (n) => !n.endsWith('_Teen')
91
+ const facial = (n) => /Beard|Moustache|MuttonChops/.test(n)
92
+ styles.hair.push(...hairLib.names('hair').filter((n) => adult(n) && !facial(n)))
93
+ styles.beard.push(...hairLib.names('hair').filter((n) => adult(n) && facial(n)))
94
+ styles.brows.push(...hairLib.names('eyebrows').filter(adult))
95
+ const tints = new Map()
96
+ const tint = (part) => {
97
+ const from = part.material
98
+ if (!tints.has(from)) tints.set(from, hairMaterial(scene, from, `hair-${tints.size}`))
99
+ return tints.get(from)
100
+ }
101
+ status.textContent = 'binding the skin…'
102
+ // Let the message paint before the binding takes the thread.
103
+ await new Promise((r) => setTimeout(r, 50))
104
+ const skin = humanSkin(pack)
105
+ const [lips, eyePart, lashPart] = await Promise.all([pack.region('lips'), pack.part('eyes'), pack.part('eyelashes')])
106
+ eyes = eyeTexture(scene, { iris: walker.iris.value })
107
+ const faceMaterials = { eye: eyeMaterial(scene, eyes), lashes: lashMaterial(scene) }
108
+ let face = null
109
+ let scalp = null
110
+ let hairMeshes = []
111
+ dress = () => {
112
+ for (const m of tints.values()) m.setHair(walker.hairLight.value, walker.hairRed.value)
113
+ if (!built || !scalp) return
114
+ for (const m of hairMeshes) m.dispose()
115
+ hairMeshes = []
116
+ for (const key of ['hair', 'beard', 'brows']) {
117
+ const parts = hairLib.piece(walker[key].value)
118
+ if (!parts) continue
119
+ // Brows lie on the skin; hair stands a little off it.
120
+ const margin = key === 'brows' ? 0.0015 : 0.003
121
+ const centreY = key === 'brows' ? scalp.brow : undefined
122
+ hairMeshes.push(...humanHair(scene, built, built.rig, scalp, parts, tint, { margin, name: key, centreY }))
123
+ }
124
+ el.register({ meshes: hairMeshes })
125
+ }
126
+ for (const key of ['hair', 'beard', 'brows']) walker[key].observe(() => dress())
127
+ // Colours repaint whoever sets them: a slider, or code.
128
+ const recolour = () => { for (const m of tints.values()) m.setHair(walker.hairLight.value, walker.hairRed.value) }
129
+ walker.hairLight.observe(recolour)
130
+ walker.hairRed.observe(recolour)
131
+ for (const key of Object.keys(DEFAULT_HUMAN_BODY)) walker[key].observe(() => rebuild())
132
+ walker.tone.observe(() => paint())
133
+ walker.redness.observe(() => paint())
134
+ walker.iris.observe(() => eyes.setIris(walker.iris.value))
135
+ // The eyes find the camera.
136
+ eyeGaze(scene, { get eyes() { return face?.eyes ?? [] }, get forward() { return face?.forward ?? [] } }, () => cam.position)
137
+ const body = () => {
138
+ const b = {}
139
+ for (const k of Object.keys(DEFAULT_HUMAN_BODY)) b[k] = Number(walker[k].value)
140
+ return macroWeights(b, (n) => pack.has(n))
141
+ }
142
+ // The weights depend only on the topology: once, on the first body.
143
+ const first = await morphHuman(pack, body())
144
+ // The weights depend only on the topology: once, on the first body.
145
+ const rig0 = humanRig(pack.manifest, first)
146
+ const weights = boneHeat(skin.positions(first), skin.triangles, skinSegments(rig0))
147
+
148
+ mat = skinMaterial(scene, { tone: walker.tone.value, redness: walker.redness.value })
149
+ // The skin's colour, and the lips' as vertex colours over it.
150
+ paint = () => {
151
+ mat.setSkin(walker.tone.value, walker.redness.value)
152
+ if (lips && built) {
153
+ built.mesh.setVerticesData('color', lipTint(skin, lips, walker.tone.value, walker.redness.value))
154
+ }
155
+ }
156
+ const stand = new BABYLON.TransformNode('stand', scene)
157
+ let built = null
158
+ let playing = null
159
+ let read = null
160
+ const play = () => {
161
+ playing?.dispose()
162
+ const clip = ual.animationGroups.find((g) => g.name === walker.clip.value)
163
+ playing = clip ? retargetClip(clip, source, read, { scene }) : null
164
+ playing?.start(true)
165
+ if (walker.paused.value) playing?.pause()
166
+ }
167
+ let busy = false
168
+ let again = false
169
+ rebuild = async () => {
170
+ if (busy) { again = true; return }
171
+ busy = true
172
+ const full = await morphHuman(pack, body())
173
+ const rig = humanRig(pack.manifest, full)
174
+ face?.dispose()
175
+ built?.dispose()
176
+ built = buildRig(scene, 'walker', skin.positions(full), skin.triangles, rig, weights)
177
+ face = humanFace(scene, built, rig, pack.manifest, full, { eyes: eyePart, eyelashes: lashPart }, faceMaterials)
178
+ built.rig = rig
179
+ scalp = headScalp(pack.manifest, full, pack.triangles, hairLib.reference)
180
+ dress()
181
+ built.mesh.material = mat
182
+ built.root.parent = stand
183
+ stand.position.y = -rig.at('root').y
184
+ el.register({ meshes: [built.mesh, ...face.meshes] })
185
+ paint()
186
+ read = readSkeleton(built.skeleton, built.root)
187
+ play()
188
+ busy = false
189
+ if (again) { again = false; rebuild() }
190
+ }
191
+ await rebuild()
192
+ status.remove()
193
+ walker.clip.observe(play)
194
+ walker.paused.observe(() => (walker.paused.value ? playing?.pause() : playing?.play(true)))
195
+ },
196
+ },
197
+ b3dSun({ timeOfDay: 15 }),
198
+ b3dLight({ intensity: 0.5 })
199
+ ),
200
+ status
201
+ )
202
+ ```
203
+ ```css
204
+ tosi-b3d { width: 100%; height: 100%; }
205
+ .status { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.6); color: white; padding: 6px 12px; border-radius: 6px; font: 13px sans-serif; }
206
+ ```
207
+
208
+ ## humanBody
209
+
210
+ `humanBody(scene, pack)` draws a pack's body as one unrigged mesh, and
211
+ `update(positions)` reshapes it in place: for a body that only needs to be
212
+ looked at.
213
+
214
+
215
+ The pack is RIGHT-HANDED (as OBJ and glTF are), so the body sits under a
216
+ container that mirrors it the way glTF's `__root__` does, and stands with its
217
+ feet at the container's origin. Move the body by moving `root`. The mesh keeps
218
+ every vertex the targets move (helpers and joint cubes too), but draws only the
219
+ body's triangles.
220
+ */
221
+ /*{ "parent": "b3d-biped", "order": 80 }*/
222
+ import * as BABYLON from '@babylonjs/core';
223
+ import { lipAlbedo, skinAlbedo } from './skin-tone.js';
224
+ export function humanBody(scene, pack, name = 'human') {
225
+ const root = new BABYLON.TransformNode(name, scene);
226
+ // The file's mirror, as glTF's `__root__` carries it.
227
+ const mirror = new BABYLON.TransformNode(`${name}-frame`, scene);
228
+ mirror.rotationQuaternion = new BABYLON.Quaternion(0, 1, 0, 0);
229
+ mirror.scaling = new BABYLON.Vector3(1, 1, -1);
230
+ mirror.parent = root;
231
+ const mesh = new BABYLON.Mesh(`${name}-body`, scene);
232
+ const indices = Array.from(pack.triangles);
233
+ const normals = new Float32Array(pack.positions.length);
234
+ const data = new BABYLON.VertexData();
235
+ data.positions = new Float32Array(pack.positions);
236
+ data.indices = indices;
237
+ data.normals = normals;
238
+ data.applyToMesh(mesh, true);
239
+ // As the glTF loader does for its mirrored root: the mirror turns the
240
+ // file's counter-clockwise faces clockwise, which is Babylon's front.
241
+ mesh.sideOrientation = BABYLON.Material.ClockWiseSideOrientation;
242
+ mesh.parent = mirror;
243
+ const material = new BABYLON.StandardMaterial(`${name}-grey`, scene);
244
+ material.diffuseColor = new BABYLON.Color3(0.62, 0.62, 0.64);
245
+ material.specularColor = new BABYLON.Color3(0.08, 0.08, 0.08);
246
+ mesh.material = material;
247
+ // Feet on the floor: the pack is centred near its middle.
248
+ const used = new Uint8Array(pack.manifest.vertexCount);
249
+ for (const i of pack.triangles)
250
+ used[i] = 1;
251
+ function update(positions) {
252
+ let foot = Infinity;
253
+ for (let i = 0; i < used.length; i++) {
254
+ if (used[i] && positions[i * 3 + 1] < foot)
255
+ foot = positions[i * 3 + 1];
256
+ }
257
+ mirror.position.y = -foot;
258
+ // Counter-clockwise, outward: the file's handedness, not Babylon's.
259
+ BABYLON.VertexData.ComputeNormals(positions, indices, normals, {
260
+ useRightHandedSystem: true,
261
+ });
262
+ mesh.updateVerticesData(BABYLON.VertexBuffer.PositionKind, positions);
263
+ mesh.updateVerticesData(BABYLON.VertexBuffer.NormalKind, normals);
264
+ mesh.refreshBoundingInfo();
265
+ }
266
+ update(pack.positions);
267
+ return {
268
+ root,
269
+ mesh,
270
+ update,
271
+ dispose() {
272
+ material.dispose();
273
+ mesh.dispose();
274
+ mirror.dispose();
275
+ root.dispose();
276
+ },
277
+ };
278
+ }
279
+ /**
280
+ * A skin material from [skin-tone](/skin-tone/)'s dials: dielectric, fairly
281
+ * rough, the albedo the tone gives. One flat colour for now: lips, nails and
282
+ * a texture's detail need the mesh's UVs, which the pack does not carry yet.
283
+ */
284
+ export function skinMaterial(scene, options = {}) {
285
+ const m = new BABYLON.PBRMaterial(options.name ?? 'skin', scene);
286
+ m.metallic = 0;
287
+ m.roughness = 0.55;
288
+ m.setSkin = (tone, redness = 0.5) => {
289
+ const c = skinAlbedo(tone, redness);
290
+ // PBR's albedo is linear, as glTF's base colour is.
291
+ m.albedoColor = new BABYLON.Color3(c.r, c.g, c.b);
292
+ };
293
+ m.setSkin(options.tone ?? 0.3, options.redness ?? 0.5);
294
+ return m;
295
+ }
296
+ /**
297
+ * Per-vertex colours (RGBA, the skin's vertices) that turn a skin material's
298
+ * colour into the lips' where `lips` (a pack region, every vertex) says so.
299
+ * They multiply the material's albedo, so they are the ratio lip / skin.
300
+ */
301
+ export function lipTint(skin, lips, tone, redness = 0.5, into) {
302
+ const s = skinAlbedo(tone, redness);
303
+ const l = lipAlbedo(tone, redness);
304
+ const ratio = [l.r / s.r, l.g / s.g, l.b / s.b];
305
+ const out = into ?? new Float32Array(skin.vertices.length * 4);
306
+ skin.vertices.forEach((v, i) => {
307
+ // The mask is how far the lip targets move a vertex, so the lips' own
308
+ // edges are faint in it: anything past half is all lip.
309
+ const m = Math.min(1, Math.max(0, (lips[v] - 0.05) / 0.45));
310
+ const t = m * m * (3 - 2 * m);
311
+ out[i * 4] = 1 + (ratio[0] - 1) * t;
312
+ out[i * 4 + 1] = 1 + (ratio[1] - 1) * t;
313
+ out[i * 4 + 2] = 1 + (ratio[2] - 1) * t;
314
+ out[i * 4 + 3] = 1;
315
+ });
316
+ return out;
317
+ }
318
+ //# sourceMappingURL=character-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"character-builder.js","sourceRoot":"","sources":["../src/character-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface AxisFit {
2
+ scale: [number, number, number];
3
+ shift: [number, number, number];
4
+ }
5
+ /** `positions` (xyz) moved by a fit, into a new array or `into`. */
6
+ export declare function applyFit(positions: ArrayLike<number>, fit: AxisFit, into?: Float32Array): Float32Array;
7
+ /**
8
+ * The per-axis scale and shift that lay `reference` (xyz) onto `scalp` (xyz,
9
+ * the skin's points above the brow). Starts with the tops and centres
10
+ * aligned, then refines by nearest points.
11
+ */
12
+ export declare function fitToScalp(reference: ArrayLike<number>, scalp: ArrayLike<number>, iterations?: number): AxisFit;
13
+ /** Area-weighted vertex normals, counter-clockwise outward. */
14
+ export declare function vertexNormals(positions: ArrayLike<number>, triangles: ArrayLike<number>): Float32Array;
15
+ /**
16
+ * Push every vertex of `positions` (xyz, in place) that is inside the skin,
17
+ * or nearer it than `margin`, out along the nearest skin point's normal.
18
+ * Returns how many moved.
19
+ */
20
+ export declare function conformOutside(positions: Float32Array, scalp: ArrayLike<number>, normals: ArrayLike<number>, margin?: number): number;
21
+ //# sourceMappingURL=hair-fit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hair-fit.d.ts","sourceRoot":"","sources":["../src/hair-fit.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,oEAAoE;AACpE,wBAAgB,QAAQ,CACtB,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,EAC5B,GAAG,EAAE,OAAO,EACZ,IAAI,CAAC,EAAE,YAAY,GAClB,YAAY,CAQd;AAwBD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,EAC5B,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,EACxB,UAAU,SAAK,GACd,OAAO,CA0DT;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAC3B,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,EAC5B,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,GAC3B,YAAY,CA4Bd;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,YAAY,EACvB,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,EACxB,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC,EAC1B,MAAM,SAAQ,GACb,MAAM,CAoBR"}
@@ -0,0 +1,169 @@
1
+ /*#
2
+ # hair-fit
3
+
4
+ Hair made for one head, worn on another. A hairstyle library such as
5
+ Quaternius' is modelled around ITS base head, in world space; a
6
+ [character builder](/character-builder/) body has a different skull for
7
+ every slider setting. Two steps carry a style across, both pure:
8
+
9
+ 1. **`fitToScalp(reference, scalp)`** fits a scale and an offset per axis that
10
+ lay a REFERENCE that hugs the library's scalp (a buzz cut) onto this body's
11
+ scalp, by iterated nearest points (ICP without rotation: both heads face
12
+ +Z, up +Y). The same fit then moves every style, eyebrows and beards too,
13
+ because they were all modelled on that one head.
14
+ 2. **`conformOutside(positions, scalp, normals, margin)`** pushes any vertex
15
+ that ends up inside the skin (or closer than `margin` to it) back out along
16
+ the skin's normal, so a shape the fit cannot capture exactly (a longer
17
+ skull, a heavier brow) never shows skin through hair.
18
+
19
+ `vertexNormals(positions, triangles)` gives the skin's normals for the second
20
+ step (counter-clockwise outward, as OBJ and glTF wind).
21
+
22
+ ```javascript
23
+ const fit = fitToScalp(buzzCut, scalp)
24
+ const placed = applyFit(styleVertices, fit)
25
+ conformOutside(placed, scalp, scalpNormals, 0.003)
26
+ ```
27
+ */
28
+ /*{ "parent": "character-builder", "order": 60 }*/
29
+ /** `positions` (xyz) moved by a fit, into a new array or `into`. */
30
+ export function applyFit(positions, fit, into) {
31
+ const out = into ?? new Float32Array(positions.length);
32
+ for (let i = 0; i < positions.length; i += 3) {
33
+ for (let k = 0; k < 3; k++) {
34
+ out[i + k] = positions[i + k] * fit.scale[k] + fit.shift[k];
35
+ }
36
+ }
37
+ return out;
38
+ }
39
+ /** The index of the point in `cloud` (xyz) nearest (x, y, z), and its squared distance. */
40
+ function nearest(cloud, x, y, z) {
41
+ let best = Infinity;
42
+ let at = 0;
43
+ for (let j = 0; j < cloud.length; j += 3) {
44
+ const dx = cloud[j] - x;
45
+ const dy = cloud[j + 1] - y;
46
+ const dz = cloud[j + 2] - z;
47
+ const d = dx * dx + dy * dy + dz * dz;
48
+ if (d < best) {
49
+ best = d;
50
+ at = j / 3;
51
+ }
52
+ }
53
+ return [at, best];
54
+ }
55
+ /**
56
+ * The per-axis scale and shift that lay `reference` (xyz) onto `scalp` (xyz,
57
+ * the skin's points above the brow). Starts with the tops and centres
58
+ * aligned, then refines by nearest points.
59
+ */
60
+ export function fitToScalp(reference, scalp, iterations = 12) {
61
+ const box = (p) => {
62
+ const lo = [Infinity, Infinity, Infinity];
63
+ const hi = [-Infinity, -Infinity, -Infinity];
64
+ for (let i = 0; i < p.length; i += 3) {
65
+ for (let k = 0; k < 3; k++) {
66
+ lo[k] = Math.min(lo[k], p[i + k]);
67
+ hi[k] = Math.max(hi[k], p[i + k]);
68
+ }
69
+ }
70
+ return { lo, hi };
71
+ };
72
+ const r = box(reference);
73
+ const s = box(scalp);
74
+ const fit = {
75
+ scale: [1, 1, 1],
76
+ shift: [
77
+ (s.lo[0] + s.hi[0] - r.lo[0] - r.hi[0]) / 2,
78
+ s.hi[1] - r.hi[1],
79
+ (s.lo[2] + s.hi[2] - r.lo[2] - r.hi[2]) / 2,
80
+ ],
81
+ };
82
+ const n = reference.length / 3;
83
+ const target = new Float32Array(reference.length);
84
+ for (let it = 0; it < iterations; it++) {
85
+ for (let i = 0; i < n; i++) {
86
+ const [j] = nearest(scalp, reference[i * 3] * fit.scale[0] + fit.shift[0], reference[i * 3 + 1] * fit.scale[1] + fit.shift[1], reference[i * 3 + 2] * fit.scale[2] + fit.shift[2]);
87
+ target[i * 3] = scalp[j * 3];
88
+ target[i * 3 + 1] = scalp[j * 3 + 1];
89
+ target[i * 3 + 2] = scalp[j * 3 + 2];
90
+ }
91
+ // Least squares per axis: target ≈ reference · scale + shift.
92
+ for (let k = 0; k < 3; k++) {
93
+ let mr = 0;
94
+ let mt = 0;
95
+ for (let i = 0; i < n; i++) {
96
+ mr += reference[i * 3 + k];
97
+ mt += target[i * 3 + k];
98
+ }
99
+ mr /= n;
100
+ mt /= n;
101
+ let num = 0;
102
+ let den = 0;
103
+ for (let i = 0; i < n; i++) {
104
+ const a = reference[i * 3 + k] - mr;
105
+ num += a * (target[i * 3 + k] - mt);
106
+ den += a * a;
107
+ }
108
+ fit.scale[k] = den > 0 ? num / den : 1;
109
+ fit.shift[k] = mt - fit.scale[k] * mr;
110
+ }
111
+ }
112
+ return fit;
113
+ }
114
+ /** Area-weighted vertex normals, counter-clockwise outward. */
115
+ export function vertexNormals(positions, triangles) {
116
+ const out = new Float32Array(positions.length);
117
+ for (let t = 0; t < triangles.length; t += 3) {
118
+ const a = triangles[t] * 3;
119
+ const b = triangles[t + 1] * 3;
120
+ const c = triangles[t + 2] * 3;
121
+ const ux = positions[b] - positions[a];
122
+ const uy = positions[b + 1] - positions[a + 1];
123
+ const uz = positions[b + 2] - positions[a + 2];
124
+ const vx = positions[c] - positions[a];
125
+ const vy = positions[c + 1] - positions[a + 1];
126
+ const vz = positions[c + 2] - positions[a + 2];
127
+ const nx = uy * vz - uz * vy;
128
+ const ny = uz * vx - ux * vz;
129
+ const nz = ux * vy - uy * vx;
130
+ for (const v of [a, b, c]) {
131
+ out[v] += nx;
132
+ out[v + 1] += ny;
133
+ out[v + 2] += nz;
134
+ }
135
+ }
136
+ for (let i = 0; i < out.length; i += 3) {
137
+ const l = Math.hypot(out[i], out[i + 1], out[i + 2]) || 1;
138
+ out[i] /= l;
139
+ out[i + 1] /= l;
140
+ out[i + 2] /= l;
141
+ }
142
+ return out;
143
+ }
144
+ /**
145
+ * Push every vertex of `positions` (xyz, in place) that is inside the skin,
146
+ * or nearer it than `margin`, out along the nearest skin point's normal.
147
+ * Returns how many moved.
148
+ */
149
+ export function conformOutside(positions, scalp, normals, margin = 0.003) {
150
+ let moved = 0;
151
+ for (let i = 0; i < positions.length; i += 3) {
152
+ const [j] = nearest(scalp, positions[i], positions[i + 1], positions[i + 2]);
153
+ const nx = normals[j * 3];
154
+ const ny = normals[j * 3 + 1];
155
+ const nz = normals[j * 3 + 2];
156
+ const out = (positions[i] - scalp[j * 3]) * nx +
157
+ (positions[i + 1] - scalp[j * 3 + 1]) * ny +
158
+ (positions[i + 2] - scalp[j * 3 + 2]) * nz;
159
+ if (out < margin) {
160
+ const push = margin - out;
161
+ positions[i] += nx * push;
162
+ positions[i + 1] += ny * push;
163
+ positions[i + 2] += nz * push;
164
+ moved++;
165
+ }
166
+ }
167
+ return moved;
168
+ }
169
+ //# sourceMappingURL=hair-fit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hair-fit.js","sourceRoot":"","sources":["../src/hair-fit.ts"],"names":[],"mappings":"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"}