The Reindex module provides efficient tools for extracting, filtering, and reorganizing geometric data while maintaining referential integrity. It operates on trueform's range-based primitives and supports both simple data extraction and complex multi-level reindexing with automatic index map generation.
Include the module with:
#include <trueform/reidx.hpp>
Reindexing in trueform revolves around two key concepts:
The module provides both immediate reindexing operations and optional index map returns for downstream processing.
For indexed data (polygons, segments), the index type is automatically deduced from the input:
// Index type deduced from polygons.faces()[0][0]
auto filtered = tf::reindexed_by_mask(polygons, mask);
// Index type deduced from segments.edges()[0][0]
auto filtered = tf::reindexed_by_ids(segments, ids);
// Explicit index type when needed (e.g., large meshes)
auto filtered = tf::reindexed_by_mask<int64_t>(polygons, mask);
For points, vectors, unit vectors, and generic ranges (which have no indices to deduce from), the index type defaults to int:
// Index defaults to int
auto filtered_points = tf::reindexed_by_mask(points, mask);
auto filtered_vectors = tf::reindexed_by_ids(vectors, ids);
// Explicit index for large data
auto filtered_points = tf::reindexed_by_mask<int64_t>(points, mask);
Reindex point and vector collections using index maps:
// Basic point reindexing with an existing index map
tf::index_map_buffer<int> point_map;
auto reindexed_points = tf::reindexed(points, point_map);
// Reindex vectors similarly
auto reindexed_vectors = tf::reindexed(vectors, point_map);
auto reindexed_unit_vectors = tf::reindexed(unit_vectors, point_map);
// Reindex into pre-allocated buffers
tf::points_buffer<float, 3> point_buffer;
tf::reindexed(points, point_map, point_buffer);
// Reindex generic ranges
auto reindexed_attributes = tf::reindexed(tf::make_range(attribute_range), point_map);
For primitives that reference other data (like polygons and segments), reindexing requires both element and point index maps:
// Polygon reindexing requires both face and point index maps
tf::index_map_buffer<int> face_map = /* ... */;
tf::index_map_buffer<int> point_map = /* ... */;
auto reindexed_polygons = tf::reindexed(polygons, face_map, point_map);
// Segment reindexing works similarly
tf::index_map_buffer<int> edge_map = /* ... */;
auto reindexed_segments = tf::reindexed(segments, edge_map, point_map);
// Reindex into pre-allocated buffers for performance
tf::polygons_buffer<int, float, 3, 3> poly_buffer; // triangles
tf::reindexed(polygons, face_map, point_map, poly_buffer);
// Dynamic polygon sizes
tf::polygons_buffer<int, float, 3, tf::dynamic_size> dynamic_buffer;
tf::reindexed(polygons, face_map, point_map, dynamic_buffer);
Filter data using boolean masks, automatically generating index maps:
// Create a mask for filtering
tf::buffer<bool> point_mask;
point_mask.allocate(points.size());
// Mark specific points to keep
tf::parallel_for_each(tf::zip(point_mask, points), [&](auto pair) {
auto &&[masked, point] = pair;
masked = meets_criteria(point);
});
// Reindex points by mask (Index defaults to int)
auto filtered_points = tf::reindexed_by_mask(points, point_mask);
// Get index map for downstream processing
auto [filtered_points, index_map] =
tf::reindexed_by_mask(points, point_mask, tf::return_index_map);
For complex primitives, mask-based reindexing automatically handles dependent point filtering:
// Face mask for polygon filtering
tf::buffer<bool> face_mask;
face_mask.allocate(polygons.size());
tf::parallel_transform(polygons, face_mask, [&](auto poly) {
return tf::area(poly) > min_area_threshold;
});
// Reindex polygons - Index deduced from faces()[0][0]
// Automatically filters unused points
auto [filtered_polygons, face_map, point_map] =
tf::reindexed_by_mask(polygons, face_mask, tf::return_index_map);
// Use maps to reindex associated data
auto filtered_normals = tf::reindexed(face_normals, face_map);
auto filtered_attributes = tf::reindexed(point_attributes, point_map);
Extract specific elements by their IDs:
// Extract specific points by ID (Index defaults to int)
std::vector<int> desired_point_ids = {10, 25, 30, 45};
auto extracted_points = tf::reindexed_by_ids(points, desired_point_ids);
// Get index map for consistency
auto [extracted_points, point_map] =
tf::reindexed_by_ids(points, desired_point_ids, tf::return_index_map);
// Extract specific polygons - Index deduced from faces()[0][0]
std::vector<int> face_ids = {0, 2, 5, 8};
auto [extracted_polygons, face_map, generated_point_map] =
tf::reindexed_by_ids(polygons, face_ids, tf::return_index_map);
Filter complex primitives based on point criteria:
// Create point mask based on spatial criteria
tf::buffer<bool> point_mask;
point_mask.allocate(points.size());
tf::parallel_transform(points, point_mask, [&](auto point) {
return point[2] > height_threshold; // Keep points above certain height
});
// Filter polygons - keep only those with ALL vertices in the mask
// Index deduced from faces()[0][0]
auto [filtered_polygons, face_map, point_map] =
tf::reindexed_by_mask_on_points(polygons, point_mask, tf::return_index_map);
// Filter segments - Index deduced from edges()[0][0]
auto [filtered_segments, edge_map, point_map_segments] =
tf::reindexed_by_mask_on_points(segments, point_mask, tf::return_index_map);
// Select specific point IDs
tf::buffer<int> selected_point_ids;
tf::generic_generate(tf::enumerate(points), selected_point_ids, [&](auto pair, auto &buffer) {
auto [point_id, point] = pair;
if (is_feature_point(point)) {
buffer.push_back(point_id);
}
});
// Extract polygons that use only the selected points
// Index deduced from faces()[0][0]
auto [feature_polygons, face_map, point_map] =
tf::reindexed_by_ids_on_points(polygons, selected_point_ids, tf::return_index_map);
The tf::concatenated function efficiently merges multiple geometric structures into a single unified structure, automatically handling index offsetting to maintain referential integrity.
// Variadic - merge multiple point clouds
tf::points_buffer<float, 3> cloud1, cloud2, cloud3;
auto combined = tf::concatenated(cloud1.points(), cloud2.points(), cloud3.points());
// From a range of point views
auto point_clouds = get_point_clouds(); // range of tf::points<...>
auto combined = tf::concatenated(point_clouds);
// From a range of points_buffer - use make_mapped_range to extract views
std::vector<tf::points_buffer<float, 3>> buffers = load_point_clouds();
auto combined = tf::concatenated(
tf::make_mapped_range(buffers, [](auto& x) { return x.points(); }));
// Also works for vectors and unit vectors
auto combined_vectors = tf::concatenated(vectors1, vectors2);
auto combined_normals = tf::concatenated(normals1, normals2);
// Variadic - works with fixed-size polygons (e.g., all triangles)
tf::polygons_buffer<int, float, 3, 3> triangles1, triangles2;
auto combined = tf::concatenated(triangles1.polygons(), triangles2.polygons());
// Also works with mixed polygon sizes (dynamic)
tf::polygons_buffer<int, float, 3, tf::dynamic_size> quads, tris;
auto mixed_mesh = tf::concatenated(quads.polygons(), tris.polygons());
// Result automatically uses dynamic_size when inputs differ
// From a range of polygon views
auto meshes = get_mesh_components(); // range of tf::polygons<...>
auto combined = tf::concatenated(meshes);
// From a range of polygons_buffer - use make_mapped_range to extract views
std::vector<tf::polygons_buffer<int, float, 3, 3>> mesh_buffers = load_meshes();
auto combined = tf::concatenated(
tf::make_mapped_range(mesh_buffers, [](auto& x) { return x.polygons(); }));
// Variadic - merge multiple line segment collections
auto edges1 = extract_boundary_edges(mesh1);
auto edges2 = extract_boundary_edges(mesh2);
auto all_edges = tf::concatenated(edges1, edges2);
// Index offsets are handled automatically:
// If edges1 references points [0-99] and edges2 references points [0-49],
// the result will have edges1 as-is and edges2 offset to reference [100-149]
// From a range of segment views
auto edge_sets = get_edge_components(); // range of tf::segments<...>
auto combined = tf::concatenated(edge_sets);
// From a range of segments_buffer - use make_mapped_range to extract views
std::vector<tf::segments_buffer<int, float, 3>> segment_buffers = load_curves();
auto combined = tf::concatenated(
tf::make_mapped_range(segment_buffers, [](auto& x) { return x.segments(); }));
When geometric structures have transformations attached via tf::tag, tf::concatenated automatically applies those transformations, producing world-space coordinates in the result.
// Create instances at different positions/orientations
auto rotation = tf::make_rotation(tf::deg(45.f), tf::axis<2>);
auto translation = tf::make_transformation_from_translation(
tf::make_vector(10.f, 0.f, 0.f));
// Tag meshes with their transformations
auto rotated = mesh.polygons() | tf::tag(rotation);
auto translated = mesh.polygons() | tf::tag(translation);
// Concatenate - transformations are applied to produce world-space coordinates
auto combined = tf::concatenated(mesh.polygons(), rotated, translated);
// Result: original mesh + rotated copy + translated copy, all in world space
This is useful for instancing workflows where multiple transformed copies of geometry need to be merged into a single mesh for processing or export.
tf::concatenated(a, b, c) allows each argument to have different policies—some can be tagged with transformations while others are not. The range overload tf::concatenated(range) requires all elements to have the same type (homogeneous range).The tf::split_into_components function performs the inverse operation of concatenation—it takes a single mesh with per-element labels and splits it into multiple separate meshes, one for each unique label.
This is particularly useful after topology analysis operations like tf::label_connected_components, allowing you to extract individual components for separate processing, visualization, or export.
auto [labels, n_components] = tf::make_manifold_edge_connected_component_labels(polygons);
// Split mesh into separate components
auto [components, component_labels] = tf::split_into_components(polygons, labels);
// Returns std::pair<std::vector<tf::polygons_buffer<...>>, std::vector<label_type>>
std::cout << "Split into " << components.size() << " meshes" << std::endl;
// Process each component independently
for (auto [component, label] : tf::zip(components, component_labels)) {
// component is tf::polygons_buffer<Index, Real, Dims, N>
// Each component is a complete, self-contained mesh
auto bbox = tf::aabb_from(component.polygons());
}
// Also works for segments
auto [edge_components, edge_labels] = tf::split_into_components(segments, edge_labels);
// Returns std::pair<std::vector<tf::segments_buffer<...>>, std::vector<label_type>>
tf::concatenated: Multiple meshes → Single meshtf::split_into_components: Single mesh + labels → Multiple meshes