SDF Utilities

isoext.sdf is a small toolbox of signed distance functions for testing and demos. Nothing in the library requires it: extraction only sees the values you put on the grid, however you compute them. Working with Grids shows examples with raw PyTorch.

import torch
import isoext
from isoext.sdf import *
from isoext import viewer

grid = isoext.UniformGrid([128, 128, 128])

Meshes

TriangleMeshSDF turns a triangle mesh into a signed distance function, so a mesh can be sampled into a grid, edited or combined like any other field, and extracted again.

TriangleMeshSDF(vertices, faces, signed=True)

isoext.assets.load_mesh downloads a few well-known test meshes on first use and caches them:

  • "armadillo" (Krishnamurthy and Levoy [1996]), "bunny" (Turk and Levoy [1994]) and "dragon" (Curless and Levoy [1996]), courtesy of the Stanford Computer Graphics Laboratory, for research use

  • "spot" from Crane [2012], public domain

The armadillo, sampled on a 256 cell grid and extracted again:

vertices, faces = isoext.assets.load_mesh("armadillo")
armadillo = TriangleMeshSDF(vertices, faces)

fine = isoext.UniformGrid([256, 256, 256])
fine.set_values(armadillo(fine.get_points()))
v, f = isoext.dual_marching_cubes(fine)
print(f"{len(faces):,} mesh triangles in, {len(f):,} out")
viewer.embed(v, f, color="lightsteelblue")
345,944 mesh triangles in, 204,664 out

The distance is the distance to the closest triangle, found through a bounding volume hierarchy on the GPU (Ericson [2005]; Jones et al. [2006] survey the approaches). The sign is the generalized winding number (Jacobson et al. [2013]), evaluated with the tree-based approximation of Barill et al. [2018], so meshes with holes like the bunny and the dragon still get a sensible sign. sign="parity" counts ray crossings instead (Nooruddin and Turk [2003]), which needs a closed mesh.

The gradient is the direction away from the closest point, so get_sdf_normal and project_to_surface work on a mesh SDF, and closest_points returns the closest point on the mesh and its triangle directly:

p = torch.tensor([[0.0, 0.0, 0.9], [0.5, 0.5, 0.5]], device="cuda")
q, face = armadillo.closest_points(p)
print("distances:", [round(d, 4) for d in armadillo(p).tolist()])
print("closest points:", [[round(x, 4) for x in row] for row in q.tolist()])
print("faces:", face.tolist())
distances: [0.1756, 0.1375]
closest points: [[-0.01, -0.0093, 0.725], [0.6087, 0.4526, 0.5696]]
faces: [281187, 12731]

Primitives

SphereSDF

SphereSDF(radius: float)
grid = isoext.UniformGrid([128, 128, 128])

sphere = SphereSDF(radius=0.7)
grid.set_values(sphere(grid.get_points()))
v, f = isoext.marching_cubes(grid)
viewer.embed(v, f)

TorusSDF

TorusSDF(R: float, r: float)  # R=major radius, r=tube radius
torus = TorusSDF(R=0.6, r=0.2)
grid.set_values(torus(grid.get_points()))
v, f = isoext.marching_cubes(grid)
viewer.embed(v, f, color="gold")

CuboidSDF

CuboidSDF(size: list[float])  # Full size in [x, y, z]
cube = CuboidSDF(size=[1.0, 1.0, 1.0])
grid.set_values(cube(grid.get_points()))
v, f = isoext.marching_cubes(grid)
viewer.embed(v, f, color="salmon")

MandelbulbSDF

A distance estimator for the Mandelbulb fractal of White and Nylander [2009]. The values approximate the distance to the surface rather than being an exact SDF; fewer iterations give a smoother shape.

bulb = MandelbulbSDF(iterations=6)

# The bulb needs slightly larger bounds than the shared grid above
bulb_grid = isoext.UniformGrid([128, 128, 128], aabb_min=[-1.2, -1.2, -1.2], aabb_max=[1.2, 1.2, 1.2])
bulb_grid.set_values(bulb(bulb_grid.get_points()))
v, f = isoext.marching_cubes(bulb_grid)
viewer.embed(v, f, color="coral")

CSG Operations

Combine shapes using Constructive Solid Geometry:

Operation

Description

UnionOp([...])

Combine shapes (min of SDFs)

IntersectionOp([...])

Keep overlap (max of SDFs)

NegationOp(sdf)

Invert inside/outside

SmoothUnionOp([...], k)

Smooth blend with radius k

# Sphere with a hole drilled through it
sphere = SphereSDF(radius=0.7)
hole = CuboidSDF(size=[0.3, 0.3, 2.0])
drilled = IntersectionOp([sphere, NegationOp(hole)])

grid.set_values(drilled(grid.get_points()))
v, f = isoext.marching_cubes(grid)
viewer.embed(v, f, color="orchid")

Transformations

Transform

Description

TranslationOp(sdf, offset)

Move by [x, y, z]

RotationOp(sdf, axis, angle)

Rotate around axis (degrees by default)

# Two spheres with smooth blending
s1 = TranslationOp(SphereSDF(radius=0.4), offset=[-0.3, 0, 0])
s2 = TranslationOp(SphereSDF(radius=0.4), offset=[0.3, 0, 0])
blended = SmoothUnionOp([s1, s2], k=0.15)

grid.set_values(blended(grid.get_points()))
v, f = isoext.marching_cubes(grid)
viewer.embed(v, f, color="tomato")

References

[1]

Venkat Krishnamurthy and Marc Levoy. Fitting smooth surfaces to dense polygon meshes. In Proceedings of the 23rd Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH '96), 313–324. 1996. doi:10.1145/237170.237270.

[2]

Greg Turk and Marc Levoy. Zippered polygon meshes from range images. In Proceedings of the 21st Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH '94), 311–318. 1994. doi:10.1145/192161.192241.

[3]

Brian Curless and Marc Levoy. A volumetric method for building complex models from range images. In Proceedings of the 23rd Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH '96), 303–312. 1996. doi:10.1145/237170.237269.

[4]

Keenan Crane. Keenan's 3D model repository. 2012. URL: https://www.cs.cmu.edu/~kmcrane/Projects/ModelRepository/.

[5]

Christer Ericson. Real-Time Collision Detection. Morgan Kaufmann, 2005. ISBN 9781558607323.

[6]

Mark W. Jones, J. Andreas Bærentzen, and Milos Sramek. 3D distance fields: a survey of techniques and applications. IEEE Transactions on Visualization and Computer Graphics, 12(4):581–599, 2006. doi:10.1109/TVCG.2006.56.

[7]

Alec Jacobson, Ladislav Kavan, and Olga Sorkine-Hornung. Robust inside-outside segmentation using generalized winding numbers. ACM Transactions on Graphics, 32(4):33:1–33:12, 2013. doi:10.1145/2461912.2461916.

[8]

Gavin Barill, Neil G. Dickson, Ryan Schmidt, David I. W. Levin, and Alec Jacobson. Fast winding numbers for soups and clouds. ACM Transactions on Graphics, 37(4):43:1–43:12, 2018. doi:10.1145/3197517.3201337.

[9]

Fakir S. Nooruddin and Greg Turk. Simplification and repair of polygonal models using volumetric techniques. IEEE Transactions on Visualization and Computer Graphics, 9(2):191–205, 2003. doi:10.1109/TVCG.2003.1196006.

[10]

Daniel White and Paul Nylander. The mandelbulb: the unravelling of the real 3D mandelbrot fractal. 2009. URL: https://www.skytopia.com/project/fractal/mandelbulb.html.