nx_mesh_qem.nx source
↩ module page · 187 lines · 7866 B
1// nx_mesh_qem.nx -- mesh decimation by QUADRIC ERROR METRICS (Garland-Heckbert 1997), pure
2// integer. Each vertex accumulates a quadric Q (sum of outer products of its incident face
3// planes); collapsing an edge costs p^T Q p (the squared distance of the new point to those
4// planes). Collapsing LOWEST-cost edges first => flat regions decimate hard while curved/feature
5// regions keep their vertices -- the quality win over uniform vertex clustering (nx_mesh_decimate)
6// on CURVED geometry. Sovereign + no-float: the mesh is scaled into a bounded [0,1024] cube so
7// the integer quadrics never overflow i64; collapse uses MIDPOINT placement (no matrix inverse).
8// Reuses nx_mesh + nx_isqrt. license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_mesh.nx"
11import "nx_mesh_print_check.nx"
12import "nx_isqrt.nx"
13
14const QEM_S: i64 = 1024 // normalized cube side
15const QEM_NQ: i64 = 256 // normal fixed-point scale
16
17// add face plane (a,b,c,d) outer-product quadric into vertex v's 10-entry quadric.
18func qem_add(quad: *i64, v: i64, a: i64, b: i64, c: i64, d: i64) -> i64 {
19 let base: i64 = v * 10
20 quad[base+0] = quad[base+0] + a*a
21 quad[base+1] = quad[base+1] + a*b
22 quad[base+2] = quad[base+2] + a*c
23 quad[base+3] = quad[base+3] + a*d
24 quad[base+4] = quad[base+4] + b*b
25 quad[base+5] = quad[base+5] + b*c
26 quad[base+6] = quad[base+6] + b*d
27 quad[base+7] = quad[base+7] + c*c
28 quad[base+8] = quad[base+8] + c*d
29 quad[base+9] = quad[base+9] + d*d
30 return 0
31}
32
33// p^T (Q[va]+Q[vb]) p -- quadric error of placing the merged vertex at (px,py,pz).
34func qem_cost(quad: *i64, va: i64, vb: i64, px: i64, py: i64, pz: i64) -> i64 {
35 let a: i64 = va*10; let b: i64 = vb*10
36 let q0: i64=quad[a+0]+quad[b+0]; let q1: i64=quad[a+1]+quad[b+1]; let q2: i64=quad[a+2]+quad[b+2]
37 let q3: i64=quad[a+3]+quad[b+3]; let q4: i64=quad[a+4]+quad[b+4]; let q5: i64=quad[a+5]+quad[b+5]
38 let q6: i64=quad[a+6]+quad[b+6]; let q7: i64=quad[a+7]+quad[b+7]; let q8: i64=quad[a+8]+quad[b+8]
39 let q9: i64=quad[a+9]+quad[b+9]
40 let r1: i64 = px*px*q0 + 2*px*py*q1 + 2*px*pz*q2 + 2*px*q3
41 let r2: i64 = py*py*q4 + 2*py*pz*q5 + 2*py*q6
42 let r3: i64 = pz*pz*q7 + 2*pz*q8 + q9
43 return r1 + r2 + r3
44}
45
46// Decimate `m` down to about `target_tris` triangles. Returns a new NxMesh.
47func nx_mesh_qem(m: *NxMesh, target_tris: i64) -> *NxMesh {
48 if (m as i64) == 0 { return 0 as *NxMesh }
49 let nv: i64 = m.n_verts
50 let nt: i64 = m.n_tris
51 if nv <= 0 { return 0 as *NxMesh }
52 if nt <= 0 { return 0 as *NxMesh }
53
54 let bb: *NxMeshBBox = nx_mesh_bbox_compute(m)
55 let minx: i64 = bb.min_x; let miny: i64 = bb.min_y; let minz: i64 = bb.min_z
56 var ext: i64 = bb.max_x - minx
57 if bb.max_y - miny > ext { ext = bb.max_y - miny }
58 if bb.max_z - minz > ext { ext = bb.max_z - minz }
59 if ext <= 0 { return 0 as *NxMesh }
60
61 // working arrays
62 let vx: *i64 = (sys_mmap(nv*8)) as *i64; let vy: *i64 = (sys_mmap(nv*8)) as *i64; let vz: *i64 = (sys_mmap(nv*8)) as *i64
63 let valive: *i64 = (sys_mmap(nv*8)) as *i64
64 let quad: *i64 = (sys_mmap(nv*10*8)) as *i64
65 let tri: *i64 = (sys_mmap(nt*3*8)) as *i64
66 let talive: *i64 = (sys_mmap(nt*8)) as *i64
67
68 var v: i64 = 0
69 while v < nv {
70 vx[v] = (nx_mesh_get_vertex_x(m,v) - minx) * QEM_S / ext
71 vy[v] = (nx_mesh_get_vertex_y(m,v) - miny) * QEM_S / ext
72 vz[v] = (nx_mesh_get_vertex_z(m,v) - minz) * QEM_S / ext
73 valive[v] = 1
74 var k: i64 = 0; while k < 10 { quad[v*10+k] = 0; k = k + 1 }
75 v = v + 1
76 }
77 var t: i64 = 0
78 while t < nt {
79 tri[t*3+0] = m.indices[t*3+0]; tri[t*3+1] = m.indices[t*3+1]; tri[t*3+2] = m.indices[t*3+2]
80 talive[t] = 1
81 t = t + 1
82 }
83
84 // accumulate face-plane quadrics
85 t = 0
86 while t < nt {
87 let v0: i64 = tri[t*3+0]; let v1: i64 = tri[t*3+1]; let v2: i64 = tri[t*3+2]
88 let e1x: i64=vx[v1]-vx[v0]; let e1y: i64=vy[v1]-vy[v0]; let e1z: i64=vz[v1]-vz[v0]
89 let e2x: i64=vx[v2]-vx[v0]; let e2y: i64=vy[v2]-vy[v0]; let e2z: i64=vz[v2]-vz[v0]
90 let nx2: i64 = e1y*e2z - e1z*e2y
91 let ny2: i64 = e1z*e2x - e1x*e2z
92 let nz2: i64 = e1x*e2y - e1y*e2x
93 let ln: i64 = nx_isqrt(nx2*nx2 + ny2*ny2 + nz2*nz2)
94 if ln > 0 {
95 let a: i64 = nx2*QEM_NQ/ln; let b: i64 = ny2*QEM_NQ/ln; let c: i64 = nz2*QEM_NQ/ln
96 let d: i64 = 0 - (a*vx[v0] + b*vy[v0] + c*vz[v0])
97 qem_add(quad, v0, a,b,c,d); qem_add(quad, v1, a,b,c,d); qem_add(quad, v2, a,b,c,d)
98 }
99 t = t + 1
100 }
101
102 // greedy collapse: repeatedly merge the lowest-cost live edge (midpoint) until target.
103 var live_t: i64 = nt
104 while live_t > target_tris {
105 var best: i64 = 0 - 1
106 var bva: i64 = 0 - 1; var bvb: i64 = 0 - 1
107 var bpx: i64 = 0; var bpy: i64 = 0; var bpz: i64 = 0
108 var found: i64 = 0
109 t = 0
110 while t < nt {
111 if talive[t] == 1 {
112 var e: i64 = 0
113 while e < 3 {
114 var va: i64 = tri[t*3+e]
115 var vb: i64 = tri[t*3+((e+1)%3)]
116 if valive[va] == 1 { if valive[vb] == 1 { if va != vb {
117 let px: i64 = (vx[va]+vx[vb])/2; let py: i64 = (vy[va]+vy[vb])/2; let pz: i64 = (vz[va]+vz[vb])/2
118 let c2: i64 = qem_cost(quad, va, vb, px, py, pz)
119 if found == 0 { found=1; best=c2; bva=va; bvb=vb; bpx=px; bpy=py; bpz=pz }
120 else { if c2 < best { best=c2; bva=va; bvb=vb; bpx=px; bpy=py; bpz=pz } }
121 } } }
122 e = e + 1
123 }
124 }
125 t = t + 1
126 }
127 if found == 0 { live_t = target_tris } // nothing collapsible -> stop
128 else {
129 // collapse vb -> va at midpoint
130 vx[bva]=bpx; vy[bva]=bpy; vz[bva]=bpz
131 var k: i64 = 0; while k < 10 { quad[bva*10+k] = quad[bva*10+k] + quad[bvb*10+k]; k = k + 1 }
132 valive[bvb] = 0
133 // retarget triangles + drop degenerates
134 t = 0
135 while t < nt {
136 if talive[t] == 1 {
137 var i0: i64 = tri[t*3+0]; var i1: i64 = tri[t*3+1]; var i2: i64 = tri[t*3+2]
138 if i0 == bvb { i0 = bva }
139 if i1 == bvb { i1 = bva }
140 if i2 == bvb { i2 = bva }
141 tri[t*3+0]=i0; tri[t*3+1]=i1; tri[t*3+2]=i2
142 if i0==i1 { talive[t]=0; live_t=live_t-1 }
143 else { if i1==i2 { talive[t]=0; live_t=live_t-1 }
144 else { if i0==i2 { talive[t]=0; live_t=live_t-1 } } }
145 }
146 t = t + 1
147 }
148 }
149 }
150
151 // reindex live vertices
152 let vmap: *i64 = (sys_mmap(nv*8)) as *i64
153 var outv: i64 = 0
154 v = 0
155 while v < nv { if valive[v]==1 { vmap[v]=outv; outv=outv+1 } else { vmap[v]=0-1 } v=v+1 }
156 // count live tris
157 var outt: i64 = 0
158 t = 0
159 while t < nt { if talive[t]==1 { outt=outt+1 } t=t+1 }
160 if outv <= 0 { return 0 as *NxMesh }
161 if outt <= 0 { return 0 as *NxMesh }
162
163 let out: *NxMesh = nx_mesh_alloc(outv, outt, 0)
164 if (out as i64)==0 { return out }
165 // emit verts scaled back to original space
166 v = 0
167 while v < nv {
168 if valive[v]==1 {
169 let ox: i64 = minx + vx[v]*ext/QEM_S
170 let oy: i64 = miny + vy[v]*ext/QEM_S
171 let oz: i64 = minz + vz[v]*ext/QEM_S
172 nx_mesh_set_vertex(out, vmap[v], ox, oy, oz, 0)
173 }
174 v = v + 1
175 }
176 // emit live tris remapped
177 var ti: i64 = 0
178 t = 0
179 while t < nt {
180 if talive[t]==1 {
181 nx_mesh_set_triangle(out, ti, vmap[tri[t*3+0]], vmap[tri[t*3+1]], vmap[tri[t*3+2]])
182 ti = ti + 1
183 }
184 t = t + 1
185 }
186 return out
187}