Polyfork BrowseKitsPricingBlog

Characters v2: a character is a config, not a mesh

One engine turns a config into a rigged low-poly humanoid. Change the age and the body re-proportions; change the build and every garment refits. The skeleton never moved, so every animation you already own still plays.

A crowd of low-poly characters mid-dance on a pale background: a naval officer, a farmer in an apron, an astronaut, a doctor in a lab coat and a striped-shirt deckhand, all in the same pose

A 3D character used to be the least reusable thing we sold. You bought a farmer and you owned a farmer: that age, that build, that shirt. Wanting the same person ten years older, or heavier, or in a coat, meant opening Blender and doing modelling work on a mesh somebody else had already finished.

That is over. Every Polyfork humanoid is now built by one engine from a config: an age, a figure, a build, an expression, and a garment in each slot. Change any of them and the model is rebuilt from scratch, in the browser, in about 14 milliseconds.

18 characters are live on it today and the rest are being rebuilt. You can drive all of it at polyfork.dev/create.

Four ages of the same character dancing in a row, from a child to an elder, with age chips reading kid, teen, adult, elder underneath
The same config at four ages. Nothing was re-modelled: the engine re-proportions the body and every garment re-fits to what it finds.

What changed, concretely

A v1 character was a bespoke module. The farmer's was 1,358 lines with 22 knobs, and it took an average of 4.6 build passes to get right, which made humanoids the most expensive thing our factory made. Every one of those modules re-derived the same work: the skeleton, the loft builders, the joint arithmetic, the mitten recipe.

The tell that this was wrong: our own spec had described a base-plus-attachment system since July, and zero attachment assets ever shipped. The spec put the fit burden on each attachment's author, who had to read one frozen base mesh and hand-derive every measurement from it. Nobody could build against that.

v2 inverts it. The engine fits the garment; the author writes a profile.

The rule that generates everything else

A garment never sees a measurement.

It names a station and asks the body for its cross-section there:

// not this
const brimY = 1.650;

// this
const brim = fit.station('hatBand');

y = 1.650 is the brim height of exactly one adult. fit.station('hatBand') is the brim height of every character the engine can produce, at any age, on any build, on either figure. Any literal distance in a garment is a bug that has not fired yet.

That one rule is why a coat written once fits a child and an elder, and why adding a garment multiplies the whole catalogue instead of adding one row to it.

Age is not a scale factor

This is the mistake that cost us the most, so it is worth stating plainly: you cannot build an age knob by scaling.

An elder is 1.64 m tall with a full-size adult head. A child is five heads tall; an adult is a bit over seven. Anything measured against the body has to be measured against the part it touches, a hat lap against the head, a hem against the leg, never against a global factor.

We got this wrong once in a way that only a gate caught: a hat liner scaled by the global factor cleared the neck on the reference build and was 3% short on the elder. It looked fine in every render anybody would have thought to take.

The skeleton never moved

Everything above is possible because of a thing we deliberately did not change.

Same bone names, same hierarchy, same strict T-pose bind, same origin on the ground between the heels. That is the interface to Mixamo, Unity Humanoid, Unreal's IK Retargeter and our own animation library, and it is frozen.

Bone lengths vary freely with age and proportion, and that is safe for a specific reason: our clips carry only rotation tracks, with no position or root-motion track anywhere in the path. A clip plays on any proportions, and a shorter figure is simply shorter. It is why a child can dance a clip recorded on a grown adult and why every animation you already own still plays on every new body.

Change a bone name and all of it breaks at once, with no fallback. So we don't.

What that buys you

A police officer mid-dance beside a panel headed polyfork dot dev slash create, listing wardrobe slots: hat police cap, hair cropped hair, top shirt, bottom pants, feet low shoes, belt duty belt
The creator, driving the same engine the store ships. Each row is a slot; changing one calls createAsset again and the body is rebuilt, not re-textured.

Across the 18 characters live today, that is 9,984 combinations before anyone picks a colour:

knobvalues
character26 configs
agekid, teen, adult, elder
figuremasculine, feminine
build0.85 to 1.20 in 8 steps
expression6

And colour sits on top of all of it: a character carries around 19 tintable zones, each one its own knob.

The number matters less than what it is made of. These are not 9,984 files sitting on a disk. They are one function, called with different arguments, and the arguments are published as JSON so an agent can read what a model can change and write the call itself.

The catch, stated plainly

Not every character is on v2 yet. 18 of our 52 published characters have been rebuilt; the rest are still v1 modules and are being worked through. You can see exactly which ones are done: they are filtered at /assets?cat=characters-v2, and that filter will disappear when it stops meaning anything.

A rebuild also has to pass a gate before it ships: the new build must still read as the same character (silhouette IoU against the published v1) and stay inside the triangle budget. Four characters failed that gate this week and are not live, which is the gate working rather than the gate being in the way.

Frequently asked questions

Do my existing animations still work?

Yes. The skeleton is unchanged from v1: same bone names, same hierarchy, same bind pose. Our clips carry rotation tracks only, so they play correctly on any proportions the engine produces, including a child.

Can I use these outside three.js?

Yes. Every character ships as a GLB as well as an ES module, and the rig is built to the conventions Mixamo, Unity Humanoid and Unreal's IK Retargeter expect.

Is the engine a dependency I have to install?

No. A published character is one file that imports nothing but three. The engine is bundled into the module at publish time, which is the same "one import, no loader, no dependencies" contract every other asset in the store ships under.

How many combinations are there really?

9,984 across the 18 characters currently on the engine: 26 configs x 4 ages x 2 figures x 8 builds x 6 expressions. That is before colour, and each character carries around 19 tintable zones on top.

What happens to the v1 characters I already bought?

Nothing. They keep working. When a character is rebuilt, the previous module is kept beside it, and the rebuild has to pass a silhouette check against the published v1 before it can go live.

Polyfork

Sign in

One account for your purchases and downloads.

Continue with Google
or

No password needed: the link signs you in directly.