nx_fbx_import.nx source
↩ module page · 156 lines · 8251 B
1// nx_fbx_import.nx -- binary FBX GEOMETRY extraction. Format knowledge lives in nx_fbx_core; this
2// file only knows what it wants (Vertices + PolygonVertexIndex -> fx1024 triangles).
3//
4// ★CONSOLIDATED (seq1788). The node walk was written twice -- here and in nx_fbx_rig.nx (the rig
5// survey: joints, skin clusters, weights, animation curves, emits nothing by design). The purposes
6// differ; the FORMAT READING did not. Both now share nx_fbx_core, so a node-format bug has one home.
7// The consolidation also FIXED A REAL CAPABILITY GAP for free: this reader used to REFUSE v7500+
8// outright while nx_fbx_rig already handled u64 offsets. Sharing the header decoder means geometry
9// extraction inherits that instead of re-deriving it.
10//
11// ★POLYGON INDICES ARE XOR-TERMINATED: a NEGATIVE index marks the last vertex of a polygon and its
12// true value is -v-1. Missing that reads every polygon one vertex short and silently yields a
13// shredded mesh -- plausible counts over garbage, the failure shape this lane keeps meeting.
14//
15// usage as a lib: fbx_read(path, vbuf, fbuf, vcap, fcap, counts) -> 0 ok, negative on error
16// counts[0]=nv counts[1]=ntri counts[2]=nodes counts[3]=nVertices
17// counts[4]=nPolyIdx counts[5]=nGeometry counts[6]=max_arraylen counts[7]=decode_fails
18// license_tier: ORIGINAL
19import "nx_fbx_core.nx"
20
21// st: 0=vbuf 1=fbuf 2=vcap 3=fcap 4=nv 5=ntri 6=nodes 7=nVerts 8=nPoly 9=nGeom 10=maxlen 11=fails 12=big
22func fbx_walk(buf: *u8, n: i64, pos0: i64, end: i64, st: *i64) -> i64 {
23 var pos: i64 = pos0
24 let hdr: *i64 = sys_mmap(FBXC_OUTN * 8) as *i64
25 let outp: *i64 = sys_mmap(64) as *i64
26 var guard: i64 = 0
27 while guard == 0 {
28 if fbxc_hdr(buf, n, pos, st[12], hdr) != 0 { guard = 1 } else {
29 let eoff: i64 = hdr[FBXC_END]
30 if eoff > end { guard = 1 } else {
31 if eoff <= pos { guard = 1 } else {
32 let nlen: i64 = hdr[FBXC_NAMELEN]
33 let nameo: i64 = hdr[FBXC_NAMEOFF]
34 let props: i64 = hdr[FBXC_PROPSOFF]
35 st[6] = st[6] + 1
36 if fbxc_name_is(buf, nameo, nlen, "Geometry" as *u8) == 1 { st[9] = st[9] + 1 }
37
38 if fbxc_name_is(buf, nameo, nlen, "Vertices" as *u8) == 1 {
39 st[7] = st[7] + 1
40 if hdr[FBXC_NPROPS] >= 1 {
41 if buf[props] == (100 as u8) {
42 let al: i64 = fbxc_u32(buf, props + 1)
43 if al > st[10] { st[10] = al }
44 let arc: i64 = fbxc_array(buf, props + 1, 8, outp)
45 if arc != 0 { st[11] = st[11] + 1 } else {
46 let dp: *u8 = outp[0] as *u8
47 let cnt: i64 = outp[2]
48 let vb: *i64 = st[0] as *i64
49 // MULTI-GEOMETRY (2026-08-23): APPEND at the running total and record
50 // this geometry's base in st[13]; PolygonVertexIndex offsets its
51 // indices by that base, so many Geometry nodes concatenate into ONE
52 // coherent vertex/index space. st[4] is now the TOTAL vertex count,
53 // not the largest single array -- a Fab character ships body/armor/
54 // hair as separate Geometry nodes and the old max-only read kept one.
55 let vbase: i64 = st[4]
56 st[13] = vbase
57 var i: i64 = 0
58 var nv: i64 = 0
59 while i + 2 < cnt {
60 if vbase + nv < st[2] {
61 vb[(vbase+nv)*3] = fbxc_f64_fx(fbxc_i64le(dp, i*8))
62 vb[(vbase+nv)*3+1] = fbxc_f64_fx(fbxc_i64le(dp, (i+1)*8))
63 vb[(vbase+nv)*3+2] = fbxc_f64_fx(fbxc_i64le(dp, (i+2)*8))
64 nv = nv + 1
65 }
66 i = i + 3
67 }
68 st[4] = vbase + nv
69 }
70 }
71 }
72 }
73 if fbxc_name_is(buf, nameo, nlen, "PolygonVertexIndex" as *u8) == 1 {
74 st[8] = st[8] + 1
75 if hdr[FBXC_NPROPS] >= 1 {
76 if buf[props] == (105 as u8) {
77 let arc2: i64 = fbxc_array(buf, props + 1, 4, outp)
78 if arc2 != 0 { st[11] = st[11] + 1 } else {
79 let dp2: *u8 = outp[0] as *u8
80 let cnt2: i64 = outp[2]
81 let fb: *i64 = st[1] as *i64
82 let poly: *i64 = sys_mmap(512 * 8) as *i64
83 var k: i64 = 0
84 var nt: i64 = st[5]
85 var np: i64 = 0
86 while k < cnt2 {
87 var iv: i64 = fbxc_i32(dp2, k * 4)
88 var last: i64 = 0
89 if iv < 0 { iv = 0 - iv - 1; last = 1 }
90 if np < 512 { poly[np] = iv + st[13]; np = np + 1 }
91 if last == 1 {
92 var j: i64 = 1
93 while j + 1 < np {
94 if nt < st[3] {
95 fb[nt*3] = poly[0]
96 fb[nt*3+1] = poly[j]
97 fb[nt*3+2] = poly[j+1]
98 nt = nt + 1
99 }
100 j = j + 1
101 }
102 np = 0
103 }
104 k = k + 1
105 }
106 sys_munmap(poly as *u8, 512 * 8)
107 st[5] = nt
108 }
109 }
110 }
111 }
112 let childs: i64 = props + hdr[FBXC_PLEN]
113 if childs + hdr[FBXC_HDRSZ] <= eoff { fbx_walk(buf, n, childs, eoff, st) }
114 pos = eoff
115 }
116 }
117 }
118 }
119 sys_munmap(hdr as *u8, FBXC_OUTN * 8)
120 sys_munmap(outp as *u8, 64)
121 return 0
122}
123
124// Read geometry from a binary FBX. 0 ok; -1 unreadable, -2 not an FBX, -4 no vertices found.
125// ⚠v7500+ is now SUPPORTED via the shared header decoder (it used to be refused outright).
126func fbx_read(path: *u8, vbuf: *i64, fbuf: *i64, vcap: i64, fcap: i64, counts: *i64) -> i64 {
127 var z: i64 = 0
128 while z < 8 { counts[z] = 0; z = z + 1 }
129 let bl: *i64 = sys_mmap(16) as *i64
130 bl[0] = 0
131 let buf: *u8 = sys_read_file(path, bl)
132 let n: i64 = bl[0]
133 if (buf as i64) == 0 { return 0 - 1 }
134 let ver: i64 = fbxc_version(buf, n)
135 if ver < 0 { return 0 - 2 }
136 let st: *i64 = sys_mmap(128) as *i64
137 var q: i64 = 0
138 while q < 16 { st[q] = 0; q = q + 1 }
139 st[0] = vbuf as i64
140 st[1] = fbuf as i64
141 st[2] = vcap
142 st[3] = fcap
143 st[12] = fbxc_big(ver)
144 fbx_walk(buf, n, 27, n, st)
145 counts[0] = st[4]
146 counts[1] = st[5]
147 counts[2] = st[6]
148 counts[3] = st[7]
149 counts[4] = st[8]
150 counts[5] = st[9]
151 counts[6] = st[10]
152 counts[7] = st[11]
153 sys_munmap(st as *u8, 128)
154 if counts[0] == 0 { return 0 - 4 }
155 return 0
156}