Skip to content

Repository files navigation

ADS: Random Sampling of Occupancy Functions using Adaptive Delaunay Scaffolding

Official code release for ADS: Random Sampling of Occupancy Functions using Adaptive Delaunay Scaffolding, Suzuran Takikawa, Leo Foord-Kelcey, Oliver Oxford, Nicholas Vining, Alla Sheffer, SIGGRAPH 2026.  [Project Page]

Image

Citation

If you find this code useful, please consider citing:

@inproceedings{takikawa2026ads,
  author    = {Takikawa, Suzuran and Foord-Kelcey, Leo and Oxford, Oliver and Vining, Nicholas and Sheffer, Alla},
  title     = {ADS: Random Sampling of Occupancy Functions using Adaptive Delaunay Scaffolding},
  year      = {2026},
  booktitle = {Proceedings of the Special Interest Group on Computer Graphics and Interactive Techniques Conference Conference Papers},
  series    = {SIGGRAPH Conference Papers '26},
  doi       = {10.1145/3799902.3811146},
}

Directory structure

  • cpp/ - C++/CUDA extension (ads_core): contains the CGAL Delaunay scaffolding and CUDA marching tetrahedra.
  • pipeline.py - the core sampling/meshing loop.
  • occupancy.py - occupancy oracle (mesh via winding number, or analytic functions) + GPU binary search.
  • sampling.py - initial Poisson-disk sampling / committed-sample loading.
  • config.py - ADSConfig dataclass + YAML loader.
  • chamfer.py - chamfer evaluation against ground truth.
  • stats.py - timing breakdown and stats export.
  • run.py - command-line entry point.
  • configs/ - experiment configs (mesh_{5e-2,3e-2,2e-2}.yaml, function_demo.yaml).
  • data/ - pre-computed poisson samples and an example mesh (data/meshes/duck.obj, see its README); add your own meshes there.
  • tools/ - the Poisson-disk sampler (only needed for resample: true).

Getting started

Dependencies

Requires CUDA, CGAL, a CUDA-compatible host compiler, and a Python environment:

pip install -r requirements.txt

The ads_core extension builds against whatever CUDA toolkit you have installed; it does not use PyTorch, so its CUDA version is independent of PyTorch's cuXXX build (you should only need a GPU driver new enough for both).

Building the extension

mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
cp ads_core*.so ..

Note (GPU architectures). This release code has been tested on a RTX 4090 and RTX 2080. On other GPUs some changes may be needed to CMakeLists.txt for the CUDA code to work properly.

Verify the build: python -c "import ads_core as da; print(da.DelaunayScaffold.has_cuda())" should print True.

Running

# mesh input (expects data/meshes/duck.obj)
python run.py --config configs/mesh_5e-2.yaml --mesh duck

# Analytic-function demo
python run.py --config configs/function_demo.yaml

Output is written to output/<label>/: final_mesh.obj and stats_original.xlsx (evaluation counts + timing). All parameters are in the config; --mesh, --input-type, --function-type, --output, and --resample can be overridden on the command line. threshold is the single quality knob (the paper uses 5e-2, 3e-2, 2e-2); the binary-search and midpoint tolerances are derived from it. The duck example mesh is in data/meshes/ (see data/meshes/README.md).

Preparing your own mesh. A mesh input must be normalized to fit within the [-1, 1]^3 cube (occupancy is defined by the mesh's inside/outside via a fast winding number, and sampling happens in that cube). Drop the prepared .obj in data/meshes/ and pass --mesh <name>.

Note (pre-computed poisson samples). The initial Poisson-disk samples (seed 0, 10k) are mesh-independent (uniform in the bounding cube), so one committed file data/samples_seed0_10k.obj serves every input and is loaded by default (resample: false). Set resample: true (or pass --resample) to regenerate; this uses the sampler in tools/poisson_sampling/ (build it with make there, or point sampler_path elsewhere). We use Cem Yuksel's weighted sample elimination because we had it on hand - any Poisson-disk method works.

Custom occupancy functions

ADS works with any occupancy oracle, not only the built-in mesh/function inputs -- e.g. a trained neural occupancy network. An oracle maps points to a sign (+1 outside, -1 inside). Subclass OccupancyBase and implement two forward-pass methods (the base handles the eval bookkeeping), then pass an instance to run(cfg, net=...):

import torch
from occupancy import OccupancyBase
from config import ADSConfig
from pipeline import run

class NeuralOccupancy(OccupancyBase):
    def __init__(self, net):
        super().__init__()                      # sets .device and the eval counters
        self.net = net.to(self.device).eval()

    @torch.no_grad()
    def _signs_gpu(self, pts):                  # (N, 3) torch on self.device -> (N,) signs
        occ = self.net(pts.to(self.device))     # your net's occupancy; here > 0.5 means inside
        return torch.where(occ.reshape(-1) > 0.5, -1, 1).to(torch.int8)

    def _signs_np(self, pts):                   # numpy entry point (warmup / non-GPU paths)
        s = self._signs_gpu(torch.as_tensor(pts, dtype=torch.float32, device=self.device))
        return s.cpu().numpy()

cfg = ADSConfig(mesh="my_shape")                # config still drives sampling/thresholds/output label
run(cfg, net=NeuralOccupancy(my_trained_net))

run(cfg, net=...) bypasses the built-in oracle; the config still controls sampling, thresholds, and the output label (cfg.mesh names the output/<label>/ folder). Adjust the inside/outside test (> 0.5) and any input scaling to your network's convention. Sampling happens in [-1, 1]^3, so the oracle must be defined over that cube.

Evaluation

python chamfer.py --config configs/mesh_5e-2.yaml --mesh duck                # point chamfer
python chamfer.py --config configs/mesh_5e-2.yaml --mesh duck --mesh-chamfer  # + surface-sampled

Third-party libraries

License

Released under the MIT License. See LICENSE.

About

ADS: Random Sampling of Occupancy Functions using Adaptive Delaunay Scaffolding (SIGGRAPH 2026)

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages