This page is meant to give a quick tour of the different API layers; there are more examples of Scheme-based designs in the Github repository.

Scheme (high-level)

The (libfive) module for Guile Scheme has a few sets of high-level functions:

All of these modules (and their documentation!) are imported into the Studio editor by default, for a seamless experience.

This CSG solid is defined by the following code:

(difference (sphere 1 [0 0 0])
  (sphere 0.6 [0 0 0])
  (cylinder-z 0.6 2 [0 0 -1])
  (reflect-xz (cylinder-z 0.6 2 [0 0 -1]))
  (reflect-yz (cylinder-z 0.6 2 [0 0 -1])))

The [x y z] notation is custom syntax for 3D vectors (in this case, the sphere center and cylinder base position).

Scheme (low-level)

The (libfive kernel) module includes a set of functions and macros to directly manipulate functional representations of solids. Here's a low-level definition of a cube:

(define-shape (cube x y z)
  (max (- x 1) (- -1 x)
       (- y 1) (- -1 y)
       (- z 1) (- -1 z)))

Functional representations excel at coordinate transforms. Here's a transform that applies a twist to the original cube:

(remap-shape (cube x y z)
  (+ (* (cos z) x) (* (sin z) y))
  (- (* (cos z) y) (* (sin z) x))
  z)

Certain expressions are easier to represent as operations on vectors. Here's a function that generates a sphere given a center and radius, using norm to find the length of a 3D vector:

(define (sphere r center)
  (lambda-shape (x y z)
    (- (norm (- center [x y z])) r)))

It's also possible to build custom CSG functions from scratch:

(define (blend a b m)
  (min a b (+ (sqrt (abs a))
              (sqrt (abs b))
              (- m)) ))

(blend
  (blend (sphere 1 [0 -1 -1])
         (sphere 1 [0  1 -1]) 0.75)
  (blend (sphere 1 [0 -1  1])
         (sphere 1 [0  1  1]) 0.75)
         0.75)

C

The C bindings are not intended for use directly; they're meant as a target for higher-level languages with a C FFI (e.g. most of them).

Still, here's an example showing a circle being constructed and saved as an SVG:

// All of these trees are manually managed and must be freed
// In higher-level languages, freeing can be attached to the object's
// destructor and run during garbage collection.
libfive_tree x = libfive_tree_x();
libfive_tree y = libfive_tree_y();

libfive_tree x2 = libfive_tree_unary(libfive_opcode_enum("square"), x);
libfive_tree y2 = libfive_tree_unary(libfive_opcode_enum("square"), y);
libfive_tree s = libfive_tree_binary(libfive_opcode_enum("add"), x2, y2);

libfive_tree one = libfive_tree_const(1);
libfive_tree out = libfive_tree_binary(libfive_opcode_enum("sub"), s, one);

// Select a 2D region to export, then write an SVG
libfive_region2 R;
R.X.lower = -2;
R.X.upper =  2;
R.Y.lower = -2;
R.Y.upper =  2;
libfive_tree_save_slice(out, libfive_region2 R, 0, 10, "circle.svg");

// Finally, clean up all of the intermediate trees
libfive_tree_free(x);
libfive_tree_free(y);
libfive_tree_free(x2);
libfive_tree_free(y2);
libfive_tree_free(s);
libfive_tree_free(one);
libfive_tree_free(out);

C++

Here at the bottom of the stack, the full power of libfive is unleashed!

There are many functions that haven't been wrapped as C or Scheme, either because they're too low-level or because no one has needed them yet; here at the C++ level, everything is on the table.

If you're building libfive into a larger C++-based application, you'll be spending a lot of time here. For example, Studio uses the C++ API extensively.

Here's a sphere; compare against the circle example above:

// Unlike the C bindings, the C++ interface manages memory automatically
// through flyweight handles and RAII.  These objects will be freed
// when they go out of scope
auto x = Kernel::Tree::X();
auto y = Kernel::Tree::Y();
auto z = Kernel::Tree::Z();

// Arithemetic is overloaded for the Kernel::Tree type
auto out = (x * x) + (y * y) + (z * z) - 1;

// Pick the target region to render
auto bounds = Region<3>({-2, -2, -2}, {2, 2, 2});

// Mesh::render returns a unique_ptr, so it cleans up automatically
Mesh::render(out, bounds)->saveSTL("sphere.stl");

The C++ API uses C++11 features to automatically manage memory, and borrows a few ideas from Rust to enforce ownership semantics (e.g. returning unique_ptr rather than bare pointers).