code wiki / (root) / nx_obj_import.nx

nx_obj_import.nx source

↩ module page · 405 lines · 16515 B

1// nx_obj_import.nx -- sovereign Wavefront OBJ READER. The missing half of nx_obj_export: the 2// ecosystem could WRITE meshes and could not READ them, so no externally-authored geometry could 3// ever enter the pipeline. This is the ingest gate for any licensed/CC scan or sculpt. 4// 5// ★BUILT FOR REAL-WORLD OBJ, NOT JUST OUR OWN EXPORT. Files from Blender/scanners/Commons differ 6// from what nx_obj_export emits in three ways that each break a naive parser: 7// 1. FLOATING-POINT vertex coords ("v 0.123456 -1.5 2.0e-1") -- our exporter writes integers. 8// Parsed into fx1024 fixed point, including exponent notation. 9// 2. NEGATIVE face indices (-1 = most recent vertex), the relative form in the OBJ spec. 10// 3. N-GONS (quads are everywhere in DCC exports) -- fan-triangulated, not rejected. 11// Also tolerates v/vt/vn face forms (a, a/b, a//c, a/b/c), CRLF, and unknown directives. 12// 13// verts land in fx1024 units, matching nx_obj_export/nx_meshgen so the mesh stack composes. 14// usage as a lib: oi_read(path, vbuf, fbuf, vcap, fcap, counts) -> 0 ok, negative on error 15// counts[0]=nv counts[1]=nf 16// license_tier: ORIGINAL 17import "nx_syscalls.nx" 18 19const OI_FX: i64 = 1024 20 21func oi_is_digit(c: i64) -> i64 { if c < 48 { return 0 } if c > 57 { return 0 } return 1 } 22// advance p past spaces/tabs 23func oi_ws(buf: *u8, n: i64, p: *i64) -> i64 { 24 var i: i64 = p[0] 25 var d: i64 = 0 26 while d == 0 { 27 if i >= n { d = 1 } else { 28 if buf[i] == (32 as u8) { i = i + 1 } else { if buf[i] == (9 as u8) { i = i + 1 } else { d = 1 } } 29 } 30 } 31 p[0] = i 32 return i 33} 34// advance p to the start of the next line 35func oi_eol(buf: *u8, n: i64, p: *i64) -> i64 { 36 var i: i64 = p[0] 37 var d: i64 = 0 38 while d == 0 { 39 if i >= n { d = 1 } else { if buf[i] == (10 as u8) { i = i + 1; d = 1 } else { i = i + 1 } } 40 } 41 p[0] = i 42 return i 43} 44// parse a decimal (optionally signed, optionally fractional, optional e-exponent) into fx1024. 45// Returns the fixed-point value; sets ok[0]=1 if any digits were consumed. 46func oi_fx(buf: *u8, n: i64, p: *i64, ok: *i64) -> i64 { 47 ok[0] = 0 48 oi_ws(buf, n, p) 49 var i: i64 = p[0] 50 var neg: i64 = 0 51 if i < n { if buf[i] == (45 as u8) { neg = 1; i = i + 1 } else { if buf[i] == (43 as u8) { i = i + 1 } } } 52 var ip: i64 = 0 53 var got: i64 = 0 54 var d1: i64 = 0 55 while d1 == 0 { 56 if i >= n { d1 = 1 } else { 57 if oi_is_digit(buf[i] as i64) == 1 { ip = ip * 10 + ((buf[i] as i64) - 48); i = i + 1; got = 1 } else { d1 = 1 } 58 } 59 } 60 var fx: i64 = ip * OI_FX 61 // fractional part: accumulate up to 6 digits then scale once (avoids per-digit rounding drift) 62 if i < n { 63 if buf[i] == (46 as u8) { 64 i = i + 1 65 var fr: i64 = 0 66 var scale: i64 = 1 67 var nd: i64 = 0 68 var d2: i64 = 0 69 while d2 == 0 { 70 if i >= n { d2 = 1 } else { 71 if oi_is_digit(buf[i] as i64) == 1 { 72 if nd < 6 { fr = fr * 10 + ((buf[i] as i64) - 48); scale = scale * 10; nd = nd + 1 } 73 i = i + 1; got = 1 74 } else { d2 = 1 } 75 } 76 } 77 if scale > 1 { fx = fx + (fr * OI_FX) / scale } 78 } 79 } 80 // exponent: scanners and Blender both emit 1.0e-3 forms 81 if i < n { 82 if buf[i] == (101 as u8) { i = i + 1 } else { if buf[i] == (69 as u8) { i = i + 1 } } 83 } 84 if i > p[0] { 85 var eneg: i64 = 0 86 var j: i64 = i 87 var hase: i64 = 0 88 if j < n { if buf[j] == (45 as u8) { eneg = 1; j = j + 1; hase = 1 } else { if buf[j] == (43 as u8) { j = j + 1; hase = 1 } } } 89 var ev: i64 = 0 90 var d3: i64 = 0 91 while d3 == 0 { 92 if j >= n { d3 = 1 } else { 93 if oi_is_digit(buf[j] as i64) == 1 { ev = ev * 10 + ((buf[j] as i64) - 48); j = j + 1; hase = 1 } else { d3 = 1 } 94 } 95 } 96 if hase == 1 { 97 if ev > 12 { ev = 12 } 98 var k: i64 = 0 99 while k < ev { if eneg == 1 { fx = fx / 10 } else { fx = fx * 10 } k = k + 1 } 100 i = j 101 } 102 } 103 p[0] = i 104 if got == 1 { ok[0] = 1 } 105 if neg == 1 { return 0 - fx } 106 return fx 107} 108// parse one face vertex reference: forms a | a/b | a//c | a/b/c. Returns the POSITION index 109// (still 1-based or negative); consumes any /vt/vn that follows. 110func oi_faceref(buf: *u8, n: i64, p: *i64, ok: *i64) -> i64 { 111 ok[0] = 0 112 oi_ws(buf, n, p) 113 var i: i64 = p[0] 114 var neg: i64 = 0 115 if i < n { if buf[i] == (45 as u8) { neg = 1; i = i + 1 } } 116 var v: i64 = 0 117 var got: i64 = 0 118 var d: i64 = 0 119 while d == 0 { 120 if i >= n { d = 1 } else { 121 if oi_is_digit(buf[i] as i64) == 1 { v = v * 10 + ((buf[i] as i64) - 48); i = i + 1; got = 1 } else { d = 1 } 122 } 123 } 124 // swallow /vt/vn 125 var d2: i64 = 0 126 while d2 == 0 { 127 if i >= n { d2 = 1 } else { 128 if buf[i] == (47 as u8) { i = i + 1 } else { 129 if oi_is_digit(buf[i] as i64) == 1 { i = i + 1 } else { 130 if buf[i] == (45 as u8) { i = i + 1 } else { d2 = 1 } 131 } 132 } 133 } 134 } 135 p[0] = i 136 if got == 0 { return 0 } 137 ok[0] = 1 138 if neg == 1 { return 0 - v } 139 return v 140} 141 142// Read an OBJ file into vbuf (nv*3 fx1024) and fbuf (nf*3 zero-based indices). 143// Returns 0 on success; -1 unreadable, -2 empty, -3 vertex overflow, -4 face overflow, 144// -5 a face referenced a vertex that does not exist. 145func oi_read(path: *u8, vbuf: *i64, fbuf: *i64, vcap: i64, fcap: i64, counts: *i64) -> i64 { 146 counts[0] = 0 147 counts[1] = 0 148 let cl: *i64 = sys_mmap(16) as *i64 149 cl[0] = 0 150 let buf: *u8 = sys_read_file(path, cl) 151 let n: i64 = cl[0] 152 if (buf as i64) == 0 { return 0 - 1 } 153 if n <= 0 { return 0 - 2 } 154 let p: *i64 = sys_mmap(16) as *i64 155 let ok: *i64 = sys_mmap(16) as *i64 156 let poly: *i64 = sys_mmap(64 * 8) as *i64 157 p[0] = 0 158 var nv: i64 = 0 159 var nf: i64 = 0 160 var rc: i64 = 0 161 var done: i64 = 0 162 while done == 0 { 163 if p[0] >= n { done = 1 } else { 164 oi_ws(buf, n, p) 165 let s: i64 = p[0] 166 if s >= n { done = 1 } else { 167 let c0: i64 = buf[s] as i64 168 var handled: i64 = 0 169 if c0 == 118 { 170 // 'v' -- but only bare "v ", not vt / vn / vp 171 let c1: i64 = buf[s+1] as i64 172 if c1 == 32 { handled = 1 } else { if c1 == 9 { handled = 1 } } 173 if handled == 1 { 174 p[0] = s + 1 175 let x: i64 = oi_fx(buf, n, p, ok) 176 let y: i64 = oi_fx(buf, n, p, ok) 177 let z: i64 = oi_fx(buf, n, p, ok) 178 if nv >= vcap { rc = 0 - 3; done = 1 } else { 179 vbuf[nv*3] = x; vbuf[nv*3+1] = y; vbuf[nv*3+2] = z 180 nv = nv + 1 181 } 182 } 183 } 184 if c0 == 102 { 185 // 'f' -- collect every vertex ref on the line, then FAN-TRIANGULATE 186 handled = 1 187 p[0] = s + 1 188 var np: i64 = 0 189 var d4: i64 = 0 190 while d4 == 0 { 191 if p[0] >= n { d4 = 1 } else { 192 if buf[p[0]] == (10 as u8) { d4 = 1 } else { 193 let r: i64 = oi_faceref(buf, n, p, ok) 194 if ok[0] == 0 { d4 = 1 } else { 195 var idx: i64 = r 196 if idx < 0 { idx = nv + idx } else { idx = idx - 1 } 197 if idx < 0 { rc = 0 - 5; d4 = 1 } else { 198 if idx >= nv { rc = 0 - 5; d4 = 1 } else { 199 if np < 64 { poly[np] = idx; np = np + 1 } 200 } 201 } 202 } 203 } 204 } 205 } 206 if rc == 0 { 207 var k: i64 = 1 208 while k + 1 < np { 209 if nf >= fcap { rc = 0 - 4; k = np } else { 210 fbuf[nf*3] = poly[0]; fbuf[nf*3+1] = poly[k]; fbuf[nf*3+2] = poly[k+1] 211 nf = nf + 1 212 k = k + 1 213 } 214 } 215 } 216 if rc != 0 { done = 1 } 217 } 218 if done == 0 { oi_eol(buf, n, p) } 219 } 220 } 221 } 222 sys_munmap(p as *u8, 16) 223 sys_munmap(ok as *u8, 16) 224 sys_munmap(poly as *u8, 64 * 8) 225 if rc != 0 { return rc } 226 counts[0] = nv 227 counts[1] = nf 228 if nv == 0 { return 0 - 2 } 229 return 0 230} 231 232// Source-space exact decimal geometry. Legacy oi_read remains fx1024. 233// Decimal exponent is representation metadata, never a physical-unit assertion. 234const OIE_MAX:i64=9223372036854775807 235const OIE_MIN:i64=0-9223372036854775807-1 236const OIE_SYNTAX:i64=-6 237const OIE_RANGE:i64=-7 238const OIE_BUFFER:i64=-8 239const OIE_TRIANGLES_ONLY:i64=0 240const OIE_FAN_UNQUALIFIED:i64=1 241struct OiExactView { 242 nv:i64, 243 nt:i64, 244 exponent10:i64, 245 nonzero:i64, 246 errorOffset:i64, 247 status:i64, 248 sourceBytes:i64, 249 unitState:i64, 250 basisState:i64, 251 sourceFaces:i64, 252 polygonFaces:i64, 253 fanTriangles:i64, 254 triangulation:i64 255} 256func oi_exact_reset(o:*OiExactView,n:i64)->i64 { 257 o.nv=0;o.nt=0;o.exponent10=0;o.nonzero=0;o.errorOffset=0;o.status=0;o.sourceBytes=n 258 o.unitState=0;o.basisState=0;o.sourceFaces=0;o.polygonFaces=0;o.fanTriangles=0;o.triangulation=OIE_TRIANGLES_ONLY;return 0 259} 260// Full token grammar: signed decimal with optional exponent. Output is normalized 261// signed significand/exponent; all arithmetic refuses nonrepresentable values. 262func oi_decimal_exact(s:*u8,n:i64,value:*i64,power:*i64)->i64 { 263 if (s as i64)<=0||(value as i64)<=0||(power as i64)<=0||n<=0{return OIE_SYNTAX} 264 var i:i64=0;var neg:i64=0 265 if s[0]==45{neg=1;i=1}else{if s[0]==43{i=1}} 266 if i>=n{return OIE_SYNTAX} 267 let start:i64=i;var dot:i64=0;var digits:i64=0;var fraction:i64=0;var last:i64=-1;var trailing:i64=0 268 while i<n{ 269 let c:i64=s[i] as i64 270 if c==101||c==69{break} 271 if c==46{if dot==1{return OIE_SYNTAX};dot=1}else{ 272 if oi_is_digit(c)==0{return OIE_SYNTAX} 273 digits=digits+1;if dot==1{fraction=fraction+1} 274 if c!=48{last=i;trailing=0}else{trailing=trailing+1} 275 };i=i+1 276 } 277 if digits==0{return OIE_SYNTAX} 278 let mend:i64=i;var ex:i64=0;var eneg:i64=0 279 if i<n{ 280 i=i+1;if i>=n{return OIE_SYNTAX} 281 if s[i]==45{eneg=1;i=i+1}else{if s[i]==43{i=i+1}} 282 if i>=n{return OIE_SYNTAX} 283 while i<n{ 284 let d:i64=(s[i] as i64)-48;if d<0||d>9{return OIE_SYNTAX} 285 if ex>(OIE_MAX-d)/10{return OIE_RANGE};ex=ex*10+d;i=i+1 286 } 287 } 288 if last<0{value[0]=0;power[0]=0;return 0} 289 if eneg==1{ex=0-ex} 290 // -fraction + trailing lies within the token length, so the subtraction is safe. 291 let adjust:i64=trailing-fraction 292 if adjust>0{if ex>OIE_MAX-adjust{return OIE_RANGE}}else{if ex<OIE_MIN-adjust{return OIE_RANGE}} 293 ex=ex+adjust 294 // Negative accumulation admits the full signed-i64 range, including MIN. 295 var a:i64=0;var limit:i64=0-OIE_MAX;if neg==1{limit=OIE_MIN} 296 i=start 297 while i<=last{ 298 let c:i64=s[i] as i64 299 if c!=46{ 300 let d:i64=c-48 301 if a<limit/10{return OIE_RANGE} 302 a=a*10;if a<limit+d{return OIE_RANGE};a=a-d 303 };i=i+1 304 } 305 if neg==0{a=0-a};value[0]=a;power[0]=ex;return 0 306} 307func oi_exact_scaled(v:i64,ex:i64,target:i64,out:*i64)->i64 { 308 if v==0{out[0]=0;return 0};if ex<target{return OIE_RANGE} 309 var a:i64=v;var e:i64=ex 310 // No subtraction of remote exponents; overflow stops after representable digits. 311 while e>target{ 312 if a>OIE_MAX/10||a<OIE_MIN/10{return OIE_RANGE} 313 a=a*10;e=e-1 314 };out[0]=a;return 0 315} 316func oi_exact_space(c:i64)->i64 {if c==32||c==9||c==13{return 1};return 0} 317func oi_exact_token(b:*u8,end:i64,p:*i64,len:*i64)->i64 { 318 var i:i64=p[0];while i<end{if oi_exact_space(b[i] as i64)==0{break};i=i+1} 319 let start:i64=i 320 while i<end{if oi_exact_space(b[i] as i64)==1{break};if b[i]==35{break};i=i+1} 321 p[0]=i;len[0]=i-start;return start 322} 323// Face references share source ordering with oi_read. Checked indices replace 324// unchecked integer accumulation only in this new strict boundary. 325func oi_exact_ref(s:*u8,n:i64,nv:i64,out:*i64)->i64 { 326 if n<=0{return OIE_SYNTAX};var i:i64=0;var neg:i64=0 327 if s[0]==45{neg=1;i=1}else{if s[0]==43{i=1}} 328 var a:i64=0;var digits:i64=0 329 while i<n{ 330 let c:i64=s[i] as i64;if c==47{break} 331 if oi_is_digit(c)==0{return OIE_SYNTAX};let d:i64=c-48 332 if a>(OIE_MAX-d)/10{return OIE_RANGE};a=a*10+d;digits=digits+1;i=i+1 333 } 334 if digits==0||a==0{return OIE_SYNTAX} 335 var index:i64=a-1;if neg==1{if a>nv{return -5};index=nv-a} 336 if index<0||index>=nv{return -5} 337 var fields:i64=0 338 while i<n{ 339 if s[i]!=47{return OIE_SYNTAX};fields=fields+1;if fields>2{return OIE_SYNTAX};i=i+1 340 var count:i64=0;var signedField:i64=0 341 if i<n{if s[i]==45||s[i]==43{signedField=1;i=i+1}} 342 while i<n{if s[i]==47{break};if oi_is_digit(s[i] as i64)==0{return OIE_SYNTAX};count=count+1;i=i+1} 343 // Empty vt is valid only in v//vn. Empty final component is malformed. 344 if count==0{if signedField!=0{return OIE_SYNTAX};if i>=n{return OIE_SYNTAX};if fields!=1{return OIE_SYNTAX}} 345 } 346 out[0]=index;return 0 347} 348func oi_exact_scan(b:*u8,n:i64,v:*i64,f:*i64,o:*OiExactView,emit:i64,vcap:i64,fcap:i64)->i64 { 349 var pos:i64=0;var nv:i64=0;var nt:i64=0;var faces:i64=0;var polygons:i64=0;var fan:i64=0 350 while pos<n{ 351 var end:i64=pos;while end<n{if b[end]==10{break};end=end+1} 352 var p:i64=pos;var len:i64=0 353 let tag:i64=oi_exact_token(b,end,&p,&len) 354 if len==1{ 355 if b[tag]==118{ 356 var axis:i64=0 357 while axis<3{ 358 let start:i64=oi_exact_token(b,end,&p,&len);o.errorOffset=start 359 if len<=0{return OIE_SYNTAX} 360 var val:i64=0;var ex:i64=0 361 let rc:i64=oi_decimal_exact((b as i64+start) as *u8,len,&val,&ex);if rc!=0{return rc} 362 if emit==0{if val!=0{if o.nonzero==0||ex<o.exponent10{o.exponent10=ex};o.nonzero=1}}else{ 363 var scaled:i64=0;let sr:i64=oi_exact_scaled(val,ex,o.exponent10,&scaled);if sr!=0{return sr} 364 if nv>=vcap{return -3};v[nv*3+axis]=scaled 365 };axis=axis+1 366 } 367 oi_exact_token(b,end,&p,&len);if len!=0{return OIE_SYNTAX} 368 if nv>=OIE_MAX/(3*__size_of(i64)){return OIE_RANGE};nv=nv+1 369 }else{if b[tag]==102{ 370 var count:i64=0;var first:i64=0;var previous:i64=0 371 while p<end{ 372 let start:i64=oi_exact_token(b,end,&p,&len);if len==0{break};o.errorOffset=start 373 var index:i64=0;let rc:i64=oi_exact_ref((b as i64+start) as *u8,len,nv,&index);if rc!=0{return rc} 374 if count==0{first=index}else{if count>=2{ 375 if nt>=OIE_MAX/(3*__size_of(i64)){return OIE_RANGE} 376 if emit==1{if nt>=fcap{return -4};f[nt*3]=first;f[nt*3+1]=previous;f[nt*3+2]=index};nt=nt+1 377 }};previous=index;count=count+1 378 } 379 if count<3{return OIE_SYNTAX};faces=faces+1 380 if count>3{polygons=polygons+1;fan=fan+count-2} 381 }} 382 } 383 pos=end;if pos<n{pos=pos+1} 384 } 385 if emit==0{o.nv=nv;o.nt=nt;o.sourceFaces=faces;o.polygonFaces=polygons;o.fanTriangles=fan;if polygons>0{o.triangulation=OIE_FAN_UNQUALIFIED}}else{if nv!=o.nv||nt!=o.nt||faces!=o.sourceFaces||polygons!=o.polygonFaces||fan!=o.fanTriangles{return OIE_SYNTAX}};if nv==0{return -2};return 0 386} 387// Borrowed source bytes are kept unchanged. Counts are sizing information until status==0. 388// Partial caller buffers on failure are invalid and must not be consumed. 389func oi_read_exact_bytes(b:*u8,n:i64,v:*i64,f:*i64,vcap:i64,fcap:i64,o:*OiExactView)->i64 { 390 if (o as i64)<=0{return OIE_BUFFER};oi_exact_reset(o,n) 391 if (b as i64)<=0||n<=0||vcap<0||fcap<0{o.status=OIE_BUFFER;return o.status} 392 var rc:i64=oi_exact_scan(b,n,v,f,o,0,vcap,fcap) 393 if rc==0{ 394 if o.nv>vcap{rc=-3}else{if o.nt>fcap{rc=-4}else{ 395 if (v as i64)<=0{rc=OIE_BUFFER}else{if o.nt>0&&(f as i64)<=0{rc=OIE_BUFFER}else{rc=oi_exact_scan(b,n,v,f,o,1,vcap,fcap)}} 396 }} 397 } 398 o.status=rc;return rc 399} 400func oi_read_exact(path:*u8,v:*i64,f:*i64,vcap:i64,fcap:i64,o:*OiExactView)->i64 { 401 if (o as i64)<=0{return OIE_BUFFER};oi_exact_reset(o,0) 402 var n:i64=0;let b:*u8=sys_map_file(path,&n) 403 if (b as i64)==0{o.status=-1;return -1} 404 let rc:i64=oi_read_exact_bytes(b,n,v,f,vcap,fcap,o);sys_munmap_direct(b,n);return rc 405}