code wiki / (root) / nx_csg_scene.nx

nx_csg_scene.nx source

↩ module page · 264 lines · 10583 B

1// nx_csg_scene.nx -- N-PRIMITIVE CSG. nx_csg_extract is hardwired to TWO primitives 2// (a op b); real parts need many features folded into ONE watertight solid -- a toilet- 3// cleaner head is plate MINUS pocket MINUS slots UNION neck UNION socket MINUS bore. 4// 5// This GROWS the capability by COMPOSING what already exists, adding no new geometry math: 6// * the boolean algebra is the EXISTING nx_csg_combine_k (min/max + smooth-min fillet) 7// * the surface mesher is the EXISTING marching-tetrahedra core (mt_tet, imported) 8// * per-primitive ROTATION (about an arbitrary pivot) is the EXISTING nx_trig (cos/sin), 9// applied by inverse-rotating the query point -- nx_sdf stays untouched 10// * the only new thing is a LEFT-FOLD of the field over a list of (prim, op, fillet-k): 11// field = combine_k(op[1], eval(p0), eval(p1), k[1]) ... combine_k(op[n-1], ., eval(p_{n-1}), k[n-1]) 12// prim[0] is the seed (its op/k ignored). 13// 14// Robust by construction: SDF booleans can never fail/self-intersect the way B-rep does, 15// and the marching grid is shared so the result is watertight by the same canonical-edge 16// argument as nx_csg_extract. Output is a pipeline-native Q14-mm NxMesh -> nx_stl_write_mesh 17// -> slicer -> G-code, exactly like every other sovereign solid. 18// 19// NOTE (tech-debt, logged not hidden): the grid+marching loop below is copied from 20// nx_csg_extract with only the 2-prim field swapped for the scene field. The clean 21// refactor is a field-eval callback in nx_csg_extract; deferred until NishiLang fn-ptr 22// support is confirmed. license_tier: ORIGINAL 23 24import "nx_syscalls.nx" 25import "nx_mesh.nx" 26import "nx_sdf.nx" 27import "nx_csg.nx" 28import "nx_trig.nx" 29const NX_MAGIC_1024: i64 = 1024 30const NX_MAGIC_65536: i64 = 65536 31 32// rotation axes (v1 implements Y -- the hook tilt; X/Z are easy follow-ons) 33const NX_ROT_NONE: i64 = 0 34const NX_ROT_X: i64 = 1 35const NX_ROT_Y: i64 = 2 36const NX_ROT_Z: i64 = 3 37 38// A scene applies its primitives left-to-right. prim[0] seeds the field; for i>=1 the 39// field so far is combined with eval(prim[i]) under ops[i] (fillet radius ks[i], 0=sharp). 40// Each prim may carry a rotation: axis rax[i] about pivot (rpx,rpy,rpz)[i] by rang[i] 41// (Q10 turn; 1024 = full circle). rax=0 -> axis-aligned (no rotation). 42struct NxCsgScene { 43 n: i64, 44 cap: i64, 45 prims: *i64, // n entries, each an (NxSdfPrim*) stored as i64 46 ops: *i64, // n entries (ops[0] unused) 47 ks: *i64, // n entries (ks[0] unused); 0 = sharp boolean, >0 = fillet radius (Q14) 48 rax: *i64, // rotation axis (NX_ROT_*) 49 rang: *i64, // rotation angle (Q10 turn) 50 rpx: *i64, // pivot x (Q14) 51 rpy: *i64, // pivot y (Q14) 52 rpz: *i64, // pivot z (Q14) 53} 54 55func nx_csg_scene_new(cap: i64) -> *NxCsgScene { 56 let s: *NxCsgScene = (sys_mmap(80)) as *NxCsgScene 57 s.n = 0 58 s.cap = cap 59 s.prims = (sys_mmap(cap * 8)) as *i64 60 s.ops = (sys_mmap(cap * 8)) as *i64 61 s.ks = (sys_mmap(cap * 8)) as *i64 62 s.rax = (sys_mmap(cap * 8)) as *i64 63 s.rang = (sys_mmap(cap * 8)) as *i64 64 s.rpx = (sys_mmap(cap * 8)) as *i64 65 s.rpy = (sys_mmap(cap * 8)) as *i64 66 s.rpz = (sys_mmap(cap * 8)) as *i64 67 return s 68} 69 70// add an axis-aligned primitive; returns its index, or -1 if full. 71func nx_csg_scene_add(s: *NxCsgScene, p: *NxSdfPrim, op: i64, k: i64) -> i64 { 72 if s.n >= s.cap { return 0 - 1 } 73 let i: i64 = s.n 74 s.prims[i] = p as i64 75 s.ops[i] = op 76 s.ks[i] = k 77 s.rax[i] = NX_ROT_NONE 78 s.rang[i] = 0 79 s.rpx[i] = 0 80 s.rpy[i] = 0 81 s.rpz[i] = 0 82 s.n = i + 1 83 return i 84} 85 86// add a ROTATED primitive (axis about pivot, angle Q10 turn); returns its index or -1. 87func nx_csg_scene_add_rot(s: *NxCsgScene, p: *NxSdfPrim, op: i64, k: i64, 88 axis: i64, ang_q10: i64, px: i64, py: i64, pz: i64) -> i64 { 89 let i: i64 = nx_csg_scene_add(s, p, op, k) 90 if i < 0 { return i } 91 s.rax[i] = axis 92 s.rang[i] = ang_q10 93 s.rpx[i] = px 94 s.rpy[i] = py 95 s.rpz[i] = pz 96 return i 97} 98 99// signed distance of primitive i at world (x,y,z), applying its rotation if any. 100// Rotating the SHAPE by +ang = inverse-rotating the query point about the pivot. 101func nx_csg_scene_eval_i(s: *NxCsgScene, i: i64, x: i64, y: i64, z: i64) -> i64 { 102 let p: *NxSdfPrim = (s.prims[i]) as *NxSdfPrim 103 let ax: i64 = s.rax[i] 104 if ax == NX_ROT_NONE { return nx_sdf_eval(p, x, y, z) } 105 let c: i64 = nx_cos_turn_q10(s.rang[i]) 106 let sn: i64 = nx_sin_turn_q10(s.rang[i]) 107 if ax == NX_ROT_Y { 108 let dx: i64 = x - s.rpx[i] 109 let dz: i64 = z - s.rpz[i] 110 let qx: i64 = (dx * c + dz * sn) / NX_MAGIC_1024 111 let qz: i64 = (0 - dx * sn + dz * c) / NX_MAGIC_1024 112 return nx_sdf_eval(p, s.rpx[i] + qx, y, s.rpz[i] + qz) 113 } 114 if ax == NX_ROT_X { 115 let dy: i64 = y - s.rpy[i] 116 let dz: i64 = z - s.rpz[i] 117 let qy: i64 = (dy * c + dz * sn) / NX_MAGIC_1024 118 let qz: i64 = (0 - dy * sn + dz * c) / NX_MAGIC_1024 119 return nx_sdf_eval(p, x, s.rpy[i] + qy, s.rpz[i] + qz) 120 } 121 // NX_ROT_Z 122 let dx2: i64 = x - s.rpx[i] 123 let dy2: i64 = y - s.rpy[i] 124 let qx2: i64 = (dx2 * c + dy2 * sn) / NX_MAGIC_1024 125 let qy2: i64 = (0 - dx2 * sn + dy2 * c) / NX_MAGIC_1024 126 return nx_sdf_eval(p, s.rpx[i] + qx2, s.rpy[i] + qy2, z) 127} 128 129// fold the scene field at (x,y,z). 130func nx_csg_scene_field(s: *NxCsgScene, x: i64, y: i64, z: i64) -> i64 { 131 if s.n <= 0 { return 1 } // empty -> everywhere outside 132 var d: i64 = nx_csg_scene_eval_i(s, 0, x, y, z) 133 var i: i64 = 1 134 while i < s.n { 135 let di: i64 = nx_csg_scene_eval_i(s, i, x, y, z) 136 d = nx_csg_combine_k(s.ops[i], d, di, s.ks[i]) 137 i = i + 1 138 } 139 return d 140} 141 142// inside-sample count over a coarse grid (debug / feature-presence proofs). 143func nx_csg_scene_count_inside(s: *NxCsgScene, 144 lox: i64, loy: i64, loz: i64, 145 hix: i64, hiy: i64, hiz: i64, step: i64) -> i64 { 146 var c: i64 = 0 147 var z: i64 = loz 148 while z <= hiz { 149 var y: i64 = loy 150 while y <= hiy { 151 var x: i64 = lox 152 while x <= hix { 153 if nx_csg_scene_field(s, x, y, z) < 0 { c = c + 1 } 154 x = x + step 155 } 156 y = y + step 157 } 158 z = z + step 159 } 160 return c 161} 162 163// Extract the composed scene over the box [lo,hi] at grid resolution res into an NxMesh. 164// Mirrors nx_csg_extract's proven grid + 6-tet (Kuhn) marching loop; the ONLY change is 165// the field is the N-primitive scene fold instead of a 2-primitive boolean. 166func nx_csg_extract_scene(s: *NxCsgScene, 167 lox: i64, loy: i64, loz: i64, 168 hix: i64, hiy: i64, hiz: i64, res: i64) -> *NxMesh { 169 let np: i64 = res + 1 170 let field: *i64 = (sys_mmap(np * np * np * 8)) as *i64 171 var k: i64 = 0 172 while k <= res { 173 let z: i64 = loz + (hiz - loz) * k / res 174 var j: i64 = 0 175 while j <= res { 176 let y: i64 = loy + (hiy - loy) * j / res 177 var ii: i64 = 0 178 while ii <= res { 179 let x: i64 = lox + (hix - lox) * ii / res 180 field[ii + j*np + k*np*np] = nx_csg_scene_field(s, x, y, z) 181 ii = ii + 1 182 } 183 j = j + 1 184 } 185 k = k + 1 186 } 187 188 // 6-tet (Kuhn) decomposition along cube diagonal 0-7 (identical to nx_csg_extract) 189 let tets: *i64 = (sys_mmap(24 * 8)) as *i64 190 tets[0]=0;tets[1]=7;tets[2]=1;tets[3]=3; tets[4]=0;tets[5]=7;tets[6]=3;tets[7]=2 191 tets[8]=0;tets[9]=7;tets[10]=2;tets[11]=6; tets[12]=0;tets[13]=7;tets[14]=6;tets[15]=4 192 tets[16]=0;tets[17]=7;tets[18]=4;tets[19]=5; tets[20]=0;tets[21]=7;tets[22]=5;tets[23]=1 193 194 // generous tri cap (bigger constant than nx_csg_extract: multi-feature parts have 195 // more surface area; a too-small cap would silently DROP tris and break watertightness). 196 let cap: i64 = 48 * res * res + NX_MAGIC_65536 197 let tribuf: *i64 = (sys_mmap(cap * 9 * 8)) as *i64 198 let ntri: *i64 = (sys_mmap(8)) as *i64 199 ntri[0] = 0 200 201 let cp: *i64 = (sys_mmap(8 * 3 * 8)) as *i64 202 let cd: *i64 = (sys_mmap(8 * 8)) as *i64 203 let P: *i64 = (sys_mmap(12 * 8)) as *i64 204 let D: *i64 = (sys_mmap(4 * 8)) as *i64 205 let ins: *i64 = (sys_mmap(4 * 8)) as *i64 206 let outs: *i64 = (sys_mmap(4 * 8)) as *i64 207 let q0: *i64 = (sys_mmap(24)) as *i64 208 let q1: *i64 = (sys_mmap(24)) as *i64 209 let q2: *i64 = (sys_mmap(24)) as *i64 210 let q3: *i64 = (sys_mmap(24)) as *i64 211 212 var ck: i64 = 0 213 while ck < res { 214 var cj: i64 = 0 215 while cj < res { 216 var ci: i64 = 0 217 while ci < res { 218 var c: i64 = 0 219 while c < 8 { 220 let gi: i64 = ci + (c & 1) 221 let gj: i64 = cj + ((c >> 1) & 1) 222 let gk: i64 = ck + ((c >> 2) & 1) 223 cp[c*3+0] = lox + (hix - lox) * gi / res 224 cp[c*3+1] = loy + (hiy - loy) * gj / res 225 cp[c*3+2] = loz + (hiz - loz) * gk / res 226 cd[c] = field[gi + gj*np + gk*np*np] 227 c = c + 1 228 } 229 var t: i64 = 0 230 while t < 6 { 231 var v: i64 = 0 232 while v < 4 { 233 let corner: i64 = tets[t*4+v] 234 P[v*3+0] = cp[corner*3+0] 235 P[v*3+1] = cp[corner*3+1] 236 P[v*3+2] = cp[corner*3+2] 237 D[v] = cd[corner] 238 v = v + 1 239 } 240 mt_tet(tribuf, ntri, cap, P, D, ins, outs, q0, q1, q2, q3) 241 t = t + 1 242 } 243 ci = ci + 1 244 } 245 cj = cj + 1 246 } 247 ck = ck + 1 248 } 249 250 let nt: i64 = ntri[0] 251 if nt <= 0 { return 0 as *NxMesh } 252 let m: *NxMesh = nx_mesh_alloc(nt * 3, nt, 0) 253 if (m as i64) == 0 { return m } 254 var t2: i64 = 0 255 while t2 < nt { 256 let base: i64 = t2 * 9 257 nx_mesh_set_vertex(m, t2*3+0, tribuf[base+0], tribuf[base+1], tribuf[base+2], 0) 258 nx_mesh_set_vertex(m, t2*3+1, tribuf[base+3], tribuf[base+4], tribuf[base+5], 0) 259 nx_mesh_set_vertex(m, t2*3+2, tribuf[base+6], tribuf[base+7], tribuf[base+8], 0) 260 nx_mesh_set_triangle(m, t2, t2*3+0, t2*3+1, t2*3+2) 261 t2 = t2 + 1 262 } 263 return m 264}