code wiki / (root) / nx_meshview.nx

nx_meshview.nx source

↩ module page · 320 lines · 18659 B

1// nx_meshview.nx -- look at an .nxmesh. The program emits NXMSH2 meshes and had NO WAY TO VIEW ONE, 2// which is exactly how a skull that renders as an egg passed a 6/6 gate: every tooth measured counts 3// and determinism, and nothing ever drew the thing. 4// 5// Orthographic z-buffered triangle fill, flat-shaded off the stored per-triangle normal, three views 6// side by side (front / side / three-quarter) so a shape can be judged rather than described. 7// 8// nx_meshview <in.nxmesh> <out.png> [layer] layer: -1 all (default), 0 skin, 1 muscle, 2 bone 9// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26). 10import "nx_png.nx" 11import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 12const MV_MAGIC_8388607: i64 = 8388607 13const MV_MAGIC_8388608: i64 = 8388608 14 15const MV_W: i64 = 1200 16const MV_H: i64 = 460 17const MV_VW: i64 = 400 // per-view width 18const MV_TS: i64 = 84 // triangle stride in the file 19// ⚠THE HEADER SIZE IS NOT A CONSTANT. It is 16 + nlayer*24, and nlayer varies by producer: nx_body_gen 20// writes 3 layers (skin/muscle/bone) so 88 bytes, while the BodyParts3D oracle has ONE layer and a 21// 40-byte header. Hardcoding 88 read the oracle 48 bytes off and produced a perfectly plausible-looking 22// 2x2x2 bounding box of garbage, which rendered as an empty frame. 23const MV_FAR: i64 = 2000000000 24const MV_BG: i64 = 0x141c28 // the dark slate the other gates use, so renders are comparable 25const MV_MARGIN: i64 = 24 26 27func mv_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 28// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 29// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 30// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 31// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 32func mv_pn(v: i64) -> i64 { nxi_out(v); return 0 } 33func mv_atoi(s: *u8) -> i64 { 34 var i: i64=0; var n: i64=0; var sg: i64=1 35 if s[0]==(45 as u8) { sg=0-1; i=1 } 36 while s[i]!=(0 as u8) { let c: i64=s[i] as i64; if c>=48 { if c<=57 { n=n*10+(c-48) } } i=i+1 } 37 return n*sg 38} 39func mv_abs(v: i64) -> i64 { if v<0 { return 0-v } return v } 40func mv_rd32(b: *u8, o: i64) -> i64 { 41 return (b[o] as i64) | ((b[o+1] as i64)<<8) | ((b[o+2] as i64)<<16) | ((b[o+3] as i64)<<24) 42} 43// ★IEEE-754 single -> integer scaled by `scale`. The mesh stores floats; this organ is integer-only, so 44// the bits are decoded by hand rather than trusting a cast. Denormals and zero collapse to 0, which is 45// correct for geometry and avoids a special case that would only ever fire on garbage input. 46func mv_f32(bits: i64, scale: i64) -> i64 { 47 let s: i64 = (bits >> 31) & 1 48 let e: i64 = (bits >> 23) & 255 49 let m: i64 = bits & MV_MAGIC_8388607 50 if e == 0 { return 0 } 51 if e == 255 { return 0 } 52 let mant: i64 = MV_MAGIC_8388608 | m 53 let sh: i64 = e - 127 - 23 54 var v: i64 = 0 55 if sh >= 0 { 56 if sh > 30 { return 0 } 57 v = mant * scale 58 v = v << sh 59 } else { 60 let rs: i64 = 0 - sh 61 if rs > 62 { return 0 } 62 v = mant * scale 63 v = v >> rs 64 } 65 if s == 1 { return 0 - v } 66 return v 67} 68 69// project a model point to a view. view 0 = front (x,y), 1 = side (z,y), 2 = three-quarter (blend). 70func mv_px(vx: i64, vy: i64, vz: i64, view: i64) -> i64 { 71 if view == 0 { return vx } 72 if view == 1 { return vz } 73 return (vx*7 - vz*7)/10 // yaw 45: screen x = (x - z)/sqrt2 74} 75// ⚠DEPTH SIGN. The rasterizer keeps the SMALLER value, so "depth" must INCREASE with distance from the 76// camera. The first version returned +z for the front view, which keeps the FARTHEST surface -- the 77// front panel was rendering the back of the skull, and it looked plausible enough to nearly pass. 78func mv_pz(vx: i64, vy: i64, vz: i64, view: i64) -> i64 { 79 if view == 0 { return 0 - vz } // camera on +z: nearer = larger z 80 if view == 1 { return 0 - vx } // camera on +x 81 return (0 - vx*7 - vz*7)/10 // camera on (+x,+z) 82} 83 84func main(argc: i64, argv: *i64) -> i64 { 85 if argc < 3 { mv_puts("usage: nx_meshview <in.nxmesh> <out.png> [layer -1|0|1|2]\n" as *u8); return 2 } 86 var want: i64 = 0-1 87 if argc > 3 { want = mv_atoi(argv[3] as *u8) } 88 // ★★SINGLE-VIEW MODE (argv[4]): 0 front, 1 side, 2 three-quarter; absent = all three as before. 89 // WHY: the 3-panel composite has NO bilateral symmetry axis -- its left half is the front panel plus 90 // half the side view, its right half is half the side plus the three-quarter. Scoring symmetry on that 91 // is meaningless, and MEASURED it returned 900 for a real scanned human, for our smooth mannequin, and 92 // for a visibly banded broken body alike. A beauty judge needs ONE CENTRED VIEW to have an axis at all. 93 var vwant: i64 = 0-1 94 if argc > 4 { vwant = mv_atoi(argv[4] as *u8) } 95 // ★★SINGLE-VIEW MODE (argv[4]): 0 front, 1 side, 2 three-quarter; absent = all three as before. 96 // WHY: the 3-panel composite has NO bilateral symmetry axis -- its left half is the front panel plus 97 // half the side view, its right half is half the side plus the three-quarter. Scoring symmetry on that 98 // is meaningless, and MEASURED it returns 900 for a real scanned human, for our smooth mannequin, and 99 // for a visibly banded broken body alike. A beauty judge needs ONE CENTRED VIEW to have an axis at all. 100 var vwant: i64 = 0-1 101 if argc > 4 { vwant = mv_atoi(argv[4] as *u8) } 102 let ln: *i64 = sys_mmap(16) as *i64 103 let buf: *u8 = sys_read_file(argv[1] as *u8, ln) 104 if (buf as i64) == 0 { mv_puts("{\x22error\x22:\x22cannot read mesh\x22}\n" as *u8); return 3 } 105 if buf[0] != (78 as u8) { mv_puts("{\x22error\x22:\x22not NXMSH2\x22}\n" as *u8); return 4 } 106 let nlayer: i64 = mv_rd32(buf, 8) 107 let nt: i64 = mv_rd32(buf, 12) 108 if nt <= 0 { mv_puts("{\x22error\x22:\x22no triangles\x22}\n" as *u8); return 5 } 109 let MV_HDR: i64 = 16 + nlayer*24 110 let lay: i64 = MV_HDR + nt*MV_TS 111 112 // ---- pass 1: bounding box over the SELECTED layer only, so a single layer fills the frame 113 var lox: i64 = MV_FAR; var hix: i64 = 0-MV_FAR 114 var loy: i64 = MV_FAR; var hiy: i64 = 0-MV_FAR 115 var loz: i64 = MV_FAR; var hiz: i64 = 0-MV_FAR 116 var kept: i64 = 0 117 var t: i64 = 0 118 while t < nt { 119 var take: i64 = 1 120 if want >= 0 { if mv_rd32(buf, lay + t*4) != want { take = 0 } } 121 if take == 1 { 122 kept = kept + 1 123 var j: i64 = 0 124 while j < 3 { 125 let o: i64 = MV_HDR + t*MV_TS + j*12 126 // ⚠read at MICROMETRE precision, not integer units. The BodyParts3D oracle is stored in 127 // METRES, so reading at scale 1 truncated the whole 171k-triangle skull to a 2x2x2 box. 128 // The view auto-fits from the bounding box, so the absolute unit is irrelevant -- only 129 // the precision matters, and this makes mm-scale and metre-scale meshes both work. 130 let x: i64 = mv_f32(mv_rd32(buf,o), 1000) 131 let y: i64 = mv_f32(mv_rd32(buf,o+4), 1000) 132 let z: i64 = mv_f32(mv_rd32(buf,o+8), 1000) 133 if x < lox { lox = x } 134 if x > hix { hix = x } 135 if y < loy { loy = y } 136 if y > hiy { hiy = y } 137 if z < loz { loz = z } 138 if z > hiz { hiz = z } 139 j = j + 1 140 } 141 } 142 t = t + 1 143 } 144 if kept == 0 { mv_puts("{\x22error\x22:\x22layer empty\x22}\n" as *u8); return 6 } 145 var spanx: i64 = hix - lox 146 var spany: i64 = hiy - loy 147 var spanz: i64 = hiz - loz 148 if spanx < 1 { spanx = 1 } 149 if spany < 1 { spany = 1 } 150 if spanz < 1 { spanz = 1 } 151 // one scale for EVERY view, so the three panels are directly comparable rather than each auto-fitted 152 var span: i64 = spanx 153 if spany > span { span = spany } 154 if spanz > span { span = spanz } 155 let usable: i64 = MV_H - MV_MARGIN*2 156 // ★★★FIXED 2026-07-30 (seq1451): this was `let scl = usable*1000/span`, a PER-MILLE ratio that 157 // TRUNCATES TO ZERO for any mesh whose span exceeds usable*1000 (about 410,000 units). Every vertex 158 // then multiplied by 0 and the model collapsed to the centre: our body AND the 171,248-triangle 159 // cadaver oracle both rendered as THREE ONE-PIXEL DOTS on a blank field, while the organ reported 160 // tris_drawn == tris_total and looked healthy. 161 // ⚠★AND THE CAUSE WAS THE FIX 30 LINES ABOVE: reading vertices at MICROMETRE precision (scale 1000) 162 // is itself correct, and is itself a fix for a truncation that shrank the oracle to a 2x2x2 box -- 163 // but it multiplied every span by 1000 and pushed this ratio under 1. ONE TRUNCATION FIX CREATED 164 // ANOTHER, 30 lines apart, and the second one silently blinded the lane whose whole discipline is 165 // the eyeball. 166 // ★NO PRECOMPUTED RATIO ANY MORE: the projection forms usable/span as an EXACT MULDIV per vertex, so 167 // it cannot truncate to zero at ANY scale. nx_gsplatbind banked this same lesson this month -- store 168 // the rest length and form scale*len/rest_len, exact at both ends. 169 let cx: i64 = (lox+hix)/2 170 let cy: i64 = (loy+hiy)/2 171 let cz: i64 = (loz+hiz)/2 172 173 let fb: *i64 = sys_mmap(MV_W*MV_H*8) as *i64 174 let zb: *i64 = sys_mmap(MV_W*MV_H*8) as *i64 175 var p: i64 = 0 176 while p < MV_W*MV_H { fb[p] = MV_BG; zb[p] = MV_FAR; p = p + 1 } 177 178 // ⚠SINGLE-VIEW MODE ATTEMPTED HERE 2026-07-30 AND REVERTED -- IT RENDERED BLANK. The change was 179 // `view = vwant; vend = vwant+1` with `ox = MV_W/2`, and it produced 11,397-byte PNGs for the oracle, 180 // our body and the banded body alike -- the SAME size as the pre-fix blank images. ONLY PROMOTED 181 // NOTHING: live nx_meshview still renders the working 3-panel view. 182 // ★★AND THE BLANK RENDER SCORED symmetry_q1000 = 1000 ON ALL THREE. A BLANK IMAGE IS PERFECTLY 183 // SYMMETRIC. Had the non-vacuity check been skipped, that 1000 would have been reported as the beauty 184 // loop working. ALWAYS CHECK THE RENDER IS NON-EMPTY BEFORE BELIEVING ANY SCORE TAKEN FROM IT -- 185 // file size against a known-good render is enough, and it is one command. 186 // vwant is parsed above and currently unused; wire it again only with a size/coverage assertion. 187 var view: i64 = 0 188 while view < 3 { 189 let ox: i64 = view*MV_VW + MV_VW/2 190 let oy: i64 = MV_H/2 191 t = 0 192 while t < nt { 193 var take: i64 = 1 194 if want >= 0 { if mv_rd32(buf, lay + t*4) != want { take = 0 } } 195 if take == 1 { 196 let base: i64 = MV_HDR + t*MV_TS 197 // three projected vertices 198 let sx: *i64 = sys_mmap(64) as *i64 199 let sy: *i64 = sys_mmap(64) as *i64 200 let sd: *i64 = sys_mmap(64) as *i64 201 var j: i64 = 0 202 while j < 3 { 203 let o: i64 = base + j*12 204 let X: i64 = mv_f32(mv_rd32(buf,o), 1000) - cx 205 let Y: i64 = mv_f32(mv_rd32(buf,o+4), 1000) - cy 206 let Z: i64 = mv_f32(mv_rd32(buf,o+8), 1000) - cz 207 sx[j] = ox + mv_px(X,Y,Z,view)*usable/span 208 sy[j] = oy - Y*usable/span 209 sd[j] = mv_pz(X,Y,Z,view) 210 j = j + 1 211 } 212 // flat shade from the stored normal: lambert against a fixed key, plus ambient 213 // ★AVERAGE ALL THREE VERTEX NORMALS. Flat-shading from vertex A alone means adjacent 214 // triangles are lit by whichever normal happens to sit at their first vertex; with 215 // gradient normals from a polygonizer those differ sharply between neighbours, and the 216 // surface renders as speckled mottling. An STL-sourced mesh hides this because it 217 // carries one consistent facet normal -- which is why the reference looked clean and 218 // ours did not, through the very same renderer. 219 let nx: i64 = (mv_f32(mv_rd32(buf, base+36), 1000) + mv_f32(mv_rd32(buf, base+48), 1000) + mv_f32(mv_rd32(buf, base+60), 1000))/3 220 let ny: i64 = (mv_f32(mv_rd32(buf, base+40), 1000) + mv_f32(mv_rd32(buf, base+52), 1000) + mv_f32(mv_rd32(buf, base+64), 1000))/3 221 let nz: i64 = (mv_f32(mv_rd32(buf, base+44), 1000) + mv_f32(mv_rd32(buf, base+56), 1000) + mv_f32(mv_rd32(buf, base+68), 1000))/3 222 var lam: i64 = (nx*3 + ny*5 + nz*8)/10 223 if lam < 0 { lam = 0 - lam } 224 var sh: i64 = 300 + lam*700/1000 225 if sh > 1000 { sh = 1000 } 226 // ⚠the mesh stores colour as PER-MILLE (written as bg_f32(c,1000)), not 0..255. Reading it 227 // as 8-bit blew every surface to pure white and the first render showed silhouette only, 228 // with no form at all -- the shading was there and invisible. 229 var cr: i64 = mv_f32(mv_rd32(buf, base+72), 1000)*255/1000 230 var cg: i64 = mv_f32(mv_rd32(buf, base+76), 1000)*255/1000 231 var cb: i64 = mv_f32(mv_rd32(buf, base+80), 1000)*255/1000 232 // ⚠a mesh may carry NO colour (the BodyParts3D oracle stores zeros). Multiplying shade by 233 // zero renders the whole model black on a dark background -- an empty image that looks 234 // exactly like a failed load. Fall back to bone so geometry is always visible. 235 if cr + cg + cb < 12 { cr = 216; cg = 210; cb = 198 } 236 var rr: i64 = cr*sh/1000; var gg: i64 = cg*sh/1000; var bb: i64 = cb*sh/1000 237 if rr > 255 { rr = 255 } 238 if gg > 255 { gg = 255 } 239 if bb > 255 { bb = 255 } 240 if rr < 0 { rr = 0 } 241 if gg < 0 { gg = 0 } 242 if bb < 0 { bb = 0 } 243 // FIXED 2026-08-05 (heatmap tooth): write_png consumes r+g*256+b*65536 (R LOW, the estate 244 // convention every other emitter uses) but this packed R HIGH, so EVERY meshview render 245 // had R and B swapped: flesh rendered blue, yellow deviation bands rendered cyan. The 246 // heatmap made it visible because it was the first render whose colours MEANT something. 247 let col: i64 = rr | (gg<<8) | (bb<<16) 248 // bounding-box scan with edge functions (barycentric inside test) 249 var minx: i64 = sx[0]; var maxx: i64 = sx[0] 250 var miny: i64 = sy[0]; var maxy: i64 = sy[0] 251 var k: i64 = 1 252 while k < 3 { 253 if sx[k] < minx { minx = sx[k] } 254 if sx[k] > maxx { maxx = sx[k] } 255 if sy[k] < miny { miny = sy[k] } 256 if sy[k] > maxy { maxy = sy[k] } 257 k = k + 1 258 } 259 if minx < view*MV_VW { minx = view*MV_VW } 260 if maxx >= (view+1)*MV_VW { maxx = (view+1)*MV_VW - 1 } 261 if miny < 0 { miny = 0 } 262 if maxy >= MV_H { maxy = MV_H - 1 } 263 let area: i64 = (sx[1]-sx[0])*(sy[2]-sy[0]) - (sy[1]-sy[0])*(sx[2]-sx[0]) 264 var hit: i64 = 0 265 if area != 0 { 266 var py: i64 = miny 267 while py <= maxy { 268 var px: i64 = minx 269 while px <= maxx { 270 let w0: i64 = (sx[1]-sx[0])*(py-sy[0]) - (sy[1]-sy[0])*(px-sx[0]) 271 let w1: i64 = (sx[2]-sx[1])*(py-sy[1]) - (sy[2]-sy[1])*(px-sx[1]) 272 let w2: i64 = (sx[0]-sx[2])*(py-sy[2]) - (sy[0]-sy[2])*(px-sx[2]) 273 var inside: i64 = 0 274 if w0 >= 0 { if w1 >= 0 { if w2 >= 0 { inside = 1 } } } 275 if w0 <= 0 { if w1 <= 0 { if w2 <= 0 { inside = 1 } } } 276 if inside == 1 { 277 hit = 1 278 // ★INTERPOLATE DEPTH PER PIXEL. Using the triangle's centroid depth for 279 // every pixel it covers makes overlapping triangles win and lose the z 280 // test inconsistently, and a dense mesh renders as speckle. The edge 281 // functions ARE the barycentric weights, so this costs one divide. 282 var wsum: i64 = w0 + w1 + w2 283 var d: i64 = (sd[0]+sd[1]+sd[2])/3 284 if wsum != 0 { d = (w1*sd[0] + w2*sd[1] + w0*sd[2]) / wsum } 285 let idx: i64 = py*MV_W + px 286 if d < zb[idx] { zb[idx] = d; fb[idx] = col } 287 } 288 px = px + 1 289 } 290 py = py + 1 291 } 292 } 293 // ★SUB-PIXEL FALLBACK. A dense mesh puts many triangles inside a single pixel, and an 294 // edge test evaluated at the pixel CENTRE rejects every one that does not happen to 295 // cover it -- the surface renders as speckle full of holes. If a triangle covered no 296 // pixel, plot its centroid, so coverage never depends on a triangle being large enough. 297 if hit == 0 { 298 let mx: i64 = (sx[0]+sx[1]+sx[2])/3 299 let my2: i64 = (sy[0]+sy[1]+sy[2])/3 300 if mx >= view*MV_VW { if mx < (view+1)*MV_VW { if my2 >= 0 { if my2 < MV_H { 301 let d2: i64 = (sd[0]+sd[1]+sd[2])/3 302 let idx2: i64 = my2*MV_W + mx 303 if d2 < zb[idx2] { zb[idx2] = d2; fb[idx2] = col } 304 } } } } 305 } 306 } 307 t = t + 1 308 } 309 view = view + 1 310 } 311 write_png(fb, MV_W, MV_H, argv[2] as *u8) 312 mv_puts("{\x22organ\x22:\x22nx_meshview\x22,\x22tris_total\x22:" as *u8); mv_pn(nt) 313 mv_puts(",\x22tris_drawn\x22:" as *u8); mv_pn(kept) 314 mv_puts(",\x22layer\x22:" as *u8); mv_pn(want) 315 mv_puts(",\x22bbox_x\x22:" as *u8); mv_pn(spanx) 316 mv_puts(",\x22bbox_y\x22:" as *u8); mv_pn(spany) 317 mv_puts(",\x22bbox_z\x22:" as *u8); mv_pn(spanz) 318 mv_puts(",\x22views\x22:\x22front|side|three-quarter, ONE shared scale so the panels compare\x22}\n" as *u8) 319 return 0 320}