047_wardrobe.html puts the #032 pianist in a photo studio so you can try hair, clothes and colour combinations quickly. It uses #032’s garments, hair and looks and #045’s path tracers for stills.
pianist/look.js). A colour change in Stylized or Cel goes through look.recolor(), which updates the swapped materials in place.pathtrace/still.js): one tracer or all of them, shown split against the page’s picture. Traces are always in the realistic look, because toon ramps and ink outlines are raster-only effects. The light presets use only spot lights and the environment, both of which pathtrace/snapshot.js carries over.?dress=…&bottom=…&skintex=…&fasian=…&fafrican=…&dcol=…&hair=…&hcol=…&fabric=…&slit=…&shoes=…&scol=…&skcol=…&trim=…&skin=…&bg=…&look=…&pose=…&light=…), and ?outfit= takes #032’s outfit keys.The Concert outfits (Outfit rail, first group) follow what women soloists wear on stage. Sources: Caroline Oltmanns, What to wear on stage; Leah Broad, Fashion, fabrics and fishtails (The Guardian, 2022); The Cross-Eyed Pianist; reviews of Yuja Wang’s recitals (Classic FM, USC Annenberg Media 2026).
The MakeHuman community repository’s catalogue and asset pages went offline when its community services shut down, so fetch-community.mjs no longer works. The asset packs are still hosted. pianist/build/import-packs.mjs imports chosen assets from the pack zips into the layout build-character.mjs reads. Each pack’s packs/<pack>.json records the asset’s old page, so an asset keeps its old key c<nid>. Only CC0 and CC-BY assets are imported. Credits are in pianist/assets/community/CREDITS.md; the node URLs listed there no longer resolve but still identify each asset.
27 assets were added, chosen from contact sheets of the packs’ thumbnails:
bottom, built by build-character.mjs from the Pants and Skirt categories, and are worn under a top through the Bottom rail. #032 and #045 leave them out of their menus because they have no way to pair a bottom with a top.character.bin is unchanged. The new parts add 38 MB to pianist/assets/community/ in total, but each one is fetched only when it is worn.
To rebuild:
# packs into ~/test/mh-assets/packs (dress01_cc0.zip, dress02_cc-by.zip, … from the asset-pack pages)
node pianist/build/import-packs.mjs ~/test/mh-assets/packs/*.zip --list # every asset, its node id and licence
node pianist/build/import-packs.mjs ~/test/mh-assets/packs/*.zip --pick toigo_halter_dress_with_fluted_skirt,elvs_goddess_dress1,…
node pianist/build/build-character.mjs
pianist/dress.js, hair.js, hair-mesh.js, shoes.js) and every MakeHuman community asset in the build (catalog.js, names and outfits as #032 / #045).rig.state.stand, from #038). rig.state.standArms adds 'sides' (arms hanging) and 'clasp' (hands together, clear of the skirt). The dress is given a proxy bench that is out of reach while she stands, so the cloth skirt hangs free and drapes over the real bench when she sits.look.recolor(), dress.setTrim(), the two arm poses in rig.js, and still.js re-bakes the tracers’ environment map when the environment intensity changes.There are 16 fabrics, defined in pianist/fabric.js and shared with #032 and #045. Each one is a set of physical-material values. Where the surface texture shows, it also gets a procedural tile generated in the page (no texture files), repeated in the garment’s own UVs:
| Fabric | How it is made |
|---|---|
| Plain, Knit | the garment’s own material; knit is the diamond knit (our dresses only) |
| Satin | low roughness, bright sheen |
| Silk charmeuse | glossier than satin, with the highlight stretched along the threads (anisotropy) |
| Chiffon | soft and matte, a sheen haze over a fine gauze. It is not see-through: the body under a garment is hidden, so real transparency would show holes |
| Crepe | a pebbly grain (value noise), almost no shine |
| Shot taffeta | fine ribs, and a change of colour with the viewing angle (iridescence) |
| Velvet | rough, with a strong sheen at grazing angles |
| Damask | tone on tone: glossy rosette medallions on a matte ground (a roughness map), lightly raised |
| Lamé | metallic threads in a plain weave |
| Sequins | tilted discs on a metallic base |
| Glitter | tiny metallic facets, each tilted its own way, finer than sequins |
| Beaded | a grid of domed glass beads, clear-coated |
| Leather | a pebbled grain (Voronoi), satin clear coat |
| Lace | Illusion lace, as on concert and red-carpet gowns: Chantilly-style corded rosettes, a scalloped ring and leaves on a vine, over a fine tulle net, all over a lining in her skin tone, so it reads as see-through. The body under a garment is hidden, so instead of real holes a shader mixes the dress colour (and the garment’s texture) with the lining colour by the lace’s coverage: 1 on the motifs and cords, ¾ on the net threads, 0.3 in the tulle between them (black tulle over nude reads smoky, as it does). The Lace lining rail picks the lining: ▦ (the default) is her skin tone, taken from the skin texture under its tint and updated when the skin changes; the swatches are nude shades, black, white, gold and blush. A black lining turns it into black lace on black. The path tracers drop shader patches, so each lace garment also carries a CPU bake of the same mix (traceMapOf() in fabric.js), which pathtrace/snapshot.js uses in place of map × colour; a traced still matches the page’s picture |
| Pleated | vertical knife pleats, about 2.3 cm, with a darker crease. On our dresses u runs round the body, so the pleats are a normal map plus a crease-shade map. A community garment’s UVs are an arbitrary atlas, so there the pleats are added in the shader from each vertex’s angle round her body axis in the rest pose (skinned like the normal), and they hang straight down whatever the UVs. The swapped stylized and cel materials keep that shader. The path tracers drop shader patches, so a community garment traces unpleated; ours traces pleated |

All 16 on our simulated floor-length halter gown (lace over her skin tone) (gown-cloth, crimson, studio light), full length and close up, from wardrobe/test/smoke.mjs’s set-up. In the cel look only lace and pleats keep a visible pattern, since toon shading flattens normal and roughness detail.
Eight concert outfits use lace and pleats:


Several other concert outfits use the other fabrics: chiffon on the Grecian gowns, silk on the sapphire column gown, crepe on the red high-neck gown, taffeta on the emerald skirt, lamé on the gold cocktail dress, glitter on the one-shoulder mini, beading on the champagne fishtail. Two outfits are new: an ivory damask halter gown and a black leather jumpsuit. Switching fabric takes ≤ 18 ms, or about 1 ms once that fabric’s tile has been made (lace’s tile is about 0.3 s the first time). Switching back restores the garment’s own maps and shader (a material’s fabric state is kept in a WeakMap, not in userData, which every clone deep-copies as JSON, textures included), which is checked by recording every material’s maps and shader flag across lace → pleated → satin → plain. The path tracers take the normal and roughness maps as they are.
young_asian_female and young_african_female, written by build-character.mjs as skin_asian.jpg and skin_african.jpg (about 0.25 MB each). ch.setSkin(key) in pianist/character.js loads them on demand.asian-female-young and african-female-young macro targets, downloaded from the MakeHuman repository into mh-assets/base/. Our build uses caucasian at 1.0, so each morph is (that target − caucasian).character.bin; community parts carry theirs in their own files.ch.setFace({ asian, african }) blends the deltas on the CPU and recomputes normals. Because it writes the geometry itself, the path tracers trace the morphed face too. rig.measureEyes() re-reads where the eyeballs are, so they still turn about their own centres.setFace, so their pianist is unchanged.node wardrobe/test/smoke.mjs [out dir] loads the page headless on SwiftShader and steps through every rail, look, pose, light and framing, saving pictures along the way. Any page error or failed request fails the run. FULL=1 goes through every dress and hair; OUTFITS=1 takes a front and a ¾ picture of every outfit; every run also switches through the three skins and sets a 60 % East Asian face; PT=1 adds a small three-gpu-pathtracer WebGL still. It needs the parent folder served at http://127.0.0.1:8000/midiviz/ (or BASE), plus PLAYWRIGHT and CHROME as for pathtrace/test/stills.mjs.
Timings on SwiftShader (no GPU), per click until the new piece is on: community dresses and tops 0.5–0.7 s, hair 0.2–0.3 s, shoes 0.1–0.4 s, a whole outfit 0.2–0.7 s (the two with #032’s simulated gowns about 4.5 s), fabric and colours ≤ 16 ms.