This example demonstrates advanced file I/O combined with batched Booleans:
- Read two meshes (
icosahedron.obj,sphere.obj) - Generate many contact placements (sphere at each triangle centroid)
- Try multiple sphere scales and both union/difference
- Iterate: import translated sphere → Boolean with current result → materialize
- Export indexed triangles (with shared vertices) per run
It showcases:
- The C++17 helper style (
Context::executewith a lambda) - Repeated
Operation::input/Operation::outputto chain state - Switching Boolean type at runtime
- Export via
Operation::exportToIndexedTrianglesF32
ExampleFramework.hh for compact POD types and utilities (readFromOBJ, writeToOBJ, small math helpers).
Result

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 <filesystem>
#include <iostream>
#include <vector>
#include <solidean.hh>
#include "ExampleFramework.hh"
int main()
{
auto const basePath = std::filesystem::path(__FILE__).parent_path();
// Read two input meshes
auto const icosahedron = example::readFromOBJ(basePath / "icosahedron.obj");
auto const sphere = example::readFromOBJ(basePath / "sphere.obj");
// Print some information on the inputs
std::cout << "Number of triangles in icosahedron: " << icosahedron.size() << std::endl;
std::cout << "Number of triangles in sphere: " << sphere.size() << std::endl;
// Define some positions where we want the sphere mesh to interact with the icosahedron mesh
std::vector<example::pos3> sphereContactPositions;
for (auto const& tri : icosahedron)
{
auto const triCenter = (tri.p0 + tri.p1 + tri.p2) / 3.0;
sphereContactPositions.push_back(triCenter);
}
// compute the maximum absolute coordinates to define the exact arithmetic
auto const getMaxCoord = [](std::vector<example::triangle> const& triangles)
{
float maxCoord = std::abs(triangles.front().p0.x);
for (auto const& t : triangles)
{
auto const max0 = std::max({std::abs(t.p0.x), std::abs(t.p0.y), std::abs(t.p0.z)});
auto const max1 = std::max({std::abs(t.p1.x), std::abs(t.p1.y), std::abs(t.p1.z)});
auto const max2 = std::max({std::abs(t.p2.x), std::abs(t.p2.y), std::abs(t.p2.z)});
maxCoord = std::max({maxCoord, max0, max1, max2});
}
return maxCoord;
};
auto const maxCoordIcosahedron = getMaxCoord(icosahedron);
auto const maxCoordSphere = getMaxCoord(sphere);
auto ctx = solidean::Context::create();
for (auto sphereScaling : {0.25f, 0.4f, 0.5f})
{
auto scaledSphere = sphere;
for (auto& tri : scaledSphere)
{
tri.p0 *= sphereScaling;
tri.p1 *= sphereScaling;
tri.p2 *= sphereScaling;
}
// As long as the two meshes (after translating, see below) at least touch each other, this coord will not be exceeded
auto const maxCoord = maxCoordIcosahedron + getMaxCoord(scaledSphere);
auto arithmetic = ctx->createExactArithmetic(maxCoord);
for (auto isUnion : {true, false})
{
std::cout << std::endl;
std::cout << "Computing " << (isUnion ? "union" : "difference") << " with spheres with radius " << sphereScaling << std::flush;
// Create the actual solidean mesh from the icosahedron triangles,
// the triangle data is reinterpreted as a solidean triangle type
auto icomesh = ctx->createMeshFromTrianglesF32(solidean::as_triangle3_span(icosahedron), *arithmetic);
// Measure time of actual solidean task
auto const start = std::chrono::high_resolution_clock::now();
// Iterate over the contact positions
for (auto const& p : sphereContactPositions)
{
// Create "tool" triangles at current contact position
auto toolTriangles = scaledSphere;
for (auto& t : toolTriangles)
{
t.p0 += p;
t.p1 += p;
t.p2 += p;
}
// Perform one union or subtraction iteration (icosahedron +/- sphere)
icomesh = ctx->execute( //
*arithmetic,
[&](solidean::Operation& op)
{
auto meshA = op.input(*icomesh);
auto meshB = op.importFromTrianglesF32(solidean::as_triangle3_span(toolTriangles));
if (isUnion)
return op.output(op.union_(meshA, meshB));
return op.output(op.difference(meshA, meshB));
});
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 " << sphereContactPositions.size() << " iterations in " << duration * 1000.0 << "ms (~"
<< duration * 1000.0 / sphereContactPositions.size() << "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(*icomesh);
return op.exportToIndexedTrianglesF32(m);
});
// The data blob contains (immutable) indexed triangle data (i.e. topology exists)
auto const triangleIndexSpan = blob->getTrianglesIndexed<example::tri_idx>();
auto const vertexPositionSpan = blob->getPositionsF32<example::pos3>();
// Copy the triangle data to a vector, e.g. for further processing
auto const indices = std::vector<example::tri_idx>(triangleIndexSpan.begin(), triangleIndexSpan.end());
auto const positions = std::vector<example::pos3>(vertexPositionSpan.begin(), vertexPositionSpan.end());
std::cout << "Total number of triangles in result mesh: " << indices.size() << std::endl;
auto const getScalingString = [](float scaling)
{
std::ostringstream out;
out << std::fixed << std::setprecision(2) << scaling;
return out.str();
};
auto const fileName = (isUnion ? "result_union" : "result_difference") + getScalingString(sphereScaling) + ".obj";
// Write the result to an obj file
example::writeToOBJ(positions, indices, basePath / fileName);
}
}
return EXIT_SUCCESS;
}
Notes
-
Exact arithmetic sizing:
For each sphere scale, the example computes a safe bound as
maxCoord = maxCoordIcosahedron + getMaxCoord(scaledSphere)and calls
Context::createExactArithmetic.
This keeps the bound tight for precision while accommodating translations. -
Placement generation:
Sphere placements come from triangle centroids of the icosahedron, producing a dense set of contact points. -
Iterative command buffer:
Each iteration records and executes:
Operation::input→ import translated sphere →Operation::unionorOperation::difference→Operation::output.
The resulting persistentMeshbecomes the input to the next iteration. -
Indexed export for topology:
UsingexportToIndexedTrianglesF32deduplicates vertices and resolves T-junctions.
Better for storage, connectivity, and downstream processing. -
Geometry guarantees:
If source meshes might self-intersect or overlap, import withMeshType::Supersolid, or run a preprocessing
Operation::selfUnion/Operation::healpass first. -
Output management:
The example writes one OBJ per(scale, op)pair (e.g.result_union0.25.obj,result_difference0.40.obj).