code wiki / (root) / nx_fbx_texm.nx

nx_fbx_texm.nx source

↩ module page · 1301 lines · 54891 B

1// nx_fbx_texm.nx -- AUTHORED FBX MATERIALS -> TEXC + TEXM. The donor half of the clay fix. 2// 3// THE MEASURED GAP THIS CLOSES. The four Fab donors (toon3d8 / dark_witch / dark_knight / 4// stylized_paladin) were ingested rigged (nx_fbx2nxa + nx_nxa_skin) but FLAT: their AUTHORED 5// texture maps were never carried, so the shipped cw_npc_atlas sampling path (TEXC+TEXM -> 6// world shader) has nothing to sample on them. nx_nxa_texm ship-mode BAKES maps from the genome; 7// a donor's surface is not bakeable -- it is authored, and the honest carry is the author's own 8// pixels behind the author's own UVs. 9// 10// RECON-MEASURED FACTS THIS DESIGN RESTS ON (2026-08-23, full 4-donor census, not sampled): 11// - Every geometry's UV layer is ByPolygonVertex + IndexToDirect. One resolution path. 12// - LayerElementMaterial is AllSame on 75 of 79 geometries; ByPolygon on 4. Policy for 13// ByPolygon: FIRST POLYGON WINS per control point -- a declared approximation at material 14// boundaries, never a fabricated map. 15// - Map roles come from OP connections (DiffuseColor / ShininessExponent / ReflectionFactor / 16// NormalMap / TransparencyFactor), never from filename guessing. 17// - Albedo sources: PNG-8 (ctype 0/2/3/6) and BASELINE JPEG (every toon JPG is SOF0). 18// Depth-16 PNG appears ONLY in normal maps -> the normal map is declared absent by depth. 19// - Knight hair material has no DiffuseColor texture -> authored scalar diffuse (P prop) fills 20// its chart. Authored scalar, not a fabricated pixel. 21// 22// SHAPE: one chart per DISTINCT diffuse source (scalar-filled charts get their own cell), grid 23// g = smallest g with g*g >= ncharts (derived), atlas res from knowledge/texture_ship_budget.conf 24// (the SAME owner nx_nxa_texm ship-mode reads -- one owner per number). TEXC stores FINAL 25// atlas-space Q16 UVs with chart ids, exactly the section shape nx_nxa_texc_lib writes, so the 26// shipped world sampler needs NO new convention. FBX v (origin bottom-left) is flipped to the 27// atlas row space (origin top-left) at ingest; declared here, applied once. Tiling UVs outside 28// [0,1] wrap by modulo before the cell mapping (a clamp would smear the border texel). 29// 30// GRAMMAR: nx_fbx_texm <in.fbx> <texdir> <in_rigged.nxa> <out.nxa> 31// EXITS: 0 OK | 1 io | 2 usage | 3 bad-input | 4 missing-section | 5 desync-refused 32// 33// REFUSALS ARE NAMED. Every skipped map and every unreadable source prints WHICH rule fired. 34 35import "nx_syscalls.nx" 36import "nx_nxa.nx" 37import "nx_fbx_core.nx" 38import "nx_inflate.nx" 39import "nx_png.nx" 40import "nx_jpeg_ascii.nx" 41 42const FT_OK: i64 = 0 43const FT_E_IO: i64 = 1 44const FT_E_USAGE: i64 = 2 45const FT_E_BAD: i64 = 3 46const FT_E_NOSEC: i64 = 4 47const FT_E_DESYNC: i64 = 5 48 49// NXA container geometry (format law shared with the texc/texm writers, not tuning). 50const FT_HDR: i64 = 32 51const FT_TOCE: i64 = 32 52const FT_MAXSEC: i64 = 64 53const FT_MODE: i64 = 0x1a4 54const FT_PAD: i64 = 4096 55const FT_WORD: i64 = 8 56 57// TEXC payload header: {nv, stride, grid, 0} then {u_q16, v_q16, chart} per vertex. 58const FT_TX_PHDR: i64 = 4 59const FT_TX_STRIDE: i64 = 3 60const FT_Q16: i64 = 65536 61 62// TEXM payload header words + per-map record words (format law shared with nx_nxa_texm). 63const FT_TM_HDRW: i64 = 6 64const FT_TM_RECW: i64 = 3 65const FT_TM_NMAP: i64 = 4 66const FT_TM_MODEL_METALROUGH: i64 = 2 67// Map slots follow the texbake order: 0 albedo, 1 spec(=metal under metalrough), 68// 2 gloss(=roughness under metalrough), 3 normal. 69const FT_SLOT_ALBEDO: i64 = 0 70const FT_SLOT_METAL: i64 = 1 71const FT_SLOT_ROUGH: i64 = 2 72 73// Ship-resolution owner: the SAME conf nx_nxa_texm ship-mode derives from. One owner per number. 74const FT_SHIP_CONF: *u8 = "knowledge/texture_ship_budget.conf" 75 76 77// Capacity ceilings DERIVED from the recon census (maxima: 33 geoms, 29 mats, 38 texs, 12988 78// conns, 380 models), sized 2-10x above so growth refuses loudly instead of truncating silently. 79const FT_MAXGEOM: i64 = 128 80const FT_MAXMAT: i64 = 128 81const FT_MAXTEX: i64 = 256 82const FT_MAXCONN: i64 = 65536 83const FT_MAXMODEL: i64 = 1024 84const FT_MAXCHART: i64 = 64 85const FT_MAXSLOT: i64 = 64 86const FT_NAMECAP: i64 = 256 87// stack frames are DERIVED per input: one pending frame can exist only per container node, 88// and a node header is at least 13 bytes -- so filesize/13 bounds them absolutely. The old 89// picked 8192 was measured flooding at 8190 on the knight (3,729 subdeformer containers). 90const FT_STACK_MIN: i64 = 1024 91 92const FT_FX_ONE: i64 = 1024 // fbxc_f64_fx unit (nx_fbx_core law) 93 94func ft_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 95func ft_err(s: *u8) -> i64 { sys_write(2, s, ft_slen(s)); return 0 } 96func ft_out(s: *u8) -> i64 { sys_write(1, s, ft_slen(s)); return 0 } 97func ft_outn(v: i64) -> i64 { 98 let b: *u8 = sys_mmap(32) 99 var x: i64 = v 100 var neg: i64 = 0 101 if x < 0 { neg = 1; x = 0 - x } 102 var n: i64 = 0 103 if x == 0 { b[0] = 48 as u8; n = 1 } else { 104 while x > 0 { b[n] = (48 + (x % 10)) as u8; x = x / 10; n = n + 1 } 105 } 106 if neg == 1 { b[n] = 45 as u8; n = n + 1 } 107 var i: i64 = 0 108 while i < n / 2 { 109 let t: u8 = b[i]; b[i] = b[n-1-i]; b[n-1-i] = t 110 i = i + 1 111 } 112 sys_write(1, b, n) 113 sys_munmap(b, 32) 114 return 0 115} 116func ft_nl() -> i64 { let b: *u8 = sys_mmap(8); b[0] = 10 as u8; sys_write(1, b, 1); sys_munmap(b, 8); return 0 } 117 118func ft_streq(a: *u8, b: *u8) -> i64 { 119 var i: i64 = 0 120 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 121 if b[i] != (0 as u8) { return 0 } 122 return 1 123} 124 125// basename of a path that may contain backslashes or slashes; copies into dst, returns length. 126func ft_basename(src: *u8, srclen: i64, dst: *u8) -> i64 { 127 var start: i64 = 0 128 var i: i64 = 0 129 while i < srclen { 130 if src[i] == (92 as u8) { start = i + 1 } 131 if src[i] == (47 as u8) { start = i + 1 } 132 i = i + 1 133 } 134 var n: i64 = 0 135 var j: i64 = start 136 while j < srclen { 137 if n < FT_NAMECAP - 1 { dst[n] = src[j]; n = n + 1 } 138 j = j + 1 139 } 140 dst[n] = 0 as u8 141 return n 142} 143 144func ft_pathjoin(dir: *u8, base: *u8, dst: *u8) -> i64 { 145 var n: i64 = 0 146 var i: i64 = 0 147 while dir[i] != (0 as u8) { dst[n] = dir[i]; n = n + 1; i = i + 1 } 148 if n > 0 { if dst[n-1] != (47 as u8) { dst[n] = 47 as u8; n = n + 1 } } 149 var j: i64 = 0 150 while base[j] != (0 as u8) { dst[n] = base[j]; n = n + 1; j = j + 1 } 151 dst[n] = 0 as u8 152 return n 153} 154 155// --------------------------------------------------------------------------------------------- 156// PNG-8 decode: inflate (inf_zlib, the estate's RFC1950 lib) + per-scanline defilter + to RGB. 157// Depth 8, ctype 0/2/3/6 (gray/rgb/palette/rgba); depth 16 and interlace REFUSE BY NAME -- 158// measured: depth-16 appears only in normal maps, interlace appears nowhere in the corpus. 159// --------------------------------------------------------------------------------------------- 160func ft_png_load(path: *u8, prgb: *i64, pw: *i64, ph: *i64) -> i64 { 161 let lp: *i64 = sys_mmap(16) as *i64 162 let f: *u8 = sys_read_file(path, lp) 163 let flen: i64 = lp[0] 164 if (f as i64) == 0 { return 0 - 1 } 165 if flen < 45 { return 0 - 2 } 166 if (f[0] as i64) != 137 { return 0 - 2 } 167 let w: i64 = ((f[16] as i64) << 24) | ((f[17] as i64) << 16) | ((f[18] as i64) << 8) | (f[19] as i64) 168 let h: i64 = ((f[20] as i64) << 24) | ((f[21] as i64) << 16) | ((f[22] as i64) << 8) | (f[23] as i64) 169 let depth: i64 = f[24] as i64 170 let ctype: i64 = f[25] as i64 171 let interlace: i64 = f[28] as i64 172 if depth != 8 { ft_err("PNG-REFUSE depth!=8\n" as *u8); return 0 - 3 } 173 if interlace != 0 { ft_err("PNG-REFUSE interlaced\n" as *u8); return 0 - 4 } 174 var bpp: i64 = 0 175 if ctype == 0 { bpp = 1 } 176 if ctype == 2 { bpp = 3 } 177 if ctype == 3 { bpp = 1 } 178 if ctype == 4 { bpp = 2 } 179 if ctype == 6 { bpp = 4 } 180 if bpp == 0 { ft_err("PNG-REFUSE unsupported color type\n" as *u8); return 0 - 5 } 181 let idat: *u8 = sys_mmap(flen + 64) 182 var idn: i64 = 0 183 var plte: i64 = 0 - 1 184 var pltelen: i64 = 0 185 var p: i64 = 8 186 var stop: i64 = 0 187 while p + 12 <= flen { 188 if stop == 1 { p = flen } else { 189 let clen: i64 = ((f[p] as i64) << 24) | ((f[p+1] as i64) << 16) | ((f[p+2] as i64) << 8) | (f[p+3] as i64) 190 let c0: i64 = f[p+4] as i64 191 let c1: i64 = f[p+5] as i64 192 let c2: i64 = f[p+6] as i64 193 let c3: i64 = f[p+7] as i64 194 if c0 == 73 { if c1 == 68 { if c2 == 65 { if c3 == 84 { 195 var k: i64 = 0 196 while k < clen { idat[idn + k] = f[p + 8 + k]; k = k + 1 } 197 idn = idn + clen 198 } } } } 199 if c0 == 80 { if c1 == 76 { if c2 == 84 { if c3 == 69 { plte = p + 8; pltelen = clen } } } } 200 if c0 == 73 { if c1 == 69 { if c2 == 78 { if c3 == 68 { stop = 1 } } } } 201 p = p + 12 + clen 202 } 203 } 204 if idn == 0 { ft_err("PNG-REFUSE no IDAT\n" as *u8); return 0 - 6 } 205 if ctype == 3 { if plte < 0 { ft_err("PNG-REFUSE palette without PLTE\n" as *u8); return 0 - 7 } } 206 let stride: i64 = w * bpp 207 let rawcap: i64 = h * (stride + 1) 208 let raw: *u8 = sys_mmap(rawcap + 64) 209 let got: i64 = inf_zlib(idat, idn, raw, rawcap) 210 sys_munmap(idat, flen + 64) 211 if got != rawcap { 212 ft_err("PNG-REFUSE inflate size mismatch got=" as *u8); ft_outn(got) 213 ft_err(" want=" as *u8); ft_outn(rawcap); ft_nl() 214 return 0 - 8 215 } 216 let pix: *u8 = sys_mmap(h * stride + 64) 217 var r: i64 = 0 218 while r < h { 219 let flt: i64 = raw[r * (stride + 1)] as i64 220 let ro: i64 = r * (stride + 1) + 1 221 let po: i64 = r * stride 222 var x: i64 = 0 223 while x < stride { 224 var a: i64 = 0 225 var bup: i64 = 0 226 var cul: i64 = 0 227 if x >= bpp { a = pix[po + x - bpp] as i64 } 228 if r > 0 { bup = pix[po - stride + x] as i64 } 229 if x >= bpp { if r > 0 { cul = pix[po - stride + x - bpp] as i64 } } 230 var v: i64 = raw[ro + x] as i64 231 if flt == 1 { v = v + a } 232 if flt == 2 { v = v + bup } 233 if flt == 3 { v = v + (a + bup) / 2 } 234 if flt == 4 { 235 let pp: i64 = a + bup - cul 236 var pa: i64 = pp - a 237 if pa < 0 { pa = 0 - pa } 238 var pb: i64 = pp - bup 239 if pb < 0 { pb = 0 - pb } 240 var pc: i64 = pp - cul 241 if pc < 0 { pc = 0 - pc } 242 var pr: i64 = cul 243 if pa <= pb { if pa <= pc { pr = a } else { pr = cul } } else { if pb <= pc { pr = bup } else { pr = cul } } 244 v = v + pr 245 } 246 pix[po + x] = (v & 255) as u8 247 x = x + 1 248 } 249 r = r + 1 250 } 251 sys_munmap(raw, rawcap + 64) 252 let rgb: *u8 = sys_mmap(w * h * 3 + 64) 253 var q: i64 = 0 254 while q < w * h { 255 if ctype == 2 { rgb[q*3] = pix[q*3]; rgb[q*3+1] = pix[q*3+1]; rgb[q*3+2] = pix[q*3+2] } 256 if ctype == 6 { rgb[q*3] = pix[q*4]; rgb[q*3+1] = pix[q*4+1]; rgb[q*3+2] = pix[q*4+2] } 257 if ctype == 0 { rgb[q*3] = pix[q]; rgb[q*3+1] = pix[q]; rgb[q*3+2] = pix[q] } 258 if ctype == 4 { rgb[q*3] = pix[q*2]; rgb[q*3+1] = pix[q*2]; rgb[q*3+2] = pix[q*2] } 259 if ctype == 3 { 260 let pi: i64 = pix[q] as i64 261 if pi * 3 + 2 < pltelen { 262 rgb[q*3] = f[plte + pi*3]; rgb[q*3+1] = f[plte + pi*3 + 1]; rgb[q*3+2] = f[plte + pi*3 + 2] 263 } 264 } 265 q = q + 1 266 } 267 sys_munmap(pix, h * stride + 64) 268 prgb[0] = rgb as i64 269 pw[0] = w 270 ph[0] = h 271 return 0 272} 273 274func ft_img_load(path: *u8, prgb: *i64, pw: *i64, ph: *i64) -> i64 { 275 let lp: *i64 = sys_mmap(16) as *i64 276 let f: *u8 = sys_read_file(path, lp) 277 let flen: i64 = lp[0] 278 if (f as i64) == 0 { return 0 - 1 } 279 if flen < 8 { return 0 - 2 } 280 if (f[0] as i64) == 137 { return ft_png_load(path, prgb, pw, ph) } 281 if (f[0] as i64) == 255 { if (f[1] as i64) == 216 { 282 let dr: i64 = nx_jpeg_decode_rgb(f, flen, prgb, pw, ph) 283 if dr != NX_JPEG_ASCII_OK { ft_err("JPEG-REFUSE decoder rc=" as *u8); ft_outn(dr); ft_nl(); return 0 - 9 } 284 return 0 285 } } 286 ft_err("IMG-REFUSE neither PNG nor JPEG magic\n" as *u8) 287 return 0 - 10 288} 289 290// --------------------------------------------------------------------------------------------- 291// FORK-ISOLATED cell load. MEASURED 2026-08-23 with a minimal probe: nx_jpeg_decode_rgb leaks 292// ~3.2 GB of RSS PER 4096x4096 DECODE (never freed across calls; rss 1.1 MB -> 12.8 GB in four 293// rounds) and trips a real 112-byte ARENA-OVERRUN inside the lib on every call. Twelve decodes 294// in one process took the whole WSL VM down. Until the lib is fixed (defect named in the ship 295// report), each decode runs in a FORKED CHILD -- the estate's own fork-per-request idiom -- so 296// process exit frees the arena and the overrun's blast radius dies with the child. The child 297// ships back exactly one downsampled cell (cell*cell*3 bytes) through a scratch file. 298// --------------------------------------------------------------------------------------------- 299func ft_cell_load(path: *u8, dst: *u8, res: i64, cx0: i64, cy0: i64, cell: i64, tmpp: *u8) -> i64 { 300 let pid: i64 = sys_fork() 301 if pid < 0 { ft_err("FBXTEXM-REFUSE fork failed\n" as *u8); return 0 - 20 } 302 if pid == 0 { 303 // child: decode, downsample, write the cell, exit. Never returns to the caller's world. 304 let pr: *i64 = sys_mmap(8) as *i64 305 let pw: *i64 = sys_mmap(8) as *i64 306 let ph: *i64 = sys_mmap(8) as *i64 307 let rc: i64 = ft_img_load(path, pr, pw, ph) 308 if rc != 0 { sys_exit(10 + (0 - rc)) } 309 let cbuf: *u8 = sys_mmap(cell * cell * 3 + 64) 310 // downsample into a 0-origin cell then ship it 311 ft_downsample_into(pr[0] as *u8, pw[0], ph[0], cbuf, cell, 0, 0, cell) 312 let fd: i64 = sys_openat_wr(tmpp, FT_MODE) 313 if fd < 0 { sys_exit(3) } 314 let wrote: i64 = sys_write(fd, cbuf, cell * cell * 3) 315 sys_close(fd) 316 if wrote != cell * cell * 3 { sys_exit(4) } 317 sys_exit(0) 318 return 0 319 } 320 let st: *i64 = sys_mmap(16) as *i64 321 sys_wait4(pid, st, 0) 322 let raw: i64 = st[0] 323 // wait4 status: exit code in bits 8..15 for a normal exit; low byte nonzero = killed by signal 324 let sig: i64 = raw & 127 325 let code: i64 = (raw >> 8) & 255 326 if sig != 0 { 327 ft_err("cell child KILLED-BY-SIGNAL " as *u8); ft_outn(sig); ft_nl() 328 return 0 - 21 329 } 330 if code != 0 { return 0 - code } 331 let lp: *i64 = sys_mmap(16) as *i64 332 let cb: *u8 = sys_read_file(tmpp, lp) 333 if (cb as i64) == 0 { ft_err("cell scratch unreadable\n" as *u8); return 0 - 22 } 334 if lp[0] != cell * cell * 3 { ft_err("cell scratch wrong size\n" as *u8); return 0 - 23 } 335 var yy: i64 = 0 336 while yy < cell { 337 var xx: i64 = 0 338 while xx < cell { 339 let so: i64 = (yy * cell + xx) * 3 340 let dow: i64 = ((cy0 + yy) * res + cx0 + xx) * 3 341 dst[dow] = cb[so] 342 dst[dow+1] = cb[so+1] 343 dst[dow+2] = cb[so+2] 344 xx = xx + 1 345 } 346 yy = yy + 1 347 } 348 sys_munmap(cb, lp[0]) 349 return 0 350} 351 352// box-downsample src (w*h rgb) into the atlas cell at (cx0, cy0) of size cell*cell. 353// source region per dest texel derives from the ratio; no picked kernel. 354func ft_downsample_into(src: *u8, w: i64, h: i64, atlas: *u8, res: i64, cx0: i64, cy0: i64, cell: i64) -> i64 { 355 var dy: i64 = 0 356 while dy < cell { 357 let sy0: i64 = dy * h / cell 358 var sy1: i64 = (dy + 1) * h / cell 359 if sy1 <= sy0 { sy1 = sy0 + 1 } 360 var dx: i64 = 0 361 while dx < cell { 362 let sx0: i64 = dx * w / cell 363 var sx1: i64 = (dx + 1) * w / cell 364 if sx1 <= sx0 { sx1 = sx0 + 1 } 365 var sr: i64 = 0 366 var sg: i64 = 0 367 var sb: i64 = 0 368 var cnt: i64 = 0 369 var sy: i64 = sy0 370 while sy < sy1 { 371 var sx: i64 = sx0 372 while sx < sx1 { 373 let o: i64 = (sy * w + sx) * 3 374 sr = sr + (src[o] as i64) 375 sg = sg + (src[o+1] as i64) 376 sb = sb + (src[o+2] as i64) 377 cnt = cnt + 1 378 sx = sx + 1 379 } 380 sy = sy + 1 381 } 382 let ao: i64 = ((cy0 + dy) * res + cx0 + dx) * 3 383 atlas[ao] = (sr / cnt) as u8 384 atlas[ao+1] = (sg / cnt) as u8 385 atlas[ao+2] = (sb / cnt) as u8 386 dx = dx + 1 387 } 388 dy = dy + 1 389 } 390 return 0 391} 392 393// SHIP RESOLUTION: the incumbent's own derivation, transplanted VERBATIM from nx_nxa_texm 394// (txm_conf_at + txm_ship_res, 2026-08-22) -- the largest measured rung whose bytes*cast_size 395// fits the declared budget, REFUSING (never defaulting) when the budget is unsourced. One owner 396// per number; the reader must be the owner's reader. HOIST DEBT: this belongs in a shared lib 397// imported by both organs -- transplant noted in the ship report so the next toucher hoists it. 398func ft_conf_at(path: *u8, key: *u8) -> i64 { 399 let lp: *i64 = sys_mmap(16) as *i64 400 let b: *u8 = sys_read_file(path, lp) 401 if (b as i64) == 0 { return 0 - 1 } 402 let n: i64 = lp[0] 403 let klen: i64 = ft_slen(key) 404 var i: i64 = 0 405 var at_line_start: i64 = 1 406 var found: i64 = 0 407 var result: i64 = 0 - 1 408 while i < n { 409 if found == 0 { if at_line_start == 1 { 410 var matched: i64 = 1 411 var k: i64 = 0 412 while k < klen { 413 if i + k >= n { matched = 0 } 414 if matched == 1 { if b[i + k] != key[k] { matched = 0 } } 415 k = k + 1 416 } 417 if matched == 1 { 418 var o: i64 = i + klen 419 var sep: i64 = 0 420 if o < n { if b[o] == (32 as u8) { sep = 1 } } 421 if o < n { if b[o] == (9 as u8) { sep = 1 } } 422 if sep == 1 { 423 var ws: i64 = 1 424 while ws == 1 { 425 if o >= n { ws = 0 } 426 if ws == 1 { 427 if b[o] == (32 as u8) { o = o + 1 } else { 428 if b[o] == (9 as u8) { o = o + 1 } else { ws = 0 } 429 } 430 } 431 } 432 var v: i64 = 0 433 var digits: i64 = 0 434 var scanning: i64 = 1 435 while scanning == 1 { 436 if o >= n { scanning = 0 } 437 if scanning == 1 { 438 let c: i64 = (b[o] & 255) as i64 439 if c < 48 { scanning = 0 } 440 if c > 57 { scanning = 0 } 441 if scanning == 1 { v = v*10 + (c - 48); digits = digits + 1; o = o + 1 } 442 } 443 } 444 if digits > 0 { result = v; found = 1 } 445 } 446 } 447 } } 448 if b[i] == (10 as u8) { at_line_start = 1 } else { at_line_start = 0 } 449 i = i + 1 450 } 451 return result 452} 453 454func ft_ship_res() -> i64 { 455 let budget: i64 = ft_conf_at(FT_SHIP_CONF, "texture_budget_bytes" as *u8) 456 if budget < 0 { return 0 - 1 } 457 let cast: i64 = ft_conf_at(FT_SHIP_CONF, "cast_size" as *u8) 458 if cast < 0 { return 0 - 1 } 459 let b4: i64 = ft_conf_at(FT_SHIP_CONF, "res_bytes_4096" as *u8) 460 if b4 >= 0 { if b4*cast <= budget { return 4096 } } 461 let b3: i64 = ft_conf_at(FT_SHIP_CONF, "res_bytes_2048" as *u8) 462 if b3 >= 0 { if b3*cast <= budget { return 2048 } } 463 let b2: i64 = ft_conf_at(FT_SHIP_CONF, "res_bytes_1024" as *u8) 464 if b2 >= 0 { if b2*cast <= budget { return 1024 } } 465 let b1: i64 = ft_conf_at(FT_SHIP_CONF, "res_bytes_512" as *u8) 466 if b1 >= 0 { if b1*cast <= budget { return 512 } } 467 return 0 - 1 468} 469 470// ---- FBX prop readers ---- 471 472func ft_prop_i64(b: *u8, p: i64) -> i64 { 473 let t: i64 = b[p] as i64 474 if t == 76 { return fbxc_i64le(b, p + 1) } 475 if t == 73 { return fbxc_i32(b, p + 1) } 476 return 0 - 1 477} 478 479// first prop as string: out[0]=ptr to a NUL-copied buffer; returns len, 0 if not a string. 480func ft_prop_str(b: *u8, p: i64, out: *i64) -> i64 { 481 let t: i64 = b[p] as i64 482 if t != 83 { return 0 } 483 let n: i64 = fbxc_u32(b, p + 1) 484 let cp: *u8 = sys_mmap(n + 2) 485 var i: i64 = 0 486 while i < n { cp[i] = b[p + 5 + i]; i = i + 1 } 487 cp[n] = 0 as u8 488 out[0] = cp as i64 489 return n 490} 491 492func ft_str_is(s: *u8, n: i64, t: *u8) -> i64 { 493 var i: i64 = 0 494 while i < n { 495 if t[i] == (0 as u8) { return 0 } 496 if s[i] != t[i] { return 0 } 497 i = i + 1 498 } 499 if t[n] != (0 as u8) { return 0 } 500 return 1 501} 502 503// P row "DiffuseColor": skip 4 strings then read up to 3 D doubles as 0..255. 504func ft_prop_dcolor(b: *u8, p0: i64, rr: *i64, gg: *i64, bb: *i64, mi: i64) -> i64 { 505 var p: i64 = p0 506 var skipped: i64 = 0 507 while skipped < 4 { 508 let t: i64 = b[p] as i64 509 if t == 83 { p = p + 5 + fbxc_u32(b, p + 1); skipped = skipped + 1 } else { return 0 } 510 } 511 let tmp: *i64 = sys_mmap(32) as *i64 512 var vals: i64 = 0 513 var stop: i64 = 0 514 while vals < 3 { 515 if stop == 1 { vals = 3 } else { 516 let t2: i64 = b[p] as i64 517 if t2 == 68 { 518 let fx: i64 = fbxc_f64_fx(fbxc_i64le(b, p + 1)) 519 var c255: i64 = fx * 255 / FT_FX_ONE 520 if c255 < 0 { c255 = 0 } 521 if c255 > 255 { c255 = 255 } 522 tmp[vals] = c255 523 p = p + 9 524 vals = vals + 1 525 } else { stop = 1 } 526 } 527 } 528 if vals >= 3 { 529 rr[mi] = tmp[0] 530 gg[mi] = tmp[1] 531 bb[mi] = tmp[2] 532 } 533 sys_munmap(tmp as *u8, 32) 534 return 0 535} 536 537// C row: S kind, L a, L b [, S prop]. returns 0 OO, 1 OP, -1 other; fills arrays at index n. 538func ft_conn_row(b: *u8, p0: i64, ca: *i64, cb: *i64, cp: *i64, n: i64) -> i64 { 539 var p: i64 = p0 540 if (b[p] as i64) != 83 { return 0 - 1 } 541 let kl: i64 = fbxc_u32(b, p + 1) 542 let ko: i64 = p + 5 543 var kind: i64 = 0 - 1 544 if kl == 2 { 545 if (b[ko] as i64) == 79 { 546 if (b[ko+1] as i64) == 79 { kind = 0 } 547 if (b[ko+1] as i64) == 80 { kind = 1 } 548 } 549 } 550 if kind < 0 { return 0 - 1 } 551 p = ko + kl 552 let a: i64 = ft_prop_i64(b, p) 553 if (b[p] as i64) == 76 { p = p + 9 } else { p = p + 5 } 554 let b2: i64 = ft_prop_i64(b, p) 555 if (b[p] as i64) == 76 { p = p + 9 } else { p = p + 5 } 556 ca[n] = a 557 cb[n] = b2 558 var role: i64 = 0 559 if kind == 1 { 560 if (b[p] as i64) == 83 { 561 let pl: i64 = fbxc_u32(b, p + 1) 562 let po: i64 = p + 5 563 if ft_str_is(((b as i64) + po) as *u8, pl, "DiffuseColor" as *u8) == 1 { role = 1 } 564 if ft_str_is(((b as i64) + po) as *u8, pl, "NormalMap" as *u8) == 1 { role = 2 } 565 if ft_str_is(((b as i64) + po) as *u8, pl, "ShininessExponent" as *u8) == 1 { role = 3 } 566 if ft_str_is(((b as i64) + po) as *u8, pl, "ReflectionFactor" as *u8) == 1 { role = 4 } 567 if ft_str_is(((b as i64) + po) as *u8, pl, "TransparencyFactor" as *u8) == 1 { role = 5 } 568 } 569 } 570 cp[n] = role 571 return kind 572} 573 574func nt2_rd64(b: *u8, off: i64) -> i64 { 575 var v: i64 = 0 576 var k: i64 = 7 577 while k >= 0 { v = v * 256 + (b[off + k] as i64); k = k - 1 } 578 return v 579} 580func nt2_wr64(b: *u8, off: i64, v: i64) -> i64 { 581 var x: i64 = v 582 var k: i64 = 0 583 while k < 8 { b[off + k] = (x & 255) as u8; x = x >> 8; k = k + 1 } 584 return 0 585} 586func nt2_tageq(b: *u8, off: i64, t: *u8) -> i64 { 587 if nt2_rd64(b, off) == nxa_tag4(t) { return 1 } 588 return 0 589} 590 591// rebuild the container: carry every section except old TEXC/TEXM, append ours, recompute checks. 592func ft_write_out(xb: *u8, xns: i64, uvq: *i64, uvwords: i64, res: i64, grid: i64, 593 moff: *i64, mlen: *i64, blobbytes: i64, blob: *u8, outp: *u8) -> i64 { 594 var blobwords: i64 = blobbytes / FT_WORD 595 if blobwords * FT_WORD < blobbytes { blobwords = blobwords + 1 } 596 let tmwords: i64 = FT_TM_HDRW + FT_TM_NMAP * FT_TM_RECW + blobwords 597 var carried: i64 = 0 598 var total: i64 = 0 599 var s: i64 = 0 600 while s < xns { 601 let e: i64 = FT_HDR + s * FT_TOCE 602 if nt2_tageq(xb, e, "TEXC" as *u8) == 0 { if nt2_tageq(xb, e, "TEXM" as *u8) == 0 { 603 total = total + nt2_rd64(xb, e + 16) * FT_WORD 604 carried = carried + 1 605 } } 606 s = s + 1 607 } 608 let nsec: i64 = carried + 2 609 if nsec >= FT_MAXSEC { ft_err("FBXTEXM-REFUSE section table full\n" as *u8); return FT_E_BAD } 610 let toclen: i64 = FT_HDR + nsec * FT_TOCE 611 total = toclen + total + uvwords * FT_WORD + tmwords * FT_WORD 612 let nb: *u8 = sys_mmap(total + FT_PAD) 613 if (nb as i64) == 0 { ft_err("FBXTEXM-REFUSE cannot allocate output\n" as *u8); return FT_E_IO } 614 nt2_wr64(nb, 0, nxa_magic()) 615 nt2_wr64(nb, 8, 1) 616 nt2_wr64(nb, 16, nsec) 617 var wo: i64 = toclen 618 var ti: i64 = 0 619 var s2: i64 = 0 620 while s2 < xns { 621 let e2: i64 = FT_HDR + s2 * FT_TOCE 622 if nt2_tageq(xb, e2, "TEXC" as *u8) == 0 { if nt2_tageq(xb, e2, "TEXM" as *u8) == 0 { 623 let oldoff: i64 = nt2_rd64(xb, e2 + 8) 624 let wl: i64 = nt2_rd64(xb, e2 + 16) 625 let te: i64 = FT_HDR + ti * FT_TOCE 626 nt2_wr64(nb, te, nt2_rd64(xb, e2)) 627 nt2_wr64(nb, te + 8, wo) 628 nt2_wr64(nb, te + 16, wl) 629 var k: i64 = 0 630 while k < wl * FT_WORD { nb[wo + k] = xb[oldoff + k]; k = k + 1 } 631 let pw: *i64 = ((nb as i64) + wo) as *i64 632 nt2_wr64(nb, te + 24, nxa_check2(1, pw, wl)) 633 wo = wo + wl * FT_WORD 634 ti = ti + 1 635 } } 636 s2 = s2 + 1 637 } 638 let teC: i64 = FT_HDR + ti * FT_TOCE 639 nt2_wr64(nb, teC, nxa_tag4("TEXC" as *u8)) 640 nt2_wr64(nb, teC + 8, wo) 641 nt2_wr64(nb, teC + 16, uvwords) 642 var k2: i64 = 0 643 while k2 < uvwords { 644 nt2_wr64(nb, wo + k2 * FT_WORD, uvq[k2]) 645 k2 = k2 + 1 646 } 647 let pwC: *i64 = ((nb as i64) + wo) as *i64 648 nt2_wr64(nb, teC + 24, nxa_check2(1, pwC, uvwords)) 649 wo = wo + uvwords * FT_WORD 650 ti = ti + 1 651 let teM: i64 = FT_HDR + ti * FT_TOCE 652 nt2_wr64(nb, teM, nxa_tag4("TEXM" as *u8)) 653 nt2_wr64(nb, teM + 8, wo) 654 nt2_wr64(nb, teM + 16, tmwords) 655 nt2_wr64(nb, wo, FT_TM_NMAP) 656 nt2_wr64(nb, wo + 8, res) 657 nt2_wr64(nb, wo + 16, FT_TM_MODEL_METALROUGH) 658 nt2_wr64(nb, wo + 24, 0) 659 nt2_wr64(nb, wo + 32, 0) 660 nt2_wr64(nb, wo + 40, blobwords) 661 var m: i64 = 0 662 while m < FT_TM_NMAP { 663 let ro: i64 = wo + (FT_TM_HDRW + m * FT_TM_RECW) * FT_WORD 664 nt2_wr64(nb, ro, m) 665 nt2_wr64(nb, ro + 8, moff[m]) 666 nt2_wr64(nb, ro + 16, mlen[m]) 667 m = m + 1 668 } 669 let blobbase: i64 = wo + (FT_TM_HDRW + FT_TM_NMAP * FT_TM_RECW) * FT_WORD 670 var z: i64 = 0 671 while z < blobwords * FT_WORD { nb[blobbase + z] = 0 as u8; z = z + 1 } 672 var c: i64 = 0 673 while c < blobbytes { nb[blobbase + c] = blob[c]; c = c + 1 } 674 let pwM: *i64 = ((nb as i64) + wo) as *i64 675 nt2_wr64(nb, teM + 24, nxa_check2(1, pwM, tmwords)) 676 wo = wo + tmwords * FT_WORD 677 let tb: *i64 = ((nb as i64) + FT_HDR) as *i64 678 nt2_wr64(nb, 24, nxa_check2(1, tb, nsec * 4)) 679 let fd: i64 = sys_openat_wr(outp, FT_MODE) 680 if fd < 0 { ft_err("FBXTEXM-REFUSE cannot open output\n" as *u8); return FT_E_IO } 681 let wrote: i64 = sys_write(fd, nb, wo) 682 sys_close(fd) 683 if wrote != wo { ft_err("FBXTEXM-REFUSE short write\n" as *u8); return FT_E_IO } 684 return FT_OK 685} 686 687func main(argc: i64, argv: *i64) -> i64 { 688 if argc < 5 { 689 ft_err("usage: nx_fbx_texm <in.fbx> <texdir> <in_rigged.nxa> <out.nxa>\n" as *u8) 690 return FT_E_USAGE 691 } 692 let fbxp: *u8 = argv[1] as *u8 693 let texdir: *u8 = argv[2] as *u8 694 let inxa: *u8 = argv[3] as *u8 695 let outp: *u8 = argv[4] as *u8 696 697 let lp: *i64 = sys_mmap(16) as *i64 698 let fb: *u8 = sys_read_file(fbxp, lp) 699 let fn: i64 = lp[0] 700 if (fb as i64) == 0 { ft_err("FBXTEXM-REFUSE cannot read fbx\n" as *u8); return FT_E_IO } 701 let ver: i64 = fbxc_version(fb, fn) 702 if ver < 0 { ft_err("FBXTEXM-REFUSE not a binary FBX\n" as *u8); return FT_E_BAD } 703 let big: i64 = fbxc_big(ver) 704 705 let geo_id: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 706 let geo_nv: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 707 let geo_vbase: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 708 let geo_uvp: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 709 let geo_uvn: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 710 let geo_uvip: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 711 let geo_uvin: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 712 let geo_pvip: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 713 let geo_pvin: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 714 let geo_matp: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 715 let geo_matn: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 716 var ngeo: i64 = 0 717 718 let mat_id: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 719 let mat_dr: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 720 let mat_dg: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 721 let mat_db: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 722 let mat_hasdc: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 723 var nmat: i64 = 0 724 725 let tex_id: *i64 = sys_mmap(FT_MAXTEX * 8) as *i64 726 let tex_fn: *i64 = sys_mmap(FT_MAXTEX * 8) as *i64 727 var ntex: i64 = 0 728 729 let cn_kind: *i64 = sys_mmap(FT_MAXCONN * 8) as *i64 730 let cn_a: *i64 = sys_mmap(FT_MAXCONN * 8) as *i64 731 let cn_b: *i64 = sys_mmap(FT_MAXCONN * 8) as *i64 732 let cn_prop: *i64 = sys_mmap(FT_MAXCONN * 8) as *i64 733 var nconn: i64 = 0 734 735 let model_id: *i64 = sys_mmap(FT_MAXMODEL * 8) as *i64 736 var nmodel: i64 = 0 737 738 // ---- iterative walk with EXPLICIT context per stack frame. Table appends happen at SCAN 739 // time in document order (the fbx2nxa vbase law); child ranges carry which geometry / 740 // material / texture they belong to, so attachment cannot cross-bind (the defect an 741 // implicit "current" pointer has under LIFO ordering). 742 // frame: {pos, end, ctxG, ctxM, ctxT, sub} sub: 0 none, 1 inside LayerElementUV, 743 // 2 inside LayerElementMaterial, 3 inside Properties70-of-material 744 var stkcap: i64 = fn / 13 + FT_STACK_MIN 745 let stk: *i64 = sys_mmap(stkcap * 6 * 8) as *i64 746 let hdr: *i64 = sys_mmap(FBXC_OUTN * 8) as *i64 747 let outw: *i64 = sys_mmap(64) as *i64 748 var sp: i64 = 1 749 stk[0] = 27 750 stk[1] = fn 751 stk[2] = 0 - 1 752 stk[3] = 0 - 1 753 stk[4] = 0 - 1 754 stk[5] = 0 755 var overflow: i64 = 0 756 var spmax: i64 = 0 757 while sp > 0 { 758 sp = sp - 1 759 var pos: i64 = stk[sp*6] 760 let end: i64 = stk[sp*6 + 1] 761 let cG: i64 = stk[sp*6 + 2] 762 let cM: i64 = stk[sp*6 + 3] 763 let cT: i64 = stk[sp*6 + 4] 764 let sub: i64 = stk[sp*6 + 5] 765 while pos < end { 766 if fbxc_hdr(fb, fn, pos, big, hdr) != 0 { pos = end } else { 767 let e: i64 = hdr[FBXC_END] 768 if e <= pos { pos = end } else { 769 let nlen: i64 = hdr[FBXC_NAMELEN] 770 let nameo: i64 = hdr[FBXC_NAMEOFF] 771 let props: i64 = hdr[FBXC_PROPSOFF] 772 let child0: i64 = props + hdr[FBXC_PLEN] 773 var pushG: i64 = cG 774 var pushM: i64 = cM 775 var pushT: i64 = cT 776 var pushSub: i64 = sub 777 var wantpush: i64 = 0 778 if fbxc_name_is(fb, nameo, nlen, "Geometry" as *u8) == 1 { 779 if ngeo >= FT_MAXGEOM { overflow = 1 } else { 780 geo_id[ngeo] = ft_prop_i64(fb, props) 781 pushG = ngeo 782 pushSub = 0 783 wantpush = 1 784 ngeo = ngeo + 1 785 } 786 } 787 if fbxc_name_is(fb, nameo, nlen, "Model" as *u8) == 1 { 788 if nmodel < FT_MAXMODEL { model_id[nmodel] = ft_prop_i64(fb, props); nmodel = nmodel + 1 } 789 } 790 if fbxc_name_is(fb, nameo, nlen, "Material" as *u8) == 1 { 791 if nmat >= FT_MAXMAT { overflow = 1 } else { 792 mat_id[nmat] = ft_prop_i64(fb, props) 793 mat_hasdc[nmat] = 0 794 pushM = nmat 795 pushSub = 0 796 wantpush = 1 797 nmat = nmat + 1 798 } 799 } 800 if fbxc_name_is(fb, nameo, nlen, "Texture" as *u8) == 1 { 801 if ntex >= FT_MAXTEX { overflow = 1 } else { 802 tex_id[ntex] = ft_prop_i64(fb, props) 803 tex_fn[ntex] = 0 804 pushT = ntex 805 pushSub = 0 806 wantpush = 1 807 ntex = ntex + 1 808 } 809 } 810 if fbxc_name_is(fb, nameo, nlen, "RelativeFilename" as *u8) == 1 { 811 if cT >= 0 { 812 let sl: i64 = ft_prop_str(fb, props, outw) 813 if sl > 0 { 814 if tex_fn[cT] == 0 { 815 let bn: *u8 = sys_mmap(FT_NAMECAP) 816 ft_basename(outw[0] as *u8, sl, bn) 817 tex_fn[cT] = bn as i64 818 } 819 } 820 } 821 } 822 if fbxc_name_is(fb, nameo, nlen, "Vertices" as *u8) == 1 { 823 if cG >= 0 { 824 if fbxc_array(fb, props + 1, 8, outw) == 0 { geo_nv[cG] = outw[2] / 3 } 825 } 826 } 827 if fbxc_name_is(fb, nameo, nlen, "PolygonVertexIndex" as *u8) == 1 { 828 if cG >= 0 { 829 if fbxc_array(fb, props + 1, 4, outw) == 0 { 830 geo_pvip[cG] = outw[0] 831 geo_pvin[cG] = outw[2] 832 } 833 } 834 } 835 if fbxc_name_is(fb, nameo, nlen, "LayerElementUV" as *u8) == 1 { 836 if cG >= 0 { pushSub = 1; wantpush = 1 } 837 } 838 if fbxc_name_is(fb, nameo, nlen, "LayerElementMaterial" as *u8) == 1 { 839 if cG >= 0 { pushSub = 2; wantpush = 1 } 840 } 841 if fbxc_name_is(fb, nameo, nlen, "UV" as *u8) == 1 { 842 if cG >= 0 { if sub == 1 { 843 if geo_uvp[cG] == 0 { 844 if fbxc_array(fb, props + 1, 8, outw) == 0 { 845 geo_uvp[cG] = outw[0] 846 geo_uvn[cG] = outw[2] / 2 847 } 848 } 849 } } 850 } 851 if fbxc_name_is(fb, nameo, nlen, "UVIndex" as *u8) == 1 { 852 if cG >= 0 { if sub == 1 { 853 if geo_uvip[cG] == 0 { 854 if fbxc_array(fb, props + 1, 4, outw) == 0 { 855 geo_uvip[cG] = outw[0] 856 geo_uvin[cG] = outw[2] 857 } 858 } 859 } } 860 } 861 if fbxc_name_is(fb, nameo, nlen, "Materials" as *u8) == 1 { 862 if cG >= 0 { if sub == 2 { 863 if geo_matp[cG] == 0 { 864 if fbxc_array(fb, props + 1, 4, outw) == 0 { 865 geo_matp[cG] = outw[0] 866 geo_matn[cG] = outw[2] 867 } 868 } 869 } } 870 } 871 if fbxc_name_is(fb, nameo, nlen, "P" as *u8) == 1 { 872 if cM >= 0 { 873 let sl2: i64 = ft_prop_str(fb, props, outw) 874 if sl2 > 0 { 875 if ft_str_is(outw[0] as *u8, sl2, "DiffuseColor" as *u8) == 1 { 876 ft_prop_dcolor(fb, props, mat_dr, mat_dg, mat_db, cM) 877 mat_hasdc[cM] = 1 878 } 879 } 880 } 881 } 882 if fbxc_name_is(fb, nameo, nlen, "C" as *u8) == 1 { 883 if nconn >= FT_MAXCONN { overflow = 1 } else { 884 let kk: i64 = ft_conn_row(fb, props, cn_a, cn_b, cn_prop, nconn) 885 if kk >= 0 { cn_kind[nconn] = kk; nconn = nconn + 1 } 886 } 887 } 888 if wantpush == 0 { wantpush = 1 } // descend everywhere; context rides the frame 889 // A leaf's trailing NUL record is 13 bytes (25 for v7500+); a child range that 890 // holds ONLY the terminator has no nodes. Pushing those flooded the stack on 891 // the knight (12,988 connection leaves) -- measured, then named, then fixed. 892 var nullen: i64 = 13 893 if big == 1 { nullen = 25 } 894 if e - child0 > nullen { 895 if sp >= stkcap - 1 { overflow = 1 } else { 896 if sp > spmax { spmax = sp } 897 stk[sp*6] = child0 898 stk[sp*6 + 1] = e 899 stk[sp*6 + 2] = pushG 900 stk[sp*6 + 3] = pushM 901 stk[sp*6 + 4] = pushT 902 stk[sp*6 + 5] = pushSub 903 sp = sp + 1 904 } 905 } 906 pos = e 907 } 908 } 909 } 910 } 911 if overflow == 1 { 912 ft_err("FBXTEXM-REFUSE a table cap was reached -- the counts at refusal name the offender:\n" as *u8) 913 ft_err(" geoms=" as *u8); ft_outn(ngeo); ft_err(" of " as *u8); ft_outn(FT_MAXGEOM) 914 ft_err(" mats=" as *u8); ft_outn(nmat); ft_err(" of " as *u8); ft_outn(FT_MAXMAT) 915 ft_err(" texs=" as *u8); ft_outn(ntex); ft_err(" of " as *u8); ft_outn(FT_MAXTEX) 916 ft_err(" models=" as *u8); ft_outn(nmodel); ft_err(" of " as *u8); ft_outn(FT_MAXMODEL) 917 ft_err(" conns=" as *u8); ft_outn(nconn); ft_err(" of " as *u8); ft_outn(FT_MAXCONN) 918 ft_err(" stack_peak=" as *u8); ft_outn(spmax); ft_err(" of " as *u8); ft_outn(stkcap) 919 ft_err("\n" as *u8) 920 return FT_E_BAD 921 } 922 ft_out("fbx walked: geoms=" as *u8); ft_outn(ngeo) 923 ft_out(" mats=" as *u8); ft_outn(nmat) 924 ft_out(" texs=" as *u8); ft_outn(ntex) 925 ft_out(" models=" as *u8); ft_outn(nmodel) 926 ft_out(" conns=" as *u8); ft_outn(nconn) 927 ft_nl() 928 if ngeo == 0 { ft_err("FBXTEXM-REFUSE no geometry\n" as *u8); return FT_E_BAD } 929 930 var vtotal: i64 = 0 931 var gi: i64 = 0 932 while gi < ngeo { 933 geo_vbase[gi] = vtotal 934 vtotal = vtotal + geo_nv[gi] 935 gi = gi + 1 936 } 937 938 let lp2: *i64 = sys_mmap(16) as *i64 939 let xb: *u8 = sys_read_file(inxa, lp2) 940 if (xb as i64) == 0 { ft_err("FBXTEXM-REFUSE cannot read rigged nxa\n" as *u8); return FT_E_IO } 941 if nt2_rd64(xb, 0) != nxa_magic() { ft_err("FBXTEXM-REFUSE rigged input is not an NXA\n" as *u8); return FT_E_BAD } 942 let xns: i64 = nt2_rd64(xb, 16) 943 var vo: i64 = 0 - 1 944 var s: i64 = 0 945 while s < xns { 946 let e5: i64 = FT_HDR + s*FT_TOCE 947 if nt2_tageq(xb, e5, "VERT" as *u8) == 1 { vo = nt2_rd64(xb, e5 + 8) } 948 s = s + 1 949 } 950 if vo < 0 { ft_err("FBXTEXM-REFUSE rigged nxa has no VERT\n" as *u8); return FT_E_NOSEC } 951 let xnv: i64 = nt2_rd64(xb, vo) 952 if xnv != vtotal { 953 ft_err("FBXTEXM-REFUSE DESYNC fbx-control-points=" as *u8); ft_outn(vtotal) 954 ft_err(" rigged-VERT=" as *u8); ft_outn(xnv); ft_nl() 955 return FT_E_DESYNC 956 } 957 958 // ---- geometry -> model (single pass over connections) ---- 959 let geo_model: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 960 var z0: i64 = 0 961 while z0 < ngeo { geo_model[z0] = 0 - 1; z0 = z0 + 1 } 962 var c0: i64 = 0 963 while c0 < nconn { 964 if cn_kind[c0] == 0 { 965 var gI: i64 = 0 966 while gI < ngeo { 967 if geo_id[gI] == cn_a[c0] { 968 var mo: i64 = 0 969 while mo < nmodel { 970 if model_id[mo] == cn_b[c0] { geo_model[gI] = cn_b[c0]; mo = nmodel } else { mo = mo + 1 } 971 } 972 gI = ngeo 973 } else { gI = gI + 1 } 974 } 975 } 976 c0 = c0 + 1 977 } 978 979 // ---- per-material role sources (single pass) ---- 980 let mat_dfn: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 981 let mat_rfn: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 982 let mat_mfn: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 983 var c1: i64 = 0 984 while c1 < nconn { 985 if cn_kind[c1] == 1 { 986 var mi0: i64 = 0 987 while mi0 < nmat { 988 if mat_id[mi0] == cn_b[c1] { 989 var ti0: i64 = 0 990 while ti0 < ntex { 991 if tex_id[ti0] == cn_a[c1] { 992 if cn_prop[c1] == 1 { if mat_dfn[mi0] == 0 { mat_dfn[mi0] = tex_fn[ti0] } } 993 if cn_prop[c1] == 3 { if mat_rfn[mi0] == 0 { mat_rfn[mi0] = tex_fn[ti0] } } 994 if cn_prop[c1] == 4 { if mat_mfn[mi0] == 0 { mat_mfn[mi0] = tex_fn[ti0] } } 995 ti0 = ntex 996 } else { ti0 = ti0 + 1 } 997 } 998 mi0 = nmat 999 } else { mi0 = mi0 + 1 } 1000 } 1001 } 1002 c1 = c1 + 1 1003 } 1004 1005 // ---- charts: one per distinct diffuse basename; scalar materials chart individually ---- 1006 let ch_file: *i64 = sys_mmap(FT_MAXCHART * 8) as *i64 1007 let ch_mat: *i64 = sys_mmap(FT_MAXCHART * 8) as *i64 1008 let ch_rough: *i64 = sys_mmap(FT_MAXCHART * 8) as *i64 1009 let ch_metal: *i64 = sys_mmap(FT_MAXCHART * 8) as *i64 1010 var nchart: i64 = 0 1011 let mat_chart: *i64 = sys_mmap(FT_MAXMAT * 8) as *i64 1012 var mi: i64 = 0 1013 while mi < nmat { 1014 var found: i64 = 0 - 1 1015 if mat_dfn[mi] != 0 { 1016 var k2c: i64 = 0 1017 while k2c < nchart { 1018 if ch_file[k2c] != 0 { 1019 if ft_streq(ch_file[k2c] as *u8, mat_dfn[mi] as *u8) == 1 { found = k2c; k2c = nchart } else { k2c = k2c + 1 } 1020 } else { k2c = k2c + 1 } 1021 } 1022 } 1023 if found < 0 { 1024 if nchart >= FT_MAXCHART { ft_err("FBXTEXM-REFUSE chart cap reached\n" as *u8); return FT_E_BAD } 1025 ch_file[nchart] = mat_dfn[mi] 1026 ch_mat[nchart] = mi 1027 ch_rough[nchart] = mat_rfn[mi] 1028 ch_metal[nchart] = mat_mfn[mi] 1029 found = nchart 1030 nchart = nchart + 1 1031 } 1032 mat_chart[mi] = found 1033 mi = mi + 1 1034 } 1035 if nchart == 0 { ft_err("FBXTEXM-REFUSE zero charts\n" as *u8); return FT_E_BAD } 1036 var g: i64 = 1 1037 while g * g < nchart { g = g + 1 } 1038 let res: i64 = ft_ship_res() 1039 if res < 0 { ft_err("FBXTEXM-REFUSE ship budget unsourced (knowledge/texture_ship_budget.conf) -- refusing, never defaulting 1040" as *u8); return FT_E_BAD } 1041 let cell: i64 = res / g 1042 ft_out("charts=" as *u8); ft_outn(nchart) 1043 ft_out(" grid=" as *u8); ft_outn(g) 1044 ft_out(" res=" as *u8); ft_outn(res) 1045 ft_out(" cell=" as *u8); ft_outn(cell) 1046 ft_nl() 1047 1048 // ---- per-geometry material slot tables (model material list in connection order) ---- 1049 let slot_tab: *i64 = sys_mmap(FT_MAXGEOM * FT_MAXSLOT * 8) as *i64 1050 let slot_n: *i64 = sys_mmap(FT_MAXGEOM * 8) as *i64 1051 var gi1: i64 = 0 1052 while gi1 < ngeo { 1053 slot_n[gi1] = 0 1054 var c2: i64 = 0 1055 while c2 < nconn { 1056 if cn_kind[c2] == 0 { if cn_b[c2] == geo_model[gi1] { 1057 var mi1: i64 = 0 1058 while mi1 < nmat { 1059 if mat_id[mi1] == cn_a[c2] { 1060 if slot_n[gi1] < FT_MAXSLOT { 1061 slot_tab[gi1 * FT_MAXSLOT + slot_n[gi1]] = mi1 1062 slot_n[gi1] = slot_n[gi1] + 1 1063 } 1064 mi1 = nmat 1065 } else { mi1 = mi1 + 1 } 1066 } 1067 } } 1068 c2 = c2 + 1 1069 } 1070 gi1 = gi1 + 1 1071 } 1072 1073 // ---- per-vertex UV + chart (first-poly-wins; ByPolygon material via slot table) ---- 1074 let uvq: *i64 = sys_mmap((FT_TX_PHDR + vtotal * FT_TX_STRIDE) * 8 + 64) as *i64 1075 let uvset: *u8 = sys_mmap(vtotal + 64) 1076 uvq[0] = vtotal 1077 uvq[1] = FT_TX_STRIDE 1078 uvq[2] = g 1079 uvq[3] = 0 1080 var covered: i64 = 0 1081 var gi3: i64 = 0 1082 while gi3 < ngeo { 1083 let base: i64 = geo_vbase[gi3] 1084 let pvin: i64 = geo_pvin[gi3] 1085 if pvin > 0 { 1086 let pvB: *u8 = geo_pvip[gi3] as *u8 1087 let uvB: *u8 = geo_uvip[gi3] as *u8 1088 let uvD: *u8 = geo_uvp[gi3] as *u8 1089 let lmB: *u8 = geo_matp[gi3] as *u8 1090 let lmn: i64 = geo_matn[gi3] 1091 // AllSame: the single Materials entry names the slot (NOT always 0) 1092 var gmat: i64 = 0 - 1 1093 var defslot: i64 = 0 1094 if lmn >= 1 { defslot = fbxc_i32(lmB, 0) } 1095 if defslot >= 0 { if defslot < slot_n[gi3] { gmat = slot_tab[gi3 * FT_MAXSLOT + defslot] } } 1096 if gmat < 0 { if slot_n[gi3] > 0 { gmat = slot_tab[gi3 * FT_MAXSLOT] } } 1097 var k3: i64 = 0 1098 var poly: i64 = 0 1099 while k3 < pvin { 1100 var iv: i64 = fbxc_i32(pvB, k3 * 4) 1101 var lastf: i64 = 0 1102 if iv < 0 { iv = 0 - iv - 1; lastf = 1 } 1103 let cpv: i64 = base + iv 1104 if cpv >= 0 { if cpv < vtotal { if uvset[cpv] == (0 as u8) { 1105 var u01: i64 = 0 1106 var v01: i64 = 0 1107 if geo_uvn[gi3] > 0 { 1108 var uvidx: i64 = k3 1109 if geo_uvin[gi3] > 0 { uvidx = fbxc_i32(uvB, k3 * 4) } 1110 if uvidx >= 0 { if uvidx < geo_uvn[gi3] { 1111 u01 = fbxc_f64_fx(fbxc_i64le(uvD, uvidx * 16)) 1112 v01 = fbxc_f64_fx(fbxc_i64le(uvD, uvidx * 16 + 8)) 1113 } } 1114 } 1115 // wrap tiling coords into [0,1) then flip v to row space 1116 u01 = ((u01 % FT_FX_ONE) + FT_FX_ONE) % FT_FX_ONE 1117 v01 = ((v01 % FT_FX_ONE) + FT_FX_ONE) % FT_FX_ONE 1118 v01 = FT_FX_ONE - v01 1119 var pm: i64 = gmat 1120 if lmn > 1 { 1121 if poly < lmn { 1122 let midx: i64 = fbxc_i32(lmB, poly * 4) 1123 if midx >= 0 { if midx < slot_n[gi3] { pm = slot_tab[gi3 * FT_MAXSLOT + midx] } } 1124 } 1125 } 1126 var chart: i64 = 0 1127 if pm >= 0 { chart = mat_chart[pm] } 1128 let tw: i64 = FT_Q16 / g 1129 let tx: i64 = chart % g 1130 let ty: i64 = chart / g 1131 let uu: i64 = tx*tw + 1 + (u01 * FT_Q16 / FT_FX_ONE) * (tw - 2) / FT_Q16 1132 let vv: i64 = ty*tw + 1 + (v01 * FT_Q16 / FT_FX_ONE) * (tw - 2) / FT_Q16 1133 uvq[FT_TX_PHDR + cpv*FT_TX_STRIDE] = uu 1134 uvq[FT_TX_PHDR + cpv*FT_TX_STRIDE + 1] = vv 1135 uvq[FT_TX_PHDR + cpv*FT_TX_STRIDE + 2] = chart 1136 uvset[cpv] = 1 as u8 1137 covered = covered + 1 1138 } } } 1139 if lastf == 1 { poly = poly + 1 } 1140 k3 = k3 + 1 1141 } 1142 } 1143 gi3 = gi3 + 1 1144 } 1145 ft_out("uv covered=" as *u8); ft_outn(covered) 1146 ft_out(" of " as *u8); ft_outn(vtotal) 1147 ft_nl() 1148 if covered * 2 < vtotal { ft_err("FBXTEXM-REFUSE under half the vertices got UVs -- wrong subject\n" as *u8); return FT_E_BAD } 1149 1150 // ---- albedo atlas (authored files; scalar-fill charts use the material's authored color) ---- 1151 let atlas: *u8 = sys_mmap(res * res * 3 + 64) 1152 let pathbuf: *u8 = sys_mmap(FT_NAMECAP * 2) 1153 // per-chart scratch for the fork-isolated decoder, derived from the output path 1154 let tmpp: *u8 = sys_mmap(FT_NAMECAP * 2) 1155 var tn: i64 = 0 1156 while outp[tn] != (0 as u8) { tmpp[tn] = outp[tn]; tn = tn + 1 } 1157 let sfx: *u8 = ".cellwork" as *u8 1158 var sj: i64 = 0 1159 while sfx[sj] != (0 as u8) { tmpp[tn] = sfx[sj]; tn = tn + 1; sj = sj + 1 } 1160 tmpp[tn] = 0 as u8 1161 var loaded: i64 = 0 1162 var scalarfills: i64 = 0 1163 var ci: i64 = 0 1164 while ci < nchart { 1165 let cx0: i64 = (ci % g) * cell 1166 let cy0: i64 = (ci / g) * cell 1167 var done: i64 = 0 1168 if ch_file[ci] != 0 { 1169 ft_pathjoin(texdir, ch_file[ci] as *u8, pathbuf) 1170 let rc: i64 = ft_cell_load(pathbuf, atlas, res, cx0, cy0, cell, tmpp) 1171 if rc == 0 { 1172 done = 1 1173 loaded = loaded + 1 1174 ft_out("chart " as *u8); ft_outn(ci) 1175 ft_out(" albedo " as *u8); ft_out(ch_file[ci] as *u8) 1176 ft_nl() 1177 } else { 1178 ft_out("chart " as *u8); ft_outn(ci) 1179 ft_out(" albedo LOAD-REFUSED rc=" as *u8); ft_outn(rc) 1180 ft_out(" file " as *u8); ft_out(ch_file[ci] as *u8) 1181 ft_nl() 1182 } 1183 } 1184 if done == 0 { 1185 let m: i64 = ch_mat[ci] 1186 var r2: i64 = 128 1187 var g2: i64 = 128 1188 var b2: i64 = 128 1189 if mat_hasdc[m] == 1 { r2 = mat_dr[m]; g2 = mat_dg[m]; b2 = mat_db[m] } 1190 var yy: i64 = 0 1191 while yy < cell { 1192 var xx: i64 = 0 1193 while xx < cell { 1194 let o2: i64 = ((cy0 + yy) * res + cx0 + xx) * 3 1195 atlas[o2] = r2 as u8 1196 atlas[o2+1] = g2 as u8 1197 atlas[o2+2] = b2 as u8 1198 xx = xx + 1 1199 } 1200 yy = yy + 1 1201 } 1202 scalarfills = scalarfills + 1 1203 ft_out("chart " as *u8); ft_outn(ci) 1204 ft_out(" SCALAR-FILL authored-diffuse hasdc=" as *u8); ft_outn(mat_hasdc[m]) 1205 ft_nl() 1206 } 1207 ci = ci + 1 1208 } 1209 ft_out("albedo atlas: loaded=" as *u8); ft_outn(loaded) 1210 ft_out(" scalar=" as *u8); ft_outn(scalarfills) 1211 ft_out(" of " as *u8); ft_outn(nchart) 1212 ft_nl() 1213 1214 // ---- rough + metal: carried only when EVERY chart decodes; else absent BY NAME ---- 1215 let rough: *u8 = sys_mmap(res * res * 3 + 64) 1216 let metal: *u8 = sys_mmap(res * res * 3 + 64) 1217 var have_rough: i64 = 1 1218 var have_metal: i64 = 1 1219 var ci2: i64 = 0 1220 while ci2 < nchart { 1221 let cx1: i64 = (ci2 % g) * cell 1222 let cy1: i64 = (ci2 / g) * cell 1223 var rok: i64 = 0 1224 if ch_rough[ci2] != 0 { 1225 ft_pathjoin(texdir, ch_rough[ci2] as *u8, pathbuf) 1226 if ft_cell_load(pathbuf, rough, res, cx1, cy1, cell, tmpp) == 0 { rok = 1 } 1227 } 1228 if rok == 0 { 1229 if have_rough == 1 { 1230 ft_out("rough chart " as *u8); ft_outn(ci2) 1231 ft_out(" no decodable source -> ROUGH MAP ABSENT\n" as *u8) 1232 } 1233 have_rough = 0 1234 } 1235 var mok: i64 = 0 1236 if ch_metal[ci2] != 0 { 1237 ft_pathjoin(texdir, ch_metal[ci2] as *u8, pathbuf) 1238 if ft_cell_load(pathbuf, metal, res, cx1, cy1, cell, tmpp) == 0 { mok = 1 } 1239 } 1240 if mok == 0 { 1241 if have_metal == 1 { 1242 ft_out("metal chart " as *u8); ft_outn(ci2) 1243 ft_out(" no decodable source -> METAL MAP ABSENT\n" as *u8) 1244 } 1245 have_metal = 0 1246 } 1247 ci2 = ci2 + 1 1248 } 1249 1250 // ---- encode maps ---- 1251 let fbp: *i64 = sys_mmap(res * res * 8 + 64) as *i64 1252 let blobcap: i64 = (res * res * 3 + res * 2 + 4096) * FT_TM_NMAP 1253 let blob: *u8 = sys_mmap(blobcap + 64) 1254 let moff: *i64 = sys_mmap(FT_TM_NMAP * 8) as *i64 1255 let mlen: *i64 = sys_mmap(FT_TM_NMAP * 8) as *i64 1256 var mslot: i64 = 0 1257 while mslot < FT_TM_NMAP { moff[mslot] = 0; mlen[mslot] = 0; mslot = mslot + 1 } 1258 var bo: i64 = 0 1259 var p2: i64 = 0 1260 while p2 < res * res { 1261 fbp[p2] = (atlas[p2*3] as i64) + (atlas[p2*3+1] as i64) * 256 + (atlas[p2*3+2] as i64) * 65536 1262 p2 = p2 + 1 1263 } 1264 let alen: i64 = write_png_buf(fbp, res, res, ((blob as i64) + bo) as *u8) 1265 if alen <= 0 { ft_err("FBXTEXM-REFUSE albedo PNG encode failed\n" as *u8); return FT_E_BAD } 1266 if bo + alen > blobcap { ft_err("FBXTEXM-REFUSE blob reserve exceeded -- refusing, not truncating\n" as *u8); return FT_E_BAD } 1267 moff[FT_SLOT_ALBEDO] = bo 1268 mlen[FT_SLOT_ALBEDO] = alen 1269 bo = bo + alen 1270 if have_metal == 1 { 1271 var p3: i64 = 0 1272 while p3 < res * res { 1273 fbp[p3] = (metal[p3*3] as i64) + (metal[p3*3+1] as i64) * 256 + (metal[p3*3+2] as i64) * 65536 1274 p3 = p3 + 1 1275 } 1276 let ml: i64 = write_png_buf(fbp, res, res, ((blob as i64) + bo) as *u8) 1277 if ml > 0 { if bo + ml <= blobcap { moff[FT_SLOT_METAL] = bo; mlen[FT_SLOT_METAL] = ml; bo = bo + ml } } 1278 } 1279 if have_rough == 1 { 1280 var p4: i64 = 0 1281 while p4 < res * res { 1282 fbp[p4] = (rough[p4*3] as i64) + (rough[p4*3+1] as i64) * 256 + (rough[p4*3+2] as i64) * 65536 1283 p4 = p4 + 1 1284 } 1285 let rl: i64 = write_png_buf(fbp, res, res, ((blob as i64) + bo) as *u8) 1286 if rl > 0 { if bo + rl <= blobcap { moff[FT_SLOT_ROUGH] = bo; mlen[FT_SLOT_ROUGH] = rl; bo = bo + rl } } 1287 } 1288 ft_out("normal map: ABSENT-BY-DEPTH (measured: donor normals are 16-bit PNG; this decoder refuses rather than quantize silently)\n" as *u8) 1289 1290 let rcw: i64 = ft_write_out(xb, xns, uvq, FT_TX_PHDR + vtotal * FT_TX_STRIDE, res, g, moff, mlen, bo, blob, outp) 1291 if rcw != 0 { return rcw } 1292 ft_out("FBXTEXM-OK verts=" as *u8); ft_outn(vtotal) 1293 ft_out(" charts=" as *u8); ft_outn(nchart) 1294 ft_out(" res=" as *u8); ft_outn(res) 1295 ft_out(" albedo_bytes=" as *u8); ft_outn(mlen[FT_SLOT_ALBEDO]) 1296 ft_out(" metal_bytes=" as *u8); ft_outn(mlen[FT_SLOT_METAL]) 1297 ft_out(" rough_bytes=" as *u8); ft_outn(mlen[FT_SLOT_ROUGH]) 1298 ft_out(" normal_bytes=0" as *u8) 1299 ft_nl() 1300 return FT_OK 1301}