Skip to content

dual_contouring_of_signed_distance_data

dual_contouring_of_signed_distance_data(S, GV, nx, ny, nz, isovalue=0.0, outer_iters=100, inner_iters=100, batch_size=200000, hermite_update=True, mu=0.1, dc_weight=0.02, svd_threshold=0.01, new_hermite_pos_weight=0.2, new_face_pos_weight=0.2, new_hermite_normal_weight=0.2, verbose=False)

Reconstructs a quad mesh from regularly sampled 3D signed distance data, using the method described in 'Dual Contouring of Signed Distance Data', by X. Carrera, N. Wang, C. Batty, O. Stein and S. Sellán [2026].

Parameters:

Name Type Description Default
S (array_like, shape(n))

sdf(GV), i.e., signed distance values at the grid vertices.

required
GV (array_like, shape(n, 3))

Grid vertex positions. Row i + nx(j + nyk) must be grid vertex (i,j,k), i.e., the first axis must vary fastest, then the second, then the third (see the examples below).

required
nx int

Number of grid vertices along the first axis.

required
ny int

Number of grid vertices along the second axis.

required
nz int

Number of grid vertices along the third axis.

required
isovalue float, optional (default 0.0)

Level set to extract.

0.0
outer_iters int, optional (default 100)

Number of iterations in the outer loop.

100
inner_iters int, optional (default 100)

Number of iterations in the inner loop (local energy minimization).

100
batch_size int, optional (default 200000)

Maximum number of SDF grid points processed per batch.

200000
hermite_update bool, optional (default True)

Whether to refine Hermite positions from the mesh.

True
mu float, optional (default 0.1)

Regularization weight.

0.1
dc_weight float, optional (default 0.02)

Weight of the Dual Contouring (Hermite) energy term.

0.02
svd_threshold float, optional (default 0.01)

Singular values below this are dropped when solving each cell's QEF.

0.01
new_hermite_pos_weight float, optional (default 0.2)

Blend weight for updating Hermite positions.

0.2
new_face_pos_weight float, optional (default 0.2)

Blend weight for updating face positions.

0.2
new_hermite_normal_weight float, optional (default 0.2)

Blend weight for updating Hermite normals.

0.2
verbose bool, optional (default False)

Whether to print progress information.

False

Returns:

Name Type Description
V (ndarray, shape(m, 3))

Reconstructed vertex positions.

F (ndarray, shape(p, 4))

Reconstructed quadrilateral faces.

Examples:

nx, ny, nz = 32, 32, 32
x = np.linspace(-1., 1., nx)
y = np.linspace(-1., 1., ny)
z = np.linspace(-1., 1., nz)
X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
GV = np.stack((X.ravel(order='F'), Y.ravel(order='F'),
    Z.ravel(order='F')), axis=-1)
S = fun(GV) # Some SDF fun
V, F = gpytoolbox.dual_contouring_of_signed_distance_data(S, GV, nx, ny, nz)
nx, ny, nz = 32, 32, 32
GV, _ = gpytoolbox.regular_cube_mesh(nx, ny, nz)
GV = np.stack([
    GV[:, d].reshape((ny, nx, nz)).transpose(1, 0, 2).ravel(order='F')
    for d in range(3)
], axis=-1)
S = fun(GV) # Some SDF fun
V, F = gpytoolbox.dual_contouring_of_signed_distance_data(S, GV, nx, ny, nz)
Source code in src/gpytoolbox/dual_contouring_of_signed_distance_data.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def dual_contouring_of_signed_distance_data(
    S,
    GV,
    nx,
    ny,
    nz,
    isovalue=0.0,
    outer_iters=100,
    inner_iters=100,
    batch_size=200000,
    hermite_update=True,
    mu=0.1,
    dc_weight=0.02,
    svd_threshold=0.01,
    new_hermite_pos_weight=0.2,
    new_face_pos_weight=0.2,
    new_hermite_normal_weight=0.2,
    verbose=False,
):
    """Reconstructs a quad mesh from regularly sampled 3D signed distance data,
    using the method described in 'Dual Contouring of Signed Distance Data', by
    X. Carrera, N. Wang, C. Batty, O. Stein and S. Sellán [2026].

    Parameters
    ----------
    S : array_like, shape (n,)
        sdf(GV), i.e., signed distance values at the grid vertices.
    GV : array_like, shape (n, 3)
        Grid vertex positions. Row i + nx*(j + ny*k) must be grid vertex
        (i,j,k), i.e., the first axis must vary fastest, then the second,
        then the third (see the examples below).
    nx : int
        Number of grid vertices along the first axis.
    ny : int
        Number of grid vertices along the second axis.
    nz : int
        Number of grid vertices along the third axis.
    isovalue : float, optional (default 0.0)
        Level set to extract.
    outer_iters : int, optional (default 100)
        Number of iterations in the outer loop.
    inner_iters : int, optional (default 100)
        Number of iterations in the inner loop (local energy minimization).
    batch_size : int, optional (default 200000)
        Maximum number of SDF grid points processed per batch.
    hermite_update : bool, optional (default True)
        Whether to refine Hermite positions from the mesh.
    mu : float, optional (default 0.1)
        Regularization weight.
    dc_weight : float, optional (default 0.02)
        Weight of the Dual Contouring (Hermite) energy term.
    svd_threshold : float, optional (default 0.01)
        Singular values below this are dropped when solving each cell's QEF.
    new_hermite_pos_weight : float, optional (default 0.2)
        Blend weight for updating Hermite positions.
    new_face_pos_weight : float, optional (default 0.2)
        Blend weight for updating face positions.
    new_hermite_normal_weight : float, optional (default 0.2)
        Blend weight for updating Hermite normals.
    verbose : bool, optional (default False)
        Whether to print progress information.

    Returns
    -------
    V : numpy.ndarray, shape (m, 3)
        Reconstructed vertex positions.
    F : numpy.ndarray, shape (p, 4)
        Reconstructed quadrilateral faces.

    Examples
    --------
    ```python
    nx, ny, nz = 32, 32, 32
    x = np.linspace(-1., 1., nx)
    y = np.linspace(-1., 1., ny)
    z = np.linspace(-1., 1., nz)
    X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
    GV = np.stack((X.ravel(order='F'), Y.ravel(order='F'),
        Z.ravel(order='F')), axis=-1)
    S = fun(GV) # Some SDF fun
    V, F = gpytoolbox.dual_contouring_of_signed_distance_data(S, GV, nx, ny, nz)
    ```

    ```python
    nx, ny, nz = 32, 32, 32
    GV, _ = gpytoolbox.regular_cube_mesh(nx, ny, nz)
    GV = np.stack([
        GV[:, d].reshape((ny, nx, nz)).transpose(1, 0, 2).ravel(order='F')
        for d in range(3)
    ], axis=-1)
    S = fun(GV) # Some SDF fun
    V, F = gpytoolbox.dual_contouring_of_signed_distance_data(S, GV, nx, ny, nz)
    ```
    """
    S = np.asarray(S, dtype=np.float64).reshape(-1)
    GV = np.asarray(GV, dtype=np.float64)

    nx = int(nx)
    ny = int(ny)
    nz = int(nz)

    if nx < 2 or ny < 2 or nz < 2:
        raise ValueError("nx, ny, and nz must all be at least 2.")

    if GV.ndim != 2 or GV.shape[1] != 3:
        raise ValueError("GV must have shape (n, 3).")

    if S.shape[0] != GV.shape[0]:
        raise ValueError(
            "GV and S must contain the same number of samples."
        )

    expected_samples = nx * ny * nz
    if S.shape[0] != expected_samples:
        raise ValueError(
            "GV and S must contain nx * ny * nz samples; "
            f"expected {expected_samples}, got {S.shape[0]}."
        )

    if outer_iters < 0:
        raise ValueError("outer_iters must be nonnegative.")

    if inner_iters < 0:
        raise ValueError("inner_iters must be nonnegative.")

    if batch_size <= 0:
        raise ValueError("batch_size must be positive.")

    return _dcsdd_cpp_impl(
        S=S,
        GV=GV,
        nx=nx,
        ny=ny,
        nz=nz,
        isovalue=float(isovalue),
        outer_iters=int(outer_iters),
        inner_iters=int(inner_iters),
        hermite_update=bool(hermite_update),
        mu=float(mu),
        dc_weight=float(dc_weight),
        svd_threshold=float(svd_threshold),
        new_hermite_pos_weight=float(new_hermite_pos_weight),
        new_face_pos_weight=float(new_face_pos_weight),
        new_hermite_normal_weight=float(new_hermite_normal_weight),
        batch_size=int(batch_size),
        verbose=bool(verbose),
    )