Geotoy - a Shadertoy-inspired web app for 3D geometry using a custom DSL

I wanted to share a project I’ve been working on for the past few months:

Geotoy

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

A screenshot of a Geotoy composition showing the editor UI and a rendering of the result.  There is a code editor at the bottom with some syntax-highligted code written in Geoscript, there is a floating window for configuring materials with various sliders and texture pickers, and there is a UI in the bottom right for viewing stats about the rendered meshes and sharing the composition.

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:

A 3D rendering of a mesh that looks somewhat like a bird bath.  It has a pure black background and is textured with a gray stone/cement-like material.

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.

A screenshot of a 3D mesh created using Geotoy.  It looks like a terraced floating island, composed out of ridges where the height of the surface of each ridge is flat.  It's rendered using a gray stone material and appears on a pure black background.

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.

A screenshot of a 3D mesh created using Geotoy.  It looks like a sphere with a couple of rings.  The mesh is rendered using a material showing off some of Three.JS's built-in functionality including normal maps, custom textures, iridescence, sheen, and PBR.

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!

6 Likes

Damn this is really good!

How quick is this to generate? I made the same shape for a project once, but did it with an overly complex gpgpu marching square approach.

Thanks!

How quick is this to generate?

That runs in ~750ms for me. ~500ms if you leave off the simplify call at the end, but that’s important because it reduces the face count from >300k to 55k. Both of those times include the work to compute normals and other shading-related stuff as well.

2 Likes

I’m not sure if you’re actually interested, but I randomly had an idea of how to get this effect even more efficiently yesterday.

Instead of displacing a sphere, I instead generate the ridged structure directly using the stitch_contours function. It takes a a 2D array of vertex positions representing contours of the mesh. It then creates a mesh by joining them together with triangle strips.

I’ve updated the composition’s code to use this method instead: https://3d.ameo.design/geotoy/edit/14

It gets a significant performance improvement, rendering the result in ~150ms on my machine. As a bonus, it also fixes those jagged edges that you can see on the previous screenshot. Here’s the new result:

A screenshot of a mesh rendered using Geotoy.  It looks like a ridged floating island, with flat terraces with flat tops and smooth, non-slanted outside edges.  It's made of a gray stone-like material, and appears on a pure black background.

I also added some variables at the top that you can tweak to adjust things like the noise frequency and amplitude.

3 Likes