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}