Getting Started | C++

Release Notes

trueform v0.10.0 — one engine.

v0.10.1

Repairs; no API breaks. orient_faces_consistently and ensure_positive_orientation now return whether the surface is orientable: every reversal is decided against the input winding and applied after the walk, so one call settles every orientable manifold-edge component, and a non-orientable component is left untouched rather than half-repaired. An integral coordinate type votes by face count, its lattice not holding a squared area. euler_characteristic counts boundary edges correctly — a disk is 1, an uncapped tube 0. fit_similarity_alignment measures the source spread in the same space as its covariance, so a scaled frame no longer skews the recovered scale. The VTK wrappers delegate orientation to the core and surface its verdict.

v0.10.0 — one engine

The engine

Every boolean runs on the CSG engine. tf::make_boolean builds the same N-form arrangement that powers tf::make_csg_graph and evaluates the operation as a boolean expression over it. One arrangement answers any number of boolean expressions, domain reads, and curve extractions; there is one pipeline, one triangulator, one classification tier.

The arrangement is two tiers. The local arrangement is the identity tier: polygon intersections are classified exactly, and every crossing, landing, and coincidence names one created identity on an exact integer lattice — a point's identity is a canonical name, not a coordinate. Coplanar faces, within a mesh and across meshes, pool onto one geometric plane, so one plane is one identity and coincident walls cannot disagree. The plane arrangement is the triangulation tier: each plane carrier triangulates against its own constraint set, and a statement that cannot stand as given is resolved — split at its crossings, welded at its coincidences — in recovery waves that repeat until nothing new is stated. Classification — components, fences, radial fans, domains — reads the finished arrangement, and every product (boolean meshes, domain cells, the outer shell, intersection curves) is a read.

Coincidence is resolved at the root. When quantization collapses geometry — duplicate vertices, sub-epsilon edges, folded slivers, vertices landing on the intersection curve — the arrangement resolves each such coincidence to a single created identity, and the surrounding faces re-emit conformed to it. Raw arrangements are watertight by construction at these sites.

tf::triangulated is the same engine with the face as the carrier. Every face triangulates on its own boundary, a shared edge is one identity in both faces, and a self-crossing or folded face is resolved — split at its own crossings, its coincident vertices welded — never refused silently or dropped. A resolved face's positions are unchanged; only its names are.

Refusals are a surface, not a silence. tf::return_refused on tf::triangulated, tf::embedded_isocurves, and tf::make_isobands returns the input faces a build declined, and failed() on a built graph names the carriers still refusing after recovery. Nothing is dropped without a name, and the surface costs zero when nothing refuses.

Tolerance is a statement about the input

A tolerance is the pitch the input's planes are quantized to; it never widens a predicate. One converter is built over the union of the input forms; every face's plane — direction and offset — is rounded onto a grid of that pitch, so two faces whose planes differ by less than it share one plane. Every original vertex is then placed, at most the tolerance away, onto the quantized planes its own faces stand on — the meet of three where it is a corner, a line of two where it lies on a crease, its own tangent plane where the surface is smooth. The result is the exact arrangement of that moved mesh: every predicate below the placement runs exact, and a weld is an identity, never a proximity. A tolerance of zero is the identity — nothing moves, no placement table is built, and the result is byte-identical to the exact arrangement of the input as given.

A wall doubled at less than the pitch becomes one wall; one doubled at more stays two. A vertex a weld retires has no output point of its own — the index map reports it absent. Plane identity is quantized-name equality, so results from calls with different converter domains are not identity-comparable; a session that needs one perturbation across calls pins one converter.

Provenance selection

One arrangement, any read. tf::csg::selection names which forms' faces reach the output, orthogonally to the boolean algebra:

auto graph = tf::make_csg_graph(forms);              // A=0, B=1, C=2
auto e = (tf::csg::op(0) | tf::csg::op(1)) - tf::csg::op(2);

auto solid = tf::make_csg_mesh(graph, e);                          // as always
auto partA = tf::make_csg_mesh(graph, tf::csg::selection(0, e));   // A's walls of it
auto inA   = tf::make_csg_mesh(graph, tf::csg::inside(2, tf::csg::op(0)));  // C inside A
auto embed = tf::make_csg_mesh(graph, tf::csg::selection({0}));    // A cut by all, no boolean

The per-form parts partition the result face-for-face and re-weld into the solid. tf::csg::inside reads the other side of the same question — the named forms' surface lying INSIDE the expression's region, both sides of a piece in it, kept in the form's stored winding, which is how a sheet's surface within a solid is asked for. tf::make_csg_domains takes the same selection — each cell's walls by contributor — in its boundary kind only. Every existing expression call is unchanged.

The module map (breaking)

The cut module was named for the verb its first consumer needed. Its ground is split by mechanism, and there is no compatibility shim: the old names are gone, not deprecated.

  • <trueform/arrangement.hpp> — mesh, polygon, and segment arrangements: the arrangement of a set of forms and its reads.
  • <trueform/iso.hpp> — the scalar-field tier: embedded_isocurves, make_isobands, make_isocontours, scalar_field_intersections.
  • <trueform/csg.hpp> — booleans, the CSG graph, expressions, selections, domains, and the outer shell.
OldNew
<trueform/cut.hpp><trueform/arrangement.hpp>
<trueform/cut/...><trueform/arrangement/...>
tf::cut::tf::arrangement::
<trueform/intersect/make_isocurves.hpp><trueform/iso.hpp>
<trueform/intersect/scalar_field_intersections.hpp><trueform/iso.hpp>
tf::cut::iso::tf::iso::

tf::make_isocontours, tf::make_isobands, tf::embedded_isocurves and tf::scalar_field_intersections keep their names; only their header and the machinery namespace below them changed. See Iso.

tf::make_boolean keeps its name but not its umbrella. A boolean is the two-operand case of the CSG arrangement, so the entry lives in <trueform/csg.hpp> — not <trueform/arrangement.hpp>. See CSG.

Removed entry points

RemovedUse instead
tf::embedded_intersection_curvestf::make_mesh_arrangements
tf::embedded_self_intersection_curvestf::make_polygon_arrangements
tf::intersections_between_polygonstf::polygon_intersections
tf::intersections_within_polygonstf::polygon_intersections
tf::triangulated_facestf::triangulated

tf::triangulated_faces returned corners alone, against a point table it did not return. That was sound only while a triangulation could name no point the input did not carry, and it no longer can: a face whose loop crosses itself is resolved, and the identity its crossing stands on is a corner the caller's own array has no row for. tf::triangulated returns the corners with the table they index. Code that needs the connectivity without the mesh reads the arrangement mesh tier (<trueform/arrangement/mesh/mesh_triangulation.hpp>) directly — it states the triangles, the minted identities and the refusal surface as one product.

tf::polygon_intersections is not exported by <trueform/intersect.hpp>; include <trueform/intersect/polygon_intersections.hpp> directly. Arity plus tf::intersect_config decide between- and within-form records, so the two alias types have no successor names.

Retired triangulators

The constrained Delaunay family is the library's one triangulation engine.

RemovedUse instead
tf::ear_cuttertf::constrained_delaunay_triangulator, or tf::triangulated for a whole mesh
tf::delaunay_triangulatortf::unconstrained_delaunay_triangulator, or tf::make_cdt(points)
tf::delaunay_flipper— no replacement; it existed only for the batch triangulator

tf::constrained_delaunay_triangulator::build and its siblings take a trailing tf::cdt_region_mode that states what region_labels() mean.

The exact lattice

The lattice integer is read off the coordinate type or named by the caller — it is never a hardcoded default. Every entry point (tf::make_cdt, the arrangements, the booleans, the iso cut) resolves it for you; direct class instantiations are affected:

  • tf::constrained_delaunay_triangulator and tf::unconstrained_delaunay_triangulator default their Int from the coordinate type (float → int32, double → int64). A direct double-precision instantiation that leaned on the old fixed-int32 default now computes on the int64 lattice.
  • The planar-graph classes — tf::planar_graph_regions, tf::face_hole_relations, tf::face_split_by_edges, tf::hole_patcher, tf::planar_embedding — carry no coordinate type, so Int has no default: tf::planar_embedding<int> does not compile; write tf::planar_embedding<int, tf::exact::int32> (or the lattice your coordinates call for).

API changes

  • make_boolean(a, b, op[, sheets][, config]) — operands listed in sheets bound no volume and cut as oriented separators. An open operand is a volume unless declared a sheet; declare it a sheet for the previous open-mesh behavior.
  • Booleans take tf::arrangement_config (an intersect_config, a triangulation_type, or both); boolean_config does not exist. Multi-nesting is always on.
  • make_boolean(..., tf::return_index_map) returns tf::stitch_index_map — a producer-agnostic carrier consumed by tf::stitch_*.
  • A swapped difference does not reverse winding.
  • intersect_config::tolerance is the distance an input vertex may move to reach the lattice — a statement about the input, exact below it. A band on the predicates' comparisons does not exist.
  • tf::intersections_within_segments::build(segments) takes no intersect_config: the segment tier is exact, like every tier.
  • make_intersection_curves defaults to primitives.
  • triangulated resolves rather than refuses: a self-crossing face is split at its crossings, coincident vertices weld into one identity at the same coordinate — a resolved face need not name every vertex it was given; positions are unchanged, only names. Region fill is labels != 0.
  • fit_similarity_alignment recovers the scale independent of the point count; it was n times too small (#21).

Bindings

The Python and TypeScript layers follow the same map in the same release, so all three languages speak the same module names. See the Python release notes and the TypeScript release notes.