nx_poisson_disk.nx
buildroot/runtime/nx_poisson_disk.nx
about
nx_poisson_disk.nx -- Bridson 2007 fast Poisson disk sampling.
LAYER 2 of the procgen substrate (Perlin = layer 1). Genuinely
modern algorithm (2007), patent-clean, O(N). Used by Townscaper-
class procedural tools for building placement, Houdini's scatter
SOP for prop distribution, agent-spawn placement in zombie-escape
city games -- anywhere "looks random AND evenly spaced AND fast"
matters more than uniform random scattering (which clumps).
Honest deviation note vs canonical Bridson paper (PDF was not
directly readable; spec verified from training data; v1 caveat):
1. Initial seed point: RANDOM (fixed in v1.1 -- previously center).
2. Annulus sample radius: drawn LINEARLY in [r, 2r] rather than
area-uniform (canonical r' = sqrt(uniform * (4r^2 - r^2) + r^2)
requires sqrt; queued for follow-up once nx_sqrt_q10 ships).
Substrate still satisfies the min-distance invariant; the bias
is a slight inner-ring density skew, not a correctness bug.
Algorithm (Bridson 2007 SIGGRAPH sketch):
1. Background grid with cell size r / sqrt(n_dim). Each cell
can hold at most one point (because two points within one
cell would be < r apart).
2. Start: place one seed point, mark it active.
3. Loop while active non-empty:
Pick random active point p. Try k = 30 candidates at
distance [r, 2r] from p. For each candidate:
- Check in-bounds.
- Check no existing point within r (grid neighbors only --
this is what makes Bridson O(N)).
- If valid, add point + mark active; break.
If all 30 fail, remove p from active.
4. Return collected points.
Why this beats Perlin-grid-scatter for city gen: Perlin scatter
has clumping (high-noise spots get many points; low-noise spots
none). Poisson disk guarantees minimum distance r AND fills
space uniformly -- exactly what zombie-spawn / building-plot
placement requires.
Cross-modal: also gives n-gram-diverse sample selection from a
dependencies 3 imports · 5 importers
imports: nx_syscalls.nxnx_tier.nxnx_prng.nx
imported by: nx_poisson_disk_test.nxnx_procgen_preset.nxnx_procgen_signature.nxnx_procgen_water.nxnx_voronoi_test.nx
structs
| none |
consts
| 64 | const NX_PD_Q: nx_int = 1024 |
| 67 | const NX_PD_INV_SQRT_2: nx_int = 724 |
| 68 | const NX_PD_K_CANDIDATES: nx_int = 30 |
| 69 | const NX_PD_N_ANGLES: nx_int = 16 |
| 73 | const NX_PD_COS_0: nx_int = 1024 |
| 74 | const NX_PD_COS_1: nx_int = 946 |
| 75 | const NX_PD_COS_2: nx_int = 724 |
| 76 | const NX_PD_COS_3: nx_int = 392 |
| 77 | const NX_PD_COS_4: nx_int = 0 |
| 78 | const NX_PD_COS_5: nx_int = -392 |
| 79 | const NX_PD_COS_6: nx_int = -724 |
| 80 | const NX_PD_COS_7: nx_int = -946 |
| 81 | const NX_PD_COS_8: nx_int = -1024 |
| 82 | const NX_PD_COS_9: nx_int = -946 |
| 83 | const NX_PD_COS_10: nx_int = -724 |
| 84 | const NX_PD_COS_11: nx_int = -392 |
| 85 | const NX_PD_COS_12: nx_int = 0 |
| 86 | const NX_PD_COS_13: nx_int = 392 |
| 87 | const NX_PD_COS_14: nx_int = 724 |
| 88 | const NX_PD_COS_15: nx_int = 946 |
| 90 | const NX_PD_SIN_0: nx_int = 0 |
| 91 | const NX_PD_SIN_1: nx_int = 392 |
| 92 | const NX_PD_SIN_2: nx_int = 724 |
| 93 | const NX_PD_SIN_3: nx_int = 946 |
| 94 | const NX_PD_SIN_4: nx_int = 1024 |
| 95 | const NX_PD_SIN_5: nx_int = 946 |
| 96 | const NX_PD_SIN_6: nx_int = 724 |
| 97 | const NX_PD_SIN_7: nx_int = 392 |
| 98 | const NX_PD_SIN_8: nx_int = 0 |
| 99 | const NX_PD_SIN_9: nx_int = -392 |
| 100 | const NX_PD_SIN_10: nx_int = -724 |
| 101 | const NX_PD_SIN_11: nx_int = -946 |
| 102 | const NX_PD_SIN_12: nx_int = -1024 |
| 103 | const NX_PD_SIN_13: nx_int = -946 |
| 104 | const NX_PD_SIN_14: nx_int = -724 |
| 105 | const NX_PD_SIN_15: nx_int = -392 |
| 113 | const NX_PD_MAX_FACTOR_Q10: nx_int = 5530 // ~ 5.4 * Q10 |
| 115 | const NX_PD_BAND_SPARSE: nx_int = 0 |
| 116 | const NX_PD_BAND_MODERATE: nx_int = 1 |
| 117 | const NX_PD_BAND_DENSE: nx_int = 2 |
| 118 | const NX_PD_BAND_SATURATED: nx_int = 3 |
| 119 | const NX_PD_N_BANDS: nx_int = 4 |
functions
| 121 | func _pd_cos(idx: nx_int) -> nx_int called by 1: nx_poisson_disk_sample |
| 140 | func _pd_sin(idx: nx_int) -> nx_int called by 1: nx_poisson_disk_sample |
| 160 | func _pd_dist_sq(ax: nx_int, ay: nx_int, bx: nx_int, by: nx_int) -> nx_int called by 1: _pd_accept |
| 170 | func _pd_accept(cx: nx_int, cy: nx_int, r: nx_int, r_sq: nx_int, |
| 210 | func nx_poisson_disk_sample(seed: nx_int, w: nx_int, h: nx_int, r: nx_int, |
| 318 | func nx_poisson_disk_classify(n_points: nx_int, w: nx_int, h: nx_int, r: nx_int) -> nx_int called by 1: main |
| 334 | func nx_poisson_disk_band_is_valid(band: nx_int) -> nx_int called by 1: main |