Polyform is a toolkit for developers and artists to load, generate, edit, and export 3D geometry with a focus on immutability, modularity, and procedural workflows.
Developers and artists are welcome to join the Discord to share feedback, get help, or discuss feature requests.
Try it now in your browser → Live Demo
To run it locally, you can download the latest release and run:
# Launches the node based editor
polyform edit
# If Golang is installed, clone and run:
npm install && npm run build-dev
go run ./cmd/polyform edit
# If Nix is installed, run:
nix run .#polyform edit- Formats
- gltf - GLTF file format
- obj - OBJ file format
- ply - PLY file format
- stl - STL file format
- colmap - Utilities for loading COLMAP reconstruction data
- opensfm - Utilities for loading OpenSFM reconstruction data
- splat - Mkkellogg's SPLAT format
- spz - Niantic Scaniverse's SPZ format
- potree - Potree V2 file format
- Modeling
- extrude - Functionality for generating geometry from 2D shapes.
- marching - Multi-threaded Cube Marching algorithm and utilities.
- meshops - All currently implemented algorithms for transforming meshes.
- primitives - Functionality pertaining to generating common geometry.
- repeat - Functionality for copying geometry in common patterns.
- triangulation - Generating meshes from a set of 2D points.
- Drawing
- Math
- bias - Generic, temperature-scaled, biased random sampler for weighted selection of items
- colors - Making working with golang colors not suck as much.
- curves - Common curves used in animation like cubic bezier curves.
- geometry - AABB, Line2D, Line3D, Plane, and Rays.
- kmeans - Generic k-means clustering algorithm across 1D to 4D vector spaces.
- mat - 4x4 Matrix implementation
- morton - 3D Morton encoder that maps floating-point vectors to and from compact 64-bit Morton codes with configurable spatial bounds and resolution.
- noise - Utilities around noise functions for common usecases like stacking multiple samples of perlin noise from different frequencies.
- quaternion - Quaternion math and helper functions.
- sdf - SDF implementations of different geometry primitives, along with common math functions. Basically slowly picking through Inigo Quilez's Distfunction article as I need them in my different projects.
- sample - Serves as a group of definitions for defining a mapping from one numeric value to another.
- trs - Math and utilities around TRS transformations.
- Generator - Application scaffolding for editing and creating meshes.
- Trees - Implementation of common spatial partitioning trees.
Packages that have spawned from polyform's undertaking and have since been refactored into their own repositories:
- Node Flow - Another Flow-based Node Graph Library
- vector - Immutable vector math library
- jbtf - GLTF-inspired JSON schema for embedding arbitrary binaries
- iter - Iterator and utilities. Some inspiration from ReactiveX
- quill - Scheduler of operations on in-memory data
- sfm - Utilities for interacting with reconstruction data from different SFM programs
- bitlib - Utilities for reading and writing binary data
Learn how to create your own nodes for others to use.
You can at the different projects under the examples folder for different examples on how to procedurally generate meshes.
This was my submission for ProcJam 2022.
[Source Here] ![]() |
[Source Here] ![]() |
[Source Here] ![]() |
[Source Here] ![]() |
[Source Here] ![]() |
[Source Here] ![]() |
Reads in a obj and applies the cube marching algorithm over the meshes 3D SDF.
package main
import (
"github.com/EliCDavis/polyform/formats/obj"
"github.com/EliCDavis/polyform/modeling"
"github.com/EliCDavis/polyform/modeling/marching"
"github.com/EliCDavis/polyform/modeling/meshops"
"github.com/EliCDavis/vector"
)
func main() {
objScene := obj.Load("test-models/stanford-bunny.obj")
resolution := 10.
scale := 12.
transformedMesh := objScene.ToMesh().Transform(
meshops.CenterAttribute3DTransformer{},
meshops.ScaleAttribute3DTransformer{Amount: vector3.Fill(scale)},
)
canvas := marching.NewMarchingCanvas(resolution)
meshSDF := marching.Mesh(transformedMesh, .1, 10)
canvas.AddFieldParallel(meshSDF)
obj.SaveMesh("chunky-bunny.obj", canvas.MarchParallel(.3))
}Results in:
Polyform's visual editor is a React + TypeScript app in web/, built with Vite and served by the Go edit command. The Go binary embeds the built JS at compile time, so frontend changes require an npm build before go build picks them up.
- Go 1.21+ (to run the edit server and build the CLI)
- Node.js 18+ and npm (to build the web UI)
git clone https://github.com/EliCDavis/polyform.git
cd polyform
npm install
npm run build-dev # writes generator/edit/html/js/index.js
go run ./cmd/polyform editOpen http://localhost:8080 (or whatever port the server prints).
generator/edit/html/js/is gitignored. A fresh clone has no bundled JS until you runnpm run build-dev.
For UI work, run the Go API server and the Vite dev server together. Vite gives you hot module replacement (HMR); API calls are proxied to Go.
Terminal 1 — Go backend:
go run ./cmd/polyform edit
# or, with live reload for Go changes:
air editTerminal 2 — React frontend:
npm run watch-devThe Vite dev server proxies REST and WebSocket traffic to localhost:8080. If your Go server uses a different port, update the proxy targets in web/vite.config.ts.
If you are only changing Go code and not the web UI, you can skip Vite and use the last built bundle:
npm run build-dev # only when web/ changed
go run ./cmd/polyform edit| Script | Purpose |
|---|---|
npm run watch-dev |
Vite dev server with HMR (local UI development) |
npm run build-dev |
Production bundle → generator/edit/html/js/index.js |
npm run build-prod |
Same as build-dev with production mode |
npm run lint |
ESLint on web/src/ |
Air reloads the Go server when backend files change. Include frontend source extensions if you want Air to restart when you touch the web tree (you still need Vite for HMR):
# .air.toml
[build]
cmd = "npm run build-dev && go build -o ./tmp/main.exe ./cmd/polyform"
include_ext = ["go", "tpl", "tmpl", "html", "ts", "tsx"]Then:
air editFor active UI work, prefer npm run watch-dev + go run ./cmd/polyform edit instead of rebuilding the bundle on every save.
Some browser features (WebXR, shared memory) need HTTPS. mkcert is a simple way to get local TLS certs:
mkcert -install
mkcert -key-file key.pem -cert-file cert.pem localhost
air edit --port 8080 --ssl
# To reach a VR headset on your LAN:
air edit --port 8080 --ssl --host 0.0.0.0When using Vite alongside TLS Go, point the Vite proxy at your HTTPS origin or run without Vite and use the embedded bundle from polyform edit --ssl.
The same web bundle is embedded into a WASM build of the CLI. Build the JS first, then compile WASM:
npm run build-dev
go install ./cmd/polywasm
GOOS=js GOARCH=wasm go build -ldflags="-w -s" -o main.wasm ./cmd/polyform
polywasm build --wasm main.wasm
polywasm serve # serves the static wrapper (splash + service worker + main.wasm)Nix release builds run npm run build-dev automatically before go build (see flake.nix).
nix run .#polyform editThe Nix derivation builds the website npm package and copies generator/edit/html/* into the Go build before compiling.
If Polyform contributes to an academic publication, cite it as:
@misc{polyform,
title = {Polyform},
author = {Eli Davis},
note = {https://www.github.com/EliCDavis/polyform},
year = {2025}
}








