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:
(libfive shapes)
: Basic 2D and 3D shapes(libfive transforms)
: Geometric transforms (e.g. rotate / scale / twist)(libfive csg)
: Constructive solid geometry (e.g. union / intersection / difference)(libfive text)
: A distance-field based font and text layout tools
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).