Iso
The Iso module is the scalar field pipeline. It is driven by field crossings rather than by polygon intersections, so it is a tier of its own: the crossings a field states on a mesh, the curves they trace, and the mesh recut so those curves are edges.
Include the module with:
#include <trueform/iso.hpp>
Isocontours
Extract curves where a scalar field crosses threshold values.
Single Isocontour
std::vector<float> scalar_field(mesh.points().size());
// Assign scalar values to vertices...
float threshold = 0.5f;
auto contour = tf::make_isocontours(mesh.polygons(), tf::make_range(scalar_field), threshold);
Multiple Isocontours
std::vector<float> levels = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
auto contours = tf::make_isocontours(mesh.polygons(), tf::make_range(scalar_field), tf::make_range(levels));
Embedded Isocurves
Isocurves are level sets of a scalar field. Embedding them creates a mesh where contour lines become edges:
tf::buffer<float> scalar_field;
scalar_field.allocate(mesh.points().size());
// Assign scalar values to vertices...
std::array<float, 3> cut_values = {0.0f, 0.5f, 1.0f};
auto [result, labels, face_labels] = tf::embedded_isocurves(
mesh.polygons(),
tf::make_range(scalar_field),
tf::make_range(cut_values));
// With curves
auto [result, labels, face_labels, curves] = tf::embedded_isocurves(
mesh.polygons(),
tf::make_range(scalar_field),
tf::make_range(cut_values),
tf::return_curves);
The labels buffer contains one integer per face, indicating which isoband the face belongs to. Labels 0, 1, 2, ... correspond to regions (-∞, cut_values[0]), [cut_values[0], cut_values[1]), etc.
Isobands
While embedded_isocurves creates a mesh with all regions, make_isobands extracts only selected bands:
std::array<float, 4> cut_values = {0.0f, 0.25f, 0.5f, 0.75f};
std::array<int, 2> selected_bands = {1, 3};
auto [result, labels, face_labels] = tf::make_isobands(
mesh.polygons(),
tf::make_range(scalar_field),
tf::make_range(cut_values),
tf::make_range(selected_bands));
Refused Faces
A cut face the triangulation declines holds no piece in any band, so its surface is simply absent from the result — emptiness is always the answer. Both emitters take tf::return_refused to also receive, ascending, the ids of those faces:
auto [result, labels, face_labels, refused] = tf::embedded_isocurves(
mesh.polygons(),
tf::make_range(scalar_field),
tf::make_range(cut_values),
tf::return_refused);
auto [bands, band_labels, band_face_labels, band_refused] = tf::make_isobands(
mesh.polygons(),
tf::make_range(scalar_field),
tf::make_range(cut_values),
tf::make_range(selected_bands),
tf::return_refused);
| Parameter | Type | Description |
|---|---|---|
tf::return_refused | tf::return_refused_t | Tag requesting the refused ids |
The mesh, labels and face labels are exactly the ones the untagged call returns, and the ids are face ids of the input mesh — an isoband call reports the whole mesh's, since a face the cut declined holds no piece in any band. The one-chord split cannot refuse and a constraint set the field states on a face it crosses is recoverable, so the list speaks only on degenerate input.
Scalar Field Intersections
Low-level access to isosurface intersection data.
tf::scalar_field_intersections stores its points in the input's own floating-point coordinates.tf::scalar_field_intersections<int, float, 3> sfi;
sfi.build(mesh.polygons(), scalar_field, threshold);
auto points = sfi.intersection_points();
for (auto group : sfi.intersections()) {
for (auto intersection : group) {
intersection.object; // Face ID
intersection.id; // Point ID
intersection.target.label; // vertex/edge where crossing occurs
intersection.target.id; // Local index
}
}
// Multiple thresholds
sfi.build_many(mesh.polygons(), scalar_field, thresholds);
