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}