How to get a reasonably good triangulated surface from a level-set volumetric field

Hello, I’m exploring some algorithms to compute curvatures on surfaces and I’m using VTK under the hoods to manage the triangulation of a level-set surface (and data structures).

The problem I’m encountering is a relatively poor quality of the triangulated surfaces once the distance function fields get triangulated (I’m using vtkMarchingCubes but I also tried the vtkExtractSurface and vtkFlyingEdges3D filters too).

Here a MWE to create a spherical surface from its level-set:

/** This example is a MWE to be posted on the VTK discourse to understand how
 * to use the vtkCleanPolyDataFilter to avoid having duplicate points when
 * triangulating a sphere with a Marching Cube on a given level-set field
 */

#include <vtkCleanPolyData.h>
#include <vtkImageData.h>
#include <vtkMarchingCubes.h>
#include <vtkXMLImageDataWriter.h>
#include <vtkXMLPolyDataWriter.h>

#include "Surface.h"

using namespace hgve;

/** @brief The distance function for a sphere */
static double SphereDistance(double x, double y, double z, double R) {
    return x*x + y*y + z*z - R*R;
}

int main(int argc, char *argv[])
{

    // Let's define a uniform grid on which to compute our distance function
    vtkSmartPointer<vtkImageData> volume =
        vtkSmartPointer<vtkImageData>::New();
    volume->SetDimensions(100, 100, 100);
    volume->AllocateScalars(VTK_DOUBLE, 1);
    volume->SetSpacing(0.05, 0.05, 0.05);

    // Let's compute the value of the distance function for the sphere in all
    // the domain (i.e. for each x, y, z)
    static const double sphereRadius = 2;
    static const double sphereCenter[3] = {2.5, 2.5, 2.5};

    auto dims = volume->GetDimensions();
    for (int i = 0; i < dims[0]; i++) {
        for (int j = 0; j < dims[1]; j++) {
            for (int k = 0; k < dims[2]; k++) {
                // Let's merge coordinate index in an array
                int ijk[3] = {i, j, k};

                // Retrieve the pointId from its ijk indices
                auto pId = volume->ComputePointId(ijk);

                // Retrieve x, y, z coordinates
                auto coords = volume->GetPoint(pId);

                // Set the value of the distance at (x, y, z)
                volume->SetScalarComponentFromDouble(i, j, k, 0,
                        SphereDistance(
                            coords[0] - sphereCenter[0],
                            coords[1] - sphereCenter[1],
                            coords[2] - sphereCenter[2],
                            sphereRadius)
                        );

            }
        }
    }

    vtkSmartPointer<vtkXMLImageDataWriter> iwriter =
        vtkSmartPointer<vtkXMLImageDataWriter>::New();
    iwriter->SetInputData(volume);
    iwriter->SetFileName("sphere.vti");
    iwriter->Write();

    // Now we have the distance in the domain, let's reconstruct the surface
    // using the marching cube
    vtkSmartPointer<vtkMarchingCubes> triangulizer =
        vtkSmartPointer<vtkMarchingCubes>::New();
    triangulizer->SetInputData(volume);
    triangulizer->Update();

    // The surface obtained with the Marching Cubes algorithm contains
    // duplicate points, so let's clean it
    vtkSmartPointer<vtkCleanPolyData> cleaner =
        vtkSmartPointer<vtkCleanPolyData>::New();
    cleaner->SetInputData(triangulizer->GetOutput());
    cleaner->SetAbsoluteTolerance(1E-3);
    cleaner->Update();

    Surface surface(cleaner->GetOutput());
    surface.ComputeCurvatures();
    surface.Write("sphere.vtp");


} // main

Is this the best I can do or not? Because in the resulting surface I get very skewed triangles. Any suggestion would be very appreciated.

Can you share an image of the poorly extracted isosurface?

Hi Bill, thank you for taking the time to reply. I didn’t get the notification so please excuse me for the late reply.

Here’s an image of the sphere with the computation of the Gauss Curvature (should be constant == 0.25 for a sphere of radius 2) made with the algorithm I’m currently evaluating [1]. As you can see I have some skewed triangles that I think belong to the intersection of the original cartesian uniform mesh cells with the sphere itself, but I don’t know in details how the vtkMarchingCubes filter works and if it does something specifically different from the reference algorithm in the literature.

[1] M. Meyer, M. Desbrun, P. Schröder, and A. H. Barr, “Discrete Differential-Geometry Operators for Triangulated 2-Manifolds,” in Visualization and Mathematics III , H.-C. Hege and K. Polthier, Eds. Berlin, Heidelberg: Springer Berlin Heidelberg, 2003, pp. 35–57.)