FORM LAB0.5

A JOY GAMES EXPERIMENT

FORM STUDY / .FORMLAB

01 SCRIPT

● wave-study.form
Ln 1, Col 1Ctrl / ⌘ + Enter to run
Preparing your workspace…

02 PREVIEW Pending Changes

SURFACE STUDY / 001

MAKE
SOME WAVES.

Y ↑Z ↙X →
DRAG TO ORBIT · SHIFT + DRAG TO PAN · SCROLL TO ZOOM · CLICK TO INSPECT
STARTING JOY ENGINE
Generation Time: — Data Size: — Vertices: — Polygons: — Points: —

THE FORM SCRIPT FIELD GUIDE

A FEW WORDS.
INFINITE WHAT-IFS.

Write commands, change a number, see what happens. Distances use your own units, Y points up, and angles are in radians. Comments start with # or //.

GEOMETRY YOU CAN KEEP WORKING ON

let g = grid(columns, rows, width, depth) creates a reusable surface. geometry() starts empty; geometry(mesh) makes a mesh editable. Bindings share geometry; copy(g) makes an independent copy.

wrangle points g {
  @position = @position + vector(0, 1, 0)
  @density = noise(@position.x, @position.z, 7777)
  if @density > 0 { @color = rgba(1, 0.4, 0.2, 1) }
}
output("Surface", g)
inspect("Intermediate", g)

Wrangle points, faces, corners, or instances. index is the current element. Assign scalar, XYZ, or RGBA attributes with @name = value; reads of missing attributes report an error. Use @uv = vector(u,v,0) on corners for seams. Position edits clear authored normals; write normals afterward.

addPoint(g, vector(...)) and addFace(g, a,b,c) return indices. removePoint(g, index) also removes incident faces. removeFace(g, index) removes a face and its corners. Edit topology outside wrangles; removals compact indices.

attribute(g, "points", "density", index) reads another element. setAttribute(g, "points", "density", index, value) writes outside a wrangle. pointCount(g) and faceCount(g) support custom loops.

Define a named record with let homeData = {kind: "home", settings: {capacity: 4}}, then group its geometry with with userData(homeData) { point(0, 0, 0) }. Nested blocks restore the previous record on exit; local variables stay inside the block. The existing userData(record) command still sets data for subsequent vertices; userData() clears it. Records support nested objects, arrays, text, numbers, booleans, and null. Use setUserData(g, pointIndex, record) and userData(g, pointIndex) for editable geometry. The USER DATA column and JSON export retain records; OBJ, PLY, and GLB do not. Records are separate from colors and numeric attributes.

revolve(profile, segments) turns ordered vector(radius,height,0) points around Y (3–96 segments). Bottom-to-top profiles face outward; radius-zero ends close at a pole. tube(path, radius, sides) sweeps ordered XYZ points with 3–64 sides and open ends. Both require 2–512 distinct adjacent points and create new geometry with corner UVs and smooth normals. Sharp tube bends may self-intersect. smoothNormals(g) returns an independent copy with area-weighted point normals after deformation.

scatter(g, count, seed[, "density"]) samples triangle area and an optional nonnegative scalar point weight, interpolating point attributes. instances(source, sites[, material]) retains a source and independent placement data. Point @position, @rotation (XYZ radians), @scale (nonzero XYZ), and @color drive placement. Use wrangle instances assembly to edit it.

output("Name", value) snapshots a result and includes it in the full build. inspect("Name", value) snapshots it for inspection only. Choose a result above the viewport, then open ATTRIBUTES. Click an index or viewport element to select it; scalar/color fields can be visualized as markers. Export always uses the complete build.

Vectors support + - * / with vectors or scalars. mix supports vectors/colors. length, normalize, dot, cross, smoothstep(low,high,value), and deterministic noise(x,y,z) simplify fields and transforms. Comparisons return 0 or 1. Noise is in −1…1.

MAKE SOMETHING

point(x, y, z)
box(x, y, z, width, height, depth)
sphere(x, y, z, radius, segments)
triangle(ax, ay, az, bx, by, bz, cx, cy, cz)
color(red, green, blue, alpha)
color(tint)
material(Steel)
material()
normal(0, 1, 0)
normal()
uv(u, v)

Colors and UVs are 0–1; alpha is optional and defaults to 1. Sphere segments are optional (default 20). Color applies to following commands. Add named materials in the inspector, then select them with material(Name); material() returns to Default.

MAKE IT YOURS

project meta(name="Wave study",
  info="SURFACE STUDY", title="MAKE\nWAVES.")

param plank = meshBox(1, 0.2, 2)
param finish = materialPbr(rgba(0.9, 0.4, 0.2, 1), 0, 0.6)
material(finish)
instance(plank, vector(0, 1, 0))
let bent = deform(plank, x, y + x*x, z)
instance(bent, vector(3, 0, 0))

param height = 3 meta(min=0, max=10, step=0.1)
param offset = vector(0, 1, 0)
  meta(min=-5, max=5, step=0.1)
param tint = rgba(1, 0.4, 0.2, 1)
color(tint)
point(offset.x, offset.y, offset.z)
let angle = pi / 4
repeat 20 as i {
  point(cos(i) * 4, i * 0.2, sin(i) * 4)
}

Metadata controls the saved name and preview heading. Parameters infer scalar, vector, color, mesh, or material controls from their defaults; use component names such as .x or .r in expressions. Bounds keep defaults and editor values in range. Parameters live outside loops, whose indices begin at zero.

MAKE A LANDSCAPE

surface(64, 64, 20, 20,
  sin(x * 0.5) * cos(z * 0.5) * 3
)

Columns, rows, width, depth, height expression. Inside the height expression, x,z are world coordinates and u,v run from 0 to 1.

A LITTLE MATH

+ - * / % ^     pi tau e
sin cos tan abs sqrt floor ceil round
exp log min(a,b) max(a,b) pow(a,b)
atan2(y,x) clamp(x,min,max) mix(a,b,t)
seed(42)
rand() # 0–1, deterministic each run

Use floor() for calculated repeat counts. Scripts cannot access files, network, or JavaScript.

Limits: 150,000 vertices · 180,000 triangles · 2 million operations · 2 second worker timeout. Point previews sample at most 40,000 markers; exports contain every point.