VTK | C++

Examples

Interactive VTK applications using trueform.

Interactive examples demonstrating trueform's VTK integration. Each example shows a focused pipeline—spatial queries, boolean operations, scalar fields, and topology analysis—with draggable meshes and real-time updates.

All examples are available in the GitHub repository.

Building

Enable VTK examples when building trueform:

cmake -B build-vtk -DTF_BUILD_VTK_INTEGRATION=ON -DTF_BUILD_VTK_EXAMPLES=ON
cmake --build build-vtk --parallel --target trueform_vtk_examples

Requires VTK 9+ with rendering components (RenderingOpenGL2, InteractionStyle).

Run an example:

./build-vtk/vtk_example_collision
./build-vtk/vtk_example_boolean

Spatial Queries

Collision Detection

A 5×5 grid of draggable meshes. Drag any mesh and colliding neighbors highlight in real-time using intersects with transforms.

// Check collision between two transformed meshes
bool collision = tf::vtk::intersects(
    std::make_pair(poly, selected_matrix),
    std::make_pair(poly, other_matrix));

if (collision) {
    actor->GetProperty()->SetColor(0.9, 0.7, 0.7);
}

Full source

Closest Pair

Two draggable meshes with a colored tube showing the closest points between them. Distance updates in real-time using neighbor_search. Tube color interpolates green (close) to red (far).

// Find closest points between two transformed meshes
auto result = tf::vtk::neighbor_search(
    std::make_pair(poly, matrices[0].Get()),
    std::make_pair(poly, matrices[1].Get()));

// Visualize with a line
line_source->SetPoint1(result.info.first.data());
line_source->SetPoint2(result.info.second.data());

// Color by distance
float t = std::sqrt(result.info.metric) / max_distance;
line_actor->GetProperty()->SetColor(t, 1.0 - t, 0.3);

Full source

Boolean Operations

Boolean

Two-viewport display: input meshes with intersection curves on the left, boolean result on the right. Drag either mesh to reposition—the boolean result and curves update in real-time. Uses the boolean filter with transforms and curve output. Result mesh colored by "Labels" cell data.

// Boolean filter pipeline
vtkNew<tf::vtk::boolean> boolean_filter;
boolean_filter->SetInputConnection(0, adapter0->GetOutputPort());
boolean_filter->SetInputConnection(1, adapter1->GetOutputPort());
boolean_filter->set_matrix0(matrix0);
boolean_filter->set_matrix1(matrix1);
boolean_filter->set_operation(tf::boolean_op::left_difference);
boolean_filter->set_return_curves(true);

// Visualize curves as tubes
vtkNew<vtkTubeFilter> tube;
tube->SetInputConnection(boolean_filter->GetOutputPort(1));

// Color result by Labels
vtkNew<vtkLookupTable> lut;
lut->SetNumberOfTableValues(2);
lut->SetTableValue(0, 0.8, 0.8, 0.9, 1.0);  // mesh 0
lut->SetTableValue(1, 0.9, 0.8, 0.8, 1.0);  // mesh 1

result_mapper->SetInputConnection(boolean_filter->GetOutputPort(0));
result_mapper->SetScalarModeToUseCellData();
result_mapper->SetLookupTable(lut);

Full source

Intersection Curves

Two draggable meshes with intersection curves. Drag either mesh—curves update in real-time. Uses the intersection_curves filter with transforms.

// Intersection curves pipeline
vtkNew<tf::vtk::intersection_curves> curves;
curves->SetInputConnection(0, adapter0->GetOutputPort());
curves->SetInputConnection(1, adapter1->GetOutputPort());
curves->set_matrix0(matrix0);
curves->set_matrix1(matrix1);

// Visualize as tubes
vtkNew<vtkTubeFilter> tube;
tube->SetInputConnection(curves->GetOutputPort());
tube->SetRadius(0.0005);

Full source

Scalar Field Operations

Isocontours

Isocontour curves on a mesh with a distance-from-centroid scalar field. Hold Shift and scroll to animate the cut values—contours sweep across the surface. Uses the isocontours filter.

// Create scalar field
auto points = tf::vtk::make_points(poly);
auto center = tf::centroid(points);
auto scalars_range = tf::vtk::make_range(scalars.Get());
tf::parallel_transform(points, scalars_range, tf::distance_f(center));

// Isocontours pipeline
vtkNew<tf::vtk::isocontours> iso;
iso->SetInputConnection(reader->GetOutputPort());
iso->set_cut_values({0.1f, 0.2f, 0.3f, ...});

// Visualize as tubes
vtkNew<vtkTubeFilter> tube;
tube->SetInputConnection(iso->GetOutputPort());

Full source

Isobands

Single viewport showing the original mesh (semi-transparent), isoband curves, and filled isoband polygons. The scalar field is signed distance to a plane through the mesh centroid. Scroll to move the isoband levels—creates a marching-stripes effect across the surface. Uses the isobands filter with curve output.

auto points = tf::vtk::make_points(poly);
auto center = tf::centroid(points);
auto normal = tf::make_unit_vector(1.f, 2.f, 1.f);
auto plane = tf::make_plane(normal, center);

auto scalars_range = tf::vtk::make_range(scalars.Get());
tf::parallel_transform(points, scalars_range, tf::distance_f(plane));

vtkNew<tf::vtk::isobands> bands;
bands->SetInputConnection(reader->GetOutputPort());
bands->set_cut_values({...});
bands->set_selected_bands({0, 2, 4, ...});  // alternating
bands->set_return_curves(true);

// Boundary curves
vtkNew<vtkTubeFilter> tube;
tube->SetInputConnection(bands->GetOutputPort(1));

Full source

Cross-Section

Extracts cross-section curves and triangulates them into filled polygons. The scalar field is signed distance to a plane through the mesh centroid. Scroll to move the cutting plane through the mesh. Uses make_isocontours and triangulated.

auto points = tf::vtk::make_points(poly);
auto center = tf::centroid(points);
auto normal = tf::make_unit_vector(1.f, 2.f, 1.f);
auto plane = tf::make_plane(normal, center);

vtkNew<vtkFloatArray> scalars;
scalars->SetName("plane_distance");
scalars->SetNumberOfTuples(poly->GetNumberOfPoints());

auto scalars_range = tf::vtk::make_range(scalars.Get());
tf::parallel_transform(points, scalars_range, tf::distance_f(plane));

auto curves = tf::vtk::make_isocontours(poly, nullptr, {cut_value});

auto curve_data = tf::vtk::make_curves(curves);
auto slices = tf::triangulated(
    tf::make_polygons(curve_data.paths(), curve_data.points()));

slice_mapper->SetInputData(tf::vtk::make_vtk_polydata(std::move(slices)));

Full source

Topology Analysis

Connected Components

Creates disconnected regions using alternating isobands, then labels and splits them into separate meshes. Uses the connected_components filter and split_into_components function.

// Create disconnected regions with isobands
vtkNew<tf::vtk::isobands> bands;
bands->set_selected_bands({0, 2, 4, 6, 8});  // alternating

// Label components
vtkNew<tf::vtk::connected_components> cc;
cc->SetInputConnection(adapt->GetOutputPort());
cc->set_connectivity(tf::connectivity_type::edge);
cc->Update();

std::cout << "Found " << cc->n_components() << " components\n";

// Split into separate meshes
auto [components, labels] = tf::vtk::split_into_components(cc->GetOutput());

// Color by ComponentLabel
mapper->SetScalarModeToUseCellData();
mapper->SelectColorArray("ComponentLabel");
mapper->SetScalarRange(0, cc->n_components() - 1);

Full source

Boundary Paths

Extracts the boundary of a cut mesh (upper half via isobands) and visualizes it as tubes. Uses the boundary_paths filter.

// Cut mesh with isobands (upper half)
vtkNew<tf::vtk::isobands> bands;
bands->set_cut_values({mid_z, max_z + 1.0f});
bands->set_selected_bands({0});

// Extract boundary
vtkNew<tf::vtk::boundary_paths> boundary;
boundary->SetInputConnection(bands->GetOutputPort(0));

// Visualize as tubes
vtkNew<vtkTubeFilter> tube;
tube->SetInputConnection(boundary->GetOutputPort());
tube->SetRadius(0.0005);

Full source

Incremental Updates

Laplacian Smoothing

Interactive mesh smoothing with a paint-style brush. Click and drag on the mesh to smooth vertices—the brush highlights affected regions and smooths in real-time. Demonstrates mod_tree for O(delta) spatial tree updates instead of full rebuilds.

// Collect affected polygon IDs from modified vertices
std::vector<vtkIdType> polygon_ids;
const auto &fm = poly->face_membership();
for (auto vid : modified_vertices) {
    for (auto poly_id : fm[vid])
        polygon_ids.push_back(poly_id);
}

// Apply Laplacian smoothing to neighborhood
auto neigh_points = tf::make_indirect_range(vertex_indices, points);
auto neigh_neighbors = tf::make_indirect_range(
    vertex_indices, tf::make_block_indirect_range(vlink, points));

tf::parallel_for_each(
    tf::zip(neigh_points, neigh_neighbors),
    [&](auto tup) {
        auto [pt, neighbors] = tup;
        pt = tf::laplacian_smoothed(pt, tf::make_points(neighbors), lambda);
    });

// Incrementally update spatial tree (O(delta) instead of O(n))
poly->update_poly_tree(polygon_ids);

Controls:

  • Left drag on mesh: Smooth vertices under brush
  • Left drag off mesh: Rotate camera
  • Ctrl + scroll: Adjust brush radius

Full source