Tracking IDs and Transporting Attributes

Track which input mesh contributed to each output triangle using WithID imports and ID-preserving export.

The goal of this tutorial is to preserve and inspect provenance through a Boolean:
We will import three meshes with tracking IDs (A=0, B=1, C=2), compute \((A \cup B) \setminus C\) in a single operation, export triangles with IDs, then split the result by source ID and compute area per source.

You’ll use: Context, ExactArithmetic, Operation::importFromTrianglesF32WithID, Operation::union, Operation::difference, Operation::exportToTrianglesF32WithID, TypedBlob, and DataSlot::PrimitiveIDs.

Note:
All C++ tutorials/examples use ExampleFramework.hh for simple POD types and helpers.

1) Project setup (CMake)

Add the C++17 binding and link it:

add_subdirectory(path/to/solidean/lang/cpp17)
target_link_libraries(YourProject PRIVATE Solidean::Cpp17)

Ensure the Solidean dynamic library is discoverable at runtime.

2) Program: (A ∪ B) \ C with tracking IDs

We create two overlapping cubes A and B, and an overlapping icosphere C.
We import each with a distinct surface ID (0, 1, 2), run a single operation, export with IDs, and post-process by ID.

#include <iostream>
#include <vector>
#include <tuple>

#include <solidean.hh>
#include "ExampleFramework.hh" // pos3, triangle, createCube, createIcoSphere, computeAreaAndVolume

int main() {
    // 1) Context + exact arithmetic
    auto ctx        = solidean::Context::create();
    auto arithmetic = ctx->createExactArithmetic(10.0f);

    // 2) Define three input meshes (triangle soups)
    //    A and B: overlapping cubes; C: icosphere overlapping both
    auto trisA = example::createCube({-0.25f, 0.0f, 0.0f}, 0.5f);
    auto trisB = example::createCube({+0.25f, 0.0f, 0.0f}, 0.5f);
    auto trisC = example::createIcoSphere(/*subdiv*/3, /*center*/{0.0f, 0.0f, 0.0f}, /*radius*/0.35f);

    // 3) Record a single operation: (A ∪ B) \ C
    auto blob = ctx->execute(*arithmetic, [&](solidean::Operation& op) {
        // Import with tracking IDs (A=0, B=1, C=2)
        auto A = op.importFromTrianglesF32WithID(solidean::as_triangle3_span(trisA), /*surfaceID*/0);
        auto B = op.importFromTrianglesF32WithID(solidean::as_triangle3_span(trisB), /*surfaceID*/1);
        auto C = op.importFromTrianglesF32WithID(solidean::as_triangle3_span(trisC), /*surfaceID*/2);

        auto U  = op.union_(A, B);
        auto R  = op.difference(U, C);

        // Export with IDs so each output triangle carries provenance
        // This works through arbitrarily many Booleans
        return op.exportToTrianglesF32WithID(R);
    });

    // 4) Split output triangles by source surface ID
    auto triSpan = blob->getTrianglesF32<example::triangle>();
    auto ids     = blob->getPrimitiveIDs();

    std::vector<example::triangle> fromA, fromB, fromC;

    for (size_t i = 0; i < triSpan.size(); ++i) {
        const auto id = ids[i];
        // ID layout: [flags | 30-bit surface_id | 32-bit prim_id]
        // The C++ API provides a convenience wrapper to access these
        const uint32_t surf = id.surface_id();
        switch (surf) {
            case 0: fromA.push_back(triSpan[i]); break;
            case 1: fromB.push_back(triSpan[i]); break;
            case 2: fromC.push_back(triSpan[i]); break;
            // everything is tracked, so the default case is never hit
        }
    }

    // 5) Compute area per source
    auto [areaA,   volA  ] = example::computeAreaAndVolume(fromA);
    auto [areaB,   volB  ] = example::computeAreaAndVolume(fromB);
    auto [areaC,   volC  ] = example::computeAreaAndVolume(fromC);
    auto [areaAll, volAll] = example::computeAreaAndVolume(std::vector<example::triangle>(triSpan.begin(), triSpan.end()));

    std::cout << "Output triangles total: " << triSpan.size() << "\n";
    std::cout << "Area by provenance:\n";
    std::cout << "  from A (ID=0): " << areaA << "\n";
    std::cout << "  from B (ID=1): " << areaB << "\n";
    std::cout << "  from C (ID=2): " << areaC << "\n";
    std::cout << "  total:         " << areaAll << "\n";

    return EXIT_SUCCESS;
}

3) What you learned

4) Variations to try

  • Export indexed triangles with IDs using the corresponding indexed export variant to preserve connectivity.
  • Use separate surfaces in one mesh and set SurfaceBuilder::TrackID to customize ID ranges.
  • Visualize provenance by coloring triangles by ID in your renderer.

Next steps