A world never sends a finished shape as a list of points. It sends how the shape is made: an outline turned on a lathe, a box with its edges rounded, a horn swept along a curve and ribbed. This page is the sculptor's kit: every tool a shape may use, in four groups, with what each makes or does, in plain words, and a short example for each group. How a shape is written (a shape is a function) is on Construction; each tool's exact call, with its parameters, units and defaults, is in the Appendix: shape tools.
Words used on this page:
- Shape
- A value of type
shape: a surface of faces (mostly four-cornered) joined edge to edge, with named values on its points, edges and faces. Every tool here takes shapes and returns a new shape. - Outline
- A flat line of points in (x, y), open or closed: the cross-section a solid is made from. Its type is
outline. - Spine
- A line of points in space, open or closed, that an outline is carried along. Its type is
spine. It is called a spine, not a path, so that it is never confused with the path of a part (a.b). - Frame
- A place on a spine and three directions there: the way the spine runs, and the two directions across it in which an outline is laid.
- Channel
- A named value on every point (or edge, or face) of a shape, left by one tool for a later one to read: how far along a lathe the point was, how sharp an edge is.
- Selection
- A way of naming some of a shape's faces, edges or points without numbering them: those facing up, those inside a box, the sharp edges.
- Catmull-Clark
- The usual smoothing: each level splits every four-cornered face into four and moves each point toward a weighted middle of its neighbors, so a coarse cage of faces becomes a smooth surface.
How a world uses them
A shape is a function whose result is a shape: fn name(param: type, ...) shape { block }. Inside its block it calls these tools; the block's last line is the shape. Several pieces are joined into one by join([...]) (Blocks: joining shapes). A part draws one shape in one material: part glass: panes(size), material: materials.lanternGlass. Tools are pure: each makes a new shape from its inputs and changes nothing, and the same inputs give the same shape on every machine.
- Coordinates: meters, right-handed, x to the right, y up, z toward the viewer. A point is
(x, y, z); an outline's point is(x, y). The directionsup,down,left,right,front(toward the viewer) andbackhave names. - Angles carry their unit:
90 deg,1.2 rad. A positive turn is counterclockwise seen from the tip of its axis. - Extents may be ranges:
box(x: -0.07 ..= 0.07, y: 0.24 ..= 0.26, z: -0.07 ..= 0.07). - Arguments: a parameter without a default may be given by position, in declared order, or by name; a parameter with a default must be named:
subdivide(cage, levels: 2)(Expressions: calls). - Resolution is a count the content states:
steps: 14,segments: 32,levels: 2. Saying instead "looks round at 1 m" and letting the browser choose the count is a later convenience written over the counts, not a replacement for them.
1. Outlines and spines
Most solids start as a line: a flat outline to turn or sweep, or a spine in space to sweep it along. These tools make those lines, and a spine answers questions about itself.
| Tool | What it makes |
|---|---|
outline.points | an outline through the points you list |
outline.polygon, outline.circle | a regular polygon; a circle (a polygon of many sides) |
outline.rectangle, outline.roundedRectangle | a rectangle, with sharp or round corners |
outline.ellipse, outline.arc, outline.star | an ellipse; part of a circle; a star of so many tips |
outline.bezier | a smooth curve from three or four control points |
outline.hull | the tightest convex outline round some points, as a band stretched round pins |
outline.offset | an outline grown outward or shrunk inward by a distance |
outline.roughen | an outline made rough like a coastline, from a seed |
spine.points, spine.line | a spine through points; a straight line |
spine.circle, spine.arc, spine.helix | a circle flat on the ground; part of one; a spiral climbing round the y axis |
spine.through | a smooth curve through every point you list |
spine.bezier, spine.beziers | one smooth curve from control points; several end to end |
What a spine answers. s.length() is its length. s.frameAt(distance) is the frame that far along. s.spotAt(distance, across: meters) is the point (x, y, z) that far along, moved sideways: to the right looking along the spine, to the left when across is negative. s.fromEnd(distance) is the distance along of the place that far back from the end, so a place counted back from the end needs no arithmetic with the length. s.points() are the spine's points as it was built, one for each piece. s.nearest(p, within: d) is the distance from a place to the spine and the distance along at the nearest point, or None if the spine is farther than d; s.indexAt(along) and s.alongAt(index) turn a distance along into the number of the control point there (with a fraction for the way to the next) and back. s.headingAt(along) is the direction the spine runs there, and s.pointNamed(name) the distance along at a point given a name when the spine was built. Distances below 0 or past the end are accepted: the ends carry on straight. A feature the land holds, such as the walkway (Terrain), is a spine laid on the ground and answers the same questions; its spots are on the ground. The full table is Shape tools: what a spine answers. A closed outline also answers o.contains(p), whether a place lies inside it, which is how a flat area of the land is stated.
let stake = [(0, -0.22), (0.008, -0.20), (0.018, -0.12), (0.018, 0.244), (0, 0.244)]; // (out from the axis, up) let lid = outline.roundedRectangle(width: 0.4, height: 0.25, radius: 0.03); let rail = spine.through(points: [(0, 1, 0), (2, 1.2, -1), (4, 1, 0)], segments: 12); let spot = walkway.spotAt(walkway.fromEnd(1.8 m), across: -1.5 m); // 1.8 m back from the end, 1.5 m to its left
A list of flat points, like stake, can be given wherever an outline is wanted: it is an open outline through them.
2. Making solids
A solid is made either whole, from a few numbers (a primitive), or from an outline (a generator).
| Tool | What it makes |
|---|---|
box | a box, from its three sizes or from a range on each axis |
cylinder, cone | a round column; a cone, or a cone with its tip cut off, each standing on the ground plane |
sphere, torus | a ball; a ring like a doughnut |
plane, disc | a flat rectangular sheet facing up, cut into a grid; a flat round one |
face | one flat polygon through points you give, seen from one side |
bar | a straight bar of rectangular section from one point to another, of a given width and thickness |
lathe | an outline turned round an axis, as wood is turned: posts, vases, bulbs, a balloon's envelope. It is a sweep of the outline round a circle. |
extrude | an outline pushed straight out to a depth: a plank, a letter, a wall with a window's outline |
sweep | an outline carried along a spine, perhaps twisting and growing or shrinking: a rope, a rail, a horn |
sweepScaled | the same, the outline's size any function of the way along |
loft | a skin stretched through several different outlines, each placed in a frame you give: a boat's hull through its sections |
loftAlong | the same, the outlines placed at stations along a spine |
sphere, box and cylinder also describe a space rather than a surface: written the same way, they give an area its volume, area(self, sphere(radius: 4 m, at: (0, 0.2, 0))).
A worked example: the lantern's roof by loftAlong
The lantern's roof is hipped: its eaves are 19 cm square, it rises shallowly for a little way and then more steeply to a top 10.4 cm square, all within 5 cm of height, and a top block 10 cm square stands on it. Three square outlines at three heights describe it, and loftAlong skins them.
let hipped = loftAlong(
profiles: [outline.rectangle(width: 0.19, height: 0.19), // the eaves
outline.rectangle(width: 0.14, height: 0.14), // the change of pitch
outline.rectangle(width: 0.104, height: 0.104)], // under the top block
spine: spine.line(from: (0, 0.388, 0), to: (0, 0.438, 0)), // 5 cm straight up
stations: [0, 0.28, 1],
caps: false); // open at both ends
- The spine runs straight up the roof's middle, from the eaves at 38.8 cm to the top block's foot at 43.8 cm.
- Each outline lies flat across the spine, as a slice through the roof: its x along the frame's
xand its y along the frame'sy. On a spine that starts straight up,frame.ypoints towardfront(+z) andframe.xtowardleft(-x), so a rectangle'sheightlies along z and itswidthalong x. For squares, as here, it makes no difference. - A station is where an outline sits, as a fraction of the spine's length from its start. Outline 0 is at 0 (the eaves, 38.8 cm up), outline 1 at 0.28 (1.4 cm higher, at 40.2 cm) and outline 2 at 1 (the top, 43.8 cm). So the first band falls from 19 to 14 cm across in 1.4 cm of height, a shallow pitch, and the second from 14 to 10.4 cm in 3.6 cm, a steeper one.
- Between stations the skin runs straight from each outline's points to the next's (
between: 0,smooth: false, the defaults);between: 4, smooth: truewould curve it through them. - Its faces face outward, away from the spine: each closed outline is turned counterclockwise first, so the stations all run the same way.
- The ends. By default (
caps: true) each end is closed by one flat face and the result is a closed solid. The lantern's roof passescaps: false: it is a skin only, open at the bottom, where it sits on the fascia, and its top is afaceof its own under the top block (The lanterns).
3. Combining and placing
These put shapes where they belong, copy them, and combine them.
| Tool | What it does |
|---|---|
move, turn, scale | moves a shape by an offset; turns it about an axis; scales it, evenly or by a different factor along x, y and z |
join | several shapes as one: join([a, b, c]) joins the shapes in its list, which a loop can build with List[shape]() <- ..., each ... yield ... |
onBone | a shape tied wholly to one bone of a body; joined with others tied to other bones it makes one skinned part (Fitted bodies) |
skinChain | a shape skinned along a chain of bones with the weights blended across each joint (to be built) |
inFrame | a shape written upright about the origin, placed in a frame got from a spine: petals and leaves carried into a stalk's frames (to be built) |
cut | a shape cut by a plane, keeping one side and leaving the cut open: a dome open underneath |
mirror | a shape and its reflection in a plane, the seam stitched (points closer than merge welded; a negative merge stitches nothing) |
symmetrize | one half of a shape kept and the other half replaced by its reflection |
arrayLinear, arrayRadial, arrayAlongSpine | copies in a row; copies round an axis (a tower's battlements, a flower's petals); copies spaced along a spine, each following its turns |
union, difference, intersection | two solids as one; one cut out of the other; only what both hold. Each may crease the line where the two surfaces meet. |
weld | points closer than a distance merged |
bridge | two shapes with an open edge each, joined by a band of faces between those edges |
let side = join([ // one side of the lantern's tracery, on the front box(x: -0.052 ..= 0.052, y: 0.292 ..= 0.300, z: 0.056 ..= 0.062), // the low rail bar(from: (-0.046, 0.300, 0.059), to: (-0.046, 0.326, 0.059), width: 0.006, thickness: 0.006) ]); let fourWays = List[shape]() <- each f in 0 ..< 4 yield turn(side, by: f * 90 deg, about: up); join(fourWays) // the same side, four ways round
4. Changing a solid
These change a solid already made: its faces, its detail, its form, its surface.
Selections
Many of these tools change only part of a shape, so they ask which faces, edges or points. A selection says which, without numbering them:
| Selection | What it chooses |
|---|---|
all | everything |
facing(up, within: 20 deg) | the faces that face a direction, give or take an angle |
inBox(y: 1 ..= 2) | what lies inside a box; an axis left out is unbounded |
sharperThan(30 deg) | the edges where the faces meet at more than an angle |
byChannel("tapes") | whatever a channel marks above 0 |
boundary | the open edges, with a face on one side only |
where(test) | the points for which a function of the point is true: its position, its direction, its channels |
Soft selections that fade at their edges (regions) are proposed on Recipes.
The tools
| Tool | What it does |
|---|---|
| Editing faces | |
extrudeFaces | chosen faces pushed out, with walls back to where they were: a chimney from a roof, a step from a floor |
inset | chosen faces shrunk inside a border, and perhaps pushed in or out: a window's frame and its recess |
solidify | a thin sheet given thickness: a leaf, a cloth, a bowl's wall |
bevel, bevelCorners | chosen edges rounded or cut at an angle; chosen corners cut off |
| Refining and reducing | |
subdivide | a coarse cage smoothed into a fine surface (Catmull-Clark), creased edges kept sharp |
decimate | fewer triangles, losing as little of the form as it can |
dissolvePlanar | faces that lie nearly flat together merged into one |
voxelRemesh | a closed shape rebuilt as an even skin of faces, overlaps and all fused |
multires | a coarse base and stored detail at finer levels: a surface sculpted in an authoring tool |
| Deforming | |
bend, twist, taper | the whole shape curled, wrung, or narrowed toward one end; twist and taper may follow any function of the way along |
deform, deformAlong | every point moved by a function of where it is; or by a function of how far along the shape it is |
curveDeform | the shape laid along a spine, following its bends |
lattice | a cage of control points round the shape; move some and the shape follows smoothly |
smooth | bumps and corners eased, with or without shrinking |
shrinkwrap | the shape pressed onto another's surface |
| Detailing | |
displace | points pushed out or in along the surface's direction, by a channel or by a function of the point |
ribs, marginRing | a channel of rib heights over the generator's along and around: regular ribs; ribs grown as a shell grows them |
roughen | the surface made uneven by seeded noise |
crease, creaseAngle | chosen edges kept sharp through smoothing; how sharp an edge must be to show as an edge when drawn |
uvPlanar, uvBox, uvCylindrical, uvSpherical | new texture coordinates, projected flat from one side, from the three axes, round a cylinder, round a ball |
uvAt | new texture coordinates worked out from each point's place by a function you write |
fn tableTop() shape { // a slab with its edges rounded
let slab = box(x: -0.6 ..= 0.6, y: 0.72 ..= 0.75, z: -0.4 ..= 0.4);
bevel(slab, edges: sharperThan(30 deg), width: 4 mm, segments: 3)
}
fn horn(seed: count) shape { // a spiral horn with ribs
let swept = sweepScaled(profile: outline.ellipse(width: 2.5, height: 2, segments: 64),
spine: spine.arc(radius: 3.3, from: 0 deg, to: 0.9 rad, segments: 360),
scale: fn(u: fraction) number { 0.36 * (0.03 + 0.97 * (1 - u)) });
let ribbed = ribs(swept, into: "ribs", frequency: 22, seed: seed);
displace(ribbed, by: fn(p: surfacePoint) meters { p.ribs * 0.14 * 0.36 * (0.03 + 0.97 * (1 - p.along)) })
}
The horn's outline is an ellipse 2.5 by 2, scaled down to 0.36 of that at its root and to almost nothing at its tip; the spine is an arc 3 m long bending through 0.9 rad. ribs leaves a channel ribs of rib heights from 0 to 1; displace reads it, and the sweep's own along, to push each rib out by 0.14 of the horn's radius there.
Channels
A channel is a named value on every point, edge or face of a shape, left by one tool for a later one. This is how the steps of a shape talk to each other, and content may use them by name.
| Channel | On | Left by | Read by |
|---|---|---|---|
along, around | points | every generator: along how far the outline had traveled, around where on the outline the point came from, each 0 to 1. A lathe is a sweep of its outline round a circle, so for a lathe along runs round the turn (0 at the start, 1 back where it began) and around runs along the outline (from its first point to its last: up a post, from the mouth to the crown of a balloon), the same meanings as a sweep's | ribs, marginRing; content, as p.along |
uv | face corners | every generator; primitives (each its natural ones); the uv projections and uvAt; mirror | materials; content, as p.uv |
crease | edges | crease; booleans with a crease | subdivide, smooth, decimate |
| a name of the content's | points | ribs, marginRing (into: "ribs") | displace(channel: ...); byChannel; content, as p.ribs |
Content reads a channel by name, in a function a tool runs for each point (p.along, p.ribs), and selects by one (byChannel("ribs")). Content writes its own channels through the tools that write them (ribs, marginRing, crease). Every tool carries every channel through, so after a bend or a subdivision a point still knows where it was in its generator.
What a drawn shape gets
- Triangles, from the browser. When a part draws a shape, the browser cuts it into the triangles a graphics card draws. Content never handles triangles.
- Normals. The shading is smooth except across edges sharper than the shape's crease angle, 30 degrees unless
creaseAngle(s, angle: 60 deg)says otherwise. A box shows its edges; a lathed post is round.creaseis a different thing: it keeps an edge sharp throughsubdivide. - Texture coordinates. A generator's own (around and along the outline); a primitive's natural ones (a box 0 to 1 across each face, a sphere round and pole to pole, a cylinder round and along: Appendix: shape tools); either changed by the
uvprojections or byuvAt. - One material. A part draws one shape in one material. A thing of several materials is several parts: the lantern's glass, bulb, roof and tracery are four parts, each its own shape in its own material.
Other modellers
Some forms are made better by a modeller of their own than by sculpting. Each makes a finished form a part draws directly; it is not a shape the tools above change.
| Package | What it makes | See |
|---|---|---|
sdf and sdfmesh | forms described by distance from a surface, combined by soft blends and cuts, then turned into a surface: blobs, fused organic forms | Recipes |
noiserock | a rock of a given size, facets and bumpiness, from a seed | Designs |
weberpenn | a tree of a species, grown from a seed, with its leaves or needles | Generators |
leafrosette | a clump of leaves from a center: a hosta, a fern, a cluster of flowers | Generators |
terraingen, terrainlod | land: heights shaped and eroded, and ground drawn finer near the eye | Terrain |
humanbody | a person's body from measurements | A villager |
garmentdrape | cloth cut to a pattern and draped on a body | Fitted and grown |
strandflow | hair and fur, grown from a surface | Fitted and grown |
groundcover | grass, flowers, moss and litter laid over the land by rules | Generators |
noiserock: the rock recipe. A rock is a pure function of its record and seed. The record's fields and defaults: seed (1), width (0.5), height (0.3), depth (0.4) (half extents, in meters), rings (10), slices (16), facets (7), facetDepth (0.78), bumpAmp (0.16), bumpFreq (1.4), roughAmp (0.025), flatBase (0.55), smooth (false) and tileMeters (2.1). The island's rocks come in three kinds and the edging stones in their own recipe:
| Recipe | Fields it sets |
|---|---|
| squat rock | seed: 500 + 17 * v, width: 0.5, height: 0.3, depth: 0.42, facets: 10, facetDepth: 0.7, bumpAmp: 0.12, rings: 9, slices: 14 |
| tall rock | the same with width: 0.34, height: 0.55, depth: 0.32, facets: 8, facetDepth: 0.66, bumpAmp: 0.1 |
| long rock | the same with width: 0.62, height: 0.32, depth: 0.3, facets: 9, facetDepth: 0.68, bumpAmp: 0.16 |
| edging stone | seed: 800 + 13 * v, width: 0.12 + 0.025 * (v % 2), height: 0.075, depth: 0.1 + 0.03 * (v / 2) (whole division), facets: 4, facetDepth: 0.9, bumpAmp: 0.12, flatBase: 0.45, rings: 6, slices: 10, smooth: true, tileMeters: 1.5 |
Variant v picks the kind by v % 3 for rocks. The rock's granite paint is surfacepatterns' RockParams.granite(size, seed) (Textures).
leafrosette: three clumps. Each is a record with a seed (1), made into a mesh standing on y = 0:
| Call | Fields (defaults) | The island's |
|---|---|---|
hostaMesh(HostaParams) | leaves (9), length (0.30), width (0.62), across (3), rows (4), droop (70) | seed: 40 + 7 * v |
fernMesh(FernParams) | fronds (9), length (0.7), pairs (12), leaflet (0.17), arch (95) | seed: 60 + 11 * v |
flowerClumpMesh(FlowerParams) | flowers (5), height (0.30), spread (0.12), petals (8), head (0.025) | seed: 80 + 5 * v, flowers: 4 + 3 * (v % 2), height: 0.38, head: 0.036, spread: 0.16 |
sculptbrush (brush strokes), quadremesh (rebuilding a surface as tidy four-cornered faces) and shapefit (measuring and fitting) are tools for making worlds, used by authoring programs; they are not content vocabulary.
What it means
- The program is the asset. A shape is sent as its calls and their numbers; the surface is made on each machine, the same everywhere, so a hash of it can be checked.
- A refused input stops the world loading. A tool that cannot do what it is asked (a boolean of a shape that is not closed, a bevel of an open edge) is an error when the world is loaded, naming the shape and the line. Content never reads a list of problems.
- Each subdivision level is a level of detail. A browser could choose how many levels to build by distance.
Limits of the tools today
- Bevel has no widths in percent, no custom profiles, sharp mitres only, and cannot bevel open edges.
- Mirror does not cut a face that crosses its plane;
symmetrizedoes. - Sweep has no mitre at a spine's sharp corners: sample a curve finely instead.
- Inset has no outset; voxel remesh refuses an open shape.
- Distance fields come out with sharp edges rounded.
How it connects
- Appendix: shape tools: every tool's exact parameters.
- Construction pipelines: how a shape is written and when it is built.
- Recipes: regions, landmarks and levels for shapes.
- Open functions: the functions
sweepScaled,deform,displaceandwheretake. - Fitted and grown: shapes fitted onto other shapes.
- Textures: the
uvchannel and what reads it.
Open questions
- How a sculpted surface travels. Strokes, a coarse base with stored detail (
multires), or not as content at all. - Method or shape: a swept horn or a distance-field horn; does content name the tool or describe the shape?
- Versioning: a shape's meaning depends on its tools' versions; how a tool's version is stated and kept is not designed.
← Composing the tools | Contents | Generators and scattering →