Scenic Draft Extras: Five Shapes Out of the Library

scenic-draft is a TypeScript library for rendering 3D scenes in the browser. You describe the shapes and lighting, and it generates a shader to draw them.

In 0.21.0, hexPrism, triPrism and link moved into scenic-draft-extras. The package also adds quad and quads, counterparts to the core library's triangle builders.

I want to keep the core small and stable, with more specialised shapes in this separate package.

Install it with:

pnpm add scenic-draft-extras
import { hexPrism, link, quad, quads, triPrism } from 'scenic-draft-extras'

The example shows a hexagonal prism, a triangular prism, three chain links, an open box made with quads and a backdrop made with quad.

The shapes

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.

As elsewhere in Scenic Draft, height and thickness are half-measures. This is common in SDF code, though I am not sure it was the clearest choice for the API.

A folded aluminium plate of three panels, given as the corners of three quadrilaterals, beside a terracotta cylinder.

Give quad corners in order around the face, all on one plane. The builder checks the face and falls back to two triangles when it cannot use the quad representation.

How the extension works

Version 0.19.0 added custom distance functions. The extras package uses that API for its shapes.

For example, hexPrism(0.7, 0.9) creates a custom node with the hexagonal prism function and its two 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;
}

This generates the same distance calculation as the former built-in primitive. Transforms, materials, booleans and checkField work as before, as do the image exporters.

The package uses exact versions of scenic-draft and scenic-draft-fluent so its functions match the compiler they were written for.

Fluent API

For chainable shapes, import from scenic-draft-extras/fluent:

import { hexPrism } from 'scenic-draft-extras/fluent'

hexPrism(0.7, 0.9)
  .rotateY(Math.PI / 6)
  .paint(materials.brass)
  .union(sphere(0.4))

Both versions produce the same scene data. A fluent Shape adds methods on its prototype, so plain and fluent nodes can be used together in one scene.

Reading the GLSL

The package exports HEX_PRISM_GLSL, TRI_PRISM_GLSL, LINK_GLSL and quadFacesGlsl(count) so you can inspect the implementations.

They use Inigo Quilez's distance functions and provide examples for writing your own primitives.

Putting it together

Here are all five shapes under a sunset, viewed with a 45mm lens at f/2.8:

All five shapes again under a sunset with a shallow depth of field: hexagonal prism, triangular prism, chain link, folded plate and a glass sphere.

Each builder returns an ordinary SceneNode. That is the useful part of this package: adding a shape doesn't require a change to the compiler. You can build your own extensions in the same way.

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.