nx_voronoi_test.nx source
↩ module page · 118 lines · 5444 B
1// nx_voronoi_test.nx -- smoke for 2D Voronoi.
2//
3// Validates:
4// 1. Per-pixel labelling assigns to nearest site
5// 2. Site at own coords gets labelled to itself
6// 3. Pixels exactly equidistant deterministically pick first-found
7// 4. Per-site counts sum to canvas size
8// 5. CV classifier produces a valid sealed-enum band
9// 6. Composition with nx_poisson_disk_sample (sites from poisson)
10// 7. Sealed-enum validity gate
11
12import "nx_syscalls.nx"
13import "nx_tier.nx"
14import "nx_poisson_disk.nx"
15import "nx_voronoi.nx"
16
17func main() -> nx_int {
18 // === Test 1: 3 hand-placed sites, label correctness ============
19 let xs: *i64 = (sys_mmap(16 * NX_SIZEOF_NX_INT)) as *i64
20 let ys: *i64 = (sys_mmap(16 * NX_SIZEOF_NX_INT)) as *i64
21 xs[0] = 5; ys[0] = 5 // top-left-ish
22 xs[1] = 25; ys[1] = 5 // top-right-ish
23 xs[2] = 15; ys[2] = 25 // bottom-center-ish
24 let labels: *i64 = (sys_mmap(32 * 32 * NX_SIZEOF_NX_INT)) as *i64
25 let n_pixels: nx_int = nx_voronoi_label(32, 32, xs, ys, 3, labels)
26 if n_pixels != 32 * 32 { return 1 }
27
28 // Site coords always label to themselves.
29 if nx_voronoi_label_at(labels, 32, 32, 5, 5) != 0 { return 2 }
30 if nx_voronoi_label_at(labels, 32, 32, 25, 5) != 1 { return 3 }
31 if nx_voronoi_label_at(labels, 32, 32, 15, 25) != 2 { return 4 }
32
33 // Pixels near each site should be that site.
34 if nx_voronoi_label_at(labels, 32, 32, 6, 6) != 0 { return 5 }
35 if nx_voronoi_label_at(labels, 32, 32, 24, 6) != 1 { return 6 }
36 if nx_voronoi_label_at(labels, 32, 32, 14, 24) != 2 { return 7 }
37
38 // === Test 2: out-of-bounds label_at returns -1 =================
39 if nx_voronoi_label_at(labels, 32, 32, 0 - 1, 0) != 0 - 1 { return 10 }
40 if nx_voronoi_label_at(labels, 32, 32, 32, 0) != 0 - 1 { return 11 }
41 if nx_voronoi_label_at(labels, 32, 32, 0, 0 - 1) != 0 - 1 { return 12 }
42 if nx_voronoi_label_at(labels, 32, 32, 0, 32) != 0 - 1 { return 13 }
43
44 // === Test 3: per-site counts sum to canvas size ================
45 let counts: *i64 = (sys_mmap(16 * NX_SIZEOF_NX_INT)) as *i64
46 nx_voronoi_site_counts(32, 32, labels, 3, counts)
47 let total: nx_int = counts[0] + counts[1] + counts[2]
48 if total != 32 * 32 { return 20 }
49 // Each of the 3 sites should own a non-trivial slice.
50 if counts[0] < 100 { return 21 }
51 if counts[1] < 100 { return 22 }
52 if counts[2] < 100 { return 23 }
53
54 // === Test 4: CV classifier returns valid band ==================
55 let band: nx_int = nx_voronoi_classify(counts, 3)
56 if nx_voronoi_band_is_valid(band) != 1 { return 30 }
57
58 // === Test 5: uniform-grid sites should classify as UNIFORM =====
59 //
60 // Place 4 sites at corners of a 32x32 canvas -- each owns a
61 // 16x16 quadrant; cell counts will be near-equal -> UNIFORM band.
62 xs[0] = 8; ys[0] = 8
63 xs[1] = 24; ys[1] = 8
64 xs[2] = 8; ys[2] = 24
65 xs[3] = 24; ys[3] = 24
66 nx_voronoi_label(32, 32, xs, ys, 4, labels)
67 nx_voronoi_site_counts(32, 32, labels, 4, counts)
68 let band_uniform: nx_int = nx_voronoi_classify(counts, 4)
69 if band_uniform != NX_VORONOI_BAND_UNIFORM { return 40 }
70
71 // === Test 6: degenerate placement classifies as DEGENERATE =====
72 //
73 // 4 sites packed in one corner -- one of them owns nearly the
74 // whole canvas, others own tiny slivers.
75 xs[0] = 0; ys[0] = 0
76 xs[1] = 1; ys[1] = 0
77 xs[2] = 0; ys[2] = 1
78 xs[3] = 31; ys[3] = 31
79 nx_voronoi_label(32, 32, xs, ys, 4, labels)
80 nx_voronoi_site_counts(32, 32, labels, 4, counts)
81 let band_degen: nx_int = nx_voronoi_classify(counts, 4)
82 if nx_voronoi_band_is_valid(band_degen) != 1 { return 50 }
83 if band_degen == NX_VORONOI_BAND_UNIFORM { return 51 }
84
85 // === Test 7: composition with Poisson disk sites ===============
86 //
87 // Generate sites via Bridson Poisson, label, classify. Poisson's
88 // uniform-blue-noise property should produce UNIFORM or BALANCED.
89 let p_xs: *i64 = (sys_mmap(64 * NX_SIZEOF_NX_INT)) as *i64
90 let p_ys: *i64 = (sys_mmap(64 * NX_SIZEOF_NX_INT)) as *i64
91 let n_p: nx_int = nx_poisson_disk_sample(13, 32, 32, 6, p_xs, p_ys, 64)
92 if n_p < 4 { return 60 }
93 nx_voronoi_label(32, 32, p_xs, p_ys, n_p, labels)
94 let p_counts: *i64 = (sys_mmap(64 * NX_SIZEOF_NX_INT)) as *i64
95 nx_voronoi_site_counts(32, 32, labels, n_p, p_counts)
96 let p_total: nx_int = 0
97 var pi: nx_int = 0
98 var sum_check: nx_int = 0
99 while pi < n_p {
100 sum_check = sum_check + p_counts[pi]
101 pi = pi + 1
102 }
103 if sum_check != 32 * 32 { return 61 }
104 let p_band: nx_int = nx_voronoi_classify(p_counts, n_p)
105 if nx_voronoi_band_is_valid(p_band) != 1 { return 62 }
106 if p_band == NX_VORONOI_BAND_DEGENERATE { return 63 }
107
108 // === Test 8: sealed-enum validity ==============================
109 if nx_voronoi_band_is_valid(NX_VORONOI_BAND_UNIFORM) != 1 { return 70 }
110 if nx_voronoi_band_is_valid(NX_VORONOI_BAND_BALANCED) != 1 { return 71 }
111 if nx_voronoi_band_is_valid(NX_VORONOI_BAND_LOPSIDED) != 1 { return 72 }
112 if nx_voronoi_band_is_valid(NX_VORONOI_BAND_DEGENERATE) != 1 { return 73 }
113 if nx_voronoi_band_is_valid(NX_VORONOI_N_BANDS) != 0 { return 74 }
114 if nx_voronoi_band_is_valid(99) != 0 { return 75 }
115 if nx_voronoi_band_is_valid(0 - 1) != 0 { return 76 }
116
117 return 0
118}