The wardrobe
How a character wears any combination of gear, one piece per slot, each piece its own file fitted to a base body, with a mask of the skin it hides. Working in the asset explorer; how it would carry into the game.
A character in the game will wear whatever the player has equipped: a helmet from one drop, body armour from another, gloves from a third. So gear can't be fused onto a character, as image-to-3D gives it. Each piece has to be its own model, fitted onto a bare body, and any pieces in different slots have to combine. The wardrobe is how. It works today in the asset explorer: pick a body, a piece (or none) for each slot, play any clip, and read how much each piece clips through the body as it moves.
Everything here is a proposal for Alex to keep, change or cut. The tools behind it,
crates/client/art/fit_armour.py and crates/client/art/clip_check.py, are in the repository,
and how the first pieces were made is in docs/art.md ("Characters from image-to-3D").
What a wardrobe is made of
- Base bodies. A rigged, animated body in tight underclothes, with no gear: its skeleton, its
clips and one mesh. One per body shape. Today there is one, the fallen's (its files are still named
kindled). - Pieces, by slot, many per slot. Each piece is its own file, made for one slot (body armour, gloves...). It holds the piece's mesh, skinned to a copy of the body's bones, and no clips: it moves with whatever the body plays.
- Fitted ahead of time, per body. A piece is generated once, on its own, as if worn by an
invisible mannequin. Then it is fitted to each body in Blender: turned square, scaled to the
torso from the bones, grown slice by slice until it encloses the body, given the body's bone
weights, and told which skin it hides. That is seconds of Blender per piece, and none of it is
work a viewer or the game should do at load (weight transfer, ray casting the body). The cost
is a file per piece per body, which is fine while there are few bodies. The unfitted piece is
kept (
design/wardrobe/<slot>/<piece>.glb), so when a body changes, its pieces are refitted by script.
How pieces combine: a mask per piece
Each fitted piece carries a mask: the list of the body's vertices it covers. A body vertex is covered when a ray along its normal meets the piece just above the skin, and the covered area is pulled back two rings from its edge, so the skin still fills the piece's rims as the shoulders move.
A body triangle is drawn unless all three of its vertices are covered by the pieces worn. With a cuirass on, the torso under it is gone; add gloves and the forearms go too; take the cuirass off and the torso comes back while the forearms stay hidden. Two pieces never uncover each other's skin, and masks that overlap (a coat over the top of the gloves) simply both hide it.
Why a mask per piece, rather than the alternatives:
- Not a body cut per slot. What a slot hides depends on the piece in it: a long coat hides the thighs, a short cuirass doesn't.
- Not a shader mask. The mask is used to rebuild the body's triangle list when the gear changes: one pass over the body's triangles, then nothing per frame. A per-vertex bit per slot in a shader would cost every frame and still not know which piece is in the slot.
- Hiding the skin at all, rather than leaving it under the gear: skin that pokes through plate as the body bends is the worst look, and hidden triangles aren't drawn.
The mask names vertices by their index in the base body's file. Blender reads a file's vertices in order (the fit checks it), and each piece records how many vertices its body had, so a piece fitted to an older body is caught by the site's build and by the clip check, instead of hiding the wrong skin.
The files, by name
The site's build reads the wardrobe from its folder names; nothing else lists it.
Path under www/art/models/ |
What it is |
|---|---|
wardrobe/<body>/base.glb |
The base body: skeleton, clips, one mesh named body. Written by fit_armour.py base. |
wardrobe/<body>/<slot>/<piece>.glb |
A piece fitted to that body. Its node's extras hold wardrobe: slot, piece, body, the body's vertex count, how it was made, the fit's options, and covers, the mask as ranges (0-15,17,20-33). Written by fit_armour.py piece (or shell). |
wardrobe/<body>/<slot>/<piece>.clips.json |
Its clip check: per clip, the frames where piece and visible body pass into each other, the worst frame and the body parts. Written by clip_check.py --json. |
<slot> is one of the game's item slots (weapon, helmet, body, gloves, boots, amulet, ring),
so a piece is already filed where the game would look for it. The build warns about a piece in an
unknown slot, one fitted to another body or an older base, or one with no clip check.
What the explorer shows
A wardrobe is one asset in the asset explorer, under Models › Concept › Wardrobe, with its body and pieces as its files. The bar under the view picks the body, a piece or none for each slot that has any, and the clip; the panel beside it lists what is worn, the triangles drawn now (the body's hidden triangles drop out), and each worn piece's clip check, clip by clip, with the rest pose first: what every pose starts from. Opening a piece's file puts it on; opening the base takes everything off.
Today's wardrobe proves the two halves: the cuirass, generated and fitted, and placeholder
vambraces in the gloves slot, plated tubes around the forearms made in Blender from the bones
(fit_armour.py shell), with no generation. They are there to show that two slots combine. Real
gloves, boots and helmets need their own fits (the cuirass fit encloses the torso; a helmet
encloses the head bone, gloves and boots follow the limb bones as the shell does) and generating,
at about 120 Tripo credits each.
In the game, later
The game already has 8 equipment slots and 45 item bases (Loot and crafting). Of the slots, helmet, body armour, gloves and boots would be fitted pieces with masks, 16 bases between them: a piece per base, per body, is that many pieces to make, and a unique could have its own. Weapons (18 bases) aren't fitted: a weapon is rigid, held by the hand bone, and hides no skin. Amulets and rings aren't drawn.
Who owns what, as any new system must say:
- The server owns equipment, as it does today. Nothing about the wardrobe changes that.
- What replicates. Today a client knows only its own player's equipment; other players' equipped items aren't replicated at all. To draw them, the server would send each player's look: the base in each drawn slot, a few small numbers, when it changes (with the equipment updates, reliably), never in every snapshot.
- What the client predicts. Nothing: a piece changes when the server confirms the equip.
What the client would do with it: one base body mesh and its textures per body, shared by every player of that body; per player, its own triangle list, rebuilt from the masks when its gear changes; each worn piece drawn skinned on that player's skeleton, shared by every player wearing it. The costs to measure before any of this goes in (performance):
- Triangles. A hero has a triangle budget; image-to-3D bodies and pieces are far denser (the counts are in the asset explorer). Bodies and pieces would be decimated before fitting, so the masks are made on what the game draws.
- Draws. Each worn piece is another skinned draw: four players in four fitted slots is sixteen more, beside the skinned-character cost already measured.
- Clipping between pieces. The clip check tests a piece against the body. Once two pieces meet (a helmet's rim on a cuirass's collar) it should test them against each other too.
Open questions for Alex
- One body for every fallen, or several builds (a fit per body per piece)?
- One look per base, or per base and rarity (a rare cuirass with more plates)?
- The unfitted pieces are committed at 2K textures (
design/wardrobe/), a megabyte or two each. Fine for tens of pieces; for the hundreds the game implies, somewhere outside git. - Placeholders from the bones for the slots not yet generated, or generate a first helmet, gloves and boots now?