nx_nxa_garment_bind_lib.nx source
↩ module page · 210 lines · 11926 B
1// Deterministic garment-to-body binding, shared by asset preparation and worker conversion.
2import "nx_nxa_gpu_lib.nx"
3
4const NGB_WORD: i64=8
5const NGB_COORD_LIMIT: i64=1<<NGP_F32_INTEGER_BITS // Exact integer positions in the incumbent float32 upload.
6const NGB_E_POLICY: i64=0-20
7const NGB_E_COORD: i64=0-21
8const NGB_E_CANDIDATE: i64=0-22
9
10func ngb_coordinates(h: *i64,at: i64) -> i64 {
11 var i: i64=0
12 while i<h[at]*NGP_COMPONENTS {
13 let v: i64=h[at+1+i]
14 if v<0-NGB_COORD_LIMIT || v>NGB_COORD_LIMIT { return NGB_E_COORD }
15 i=i+1
16 }
17 return 0
18}
19// step and lower_band are caller-owned binding policy, never hidden asset-specific constants.
20// Below lower_band, queries bind only above that band; otherwise the candidate floor is zero.
21// Coordinates are exact float32 integers and squared distances fit exact binary64 integers.
22// Ties select the lowest eligible source index. No candidate is an error, never vertex-zero fallback.
23func ngb_map(b: *u8,n: i64,out: *u8,cap: i64,step: i64,lower_band: i64) -> i64 {
24 if step<=0 || lower_band<0 || lower_band>NGB_COORD_LIMIT { return NGB_E_POLICY }
25 let va: i64=ngp_section(b,n,"VERT" as *u8)
26 if va<0 { return va }
27 let ga: i64=ngp_section(b,n,"GVRT" as *u8)
28 if ga<0 { return ga }
29 let h: *i64=b as *i64
30 let nv: i64=h[va];let ng: i64=h[ga]
31 let need: i64=ng*NGB_WORD
32 let valid: i64=ngp_output(b,n,out,cap,need)
33 if valid<0 { return valid }
34 let vc: i64=ngb_coordinates(h,va)
35 if vc<0 { return vc }
36 let gc: i64=ngb_coordinates(h,ga)
37 if gc<0 { return gc }
38 var above_zero: i64=0;var above_band: i64=0
39 var u: i64=0
40 while u<nv {
41 let z: i64=h[va+1+u*NGP_COMPONENTS+2]
42 if z>=0 { above_zero=1 }
43 if z>=lower_band { above_band=1 }
44 // Subtraction prevents an arbitrary caller stride overflowing the loop index.
45 if step>=nv-u { u=nv } else { u=u+step }
46 }
47 var v: i64=0
48 while v<ng {
49 let z: i64=h[ga+1+v*NGP_COMPONENTS+2]
50 if z<lower_band { if above_band==0 { return NGB_E_CANDIDATE } }
51 else { if above_zero==0 { return NGB_E_CANDIDATE } }
52 v=v+1
53 }
54 // Every refusal precedes the first destination write.
55 let dst: *i64=out as *i64
56 v=0
57 while v<ng {
58 let query: i64=ga+1+v*NGP_COMPONENTS
59 var floor: i64=0
60 if h[query+2]<lower_band { floor=lower_band }
61 var best: i64=0-1;var distance: i64=0
62 u=0
63 while u<nv {
64 let candidate: i64=va+1+u*NGP_COMPONENTS
65 if h[candidate+2]>=floor {
66 let dx: i64=h[candidate]-h[query]
67 let dy: i64=h[candidate+1]-h[query+1]
68 let dz: i64=h[candidate+2]-h[query+2]
69 let d: i64=dx*dx+dy*dy+dz*dz
70 if best<0 || d<distance { best=u;distance=d }
71 }
72 if step>=nv-u { u=nv } else { u=u+step }
73 }
74 dst[v]=best;v=v+1
75 }
76 return need
77}
78
79const NGB_CLIP_E_INPUT:i64=-30
80const NGB_CLIP_E_RANGE:i64=-32
81const NGB_CLIP_I64_MAX:i64=9223372036854775807
82func ngb_abs(x:i64)->i64{if x<0{return 0-x};return x}
83func ngb_clip_product(a:i64,b:i64)->i64 {
84 if a<0||b<0{return -1};if b>0{if a>NGB_CLIP_I64_MAX/b{return -1}};return a*b
85}
86func ngb_clip_gcd(a:i64,b:i64)->i64 {var x:i64=a;var y:i64=b;while y>0{let z:i64=x%y;x=y;y=z};return x}
87
88// Twice polygon area in the canonical source triangle's (bary1,bary2) plane.
89// Exact numerator/denominator; positive winding and <=1 means no inverted/expanded source coverage.
90// Input records are already validated by the clipping boundary. Large common denominators refuse.
91func ngb_clip_area_ratio_v1(poly:*i64,n:i64,result:*i64)->i64{
92 if n<3||n>7{return NGB_CLIP_E_INPUT};var den:i64=1;var i:i64=0
93 while i<n{if poly[i*4+3]<=0{return NGB_CLIP_E_INPUT};den=ngb_clip_product(den,poly[i*4+3]);if den<0{return NGB_CLIP_E_RANGE};i=i+1}
94 var sum:i64=0;i=0
95 while i<n{let j:i64=(i+1)%n;let a:i64=ngb_clip_product(poly[i*4+1],poly[j*4+2]);let b:i64=ngb_clip_product(poly[i*4+2],poly[j*4+1]);if a<0||b<0{return NGB_CLIP_E_RANGE}
96 let pair:i64=ngb_clip_product(poly[i*4+3],poly[j*4+3]);if pair<=0{return NGB_CLIP_E_RANGE}
97 let term:i64=ngb_clip_product(ngb_abs(a-b),den/pair);if term<0{return NGB_CLIP_E_RANGE}
98 if a>=b{if sum>NGB_CLIP_I64_MAX-term{return NGB_CLIP_E_RANGE};sum=sum+term}
99 else{if sum<0-NGB_CLIP_I64_MAX+term{return NGB_CLIP_E_RANGE};sum=sum-term};i=i+1
100 }
101 let div:i64=ngb_clip_gcd(ngb_abs(sum),den);result[0]=sum/div;result[1]=den/div;return 0
102}
103
104// GAT1 v1: additive source-surface attachments; legacy GARM is unchanged.
105// Header 12 words: version,record_count,triangle_count,record_stride,VERT_check,TRIS_check,
106// source_nv,source_nt,kind,total_words,triangle_stride,reserved.
107// Record 8 words: source_face,bary0,bary1,bary2,denominator,normal_offset_units,material,flags.
108// Triangle 3 words: attachment record indices. Coefficients remain exact i64 on disk.
109const NGB_ATTACH_HDR:i64=12
110const NGB_ATTACH_REC:i64=8
111const NGB_ATTACH_COORD_MAX:i64=16777216
112const NGB_ATTACH_E_SOURCE:i64=-40
113const NGB_ATTACH_E_SHAPE:i64=-41
114const NGB_ATTACH_E_GEOMETRY:i64=-42
115// Exact integer coordinates and products of differences fit i64 under this boundary.
116func ngb_attach_source_v1(b:*u8,n:i64)->i64{
117 if (b as i64)==0{return NGB_ATTACH_E_SOURCE}
118 let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3)
119 if va<0||ta<0{return NGB_ATTACH_E_SOURCE};let h:*i64=b as *i64;let nv:i64=h[va];let nt:i64=h[ta]
120 if nv<3||nt<1{return NGB_ATTACH_E_SOURCE};var i:i64=0
121 while i<nv*3{let p:i64=h[va+1+i];if p<0-NGB_ATTACH_COORD_MAX||p>NGB_ATTACH_COORD_MAX{return NGB_ATTACH_E_SOURCE};i=i+1}
122 i=0;while i<nt*3{let v:i64=h[ta+1+i];if v<0||v>=nv{return NGB_ATTACH_E_SOURCE};i=i+1};return 0
123}
124func ngb_attach_face_v1(pos:*i64,idx:*i64,face:i64)->i64{
125 let a:i64=idx[face*3]*3;let b:i64=idx[face*3+1]*3;let c:i64=idx[face*3+2]*3
126 let x:i64=pos[b]-pos[a];let y:i64=pos[b+1]-pos[a+1];let z:i64=pos[b+2]-pos[a+2]
127 let u:i64=pos[c]-pos[a];let v:i64=pos[c+1]-pos[a+1];let w:i64=pos[c+2]-pos[a+2]
128 if y*w-z*v==0&&z*u-x*w==0&&x*v-y*u==0{return NGB_ATTACH_E_GEOMETRY};return 0
129}
130// Validate before consumer upload. Native NXA checks are integrity checks, not cryptographic authentication.
131// Caller workspace: 14 i64 words, disjoint from section/source; no output mesh or device mutation here.
132func ngb_attach_validate_v1(b:*u8,n:i64,s:*i64,nw:i64,work:*i64,work_words:i64)->i64{
133 if (s as i64)==0||(work as i64)==0||nw<NGB_ATTACH_HDR||work_words<14{return NGB_ATTACH_E_SHAPE}
134 let sr:i64=ngb_attach_source_v1(b,n);if sr<0{return sr}
135 if s[0]!=1||s[3]!=NGB_ATTACH_REC||s[10]!=3||s[11]!=0||s[9]!=nw{return NGB_ATTACH_E_SHAPE}
136 let nr:i64=s[1];let nt:i64=s[2];if nr<3||nt<1||nr>(nw-NGB_ATTACH_HDR)/NGB_ATTACH_REC{return NGB_ATTACH_E_SHAPE}
137 let to:i64=NGB_ATTACH_HDR+nr*NGB_ATTACH_REC;if (nw-to)%3!=0||nt!=(nw-to)/3{return NGB_ATTACH_E_SHAPE}
138 if s[8]<1||s[8]>9{return NGB_ATTACH_E_SHAPE}
139 let ve:i64=nxa_section_entry(b,n,nxa_tag4("VERT"));let te:i64=nxa_section_entry(b,n,nxa_tag4("TRIS"));let h:*i64=b as *i64
140 let va:i64=h[ve+1]/8;let ta:i64=h[te+1]/8
141 if s[4]!=h[ve+3]||s[5]!=h[te+3]||s[6]!=h[va]||s[7]!=h[ta]{return NGB_ATTACH_E_SOURCE}
142 let pos:*i64=((b as i64)+(va+1)*8) as *i64;let idx:*i64=((b as i64)+(ta+1)*8) as *i64
143 var i:i64=0;while i<nr{let r:i64=NGB_ATTACH_HDR+i*NGB_ATTACH_REC;let face:i64=s[r];let den:i64=s[r+4]
144 if face<0||face>=s[7]||den<=0{return NGB_ATTACH_E_SHAPE}
145 var sum:i64=0;var k:i64=1;while k<4{let a:i64=s[r+k];if a<0||a>den-sum{return NGB_ATTACH_E_SHAPE};sum=sum+a;k=k+1}
146 if sum!=den||s[r+5]<0||s[r+5]>NGB_ATTACH_COORD_MAX||s[r+6]!=s[8]||s[r+7]!=0{return NGB_ATTACH_E_SHAPE}
147 if ngb_attach_face_v1(pos,idx,face)<0{return NGB_ATTACH_E_GEOMETRY};i=i+1
148 }
149 i=0;while i<nt{var face:i64=-1;var k:i64=0
150 while k<3{let v:i64=s[to+i*3+k];if v<0||v>=nr{return NGB_ATTACH_E_SHAPE};let r:i64=NGB_ATTACH_HDR+v*NGB_ATTACH_REC
151 if k==0{face=s[r]}else{if face!=s[r]{return NGB_ATTACH_E_GEOMETRY}}
152 var j:i64=0;while j<4{work[k*4+j]=s[r+1+j];j=j+1};k=k+1
153 }
154 let area:*i64=((work as i64)+96) as *i64;let ar:i64=ngb_clip_area_ratio_v1(work,3,area)
155 if ar<0{return ar};if area[0]<=0||area[0]>area[1]{return NGB_ATTACH_E_GEOMETRY};i=i+1
156 };return 0
157}
158
159// GAP1 upload v1: 16 uint32 header lanes, then position xyz, source-index xyzw,
160// barycentric xyz/source-unit normal-offset w and uint32 triangle indices.
161// Disk GAT1 remains exact i64. GPU conversion occurs only after complete native validation.
162const NGB_PACK_HEADER:i64=64
163const NGB_PACK_VERTEX:i64=44
164const NGB_E_PACK_INDEX:i64=-43
165const NGB_E_PACK_EXTENT:i64=-44
166// This is an integer-only exactness test; it does not round before checking.
167func ngb_source_index_f32_v1(v:i64,nv:i64)->i64{
168 if v<0||v>=nv||v>2147483647{return NGB_E_PACK_INDEX}
169 var top:i64=16777216;var spacing:i64=1
170 while v>=top*2{top=top*2;spacing=spacing*2}
171 if v>top{spacing=spacing*2}
172 if v%spacing!=0{return NGB_E_PACK_INDEX};return 0
173}
174func ngb_attachment_bytes_v1(b:*u8,n:i64,work:*i64,work_bytes:i64)->i64{
175 if (b as i64)==0||work_bytes<112{return NGB_ATTACH_E_SHAPE}
176 let wr:i64=ngp_output(b,n,work as *u8,work_bytes,112);if wr<0{return wr}
177 let ae:i64=nxa_section_entry(b,n,nxa_tag4("GAT1"));if ae<0{return ae}
178 let h:*i64=b as *i64;var vi:i64=0;var ti:i64=0;var ai:i64=0;var entry:i64=0
179 while entry<h[2]{let tag:i64=h[4+entry*4];if tag==nxa_tag4("VERT"){vi=vi+1};if tag==nxa_tag4("TRIS"){ti=ti+1};if tag==nxa_tag4("GAT1"){ai=ai+1};entry=entry+1}
180 if vi!=1||ti!=1||ai!=1{return NGB_ATTACH_E_SHAPE}
181 let s:*i64=((b as i64)+h[ae+1]) as *i64
182 let rc:i64=ngb_attach_validate_v1(b,n,s,h[ae+2],work,14);if rc<0{return rc}
183 let nr:i64=s[1];let nt:i64=s[2]
184 if nr>(4294967295-NGB_PACK_HEADER)/NGB_PACK_VERTEX{return NGB_E_PACK_EXTENT}
185 let base:i64=NGB_PACK_HEADER+nr*NGB_PACK_VERTEX
186 if nt>(4294967295-base)/12{return NGB_E_PACK_EXTENT}
187 let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3)
188 var i:i64=0;while i<nr{let face:i64=s[NGB_ATTACH_HDR+i*NGB_ATTACH_REC];var k:i64=0
189 while k<3{let si:i64=h[ta+1+face*3+k];if ngb_source_index_f32_v1(si,s[6])<0{return NGB_E_PACK_INDEX};k=k+1};i=i+1}
190 return base+nt*12
191}
192func ngb_attachment_pack_v1(b:*u8,n:i64,out:*u8,cap:i64,work:*i64,work_bytes:i64)->i64{
193 if cap>0&&(out as i64)>0{let early:i64=ngp_output(work as *u8,112,out,cap,cap);if early<0{return early}}
194 let need:i64=ngb_attachment_bytes_v1(b,n,work,work_bytes);if need<0{return need}
195 let rc:i64=ngp_output(b,n,out,cap,need);if rc<0{return rc}
196 let overlap:i64=ngp_output(work as *u8,112,out,cap,need);if overlap<0{return overlap}
197 let ae:i64=nxa_section_entry(b,n,nxa_tag4("GAT1"));let h:*i64=b as *i64;let s:*i64=((b as i64)+h[ae+1]) as *i64
198 let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3)
199 let nr:i64=s[1];let nt:i64=s[2];let po:i64=64;let jo:i64=po+nr*12;let wo:i64=jo+nr*16;let io:i64=wo+nr*16
200 let dst:*u32=out as *u32;var i:i64=0;while i<16{dst[i]=0 as u32;i=i+1}
201 dst[0]=827343175 as u32;dst[1]=1 as u32;dst[2]=nr as u32;dst[3]=nt as u32;dst[4]=s[6] as u32;dst[5]=s[8] as u32
202 dst[6]=po as u32;dst[7]=jo as u32;dst[8]=wo as u32;dst[9]=io as u32;dst[10]=need as u32;dst[11]=1 as u32
203 i=0;while i<nr{let r:i64=NGB_ATTACH_HDR+i*NGB_ATTACH_REC;let face:i64=s[r];let den:i64=__f32_from_i64(s[r+4]);var k:i64=0
204 while k<3{let si:i64=h[ta+1+face*3+k];dst[jo/4+i*4+k]=__f32_from_i64(si) as u32;dst[wo/4+i*4+k]=__f32_div(__f32_from_i64(s[r+1+k]),den) as u32;k=k+1}
205 dst[jo/4+i*4+3]=0 as u32;dst[wo/4+i*4+3]=__f32_from_i64(s[r+5]) as u32
206 var q:i64=0;while q<3{var acc:i64=__f32_from_i64(0);k=0
207 while k<3{let si:i64=h[ta+1+face*3+k];let ratio:i64=dst[wo/4+i*4+k] as i64;acc=__f32_add(acc,__f32_mul(__f32_from_i64(h[va+1+si*3+q]),ratio));k=k+1}
208 dst[po/4+i*3+q]=acc as u32;q=q+1};i=i+1}
209 let to:i64=NGB_ATTACH_HDR+nr*NGB_ATTACH_REC;i=0;while i<nt*3{dst[io/4+i]=s[to+i] as u32;i=i+1};return need
210}