code wiki / (root) / nx_sdf.nx

nx_sdf.nx source

↩ module page · 120 lines · 5661 B

1// nx_sdf.nx -- signed distance fields for solid primitives + boolean combinators, 2// the algebra under CSG (Constructive Solid Geometry). A primitive returns a Q14-mm 3// signed distance (negative = inside). Booleans are min/max combinators -- which is 4// why SDF CSG is UNCONDITIONALLY robust: union/difference/intersection can never fail 5// or self-intersect the way B-rep mesh booleans do on degenerate geometry. Sign- 6// correct fields (the zero-isosurface is the exact surface) are all marching needs. 7// Reuses nx_isqrt. No dup: grepped, no nx_sdf/csg existed. license_tier: ORIGINAL 8 9import "nx_syscalls.nx" 10import "nx_isqrt.nx" 11 12const NX_SDF_SPHERE: i64 = 0 13const NX_SDF_BOX: i64 = 1 14const NX_SDF_CYL: i64 = 2 // axis = Z 15const NX_SDF_TORUS: i64 = 3 // hole along Z; a = major R, b = minor r 16const NX_SDF_ROUNDBOX: i64 = 4 // a,b,c = half extents; d = corner round radius 17 18// kind + centre (cx,cy,cz) + params a,b,c: 19// sphere: a = radius 20// box: a,b,c = half extents (x,y,z) 21// cylinder: a = radius, b = half height (along Z) 22struct NxSdfPrim { kind: i64, cx: i64, cy: i64, cz: i64, a: i64, b: i64, c: i64, d: i64 } 23 24func sdf_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 25func sdf_max(a: i64, b: i64) -> i64 { if a > b { return a } return b } 26func sdf_min(a: i64, b: i64) -> i64 { if a < b { return a } return b } 27 28func nx_sdf_make(kind: i64, cx: i64, cy: i64, cz: i64, a: i64, b: i64, c: i64) -> *NxSdfPrim { 29 let p: *NxSdfPrim = (sys_mmap(64)) as *NxSdfPrim 30 p.kind = kind; p.cx = cx; p.cy = cy; p.cz = cz; p.a = a; p.b = b; p.c = c; p.d = 0 31 return p 32} 33 34// 4-param variant (the 4th param `d` feeds primitives that need an extra scalar, 35// e.g. the rounded box's corner radius). Existing callers keep using nx_sdf_make. 36func nx_sdf_make4(kind: i64, cx: i64, cy: i64, cz: i64, a: i64, b: i64, c: i64, d: i64) -> *NxSdfPrim { 37 let p: *NxSdfPrim = (sys_mmap(64)) as *NxSdfPrim 38 p.kind = kind; p.cx = cx; p.cy = cy; p.cz = cz; p.a = a; p.b = b; p.c = c; p.d = d 39 return p 40} 41 42// signed distance (Q14 mm) of point (x,y,z) to primitive p. <0 = inside. 43func nx_sdf_eval(p: *NxSdfPrim, x: i64, y: i64, z: i64) -> i64 { 44 let dx: i64 = x - p.cx 45 let dy: i64 = y - p.cy 46 let dz: i64 = z - p.cz 47 if p.kind == NX_SDF_SPHERE { 48 return nx_isqrt(dx * dx + dy * dy + dz * dz) - p.a 49 } 50 if p.kind == NX_SDF_BOX { 51 // sign-correct box field: zero-set is exactly the box surface 52 return sdf_max(sdf_max(sdf_abs(dx) - p.a, sdf_abs(dy) - p.b), sdf_abs(dz) - p.c) 53 } 54 if p.kind == NX_SDF_TORUS { 55 // distance from the ring of radius a (major) in the XY plane, then minus b (minor) 56 let q: i64 = nx_isqrt(dx * dx + dy * dy) - p.a 57 return nx_isqrt(q * q + dz * dz) - p.b 58 } 59 if p.kind == NX_SDF_ROUNDBOX { 60 // EXACT (Euclidean) box SDF minus the round radius d -> rounded edges/corners 61 let qx: i64 = sdf_abs(dx) - p.a 62 let qy: i64 = sdf_abs(dy) - p.b 63 let qz: i64 = sdf_abs(dz) - p.c 64 let ox: i64 = sdf_max(qx, 0) 65 let oy: i64 = sdf_max(qy, 0) 66 let oz: i64 = sdf_max(qz, 0) 67 let outside: i64 = nx_isqrt(ox * ox + oy * oy + oz * oz) 68 let inside: i64 = sdf_min(sdf_max(qx, sdf_max(qy, qz)), 0) 69 return outside + inside - p.d 70 } 71 // cylinder along Z: radial distance & axial slab, intersected 72 let dr: i64 = nx_isqrt(dx * dx + dy * dy) - p.a 73 let dh: i64 = sdf_abs(dz) - p.b 74 return sdf_max(dr, dh) 75} 76 77const NX_CSG_UNION: i64 = 0 78const NX_CSG_INTERSECT: i64 = 1 79const NX_CSG_DIFF: i64 = 2 // A minus B 80 81// combine two signed distances under a boolean op. 82func nx_csg_combine(op: i64, da: i64, db: i64) -> i64 { 83 if op == NX_CSG_UNION { return sdf_min(da, db) } 84 if op == NX_CSG_INTERSECT { return sdf_max(da, db) } 85 return sdf_max(da, 0 - db) // A - B (subtract B by intersecting with its complement) 86} 87 88// the combined field of a two-primitive CSG scene. 89func nx_csg_field(a: *NxSdfPrim, b: *NxSdfPrim, op: i64, x: i64, y: i64, z: i64) -> i64 { 90 return nx_csg_combine(op, nx_sdf_eval(a, x, y, z), nx_sdf_eval(b, x, y, z)) 91} 92 93// ===== SMOOTH booleans (fillets) ================================= 94// 95// Polynomial smooth-minimum (iquilezles form) in Q14: blends two fields over a 96// radius k_q14, producing a fillet at the junction. This is where SDF CSG is 97// genuinely AHEAD of B-rep: a fillet that is unconditionally robust + a single 98// scalar parameter, no edge-loop surgery, never fails on tight geometry. 99func nx_csg_smin(a: i64, b: i64, k: i64) -> i64 { 100 if k <= 0 { if a < b { return a } return b } 101 var hq: i64 = 8192 + (8192 * (b - a)) / k // h = clamp(0.5 + 0.5*(b-a)/k) in Q14 102 if hq < 0 { hq = 0 } 103 if hq > 16384 { hq = 16384 } 104 let term1: i64 = b + ((a - b) * hq) / 16384 // mix(b,a,h) 105 let hh: i64 = (hq * (16384 - hq)) / 16384 // h*(1-h) (Q14) 106 let term2: i64 = (k * hh) / 16384 // k*h*(1-h) 107 return term1 - term2 108} 109 110// smooth combine; k_q14<=0 falls back to the sharp boolean (so callers can pass 0). 111func nx_csg_combine_k(op: i64, da: i64, db: i64, k: i64) -> i64 { 112 if k <= 0 { return nx_csg_combine(op, da, db) } 113 if op == NX_CSG_UNION { return nx_csg_smin(da, db, k) } 114 if op == NX_CSG_INTERSECT { return 0 - nx_csg_smin(0 - da, 0 - db, k) } // smooth max 115 return 0 - nx_csg_smin(0 - da, db, k) // smooth difference A - B 116} 117 118func nx_csg_field_k(a: *NxSdfPrim, b: *NxSdfPrim, op: i64, k: i64, x: i64, y: i64, z: i64) -> i64 { 119 return nx_csg_combine_k(op, nx_sdf_eval(a, x, y, z), nx_sdf_eval(b, x, y, z), k) 120}