I wanted to share a project I’ve been working on for the past few months:
Geotoy is a web app in the same vein as Shadertoy that allows you to create 3D meshes where the geometry is completely defined by code. By combining simple mesh primitives with boolean operations and other builtin manipulations, complex and detailed scenes can be built.
Naturally, all of the materials and rendering are handled with Three.JS
Background
I was first inspired by build Geotoy when I discovered the manifold
library. This library solves the very difficult problem of performing robust mesh boolean operations while preserving manifold-ness in its outputs. This is an important property for many computational geometry algorithms, and it means that these boolean operations can be applied repeatedly and composed indefinitely.
Anyway, when I first started to test out that library, it quickly became tedious trying to string together complex chains of boolean operations in JS. Rather than having to manually convert meshes between formats and pack/unpack vertex/index data, I wanted to find a way to easily test out ideas.
At some point, I thought that having a custom little programming language just for mesh operations would be a cool thing to try. My initial experiments were successful and I kept experimenting and adding more until Geoscript was born.
Geoscript
Geotoy is a small, purpose-built programming language I created for generating and manipulating 3D meshes.
Its design is somewhat inspired by functional programming languages, but its syntax is purposely kept simple and conventional. My goal is for it to be easy to learn and familiar to JavaScript/Python/Rust devs.
Here’s a little example of Geoscript syntax for a program that generates a mesh that looks kind of like a bird bath:
// start out with a basic cylinder shape
cylinder(radius=4, height=10, radial_segments=20, height_segments=5)
// warp the mesh to bend it inward in the middle, forming a thin
// stem and flared base and top
-> |v| {
dist = abs(v.y)
displ = 0.23 + pow(dist * 0.2 + 0.1, 3.)
v * v3(displ, 1, displ)
}
// carve out the bowl on top by subtracting a deformed sphere
| sub(b=icosphere(radius=5.7, resolution=2) + vec3(0, 10.3, 0) | scale(vec3(1, 1.4, 1)))
// trim the edges a bit
| intersect(cylinder(radius=5.3, height=20, radial_segments=20, height_segments=20))
// shrink the base a bit
-> |v| {
y = max(v.y, -3.8)
shrink = if y < 0 { 1 + y * 0.08 } else { 1 }
v * v3(shrink, 1, shrink)
}
// render the output to the scene
| render
And this is what the result looks like:
My goal for Geoscript is to allow simple building blocks to be combined together to build interesting results. It has a strong focus on function pipelining (with dedicated syntax inspired by Bash/F#)
I built a little learnxinyminutes
-style quickstart guide for Geoscript here: https://3d.ameo.design/geoscript/docs/quickstart
In addition to mesh boolean operations, I’ve added a variety of other functionality to Geotoy based on things I was interested in trying out.
There are builtin functions for:
- sampling random points on the surface of meshes
- tracing geodesic paths, extruding 2D meshes into 3D
- building meshes from contours
- sampling noise and randomness
- subdividing and deforming meshes
- and a lot more.
Really, there’s a lot to check out.
Geotoy
Geotoy is a web app I built to create and share scenes built with Geoscript. As its name suggests, it’s very much inspired by Shadertoy and has the same spirit of sharing and collaboration.
Geotoy’s primary goal is to provide a convenient place to experiment with Geoscript in a repl-like environment where the results of your code are immediately visible. I’ve created several example compositions as well that you can browse to get a feel for all of what Geoscript can do.
I also built a material editor that allows for multiple different materials to be used in a scene, custom textures to be uploaded, and many of the properties from Three.JS’s MeshPhysicalMaterial
to be edited. This unlocks a lot of possibilities and allows for fully-fledged procedural mesh generation workflow.
Details
The whole project is 100% free and open source: https://github.com/ameobea/sketches-3d
On the frontend side, all of the rendering is done using Three.JS with some added shader customizations and post-processing. I’m using the postprocessing
library for anti-aliasing as well as the excellent n8ao
. This effect really adds a lot - especially for scenes with simpler materials.
I’m using a custom addition to Three.JS’s MeshPhysicalMaterial
to add triplanar mapping - which allows for meshes to be textured without having any defined UV mapping. This is crucial for Geotoy since all the meshes are procedurally generated.
The frontend is built with Sveltekit. There is a small Rust backend for the login/sharing and a little standalone service for rendering thumbnails using Puppeteer.
Conclusion
My goal for creating this was to explore what was possible and build cool s**t in the browser. I’ve been having a lot of fun just trying stuff out with it myself, and I plan on using some of the meshes and scenes I created in my browser-based parkour game.
I’d love to hear what people think of the project. I’ve put pretty decent effort into setting up docs and tutorials for the language and tool, but I’m probably missing pieces given that it’s a whole programming langauge.
I’d be very eager to answer any questions people have or hear any other feedback!