Scenic Draft Extras: Five Shapes Out of the Library
scenic-draft is a dependency-free TypeScript library that compiles a declarative scene (a background plus a tree of signed distance functions and constructive solid geometry) into a GLSL fragment shader, and progressively path-traces it on a WebGL2 canvas.
0.21.0 makes it smaller. Three primitives that had been in the core library (hexPrism, triPrism and link) are no longer in it. They are in a new package scenic-draft-extras, along with two new ones: quad and quads, the four-cornered counterparts of the triangle and triangles that arrived in the core library in 0.21.0. I'm aiming to stabilise the core library (if anything make it smaller).
Install with, for example:
pnpm add scenic-draft-extras
import { hexPrism, link, quad, quads, triPrism } from 'scenic-draft-extras'
A hexagonal prism, a triangular prism, three links of chain, an open box from quads, and the backdrop panel behind them from quad.
The five
hexPrism(radius, height) | A hexagonal prism along y. radius is the apothem — centre to the middle of a flat, not to a corner. |
triPrism(radius, height) | An equilateral triangular prism along y. radius is the circumradius, so the sides are radius * √3 long. |
link(major, minor, height) | A chain link: a torus stood on edge in the x–y plane and drawn apart along y by height. |
quad(a, b, c, d, thickness) | A quadrilateral face through four corners given in order around it, thickness out from their plane on each side. |
quads(faces, thickness) | A whole list of those as one shape: a folded plate, a set of panels, a quad mesh. |
Every measure is a half-measure, as everywhere in scenic-draft: a height is the half-length and a thickness the half-thickness. Honestly this may not have been the best idea though is often used in the SDF world for convenience.

A quad should have corners that go round it in order and must lie on one plane. All three are checked when the node is built. A face that breaks any of them will be treated as two triangles.
Extending Scenic Draft, or how does this work?
0.19.0 added custom(), where a scene carries a distance function you wrote and the library calls it wherever the tree walk has reached. scenic-draft-extras also serves as an example of how to do this. So, for example, hexPrism(0.7, 0.9) builds a custom node holding Quilez's hexagonal prism field — the same source the compiler used to write inline — with the two numbers as its arguments:
float map(vec3 p) {
vec3 q0 = vec3(0.92106 * p.x - 0.38942 * p.z, p.y, 0.92106 * p.z + 0.38942 * p.x);
float d1 = sdHexPrism(q0, 0.7, 0.9);
return d1;
}
That is the line the built-in used to compile to, unchanged, which is the whole point: the shapes did not become slower, or less exact, or harder to use, by moving out. translate, repeat, paint, the smooth booleans, checkField and the headless exporters all take these as they take anything else, because a custom node is an ordinary node and always was.
The package fixes scenic-draft and scenic-draft-fluent at exact versions, so the compiler reading these fields is the one they were written for.
Fluent API
scenic-draft-extras returns plain nodes but the package also supports the fluent API. To use this import from scenic-draft-extras/fluent which returns chainable shapes.
import { hexPrism } from 'scenic-draft-extras/fluent'
hexPrism(0.7, 0.9)
.rotateY(Math.PI / 6)
.paint(materials.brass)
.union(sphere(0.4))
They build the identical node, since a fluent Shape is a SceneNode with the chaining methods on its prototype, so the two mix freely in one scene. There is nothing else in the fluent entry point: the vocabulary a scene is written in stays scenic-draft-fluent's, and this package adds five leaves to it rather than wrapping it again.
The GLSL, on purpose
HEX_PRISM_GLSL, TRI_PRISM_GLSL, LINK_GLSL and quadFacesGlsl(count) are exported, because a worked example of a custom primitive is the most useful thing in a package of custom primitives. They are Inigo Quilez's distance functions, unchanged from the ones the compiler used to write inline.
Everything at once
All five, under a sunset, through a 45mm at f/2.8:

None of it is special from the library's side. Every builder returns a plain SceneNode; nothing needs unwrapping and nothing needs to know these came from somewhere else. That is the argument the package exists to make — not that a chain link is worth having, but that adding one should never have needed the compiler's permission.
Installing
pnpm add scenic-draft-extras # it carries scenic-draft and scenic-draft-fluent
Like every scenic-draft package it is PolyForm Noncommercial 1.0.0: free for personal projects, study, teaching and public bodies, with commercial use a separate conversation.
Documentation lives at scenic-draft.pages.dev.