code wiki / (root) / nx_modelfacts_lib.nx

nx_modelfacts_lib.nx source

↩ module page · 185 lines · 7449 B

1// nx_modelfacts_lib.nx -- 3D MODEL FACT EXTRACTION CORE (C1 of the conversion tier, 2// knowledge/render_docs_ingest_program.txt). GLB / STL / OBJ / PLY headers -> closed-vocabulary 3// integer measurements for the refcorpus plane. Completes the MEASURE half of mesh rung R2 and 4// the twice-queued glb fact-extractor in one composer. 5// 6// Same walls as nx_mediafacts_lib: no byte from the model file ever flows to output (computed 7// integers + fixed labels only), and -1 means NOT MEASURED -- never a fabricated zero. 8// ★TRUNCATION HONESTY: text-format counts (OBJ/PLY/ascii-STL) and GLB token counts are only 9// emitted when the RELEVANT REGION fits the read window; a count over a truncated file would be 10// a plausible undercount, which is worse than absence. Binary STL's count comes from its HEADER 11// and is verified against the true file size, so it is window-safe. 12// 13// Fact slots (flat i64, MDL_N_SLOTS): 14// [0] format 0=unknown 1=glb 2=stl 3=obj 4=ply 15// [1] tris (stl) [2] verts (obj/ply) [3] faces (obj/ply) [4] uv_present 16// [5] glb_json_bytes [6] glb_bin_bytes [7] glb_accessors [8] glb_skinned_prims 17// [9] vrm_present 18// license_tier: ORIGINAL No hw writes (Rule 26). 19import "nx_syscalls.nx" 20const MDL_MAGIC_4096: i64 = 4096 21const MDL_MAGIC_65536: i64 = 65536 22 23const MDL_N_SLOTS: i64 = 10 24const MDL_FMT_UNKNOWN: i64 = 0 25const MDL_FMT_GLB: i64 = 1 26const MDL_FMT_STL: i64 = 2 27const MDL_FMT_OBJ: i64 = 3 28const MDL_FMT_PLY: i64 = 4 29 30func mdl_b(buf: *u8, o: i64) -> i64 { return buf[o] as i64 & 0xff } 31func mdl_u32le(b: *u8, o: i64) -> i64 { return (b[o] as i64 & 0xff) | ((b[o+1] as i64 & 0xff) << 8) | ((b[o+2] as i64 & 0xff) << 16) | ((b[o+3] as i64 & 0xff) << 24) } 32func mdl_align4(x: i64) -> i64 { return (x + 3) / 4 * 4 } 33func mdl_nlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 34 35// count occurrences of needle in buf[start,end) 36func mdl_count(buf: *u8, start: i64, end: i64, needle: *u8) -> i64 { 37 let m: i64 = mdl_nlen(needle) 38 var c: i64 = 0 39 var i: i64 = start 40 while i + m <= end { 41 var k: i64 = 0 42 while k < m { if (buf[i+k] as i64) != (needle[k] as i64) { k = m + 9 } else { k = k + 1 } } 43 if k == m { c = c + 1; i = i + m } else { i = i + 1 } 44 } 45 return c 46} 47 48// first integer following needle in buf[start,end), -1 absent (the gll_nth_num shape, n=1) 49func mdl_num_after(buf: *u8, start: i64, end: i64, needle: *u8) -> i64 { 50 let m: i64 = mdl_nlen(needle) 51 var i: i64 = start 52 while i + m <= end { 53 var k: i64 = 0 54 while k < m { if (buf[i+k] as i64) != (needle[k] as i64) { k = m + 9 } else { k = k + 1 } } 55 if k == m { 56 var p: i64 = i + m 57 while p < end { if (buf[p] as i64) == 32 { p = p + 1 } else { 58 var v: i64 = 0 59 var got: i64 = 0 60 var sc: i64 = 0 61 while sc == 0 { 62 if p >= end { sc = 1 } else { 63 let c: i64 = buf[p] as i64 64 if c >= 48 { if c <= 57 { v = v*10 + (c - 48); got = 1; p = p + 1 } else { sc = 1 } } else { sc = 1 } 65 } 66 } 67 if got == 1 { return v } 68 return 0 - 1 69 } } 70 return 0 - 1 71 } 72 i = i + 1 73 } 74 return 0 - 1 75} 76 77func mdl_starts(buf: *u8, n: i64, s: *u8) -> i64 { 78 let m: i64 = mdl_nlen(s) 79 if n < m { return 0 } 80 var i: i64 = 0 81 while i < m { if (buf[i] as i64) != (s[i] as i64) { return 0 } i = i + 1 } 82 return 1 83} 84 85// count lines in [0,end) whose first bytes (at line start) match tag followed by space 86func mdl_lines(buf: *u8, end: i64, c0: i64, c1: i64) -> i64 { 87 var c: i64 = 0 88 var i: i64 = 0 89 var at_start: i64 = 1 90 while i < end { 91 if at_start == 1 { 92 if (buf[i] as i64) == c0 { 93 if c1 == 32 { if i + 1 < end { if (buf[i+1] as i64) == 32 { c = c + 1 } } } 94 else { if i + 2 < end { if (buf[i+1] as i64) == c1 { if (buf[i+2] as i64) == 32 { c = c + 1 } } } } 95 } 96 } 97 if (buf[i] as i64) == 10 { at_start = 1 } else { at_start = 0 } 98 i = i + 1 99 } 100 return c 101} 102 103// is the text-ish window plausibly an OBJ? first 4KB must contain a "v " line 104func mdl_looks_obj(buf: *u8, n: i64) -> i64 { 105 var scan: i64 = n 106 if scan > MDL_MAGIC_4096 { scan = MDL_MAGIC_4096 } 107 if mdl_lines(buf, scan, 118, 32) > 0 { return 1 } 108 return 0 109} 110 111// probe: buf holds the first `n` bytes; file_size is the TRUE size (drained by the caller) so 112// window truncation is a KNOWN fact, not a silent one. Slots stay -1 where not measured. 113func mdl_probe(buf: *u8, n: i64, file_size: i64, facts: *i64) -> i64 { 114 var i: i64 = 0 115 while i < MDL_N_SLOTS { facts[i] = 0 - 1; i = i + 1 } 116 facts[0] = MDL_FMT_UNKNOWN 117 if n < 8 { return 0 } 118 let truncated: i64 = (file_size > n) as i64 119 120 // ---- GLB: "glTF" magic, u32 version, u32 length; chunk0 JSON, chunk1 BIN ---- 121 if mdl_starts(buf, n, "glTF" as *u8) == 1 { 122 facts[0] = MDL_FMT_GLB 123 if n < 20 { return 0 } 124 let jlen: i64 = mdl_u32le(buf, 12) 125 facts[5] = jlen 126 let jend: i64 = 20 + jlen 127 let boff: i64 = 20 + mdl_align4(jlen) 128 if boff + 8 <= n { facts[6] = mdl_u32le(buf, boff) } 129 if jend <= n { 130 facts[7] = mdl_count(buf, 20, jend, "componentType" as *u8) 131 facts[8] = mdl_count(buf, 20, jend, "JOINTS_0" as *u8) 132 var uv: i64 = 0 133 if mdl_count(buf, 20, jend, "TEXCOORD_0" as *u8) > 0 { uv = 1 } 134 facts[4] = uv 135 var vrm: i64 = 0 136 if mdl_count(buf, 20, jend, "VRMC_" as *u8) > 0 { vrm = 1 } 137 if mdl_count(buf, 20, jend, "\"VRM\"" as *u8) > 0 { vrm = 1 } 138 facts[9] = vrm 139 } 140 return 0 141 } 142 143 // ---- PLY: "ply" + newline; counts from header lines (always near the top) ---- 144 if mdl_starts(buf, n, "ply" as *u8) == 1 { 145 facts[0] = MDL_FMT_PLY 146 var hend: i64 = n 147 if hend > MDL_MAGIC_65536 { hend = MDL_MAGIC_65536 } 148 facts[2] = mdl_num_after(buf, 0, hend, "element vertex " as *u8) 149 facts[3] = mdl_num_after(buf, 0, hend, "element face " as *u8) 150 return 0 151 } 152 153 // ---- ascii STL: "solid"; tri count = endfacet occurrences (window-honest) ---- 154 if mdl_starts(buf, n, "solid" as *u8) == 1 { 155 facts[0] = MDL_FMT_STL 156 if truncated == 0 { facts[1] = mdl_count(buf, 0, n, "endfacet" as *u8) } 157 return 0 158 } 159 160 // ---- binary STL: NO magic -- the tri count at byte 80 must RECONSTRUCT the true file size 161 // (84 + 50*n). Without that equation any binary blob would false-positive. ---- 162 if file_size >= 84 { if n >= 84 { 163 let tc: i64 = mdl_u32le(buf, 80) 164 if tc >= 0 { if file_size == 84 + tc * 50 { 165 facts[0] = MDL_FMT_STL 166 facts[1] = tc 167 facts[4] = 0 168 return 0 169 } } 170 } } 171 172 // ---- OBJ: plain text with v-lines (no magic; sniff the first 4KB) ---- 173 if mdl_looks_obj(buf, n) == 1 { 174 facts[0] = MDL_FMT_OBJ 175 if truncated == 0 { 176 facts[2] = mdl_lines(buf, n, 118, 32) // "v " 177 facts[3] = mdl_lines(buf, n, 102, 32) // "f " 178 var uv2: i64 = 0 179 if mdl_lines(buf, n, 118, 116) > 0 { uv2 = 1 } // "vt " 180 facts[4] = uv2 181 } 182 return 0 183 } 184 return 0 185}