Basic Iterated Booleans

Subtract a moving tool from a workpiece over many steps using the C++17 API.

This example demonstrates iterated CSG: we repeatedly subtract a small sphere (“tool”) from a cube (“workpiece”) along a circular tool path. It showcases:

  • The C++17 helper style (Context::execute with a lambda) for concise command-buffer setup
  • Using Operation::input / Operation::output to pass persistent results between iterations
  • Predictable, output-sensitive performance for iterated Booleans
Note:
This C++ example uses ExampleFramework.hh for compact POD types and tiny utilities.

Result

Series of spheres subtracted from the top of a cube

CMake setup

To use the C++ 17 API from CMake, add the Solidean C++ 17 language target and link it to your project:

add_subdirectory(path/to/solidean/lang/cpp17)              # Add the Solidean C++ 17 API to your project
target_link_libraries(YourProject PRIVATE Solidean::Cpp17) # Link against the Solidean C++ 17 API

Code

#include <chrono>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>

#include <solidean.hh>

#include "ExampleFramework.hh"

int main()
{
    auto ctx = solidean::Context::create();

    // uniform cube of 20 units side length, centered at the origin
    auto arithmetic = ctx->createExactArithmetic(10.f);

    // Define a cube-shaped workpiece of sidelength 1.0
    auto const workpieceTriangles = example::createCube();

    // Define an icosphere with radius 0.15
    auto const icoTriangles = example::createIcoSphere(3, {0.0f, 0.0f, 0.0f}, 0.15f);

    // Compute a simple circular tool path
    std::vector<example::pos3> toolPath;
    auto const steps = 50;
    for (auto i = 0; i < steps; ++i)
    {
        constexpr auto pi = float(3.14159265358979323846);
        auto const x = 0.35 * std::cos(double(i) / steps * 2.0 * pi);
        auto const z = 0.35 * std::sin(double(i) / steps * 2.0 * pi);
        toolPath.push_back({float(x), 0.5f, float(z)});
    }

    // Print some information on the inputs
    std::cout << "Number of triangles in workpiece: " << workpieceTriangles.size() << std::endl;
    std::cout << "Number of triangles in sphere: " << icoTriangles.size() << std::endl;

    // Create the workpiece - the triangle data is reinterpreted as a solidean triangle type
    auto workpiece = ctx->createMeshFromTrianglesF32(solidean::as_triangle3_span(workpieceTriangles), *arithmetic);

    std::cout << "Iteratively subtracting sphere from workpiece in circular motion in " << steps << " iterations" << std::endl;

    // Measure time of actual subtraction task
    auto const start = std::chrono::high_resolution_clock::now();

    // Iterate over the tool path
    for (auto const& p : toolPath)
    {
        // Create "tool" triangles at current tool path position
        std::vector<example::triangle> stepTriangles = icoTriangles;
        for (auto& t : stepTriangles)
        {
            t.p0 += p;
            t.p1 += p;
            t.p2 += p;
        }

        // Perform one subtraction iteration (workpiece - tool)
        workpiece = ctx->execute( //
            *arithmetic,
            [&](solidean::Operation& op)
            {
                auto meshA = op.input(*workpiece);
                auto meshB = op.importFromTrianglesF32(solidean::as_triangle3_span(stepTriangles), solidean::MeshType::Supersolid);
                return op.output(op.difference(meshA, meshB));
            },
            solidean::ExecuteMode::Multithreaded);

        std::cout << "." << std::flush;
    }
    std::cout << std::endl;

    auto const end = std::chrono::high_resolution_clock::now();
    auto const duration = std::chrono::duration<double>(end - start).count();

    std::cout << "Processed " << steps << " iterations in " << duration * 1000.0 << "ms (~" << duration * 1000.0 / steps << "ms per iteration)" << std::endl;

    // Export the final solidean data to floating point triangles for potential further evaluation or display
    auto blob = ctx->execute(*arithmetic,
                             [&](solidean::Operation& op)
                             {
                                 auto m = op.input(*workpiece);
                                 return op.exportToTrianglesF32(m);
                             });

    // The data blob contains (immutable) unrolled triangle data
    auto const triangleSpan = blob->getTrianglesF32<example::triangle>();

    // Copy the triangle data to a vector, e.g. for further processing
    auto const triangles = std::vector<example::triangle>(triangleSpan.begin(), triangleSpan.end());

    std::cout << "Total number of triangles in processed workpiece: " << triangles.size() << std::endl;

    return EXIT_SUCCESS;
}

Notes

  • High-level flow:

  • C++17 helper vs. abstract API:

    • The C++17 API uses Context::execute with a lambda that returns the desired output handle (e.g., op.output(...) or an export).
    • The abstract API exposes the same steps but typically requires explicit sequencing; the C++17 helper wraps the record-then-execute pattern.
  • Passing results across iterations:

    • Operation::output materializes the result as a persistent Mesh; the next iteration re-enters it via Operation::input.
    • This is the correct way to chain operations; using a MeshOperand across different operations is invalid by design.
  • Geometry guarantees:

    • The tool mesh is imported as MeshType::Supersolid, since the a generic tool mesh could overlap itself (it does not in this example though, so the default MeshType::Solid would have been fine as well).
    • The exactness model is unchanged: float inputs are storage only and are converted to internal exact arithmetic upon import.
  • Execution mode & performance:

    • The example uses ExecuteMode::Multithreaded (default) for throughput.
    • Iterated Booleans are engineered for output-sensitive behavior; performance scales with what changes, not with full recomputation each step.
  • Export:

  • Timing:

    • The example times only the iterative subtraction loop to highlight Boolean performance in isolation.