What a recipe is
A recipe is the formula from which a visitor's machine builds one specific shape: this horn, this head, this stool. It is a set of calls to tools, with concrete arguments, arranged so that what each call makes feeds the next. The recipe crosses the network; the mesh is made on arrival, at the detail the visitor's distance and hardware call for.
Three things are kept apart:
- The recipe, which this page describes: the shortest specification of a particular shape in terms of tools that already exist. It is fixed, exact and deterministic: the same recipe builds the same shape on every machine.
- The tools a recipe calls: the browser's primitives (Sculpting), named forms from packages (a rock, a tree of a species, a body), and tools a world defines for itself. How tools are defined and parameterized is not a recipe's concern; a recipe only uses them.
- How recipes are produced: by editors, by AI, by fitting to a photograph or a scan, by normalizing an artist's recorded work into its shortest form. That happens outside the language (last section).
A recipe is judged on four things held in balance: small (a few hundred bytes), fast to build, high quality (it builds what was intended), and flexible (it gives a far copy cheaply and a near copy richly, and keeps any amounts the shape exposes for animation or for copies to vary).
1. Calls, and what passes between them
Each call takes things and makes a thing. Five kinds of thing pass between calls: a flat outline, a spine (a line in 3-D) with a frame at each point, an editable surface carrying named values on every point, a distance field, and the drawable triangle mesh. A recipe usually runs: outline, generator (outline to surface), edits (surface to surface), and the engine finishes it into a mesh. Most sculpt tools return a new surface and leave their input untouched, which is what lets calls be composed freely.
lathe(hornOutline, steps: 24).bend(70 deg).subdivide(1) // a recipe as a chain of calls
A call is written as a method of what the last call made (a uniform call: any tool whose first parameter is the surface may be called on it), so a recipe reads in the order it builds. Where a call needs two earlier products, a block names them (Blocks).
2. Addressing part of a shape
Many calls change only part of a shape: curl the outer half, flatten the underside, bevel the outside edges. A recipe never names vertices, which change with every refinement. It addresses part of a shape in three ways.
Coordinates the generator leaves
A generator records where every point came from as named values on the point: a lathe records along (round the turn) and around (along its outline, 0 at the base, 1 at the tip), since a lathe is a sweep round a circle; a sweep, the distance along its spine and the place on its outline (Appendix: shape tools). Every later call carries them through (Construction), so the recipe can speak in them: "every tenth of the way along", "the outer half". The lathe already records them. The proposal: every generator declares its coordinates, and a call's shaping function is given the point's coordinates by name (at present it receives a vertex number).
Regions
A region is an argument saying where a call applies and how strongly: a weight from 0 (untouched) to 1 (fully changed), soft at its edges so changes blend. It is built from coordinates (region(along: 0.5 ..= 1, fade: 0.2)), from distance to a landmark or a seam (near(tip, 3 cm)), from surface properties (facing(down), convexEdges(sharperThan: 30 deg)), from groups a generator named (group(cap)), and by combining regions: where both apply, where either does, the opposite of one, or one scaled by a strength (regions are weights, so these are not the bool operators; their spelling is open). The proposal: every editing call takes an optional where:. At present only the sculpt package's brushes honor its masks; bend, twist, displace and smooth change the whole surface.
lathe(hornOutline, steps: 24).bend(70 deg, where: region(along: 0.5 ..= 1, fade: 0.2))
Landmarks
A landmark is a named point with a frame, defined in a generator's coordinates (let tip = landmark(along: 1)) or from other landmarks (let crown = landmark(skull.top)), and carried through every later call. A recipe uses landmarks to place pieces (at: crown), to make regions (near(tip, 3 cm)) and to aim tools (a bend's axis from one landmark to another).
3. Pieces and the one join
A shape made of several pieces has a normal form: a tree that closes once. Each piece is built and edited completely on its own branch; the branches meet in one join; and after the join come only calls that need the joined shape: work on the seam, calls whose region spans several pieces (a hole drilled through a table top and its leg together), and overall surface detail. A call after the join that touches only one piece belongs on that piece's branch. This keeps recipes simple: regions after the join never need to know which piece a point came from, pieces build in parallel, and a piece used in many shapes is built once and cached.
The join is the join([...]) call every shape uses to gather its pieces (Blocks: joining shapes), given a how: option, which names how the pieces meet (a proposal of this page): blend(radius) (soft), fuse (overlap and re-skin, as sculpt's voxelRemesh does), add and cut (exact, as its booleans do), bridge (connect two openings), or layer (keep separate: eyes, teeth, clothes). It makes a seam, a region the calls after it can use.
let head = join([skull, leftHorn, rightHorn], how: blend(1.5 cm)); head.smooth(0.5, where: near(seam, 2 cm))
4. Levels of detail
A call may be marked with a level: level: secondary or level: tertiary. A far copy is built from the unmarked (primary) calls only, a middle copy through the secondary ones, and a near copy from everything. That is detail by distance from the recipe itself, with nothing extra sent: the far horn is the lathed form with its curl; the near one has its ribs and wear.
lathe(hornOutline, steps: 24) .bend(70 deg, where: region(along: 0.5 ..= 1, fade: 0.2)) .ripple(along, count: 9, depth: 3 mm, level: secondary) .roughen(noise(seed), depth: 0.4 mm, level: tertiary)
5. Amounts the shape exposes
Most numbers in a recipe are fixed. A shape may expose some as amounts: values a copy may set differently, or an animator may move. A face exposes its smile; a horn used by forty copies of a design exposes its curl. An exposed amount is named, and is part of what the recipe must reproduce: a recipe for a face that smiles builds the face for every value of the smile. Moving an amount reruns only the calls after it, as a spreadsheet recalculates only what depends on a changed cell (Subscriptions); a call that only moves points (a smile) can run every frame on the GPU, as a blend shape does.
fn face(seed: count, smile: amount 0 ..= 1 = 0) shape {
headForm(seed)
.pull(toward: (cheekUp + outward) * 6 mm, where: near(mouthCorners, 2 cm), amount: smile)
}
part head: face(seed: 7, smile: smile), material: materials.skin // illustration
field smile: fraction = 0, within: 0 ..= 1, mutable: true
on(hailed(by: v), start(transition(head.smile, to: 1, over: sim(0.33 s))))
6. The normal form
A shipped recipe is the shortest specification of its shape over the tools it uses. Whatever produced it, a recipe in normal form has these properties:
- No dead calls: nothing whose effect a later call removes or covers completely; one drag of a point, not two.
- No mergeable calls: two calls of the same kind on the same region are one call.
- Pieces before the join: as in section 3.
- A fixed order for calls that don't affect each other, so the same shape gives the same recipe and the same hash.
- Nothing below visibility: no call whose effect can't be seen at its level.
- Numbers at the precision the result needs.
- The highest-level tool that does the job: one
ridgecall, not fifty strokes that make a ridge. - Every exposed amount kept: the shape being reproduced includes its amounts.
7. How the engine builds a recipe
The recipe says what the shape is; the engine chooses how to compute it, and may choose differently by distance. There are two ways to model: surfaces built by calls (sculpt), which keep sharp edges and exact control, and fields combined by arithmetic on distances (sdf, meshed by sdfmesh), which blend softly and never tear. A soft join may be computed as a blended field near the seam and as surfaces elsewhere; a far rock as a coarse field meshed once. This needs one tool today's library lacks: turning a surface into a field. Built pieces are cached by the hash of their branch, so a horn shared by forty heads is built once, and a copy that comes closer adds its next level instead of rebuilding.
8. Examples
A horn
fn horn(seed: count, curl: amount 40 ..= 90 deg = 70 deg) shape { // curl exposed: copies vary it
lathe(line(0.3 m).offset(fn(t) { 4 cm * (1 - 0.8 * t) }), steps: 24)
.bend(curl, where: region(along: 0.5 ..= 1, fade: 0.2))
.ripple(along, count: 9, depth: 3 mm, level: secondary)
.roughen(noise(seed), depth: 0.4 mm, level: tertiary)
}
A horned head
fn head(seed: count) shape {
let skull = grow(skullSkeleton, blend: 2 cm);
let crown = landmark(skull.top);
let left = horn(seed).placed(at: crown + (6 cm, 0, 0), tilt: -40 deg);
let joined = join([skull, left, left.mirrored(x)], how: blend(1.5 cm));
joined.smooth(0.5, where: near(seam, 2 cm))
.roughen(noise(seed), depth: 0.3 mm, level: tertiary)
}
A stool
fn stool() shape {
let top = slab(0.4 m, 0.4 m, 3 cm).bevel(4 mm, where: convexEdges(sharperThan: 45 deg));
let leg = turnedLeg(height: 0.45 m);
let pieces = List[shape]() <- top, each c in corners(top, inset: 4 cm) yield leg.placed(at: c, splay: 6 deg);
join(pieces, how: add)
}
grow, slab, turnedLeg, ripple, roughen, pull, placed, mirrored and the region functions are names a tool set would supply; most are proposals, and today's equivalents are on Sculpting and in the Appendix: shape tools. A shape is a function, fn name(parameters) shape { block }, as everywhere in the manual (Construction); an exposed amount is a parameter marked amount.
9. What recipes need from the tools
Recipes only work if the tools they call keep a few promises. These are requirements on the tool set, wherever its tools come from:
- Every generator declares the coordinates and groups it leaves on its output.
- Every editing tool accepts a region, and a shaping function receives each point's coordinates by name.
- Every tool returns a new product and is deterministic: same inputs, same output, on every machine.
- Every tool has a known cost, so whatever produces a recipe can choose the cheapest path that meets the quality wanted.
- Landmarks and the seam of a join are carried through every tool.
10. Outside the language: producing recipes
Recipes are produced by tools: an editor that records an artist's work and reduces it to normal form, an AI asked for a shape, a program fitting a recipe to a photograph or a scan by building candidates and measuring them (the shapefit package measures). Editors offer undo, history, brushes and live feedback; none of that reaches the recipe. Which amounts a produced recipe exposes is part of what its producer is asked for.
Open questions
- Uniform call: may any tool whose first parameter is the surface be called as its method? A language question for Cone as much as ConeScript; otherwise a pipe operator returns as a question.
- Coordinates: what every generator must declare, and what coordinates a joined shape has.
- Regions: functions evaluated per point, stored weights, or both (functions in the recipe, weights as the engine's cache).
- Levels: three fixed levels or any number; who sets the distances.
- Amounts: the spelling of an exposed amount, and which calls may be moved every frame.
- Evaluation: how freely the engine may choose between surfaces and fields without a visible difference.
- Normal form: how close to "one shape, one recipe" it can get; which reorderings are safe.
- Encoding: since recipes are produced by tools, whether the main form is a compact binary one with a readable text twin.