code wiki / (root) / nx_nxmesh_lib.nx

nx_nxmesh_lib.nx source

↩ module page · 148 lines · 8878 B

1// nx_nxmesh_lib.nx -- THE ONE NXMSH2 LAYOUT (2026-08-24). Extracted, never retyped. 2// 3// nx_bodybench spells this layout inline (`16 + oNl*24`, `oTB + oNt*84`, `triBase + t*84 + v*12`) and 4// nx_gltf2mesh declares it again as G2_HDR / G2_LAYROW / G2_TRIREC. Two rulers for one file format is 5// how a format drifts: change one, the other still parses and silently reads the wrong window. This lib 6// is the single owner; the two incumbents are the next dedupe (procgen.plan PG19 class). 7// 8// Layout, read from both incumbents and agreeing: 9// header 16 bytes: magic at 0, nlayers u32 LE at 8, ntris u32 LE at 12 10// layers: nlayers records of 24 bytes starting at 16 11// triangles: ntris records of 84 bytes; vertex v of triangle t at tri_base + t*84 + v*12, three 12// little-endian float32 x y z at +0 +4 +8. The next 36 bytes carry three per-vertex normals and the 13// final 12 carry ONE per-TRIANGLE RGB colour -- see the colour codec at the foot of this file. 14// license_tier: ORIGINAL 15 16import "nx_syscalls.nx" 17import "nx_vecmath.nx" 18 19const NM_HDR: i64 = 16 20const NM_OFF_NLAYERS: i64 = 8 21const NM_OFF_NTRIS: i64 = 12 22const NM_LAYER_REC: i64 = 24 23const NM_TRI_REC: i64 = 84 24const NM_VERT_STRIDE: i64 = 12 25const NM_F32_BYTES: i64 = 4 26 27// ---- THE POSITION UNIT: CORRECTED 2026-08-26, AND THE OLD VALUE WAS UNFALSIFIABLE BY ANY ROUND TRIP. 28// This file used to declare NM_UNIT_PER_M = 10000 with the comment "Metres in the file", and BOTH 29// nm_coord and nm_put_tri used it -- so the constant CANCELS in an encode-decode round trip. Every 30// round-trip tooth in nx_mmdev_gate and nx_xport_gate passed while the decode was wrong by 1000x on 31// every real file, because those gates write their fixtures through the same constant they read. 32// A CONSTANT THAT CANCELS IN A ROUND TRIP IS INVISIBLE TO EVERY ROUND-TRIP TEST -- ONLY A REAL 33// PRODUCER'S FILE CAN FALSIFY IT. 34// 35// MEASURED: the file holds float32 MILLIMETRES. Four witnesses, not one of them a round trip: 36// 1. nx_meshdist's md_f32q multiplies the raw float by MD_Q_PER_MM = 100 to reach 0.01 mm quanta. 37// 2. Run on nx_gltf2mesh output it reports mean 64.9 mm / p95 230.7 mm / max 305.7 mm between two 38// human bodies -- physically sensible, and matching the per-part table already published on 39// /exceed/vrm/seed-san (torso 5-17 mm, fingertips ~350 mm). Read as metres those are 0.065 mm. 40// 3. nx_gltf2mesh prints maxy_mm=1579 for the very file it writes, and md_paint's height ratio 41// 1579/1594 = 990 permille comes out exactly right THROUGH the millimetre decode. 42// 4. nx_mmdev REFUSED every real mesh on its 2.5 m extent bound -- correctly, because the old decode 43// inflated a 1.594 m body to 1594 "metres" and the point-triangle products then overflow i64. 44// The old value did not cause a small error: it meant nx_mmdev, this estate's EXACT surface-deviation 45// ruler, could not read a single mesh that any producer in this estate actually writes. 46const NM_FILE_UNIT_PER_M: i64 = 1000 // the FILE's own unit: millimetres, measured above 47const NM_UNIT_PER_M: i64 = 10000 // the RULER's unit: tenths of a mm (nx_xport_lib reads this) 48// DERIVED, not picked: NM_UNIT_PER_M / NM_FILE_UNIT_PER_M = 10000/1000. Spelled as a literal because 49// this dialect is single-pass; nx_devpaint_gate asserts the three constants still satisfy that identity, 50// so the derivation cannot silently drift. 51const NM_UNIT_PER_FILE_UNIT: i64 = 10 52 53// ---- THE COLOUR CODEC: ONE OWNER, ADDED 2026-08-26 --------------------------------------------- 54// The final 12 bytes of a triangle record are ONE RGB colour for the whole triangle, three float32. 55// The stored value is a per-mille colour ALREADY divided by NM_COL_PERMIL -- i.e. a float in 0..1. 56// Before this lib owned it, FOUR organs each spelled the codec themselves and they did not agree: 57// nx_meshview reads mv_f32(w,1000)*255/1000 -- correct; its own comment names the convention 58// nx_meshdist writes md_enc(permil, 1000) -- correct 59// nx_mesh2glb read mg_f32i(w,255)/1000 -- WRONG. mg_f32i already returns value*scale, so a 60// legal 0.9 decodes to (0.9*255)/1000 = 0, and zero 61// on all three channels then trips that organ's own 62// bone fallback. Every painted deviation heatmap 63// emitted as UNIFORM BONE while the organ printed 64// "per-tri colour carried". PROVEN BY CONTROL: the 65// .glb built from a fully-painted 120,704-triangle 66// heatmap was BYTE-IDENTICAL to the .glb built from 67// the same mesh unpainted -- not lossy, TOTAL. 68// nx_bvhfk writes fk_f32bits(900) -- stores 900.0, not 0.9: a fourth spelling, which 69// nx_meshview then clamps to saturated white. 70// FOUR SPELLINGS OF ONE WIRE FORMAT IS NOT A STYLE PROBLEM: two were silently wrong, and both failed 71// in the flattering direction -- a plausible picture rather than an error anyone could see. 72const NM_OFF_COLOUR: i64 = 72 // byte offset of the colour triple within a triangle record 73const NM_COL_PERMIL: i64 = 1000 // the stored float is permille/NM_COL_PERMIL, i.e. 0..1 74const NM_COL_U8_MAX: i64 = 255 // glTF COLOR_0 is normalized UNSIGNED_BYTE 75const NM_COL_CHANNELS: i64 = 3 76 77func nm_u32(b: *u8, o: i64) -> i64 { 78 return (b[o] as i64) | ((b[o+1] as i64) << 8) | ((b[o+2] as i64) << 16) | ((b[o+3] as i64) << 24) 79} 80func nm_put_u32(b: *u8, o: i64, v: i64) -> i64 { 81 b[o] = (v & 255) as u8; b[o+1] = ((v >> 8) & 255) as u8 82 b[o+2] = ((v >> 16) & 255) as u8; b[o+3] = ((v >> 24) & 255) as u8 83 return 0 84} 85 86func nm_nlayers(b: *u8) -> i64 { return nm_u32(b, NM_OFF_NLAYERS) } 87func nm_ntris(b: *u8) -> i64 { return nm_u32(b, NM_OFF_NTRIS) } 88func nm_tri_base(b: *u8) -> i64 { return NM_HDR + nm_nlayers(b) * NM_LAYER_REC } 89 90// Vertex v (0..2) of triangle t, one coordinate axis (0..2), in NM_UNIT_PER_M units (tenths of a mm). 91func nm_coord(b: *u8, tri_base: i64, t: i64, v: i64, axis: i64) -> i64 { 92 let o: i64 = tri_base + t * NM_TRI_REC + v * NM_VERT_STRIDE + axis * NM_F32_BYTES 93 return vm_f32_to_int(nm_u32(b, o), NM_UNIT_PER_FILE_UNIT) 94} 95 96// Total byte size of a file holding nl layers and nt triangles -- so a WRITER can size its buffer from 97// the same constants a READER uses, instead of each side counting bytes by hand. 98func nm_file_bytes(nl: i64, nt: i64) -> i64 { return NM_HDR + nl * NM_LAYER_REC + nt * NM_TRI_REC } 99 100// Write a triangle's three vertices (coordinates in NM_UNIT_PER_M units) into a buffer laid out by 101// nm_file_bytes. Attribute bytes are left as the caller found them. 102func nm_put_tri(b: *u8, tri_base: i64, t: i64, xyz9: *i64) -> i64 { 103 var v: i64 = 0 104 while v < 3 { 105 var a: i64 = 0 106 while a < 3 { 107 let o: i64 = tri_base + t * NM_TRI_REC + v * NM_VERT_STRIDE + a * NM_F32_BYTES 108 nm_put_u32(b, o, vm_int_to_f32(xyz9[v*3 + a], NM_UNIT_PER_FILE_UNIT)) 109 a = a + 1 110 } 111 v = v + 1 112 } 113 return 0 114} 115 116// ---- colour accessors ------------------------------------------------------------------------- 117// raw float32 bits -> per-mille 0..1000 (nx_meshview's native colour unit) 118func nm_col_permil_of(bits: i64) -> i64 { 119 var c: i64 = vm_f32_to_int(bits, NM_COL_PERMIL) 120 if c < 0 { c = 0 } 121 if c > NM_COL_PERMIL { c = NM_COL_PERMIL } 122 return c 123} 124// raw float32 bits -> 0..255 for a glTF COLOR_0 byte. Scaled in ONE step and never permille-then- 125// divide: that divide is exactly what made nx_mesh2glb return 0 for every legal colour. 126func nm_col_u8_of(bits: i64) -> i64 { 127 var c: i64 = vm_f32_to_int(bits, NM_COL_U8_MAX) 128 if c < 0 { c = 0 } 129 if c > NM_COL_U8_MAX { c = NM_COL_U8_MAX } 130 return c 131} 132// per-mille 0..1000 -> the float32 bits this format stores 133func nm_col_bits(permil: i64) -> i64 { return vm_int_to_f32(permil, NM_COL_PERMIL) } 134 135// raw colour word of channel ch (0=R 1=G 2=B) of triangle t 136func nm_col_word(b: *u8, tri_base: i64, t: i64, ch: i64) -> i64 { 137 return nm_u32(b, tri_base + t * NM_TRI_REC + NM_OFF_COLOUR + ch * NM_F32_BYTES) 138} 139func nm_col_get(b: *u8, tri_base: i64, t: i64, ch: i64) -> i64 { 140 return nm_col_permil_of(nm_col_word(b, tri_base, t, ch)) 141} 142func nm_col_put(b: *u8, tri_base: i64, t: i64, r: i64, g: i64, bl: i64) -> i64 { 143 let o: i64 = tri_base + t * NM_TRI_REC + NM_OFF_COLOUR 144 nm_put_u32(b, o, nm_col_bits(r)) 145 nm_put_u32(b, o + NM_F32_BYTES, nm_col_bits(g)) 146 nm_put_u32(b, o + NM_F32_BYTES * 2, nm_col_bits(bl)) 147 return 0 148}