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]
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},
}
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-ADSConfigdataclass + 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 forresample: true).
Requires CUDA, CGAL, a CUDA-compatible host compiler, and a Python environment:
pip install -r requirements.txtThe 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).
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.
# 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.yamlOutput 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.
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.
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- CGAL - For Delaunay triangulation.
- libigl - fast winding numbers.
- robin_hood - hash containers (
cpp/third_party/). - Weighted Sample Elimination (cyCodeBase, Cem Yuksel) - Poisson-disk sampling.
Released under the MIT License. See LICENSE.
