nx_trimesh.nx source
↩ module page · 667 lines · 38892 B
1// nx_trimesh.nx -- ★SOVEREIGN TRIANGLE-MESH RASTERIZER (operator 2026-07-10, frank feedback: "we just have circles,
2// no triangle or other capabilities for shapes to make more and more real things"). Correct: everything so far is
3// axis-aligned ELLIPSOIDS (smooth blobs) -- no flat faces, sharp edges, or arbitrary form. This adds the UNIVERSAL
4// primitive: the TRIANGLE. A real integer rasterizer -- transform -> perspective project -> barycentric fill with a
5// Z-BUFFER + flat per-face shading -> arbitrary polygon meshes with FLAT FACES and SHARP EDGES. Plus a primitive
6// library (cube/tetra/octa/pyramid/prism/icosahedron/uv-sphere) and composition (build real things from many meshes).
7// 100% integer, sovereign (own trig, own raster). license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_itrig.nx"
10const TM_MAGIC_1024: i64 = 1024
11const TM_MAGIC_1000000000: i64 = 1000000000
12const TM_MAGIC_374761393: i64 = 374761393
13const TM_MAGIC_668265263: i64 = 668265263
14const TM_MAGIC_1610612741: i64 = 1610612741
15const TM_MAGIC_1274126177: i64 = 1274126177
16const TM_MAGIC_1048576: i64 = 1048576
17const TM_MAGIC_4096: i64 = 4096
18const TM_MAGIC_1618: i64 = 1618
19const TM_MAGIC_65536: i64 = 65536
20const TM_MAGIC_1300: i64 = 1300
21const TM_MAGIC_2400: i64 = 2400
22const TM_MAGIC_3000: i64 = 3000
23const TM_MAGIC_100000: i64 = 100000
24const TM_MAGIC_90000: i64 = 90000
25const TM_MAGIC_65535: i64 = 65535
26const TM_MAGIC_37777: i64 = 37777
27const TM_MAGIC_91131: i64 = 91131
28const TM_MAGIC_6144: i64 = 6144
29const TM_MAGIC_81111: i64 = 81111
30const TM_MAGIC_2000000000: i64 = 2000000000
31
32// ★CAPACITY IS DERIVED, NOT TYPED. Surface nets emits at most one vertex per grid cell, so a mesh from
33// an N^3 grid cannot exceed N^3 vertices -- but the surface is 2-DIMENSIONAL, so the real bound is the
34// number of cells the surface passes through, which scales as N^2. TM_GRID is the largest grid any
35// caller polygonizes; everything else follows from it, with a safety factor for a folded surface that
36// crosses a cell column more than once. Change the grid and the caps move with it.
37// (The previous 48k was typed, and silently truncated a skull at 2mm cells -- the organ then reported a
38// bounding box measured from an INCOMPLETE mesh, a wrong number that looks like a right one.)
39const TM_GRID: i64 = 256 // max polygonizer grid resolution supported
40const TM_FOLD: i64 = 6 // surface-crossings per cell column, worst case
41const TM_VCAP: i64 = TM_GRID*TM_GRID*TM_FOLD
42const TM_TCAP: i64 = TM_VCAP*2 // surface nets emits ~2 triangles per vertex
43static TM_VX: i64 // model-space vert x,y,z (3 arrays interleaved via ptrs)
44static TM_VY: i64
45static TM_VZ: i64
46static TM_TA: i64 // tri vert index a,b,c + colour (4 arrays)
47static TM_TB: i64
48static TM_TC: i64
49static TM_TCOL: i64
50static TM_VNX: i64 // ★F3: per-vertex model-space normal (for Gouraud smooth shading)
51static TM_VNY: i64
52static TM_VNZ: i64
53static TM_VCOL: i64 // per-vertex colour (for multi-material meshes, e.g. the meshed anatomy)
54static TM_NV: i64
55static TM_NT: i64
56static TM_OVF: i64 // set to 1 if a vertex/triangle add hit the capacity (so callers detect truncation)
57func tm_ovf() -> i64 { return TM_OVF }
58static TM_SPEC: i64 // ★F3 Blinn-Phong specular strength (0 = off/matte; ~110 = shiny). opt-in, zero-cost when 0.
59func tm_set_spec(v: i64) -> i64 { TM_SPEC = v; return 0 }
60static TM_TEX: i64 // ★F3 procedural 3D solid-noise texture: lattice scale in units (0 = off). opt-in.
61func tm_set_tex(scale: i64) -> i64 { TM_TEX = scale; return 0 }
62// ★Q2 SUN SHADOWS (2026-07-12, the quality ladder): a world-space orthographic SHADOW MAP. Bake once per scene
63// (model space -- camera-independent, so one bake serves every view), then the pixel path darkens fragments whose
64// sun-depth is behind the nearest blocker. Opt-in: tm_set_sun + tm_shadow_bake + tm_set_shadow(1); zero-cost off.
65const TM_SHRES: i64 = 576
66static TM_SUNX: i64 // direction TOWARD the sun, normalized *1024
67static TM_SUNY: i64
68static TM_SUNZ: i64
69static TM_SHON: i64 // 1 = apply the baked map in the pixel path
70static TM_SH: i64 // the map: TM_SHRES^2 i64 sun-depths (max = nearest blocker)
71static TM_SRX: i64 // ortho basis R (*1024)
72static TM_SRY: i64
73static TM_SRZ: i64
74static TM_SUX: i64 // ortho basis U (*1024)
75static TM_SUY: i64
76static TM_SUZ: i64
77static TM_SHA0: i64 // a-min / b-min / spans (world units) -> texel mapping
78static TM_SHB0: i64
79static TM_SHSA: i64
80static TM_SHSB: i64
81func tm_set_sun(x: i64, y: i64, z: i64) -> i64 {
82 let l: i64 = tm_isqrt(x*x + y*y + z*z) + 1
83 TM_SUNX = x*TM_MAGIC_1024/l
84 TM_SUNY = y*TM_MAGIC_1024/l
85 TM_SUNZ = z*TM_MAGIC_1024/l
86 return 0
87}
88func tm_set_shadow(v: i64) -> i64 { TM_SHON = v; return 0 }
89func tm_shadow_bake() -> i64 {
90 if TM_SH == 0 { TM_SH = sys_mmap(TM_SHRES*TM_SHRES*8) as i64 }
91 let sh: *i64 = TM_SH as *i64
92 var i: i64 = 0
93 while i < TM_SHRES*TM_SHRES { sh[i] = 0 - TM_MAGIC_1000000000; i = i + 1 }
94 // ortho basis from the sun dir: R = S x up (fallback x-axis), U = S x R
95 var rx: i64 = TM_SUNZ
96 var ry: i64 = 0
97 var rz: i64 = 0 - TM_SUNX
98 var rl: i64 = tm_isqrt(rx*rx + rz*rz)
99 if rl < 40 { rx = TM_MAGIC_1024; ry = 0; rz = 0; rl = TM_MAGIC_1024 }
100 rx = rx*TM_MAGIC_1024/rl; rz = rz*TM_MAGIC_1024/rl
101 let ux: i64 = (TM_SUNY*rz - TM_SUNZ*ry)/TM_MAGIC_1024
102 let uy: i64 = (TM_SUNZ*rx - TM_SUNX*rz)/TM_MAGIC_1024
103 let uz: i64 = (TM_SUNX*ry - TM_SUNY*rx)/TM_MAGIC_1024
104 TM_SRX = rx; TM_SRY = ry; TM_SRZ = rz
105 TM_SUX = ux; TM_SUY = uy; TM_SUZ = uz
106 // project all verts to (a,b,d) sun space
107 let px: *i64 = TM_VX as *i64
108 let py: *i64 = TM_VY as *i64
109 let pz: *i64 = TM_VZ as *i64
110 let sa: *i64 = sys_mmap(TM_NV*8) as *i64
111 let sb: *i64 = sys_mmap(TM_NV*8) as *i64
112 let sd: *i64 = sys_mmap(TM_NV*8) as *i64
113 var amin: i64 = TM_MAGIC_1000000000
114 var amax: i64 = 0-TM_MAGIC_1000000000
115 var bmin: i64 = TM_MAGIC_1000000000
116 var bmax: i64 = 0-TM_MAGIC_1000000000
117 i = 0
118 while i < TM_NV {
119 let a: i64 = (px[i]*rx + py[i]*ry + pz[i]*rz)/TM_MAGIC_1024
120 let b: i64 = (px[i]*ux + py[i]*uy + pz[i]*uz)/TM_MAGIC_1024
121 let d: i64 = (px[i]*TM_SUNX + py[i]*TM_SUNY + pz[i]*TM_SUNZ)/TM_MAGIC_1024
122 sa[i] = a; sb[i] = b; sd[i] = d
123 if a < amin { amin = a } if a > amax { amax = a }
124 if b < bmin { bmin = b } if b > bmax { bmax = b }
125 i = i + 1
126 }
127 TM_SHA0 = amin
128 TM_SHB0 = bmin
129 TM_SHSA = amax - amin + 1
130 TM_SHSB = bmax - bmin + 1
131 // rasterize every triangle into the map, keeping MAX sun-depth (nearest blocker)
132 let TA: *i64 = TM_TA as *i64
133 let TB: *i64 = TM_TB as *i64
134 let TC: *i64 = TM_TC as *i64
135 var t: i64 = 0
136 while t < TM_NT {
137 let i0: i64 = TA[t]; let i1: i64 = TB[t]; let i2: i64 = TC[t]
138 let ax: i64 = (sa[i0]-amin)*TM_SHRES/TM_SHSA; let ay: i64 = (sb[i0]-bmin)*TM_SHRES/TM_SHSB
139 let bx: i64 = (sa[i1]-amin)*TM_SHRES/TM_SHSA; let by: i64 = (sb[i1]-bmin)*TM_SHRES/TM_SHSB
140 let cx: i64 = (sa[i2]-amin)*TM_SHRES/TM_SHSA; let cy: i64 = (sb[i2]-bmin)*TM_SHRES/TM_SHSB
141 let area: i64 = (bx-ax)*(cy-ay) - (by-ay)*(cx-ax)
142 if area != 0 {
143 var minx: i64 = tm_min3(ax,bx,cx); var maxx: i64 = tm_max3(ax,bx,cx)
144 var miny: i64 = tm_min3(ay,by,cy); var maxy: i64 = tm_max3(ay,by,cy)
145 if minx < 0 { minx = 0 } if miny < 0 { miny = 0 }
146 if maxx > TM_SHRES-1 { maxx = TM_SHRES-1 } if maxy > TM_SHRES-1 { maxy = TM_SHRES-1 }
147 var qy: i64 = miny
148 while qy <= maxy {
149 var qx: i64 = minx
150 while qx <= maxx {
151 let e0: i64 = (cx-bx)*(qy-by) - (cy-by)*(qx-bx)
152 let e1: i64 = (ax-cx)*(qy-cy) - (ay-cy)*(qx-cx)
153 let e2: i64 = (bx-ax)*(qy-ay) - (by-ay)*(qx-ax)
154 var inside: i64 = 0
155 if area > 0 { if e0>=0 { if e1>=0 { if e2>=0 { inside=1 } } } }
156 else { if e0<=0 { if e1<=0 { if e2<=0 { inside=1 } } } }
157 if inside == 1 {
158 let dd: i64 = (e0*sd[i0] + e1*sd[i1] + e2*sd[i2]) / area
159 let mi: i64 = qy*TM_SHRES + qx
160 if dd > sh[mi] { sh[mi] = dd }
161 }
162 qx = qx + 1
163 }
164 qy = qy + 1
165 }
166 }
167 t = t + 1
168 }
169 return 0
170}
171// shadow factor at a model-space point: 1024 = lit, else attenuated. Bias scales with texel size (acne guard).
172func tm_shadow_at(wx: i64, wy: i64, wz: i64) -> i64 {
173 let a: i64 = (wx*TM_SRX + wy*TM_SRY + wz*TM_SRZ)/TM_MAGIC_1024
174 let b: i64 = (wx*TM_SUX + wy*TM_SUY + wz*TM_SUZ)/TM_MAGIC_1024
175 let tx: i64 = (a - TM_SHA0)*TM_SHRES/TM_SHSA
176 let ty: i64 = (b - TM_SHB0)*TM_SHRES/TM_SHSB
177 if tx < 0 { return TM_MAGIC_1024 }
178 if ty < 0 { return TM_MAGIC_1024 }
179 if tx > TM_SHRES-1 { return TM_MAGIC_1024 }
180 if ty > TM_SHRES-1 { return TM_MAGIC_1024 }
181 let d: i64 = (wx*TM_SUNX + wy*TM_SUNY + wz*TM_SUNZ)/TM_MAGIC_1024
182 let sh: *i64 = TM_SH as *i64
183 var bias: i64 = TM_SHSA/TM_SHRES*3
184 if bias < 22 { bias = 22 }
185 if sh[ty*TM_SHRES + tx] > d + bias { return 340 }
186 return TM_MAGIC_1024
187}
188static TM_IMG: i64 // ★F3 IMAGE texture (packed w*h*3 RGB) sampled via per-vertex UV. 0 = off.
189static TM_IMGW: i64
190static TM_IMGH: i64
191func tm_set_image(rgb: *u8, w: i64, h: i64) -> i64 { TM_IMG = rgb as i64; TM_IMGW = w; TM_IMGH = h; return 0 }
192static TM_VU: i64 // per-vertex UV, Q16 (65536 = 1.0)
193static TM_VV: i64
194func tm_vuv(idx: i64, u: i64, v: i64) -> i64 { let pu: *i64 = TM_VU as *i64; let pv: *i64 = TM_VV as *i64; pu[idx] = u; pv[idx] = v; return 0 }
195func tm_hash3(a: i64, b: i64, c: i64) -> i64 {
196 var h: i64 = a*TM_MAGIC_374761393 + b*TM_MAGIC_668265263 + c*TM_MAGIC_1610612741
197 h = h ^ (h >> 13); h = h * TM_MAGIC_1274126177; h = h ^ (h >> 16)
198 if h < 0 { h = 0 - h }
199 return h % TM_MAGIC_1024
200}
201// trilinear value noise at 3D position, lattice scale S. Returns 0..1023 (smooth). Offset keeps floor-division positive.
202func tm_noise3(x: i64, y: i64, z: i64, S: i64) -> i64 {
203 let ox: i64 = x + TM_MAGIC_1048576; let oy: i64 = y + TM_MAGIC_1048576; let oz: i64 = z + TM_MAGIC_1048576
204 let gx: i64 = ox/S; let gy: i64 = oy/S; let gz: i64 = oz/S
205 let fx: i64 = (ox - gx*S)*TM_MAGIC_1024/S; let fy: i64 = (oy - gy*S)*TM_MAGIC_1024/S; let fz: i64 = (oz - gz*S)*TM_MAGIC_1024/S
206 let c000: i64 = tm_hash3(gx,gy,gz); let c100: i64 = tm_hash3(gx+1,gy,gz)
207 let c010: i64 = tm_hash3(gx,gy+1,gz); let c110: i64 = tm_hash3(gx+1,gy+1,gz)
208 let c001: i64 = tm_hash3(gx,gy,gz+1); let c101: i64 = tm_hash3(gx+1,gy,gz+1)
209 let c011: i64 = tm_hash3(gx,gy+1,gz+1); let c111: i64 = tm_hash3(gx+1,gy+1,gz+1)
210 let x00: i64 = c000 + (c100-c000)*fx/TM_MAGIC_1024
211 let x10: i64 = c010 + (c110-c010)*fx/TM_MAGIC_1024
212 let x01: i64 = c001 + (c101-c001)*fx/TM_MAGIC_1024
213 let x11: i64 = c011 + (c111-c011)*fx/TM_MAGIC_1024
214 let y0: i64 = x00 + (x10-x00)*fy/TM_MAGIC_1024
215 let y1: i64 = x01 + (x11-x01)*fy/TM_MAGIC_1024
216 return y0 + (y1-y0)*fz/TM_MAGIC_1024
217}
218
219func tm_isqrt(v: i64) -> i64 { if v <= 0 { return 0 } var x: i64 = v; var y: i64 = (x+1)/2; while y < x { x=y; y=(x+v/x)/2 } return x }
220func tm_min(a: i64, b: i64) -> i64 { if a<b {return a} return b }
221func tm_max(a: i64, b: i64) -> i64 { if a>b {return a} return b }
222func tm_min3(a: i64, b: i64, c: i64) -> i64 { return tm_min(tm_min(a,b),c) }
223func tm_max3(a: i64, b: i64, c: i64) -> i64 { return tm_max(tm_max(a,b),c) }
224
225func tm_reset() -> i64 {
226 if TM_VX == 0 {
227 TM_VX = sys_mmap(TM_VCAP*8) as i64; TM_VY = sys_mmap(TM_VCAP*8) as i64; TM_VZ = sys_mmap(TM_VCAP*8) as i64
228 TM_TA = sys_mmap(TM_TCAP*8) as i64; TM_TB = sys_mmap(TM_TCAP*8) as i64; TM_TC = sys_mmap(TM_TCAP*8) as i64; TM_TCOL = sys_mmap(TM_TCAP*8) as i64
229 TM_VNX = sys_mmap(TM_VCAP*8) as i64; TM_VNY = sys_mmap(TM_VCAP*8) as i64; TM_VNZ = sys_mmap(TM_VCAP*8) as i64
230 TM_VCOL = sys_mmap(TM_VCAP*8) as i64
231 TM_VU = sys_mmap(TM_VCAP*8) as i64; TM_VV = sys_mmap(TM_VCAP*8) as i64
232 }
233 TM_NV = 0; TM_NT = 0; TM_OVF = 0
234 return 0
235}
236func tm_vcol(idx: i64, col: i64) -> i64 { let p: *i64 = TM_VCOL as *i64; p[idx] = col; return 0 }
237// NOTE: declare the pointer up front -- inline-cast-index READ `(TM_VX as *i64)[i]` miscompiles to the base pointer.
238func tm_vpos(idx: i64, out: *i64) -> i64 { let px: *i64=TM_VX as *i64; let py: *i64=TM_VY as *i64; let pz: *i64=TM_VZ as *i64; out[0]=px[idx]; out[1]=py[idx]; out[2]=pz[idx]; return 0 }
239func tm_vnorm(idx: i64, out: *i64) -> i64 { let px: *i64=TM_VNX as *i64; let py: *i64=TM_VNY as *i64; let pz: *i64=TM_VNZ as *i64; out[0]=px[idx]; out[1]=py[idx]; out[2]=pz[idx]; return 0 }
240func tm_getvcol(idx: i64) -> i64 { let p: *i64=TM_VCOL as *i64; return p[idx] }
241// ★read-only accessors so a caller can EXPORT what surface_nets built (e.g. write NXMSH2 and then look
242// at it). Without these the polygonizer could fill these buffers and nothing could get the geometry
243// back out, which is how meshes got produced that nobody could view.
244func tm_vx(i: i64) -> i64 { let p: *i64=TM_VX as *i64; return p[i] }
245func tm_vy(i: i64) -> i64 { let p: *i64=TM_VY as *i64; return p[i] }
246func tm_vz(i: i64) -> i64 { let p: *i64=TM_VZ as *i64; return p[i] }
247func tm_vnx(i: i64) -> i64 { let p: *i64=TM_VNX as *i64; return p[i] }
248func tm_vny(i: i64) -> i64 { let p: *i64=TM_VNY as *i64; return p[i] }
249func tm_vnz(i: i64) -> i64 { let p: *i64=TM_VNZ as *i64; return p[i] }
250func tm_ta(i: i64) -> i64 { let p: *i64=TM_TA as *i64; return p[i] }
251func tm_tb(i: i64) -> i64 { let p: *i64=TM_TB as *i64; return p[i] }
252func tm_tc(i: i64) -> i64 { let p: *i64=TM_TC as *i64; return p[i] }
253func tm_tget(t: i64, out: *i64) -> i64 { let pa: *i64=TM_TA as *i64; let pb: *i64=TM_TB as *i64; let pc: *i64=TM_TC as *i64; out[0]=pa[t]; out[1]=pb[t]; out[2]=pc[t]; return 0 }
254// translate (and optionally uniform-scale, mil=1000 -> 1.0) vertices [from,to) -- scene placement of a loaded asset
255func tm_place(from: i64, to: i64, dx: i64, dy: i64, dz: i64, mil: i64) -> i64 {
256 let px: *i64 = TM_VX as *i64; let py: *i64 = TM_VY as *i64; let pz: *i64 = TM_VZ as *i64
257 var i: i64 = from
258 while i < to {
259 px[i] = px[i]*mil/1000 + dx
260 py[i] = py[i]*mil/1000 + dy
261 pz[i] = pz[i]*mil/1000 + dz
262 i = i + 1
263 }
264 return 0
265}
266// ★F3: compute per-vertex normals = normalized sum of adjacent FACE normals (model space). Enables Gouraud smooth shading.
267func tm_compute_normals() -> i64 {
268 let nx: *i64 = TM_VNX as *i64; let ny: *i64 = TM_VNY as *i64; let nz: *i64 = TM_VNZ as *i64
269 let vx: *i64 = TM_VX as *i64; let vy: *i64 = TM_VY as *i64; let vz: *i64 = TM_VZ as *i64
270 var i: i64 = 0
271 while i < TM_NV { nx[i]=0; ny[i]=0; nz[i]=0; i=i+1 }
272 let TA: *i64 = TM_TA as *i64; let TB: *i64 = TM_TB as *i64; let TC: *i64 = TM_TC as *i64
273 var t: i64 = 0
274 while t < TM_NT {
275 let a: i64 = TA[t]; let b: i64 = TB[t]; let c: i64 = TC[t]
276 let ux: i64 = vx[b]-vx[a]; let uy: i64 = vy[b]-vy[a]; let uz: i64 = vz[b]-vz[a]
277 let wx: i64 = vx[c]-vx[a]; let wy: i64 = vy[c]-vy[a]; let wz: i64 = vz[c]-vz[a]
278 var fnx: i64 = uy*wz-uz*wy; var fny: i64 = uz*wx-ux*wz; var fnz: i64 = ux*wy-uy*wx
279 let fl: i64 = tm_isqrt(fnx*fnx+fny*fny+fnz*fnz) + 1 // unit-ish (256) so big tris don't dominate the average
280 fnx = fnx*256/fl; fny = fny*256/fl; fnz = fnz*256/fl
281 nx[a]=nx[a]+fnx; ny[a]=ny[a]+fny; nz[a]=nz[a]+fnz
282 nx[b]=nx[b]+fnx; ny[b]=ny[b]+fny; nz[b]=nz[b]+fnz
283 nx[c]=nx[c]+fnx; ny[c]=ny[c]+fny; nz[c]=nz[c]+fnz
284 t = t + 1
285 }
286 i = 0
287 while i < TM_NV {
288 let l: i64 = tm_isqrt(nx[i]*nx[i]+ny[i]*ny[i]+nz[i]*nz[i]) + 1
289 nx[i]=nx[i]*TM_MAGIC_4096/l; ny[i]=ny[i]*TM_MAGIC_4096/l; nz[i]=nz[i]*TM_MAGIC_4096/l // unit normal fx4096
290 i = i + 1
291 }
292 return 0
293}
294func tm_vert(x: i64, y: i64, z: i64) -> i64 {
295 if TM_NV >= TM_VCAP { TM_OVF = 1; return TM_VCAP-1 } // capacity guard: never write OOB
296 let i: i64 = TM_NV
297 let px: *i64 = TM_VX as *i64; let py: *i64 = TM_VY as *i64; let pz: *i64 = TM_VZ as *i64
298 px[i] = x; py[i] = y; pz[i] = z
299 TM_NV = TM_NV + 1
300 return i
301}
302// add a vertex WITH an explicit per-vertex normal (for isosurface: SDF-gradient normals, winding-independent smooth shading)
303func tm_vert_n(x: i64, y: i64, z: i64, nx: i64, ny: i64, nz: i64) -> i64 {
304 if TM_NV >= TM_VCAP { TM_OVF = 1; return TM_VCAP-1 } // capacity guard
305 let i: i64 = TM_NV
306 let px: *i64 = TM_VX as *i64; let py: *i64 = TM_VY as *i64; let pz: *i64 = TM_VZ as *i64
307 let qx: *i64 = TM_VNX as *i64; let qy: *i64 = TM_VNY as *i64; let qz: *i64 = TM_VNZ as *i64
308 px[i]=x; py[i]=y; pz[i]=z; qx[i]=nx; qy[i]=ny; qz[i]=nz
309 TM_NV = TM_NV + 1
310 return i
311}
312func tm_tri(a: i64, b: i64, c: i64, col: i64) -> i64 {
313 if TM_NT >= TM_TCAP { TM_OVF = 1; return TM_TCAP-1 } // capacity guard
314 let i: i64 = TM_NT
315 let pa: *i64 = TM_TA as *i64; let pb: *i64 = TM_TB as *i64; let pc: *i64 = TM_TC as *i64; let pcol: *i64 = TM_TCOL as *i64
316 pa[i] = a; pb[i] = b; pc[i] = c; pcol[i] = col
317 TM_NT = TM_NT + 1
318 return i
319}
320func tm_quad(a: i64, b: i64, c: i64, d: i64, col: i64) -> i64 { tm_tri(a,b,c,col); tm_tri(a,c,d,col); return 0 }
321func tm_nt() -> i64 { return TM_NT }
322func tm_nv() -> i64 { return TM_NV }
323
324// ---- primitive mesh builders (append at center cx,cy,cz, half-size s, colour col) ----
325func tm_cube(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 {
326 let b: i64 = TM_NV
327 tm_vert(cx-s,cy-s,cz-s); tm_vert(cx+s,cy-s,cz-s); tm_vert(cx+s,cy+s,cz-s); tm_vert(cx-s,cy+s,cz-s) // back z-
328 tm_vert(cx-s,cy-s,cz+s); tm_vert(cx+s,cy-s,cz+s); tm_vert(cx+s,cy+s,cz+s); tm_vert(cx-s,cy+s,cz+s) // front z+
329 tm_quad(b+4,b+5,b+6,b+7,col) // front
330 tm_quad(b+1,b+0,b+3,b+2,col) // back
331 tm_quad(b+0,b+4,b+7,b+3,col) // left
332 tm_quad(b+5,b+1,b+2,b+6,col) // right
333 tm_quad(b+3,b+7,b+6,b+2,col) // top
334 tm_quad(b+0,b+1,b+5,b+4,col) // bottom
335 return 0
336}
337func tm_tetra(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 {
338 let b: i64 = TM_NV
339 tm_vert(cx+s,cy+s,cz+s); tm_vert(cx+s,cy-s,cz-s); tm_vert(cx-s,cy+s,cz-s); tm_vert(cx-s,cy-s,cz+s) // alternating cube corners = regular tetra
340 tm_tri(b+0,b+1,b+2,col); tm_tri(b+0,b+3,b+1,col); tm_tri(b+0,b+2,b+3,col); tm_tri(b+1,b+3,b+2,col)
341 return 0
342}
343func tm_octa(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 {
344 let b: i64 = TM_NV
345 tm_vert(cx+s,cy,cz); tm_vert(cx-s,cy,cz); tm_vert(cx,cy+s,cz); tm_vert(cx,cy-s,cz); tm_vert(cx,cy,cz+s); tm_vert(cx,cy,cz-s) // +x -x +y -y +z -z
346 tm_tri(b+2,b+0,b+4,col); tm_tri(b+2,b+4,b+1,col); tm_tri(b+2,b+1,b+5,col); tm_tri(b+2,b+5,b+0,col) // top 4
347 tm_tri(b+3,b+4,b+0,col); tm_tri(b+3,b+1,b+4,col); tm_tri(b+3,b+5,b+1,col); tm_tri(b+3,b+0,b+5,col) // bottom 4
348 return 0
349}
350func tm_pyramid(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 {
351 let b: i64 = TM_NV
352 tm_vert(cx-s,cy-s,cz-s); tm_vert(cx+s,cy-s,cz-s); tm_vert(cx+s,cy-s,cz+s); tm_vert(cx-s,cy-s,cz+s) // square base
353 tm_vert(cx,cy+s,cz) // apex
354 tm_tri(b+0,b+1,b+4,col); tm_tri(b+1,b+2,b+4,col); tm_tri(b+2,b+3,b+4,col); tm_tri(b+3,b+0,b+4,col) // 4 sides
355 tm_quad(b+0,b+3,b+2,b+1,col) // base
356 return 0
357}
358func tm_prism(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 { // triangular prism (extruded triangle along z)
359 let b: i64 = TM_NV
360 tm_vert(cx-s,cy-s,cz-s); tm_vert(cx+s,cy-s,cz-s); tm_vert(cx,cy+s,cz-s) // back triangle
361 tm_vert(cx-s,cy-s,cz+s); tm_vert(cx+s,cy-s,cz+s); tm_vert(cx,cy+s,cz+s) // front triangle
362 tm_tri(b+3,b+4,b+5,col); tm_tri(b+1,b+0,b+2,col) // caps
363 tm_quad(b+0,b+1,b+4,b+3,col); tm_quad(b+1,b+2,b+5,b+4,col); tm_quad(b+2,b+0,b+3,b+5,col) // 3 rects
364 return 0
365}
366// icosahedron (12 verts via golden ratio phi~1.618, integer *1000). 20 triangular faces -- the iconic polyhedron.
367func tm_icosa(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 {
368 let b: i64 = TM_NV
369 let p: i64 = s*TM_MAGIC_1618/1000
370 tm_vert(cx,cy+s,cz+p); tm_vert(cx,cy-s,cz+p); tm_vert(cx,cy+s,cz-p); tm_vert(cx,cy-s,cz-p) // 0..3 (0,±1,±phi)
371 tm_vert(cx+s,cy+p,cz); tm_vert(cx-s,cy+p,cz); tm_vert(cx+s,cy-p,cz); tm_vert(cx-s,cy-p,cz) // 4..7 (±1,±phi,0)
372 tm_vert(cx+p,cy,cz+s); tm_vert(cx-p,cy,cz+s); tm_vert(cx+p,cy,cz-s); tm_vert(cx-p,cy,cz-s) // 8..11 (±phi,0,±1)
373 tm_tri(b+0,b+1,b+8,col); tm_tri(b+0,b+8,b+4,col); tm_tri(b+0,b+4,b+5,col); tm_tri(b+0,b+5,b+9,col); tm_tri(b+0,b+9,b+1,col)
374 tm_tri(b+1,b+6,b+8,col); tm_tri(b+8,b+6,b+10,col); tm_tri(b+8,b+10,b+4,col); tm_tri(b+4,b+10,b+2,col); tm_tri(b+4,b+2,b+5,col)
375 tm_tri(b+5,b+2,b+11,col); tm_tri(b+5,b+11,b+9,col); tm_tri(b+9,b+11,b+7,col); tm_tri(b+9,b+7,b+1,col); tm_tri(b+1,b+7,b+6,col)
376 tm_tri(b+3,b+6,b+7,col); tm_tri(b+3,b+7,b+11,col); tm_tri(b+3,b+11,b+2,col); tm_tri(b+3,b+2,b+10,col); tm_tri(b+3,b+10,b+6,col)
377 return 0
378}
379// triangulated UV-sphere (rings x segs) -- shows even a ROUND shape is now real triangles, not an SDF blob.
380func tm_uvsphere(cx: i64, cy: i64, cz: i64, s: i64, rings: i64, segs: i64, col: i64) -> i64 {
381 let b: i64 = TM_NV
382 var r: i64 = 0
383 while r <= rings {
384 let theta: i64 = IT_PI * r / rings // 0..PI (lat)
385 let st: i64 = it_sin4096(theta); let ct: i64 = it_cos4096(theta)
386 var sg: i64 = 0
387 while sg <= segs {
388 let phi: i64 = 2*IT_PI * sg / segs // 0..2PI (lon)
389 let sp: i64 = it_sin4096(phi); let cp: i64 = it_cos4096(phi)
390 let x: i64 = cx + s*st/TM_MAGIC_4096*cp/TM_MAGIC_4096
391 let y: i64 = cy + s*ct/TM_MAGIC_4096
392 let z: i64 = cz + s*st/TM_MAGIC_4096*sp/TM_MAGIC_4096
393 tm_vert(x,y,z)
394 sg = sg + 1
395 }
396 r = r + 1
397 }
398 let w: i64 = segs+1
399 r = 0
400 while r < rings {
401 var sg: i64 = 0
402 while sg < segs {
403 let v0: i64 = b + r*w + sg
404 let v1: i64 = b + r*w + sg+1
405 let v2: i64 = b + (r+1)*w + sg
406 let v3: i64 = b + (r+1)*w + sg+1
407 tm_quad(v0,v1,v3,v2,col)
408 sg = sg + 1
409 }
410 r = r + 1
411 }
412 return 0
413}
414// ★compose a RECOGNIZABLE thing from primitives: a low-poly humanoid (boxes) -- the shape kernel applied to a figure,
415// the angular counterpart to the ellipsoid being. Proves we can build real things from triangles.
416func tm_figure(cx: i64, cy: i64, cz: i64, col: i64) -> i64 {
417 let skin: i64 = 214 + 170*256 + 150*TM_MAGIC_65536
418 tm_cube(cx, cy+300, cz, 78, skin) // head
419 tm_cube(cx, cy+120, cz, 96, col) // torso (taller via two stacked -> keep one for low-poly)
420 tm_cube(cx, cy-10, cz, 88, col) // lower torso
421 tm_cube(cx-150, cy+150, cz, 42, skin) // L upper arm
422 tm_cube(cx+150, cy+150, cz, 42, skin) // R upper arm
423 tm_cube(cx-150, cy-30, cz, 40, skin) // L forearm
424 tm_cube(cx+150, cy-30, cz, 40, skin) // R forearm
425 tm_cube(cx-56, cy-230, cz, 52, col) // L thigh
426 tm_cube(cx+56, cy-230, cz, 52, col) // R thigh
427 tm_cube(cx-56, cy-410, cz, 48, col) // L shin
428 tm_cube(cx+56, cy-410, cz, 48, col) // R shin
429 return 0
430}
431
432// ---- the rasterizer: transform -> project -> barycentric fill + Z-BUFFER + flat per-face shading ----
433// fb, zbuf sized W*H. yaw/pitch it4096. camz shifts model into +Z. Background left as-is by caller.
434func trimesh_render(fb: *i64, zbuf: *i64, W: i64, H: i64, yaw: i64, pitch: i64, camz: i64, focal: i64, smooth: i64) -> i64 {
435 let cyw: i64 = it_cos4096(yaw); let syw: i64 = it_sin4096(yaw)
436 let cpt: i64 = it_cos4096(pitch); let spt: i64 = it_sin4096(pitch)
437 // light dir (view space), upper-left-front
438 let lx: i64 = 0-TM_MAGIC_1300; let ly: i64 = TM_MAGIC_2400; let lz: i64 = 0-TM_MAGIC_3000
439 let llen: i64 = tm_isqrt(lx*lx+ly*ly+lz*lz) + 1
440 // per-vertex projected screen + view-space
441 let SX: *i64 = sys_mmap(TM_NV*8) as *i64
442 let SY: *i64 = sys_mmap(TM_NV*8) as *i64
443 let VX: *i64 = sys_mmap(TM_NV*8) as *i64
444 let VY: *i64 = sys_mmap(TM_NV*8) as *i64
445 let VZ: *i64 = sys_mmap(TM_NV*8) as *i64
446 let mvx: *i64 = TM_VX as *i64; let mvy: *i64 = TM_VY as *i64; let mvz: *i64 = TM_VZ as *i64
447 var i: i64 = 0
448 while i < TM_NV {
449 let x: i64 = mvx[i]; let y: i64 = mvy[i]; let z: i64 = mvz[i]
450 let x1: i64 = (x*cyw + z*syw)/TM_MAGIC_4096
451 let z1: i64 = (0-x*syw + z*cyw)/TM_MAGIC_4096
452 let y2: i64 = (y*cpt - z1*spt)/TM_MAGIC_4096
453 let z2: i64 = (y*spt + z1*cpt)/TM_MAGIC_4096
454 let vz: i64 = z2 + camz
455 VX[i] = x1; VY[i] = y2; VZ[i] = vz
456 if vz > 20 { SX[i] = W/2 + focal*x1/vz; SY[i] = H/2 - focal*y2/vz }
457 else { SX[i] = 0-TM_MAGIC_100000; SY[i] = 0-TM_MAGIC_100000 }
458 i = i + 1
459 }
460 // ★F3 GOURAUD: per-vertex Lambert (rotate model normal into view space, dot with light). fx1024, 0..1024.
461 // ★F3 BLINN-PHONG: per-vertex specular = (N.H)^16 * TM_SPEC, H = halfway(light, view); view dir toward camera = -z.
462 let LAMV: *i64 = sys_mmap(TM_NV*8) as *i64
463 let SPECV: *i64 = sys_mmap(TM_NV*8) as *i64
464 let hx: i64 = lx*TM_MAGIC_1024/llen
465 let hy: i64 = ly*TM_MAGIC_1024/llen
466 let hz: i64 = lz*TM_MAGIC_1024/llen - TM_MAGIC_1024 // + Vhat=(0,0,-1)
467 let hlen: i64 = tm_isqrt(hx*hx + hy*hy + hz*hz) + 1
468 if smooth >= 1 { // needed for BOTH smooth==1 (Gouraud) and smooth==2 (per-vertex colour)
469 let mnx: *i64 = TM_VNX as *i64; let mny: *i64 = TM_VNY as *i64; let mnz: *i64 = TM_VNZ as *i64
470 var q: i64 = 0
471 while q < TM_NV {
472 let mx: i64 = mnx[q]; let my: i64 = mny[q]; let mz: i64 = mnz[q]
473 let nx1: i64 = (mx*cyw + mz*syw)/TM_MAGIC_4096
474 let nz1: i64 = (0-mx*syw + mz*cyw)/TM_MAGIC_4096
475 let nvy: i64 = (my*cpt - nz1*spt)/TM_MAGIC_4096
476 let nvz: i64 = (my*spt + nz1*cpt)/TM_MAGIC_4096
477 let nvx: i64 = nx1
478 var lam: i64 = (nvx*lx + nvy*ly + nvz*lz) / (4*llen) // |n|=TM_MAGIC_4096 -> /(TM_MAGIC_4096*llen/TM_MAGIC_1024)=/(4*llen)
479 if lam < 0 { lam = 0 }
480 if lam > TM_MAGIC_1024 { lam = TM_MAGIC_1024 }
481 LAMV[q] = lam
482 var sv: i64 = 0
483 if TM_SPEC > 0 {
484 var sc: i64 = (nvx*hx + nvy*hy + nvz*hz) / (4*hlen) // N.H, fx1024 (|N|=TM_MAGIC_4096)
485 if sc < 0 { sc = 0 }
486 if sc > TM_MAGIC_1024 { sc = TM_MAGIC_1024 }
487 let c2: i64 = sc*sc/TM_MAGIC_1024; let c4: i64 = c2*c2/TM_MAGIC_1024; let c8: i64 = c4*c4/TM_MAGIC_1024; let c16: i64 = c8*c8/TM_MAGIC_1024
488 sv = TM_SPEC*c16/TM_MAGIC_1024
489 }
490 SPECV[q] = sv
491 q = q + 1
492 }
493 }
494 let TA: *i64 = TM_TA as *i64; let TB: *i64 = TM_TB as *i64; let TC: *i64 = TM_TC as *i64; let TCOL: *i64 = TM_TCOL as *i64
495 let VCOL: *i64 = TM_VCOL as *i64
496 var t: i64 = 0
497 while t < TM_NT {
498 let i0: i64 = TA[t]; let i1: i64 = TB[t]; let i2: i64 = TC[t]
499 let ax: i64 = SX[i0]; let ay: i64 = SY[i0]
500 let bx: i64 = SX[i1]; let by: i64 = SY[i1]
501 let cx: i64 = SX[i2]; let cy: i64 = SY[i2]
502 var skip: i64 = 0
503 if ax < 0-TM_MAGIC_90000 { skip = 1 } if bx < 0-TM_MAGIC_90000 { skip = 1 } if cx < 0-TM_MAGIC_90000 { skip = 1 }
504 let area: i64 = (bx-ax)*(cy-ay) - (by-ay)*(cx-ax)
505 if area == 0 { skip = 1 }
506 if skip == 0 {
507 // face normal (view space), oriented toward the camera (-Z)
508 let ux: i64 = VX[i1]-VX[i0]; let uy: i64 = VY[i1]-VY[i0]; let uz: i64 = VZ[i1]-VZ[i0]
509 let wx: i64 = VX[i2]-VX[i0]; let wy: i64 = VY[i2]-VY[i0]; let wz: i64 = VZ[i2]-VZ[i0]
510 var nx: i64 = uy*wz - uz*wy; var ny: i64 = uz*wx - ux*wz; var nz: i64 = ux*wy - uy*wx
511 if nz > 0 { nx = 0-nx; ny = 0-ny; nz = 0-nz }
512 let nl: i64 = tm_isqrt(nx*nx+ny*ny+nz*nz) + 1
513 var lam: i64 = (nx*lx + ny*ly + nz*lz) / (nl*llen/TM_MAGIC_1024)
514 if lam < 0 { lam = 0 }
515 let col: i64 = TCOL[t]
516 let br: i64 = col&255; let bg: i64 = (col>>8)&255; let bb: i64 = (col>>16)&255
517 let sh: i64 = 42 + 86*lam/TM_MAGIC_1024
518 var cr: i64 = br*sh/128; var cg: i64 = bg*sh/128; var cb: i64 = bb*sh/128
519 if cr>255 {cr=255} if cg>255 {cg=255} if cb>255 {cb=255}
520 let fcol: i64 = cr + cg*256 + cb*TM_MAGIC_65536
521 // per-vertex colours (smooth==2 multi-material meshes)
522 let k0: i64 = VCOL[i0]; let k1: i64 = VCOL[i1]; let k2: i64 = VCOL[i2]
523 let k0r: i64 = k0&255; let k0g: i64 = (k0>>8)&255; let k0b: i64 = (k0>>16)&255
524 let k1r: i64 = k1&255; let k1g: i64 = (k1>>8)&255; let k1b: i64 = (k1>>16)&255
525 let k2r: i64 = k2&255; let k2g: i64 = (k2>>8)&255; let k2b: i64 = (k2>>16)&255
526 // per-vertex UVs (Q16) for image-texture sampling
527 let TVU: *i64 = TM_VU as *i64
528 let TVV: *i64 = TM_VV as *i64
529 let u0: i64 = TVU[i0]; let u1: i64 = TVU[i1]; let u2: i64 = TVU[i2]
530 let w0: i64 = TVV[i0]; let w1: i64 = TVV[i1]; let w2: i64 = TVV[i2]
531 // bbox (clamped)
532 var minx: i64 = tm_min3(ax,bx,cx); var maxx: i64 = tm_max3(ax,bx,cx)
533 var miny: i64 = tm_min3(ay,by,cy); var maxy: i64 = tm_max3(ay,by,cy)
534 if minx < 0 { minx = 0 } if miny < 0 { miny = 0 }
535 if maxx > W-1 { maxx = W-1 } if maxy > H-1 { maxy = H-1 }
536 var py: i64 = miny
537 while py <= maxy {
538 var px: i64 = minx
539 while px <= maxx {
540 let e0: i64 = (cx-bx)*(py-by) - (cy-by)*(px-bx) // weight of vertex i0
541 let e1: i64 = (ax-cx)*(py-cy) - (ay-cy)*(px-cx) // weight of i1
542 let e2: i64 = (bx-ax)*(py-ay) - (by-ay)*(px-ax) // weight of i2
543 var inside: i64 = 0
544 if area > 0 { if e0>=0 { if e1>=0 { if e2>=0 { inside=1 } } } }
545 else { if e0<=0 { if e1<=0 { if e2<=0 { inside=1 } } } }
546 if inside == 1 {
547 let zz: i64 = (e0*VZ[i0] + e1*VZ[i1] + e2*VZ[i2]) / area // interpolated view depth
548 let idx: i64 = py*W + px
549 if zz < zbuf[idx] {
550 zbuf[idx] = zz
551 if smooth == 1 {
552 var l2: i64 = (e0*LAMV[i0] + e1*LAMV[i1] + e2*LAMV[i2]) / area // interpolated (Gouraud)
553 if l2 < 0 { l2 = 0 }
554 var sp2: i64 = (e0*SPECV[i0] + e1*SPECV[i1] + e2*SPECV[i2]) / area // specular highlight
555 if sp2 < 0 { sp2 = 0 }
556 if TM_SHON == 1 { // ★Q2 sun shadow
557 let wx2: i64 = (e0*mvx[i0] + e1*mvx[i1] + e2*mvx[i2]) / area
558 let wy2: i64 = (e0*mvy[i0] + e1*mvy[i1] + e2*mvy[i2]) / area
559 let wz2: i64 = (e0*mvz[i0] + e1*mvz[i1] + e2*mvz[i2]) / area
560 let sf2: i64 = tm_shadow_at(wx2, wy2, wz2)
561 l2 = l2*sf2/TM_MAGIC_1024
562 sp2 = sp2*sf2/TM_MAGIC_1024
563 }
564 let s2: i64 = 42 + 86*l2/TM_MAGIC_1024
565 var r2: i64 = br*s2/128 + sp2; var g2: i64 = bg*s2/128 + sp2; var b2: i64 = bb*s2/128 + sp2
566 if r2>255 {r2=255} if g2>255 {g2=255} if b2>255 {b2=255}
567 fb[idx] = r2 + g2*256 + b2*TM_MAGIC_65536
568 } else { if smooth == 2 {
569 var l3: i64 = (e0*LAMV[i0] + e1*LAMV[i1] + e2*LAMV[i2]) / area // Gouraud shade
570 if l3 < 0 { l3 = 0 }
571 var sp3: i64 = (e0*SPECV[i0] + e1*SPECV[i1] + e2*SPECV[i2]) / area
572 if sp3 < 0 { sp3 = 0 }
573 var mr: i64 = (e0*k0r + e1*k1r + e2*k2r) / area // interpolated per-vertex colour
574 var mg: i64 = (e0*k0g + e1*k1g + e2*k2g) / area
575 var mb: i64 = (e0*k0b + e1*k1b + e2*k2b) / area
576 if TM_IMG != 0 { // ★IMAGE texture via interpolated UV (REPEAT wrap)
577 var iu: i64 = ((e0*u0 + e1*u1 + e2*u2) / area) & TM_MAGIC_65535
578 var iv: i64 = ((e0*w0 + e1*w1 + e2*w2) / area) & TM_MAGIC_65535
579 let tx: i64 = iu*TM_IMGW/TM_MAGIC_65536
580 let ty: i64 = iv*TM_IMGH/TM_MAGIC_65536
581 let timg: *u8 = TM_IMG as *u8
582 let toff: i64 = (ty*TM_IMGW + tx)*3
583 mr = timg[toff] as i64
584 mg = timg[toff+1] as i64
585 mb = timg[toff+2] as i64
586 }
587 var needw: i64 = 0
588 if TM_TEX > 0 { needw = 1 }
589 if TM_SHON == 1 { needw = 1 }
590 if needw == 1 {
591 let wx: i64 = (e0*mvx[i0] + e1*mvx[i1] + e2*mvx[i2]) / area
592 let wy: i64 = (e0*mvy[i0] + e1*mvy[i1] + e2*mvy[i2]) / area
593 let wz: i64 = (e0*mvz[i0] + e1*mvz[i1] + e2*mvz[i2]) / area
594 if TM_TEX > 0 {
595 // ★Q1 MULTI-OCTAVE ALBEDO (not one grey mottle): 3 octaves of brightness
596 // + a decorrelated HUE octave (warm/cool tonal variation) -- the natural
597 // self-similar detail the photoreal judge (ns_assess MSCN/scale) measures.
598 let n1: i64 = tm_noise3(wx, wy, wz, TM_TEX as i64)
599 let n2: i64 = tm_noise3(wx, wy+TM_MAGIC_37777, wz, TM_TEX/3+1)
600 let n3: i64 = tm_noise3(wx+TM_MAGIC_91131, wy, wz, TM_TEX/9+1)
601 let tf: i64 = 620 + (n1*3 + n2*2 + n3)*470/TM_MAGIC_6144 // 0.60..1.06
602 let hue: i64 = tm_noise3(wx, wy+TM_MAGIC_81111, wz, TM_TEX*2) - 512 // warm/cool +-
603 mr = mr*tf/TM_MAGIC_1024 + hue*22/512
604 mg = mg*tf/TM_MAGIC_1024 + hue*8/512
605 mb = mb*tf/TM_MAGIC_1024 - hue*20/512
606 if mr < 0 { mr = 0 }
607 if mg < 0 { mg = 0 }
608 if mb < 0 { mb = 0 }
609 }
610 if TM_SHON == 1 { // ★Q2 sun shadow
611 let sf: i64 = tm_shadow_at(wx, wy, wz)
612 l3 = l3*sf/TM_MAGIC_1024
613 sp3 = sp3*sf/TM_MAGIC_1024
614 }
615 }
616 let s3: i64 = 42 + 86*l3/TM_MAGIC_1024
617 var r3: i64 = mr*s3/128 + sp3; var g3: i64 = mg*s3/128 + sp3; var b3: i64 = mb*s3/128 + sp3
618 if r3>255 {r3=255} if g3>255 {g3=255} if b3>255 {b3=255}
619 fb[idx] = r3 + g3*256 + b3*TM_MAGIC_65536
620 } else { if smooth == 3 {
621 // UNLIT per-vertex colour (radiosity display: the vertex colour IS the lighting)
622 var pr: i64 = (e0*k0r + e1*k1r + e2*k2r) / area
623 var pg: i64 = (e0*k0g + e1*k1g + e2*k2g) / area
624 var pb: i64 = (e0*k0b + e1*k1b + e2*k2b) / area
625 if pr<0 {pr=0} if pg<0 {pg=0} if pb<0 {pb=0}
626 if pr>255 {pr=255} if pg>255 {pg=255} if pb>255 {pb=255}
627 fb[idx] = pr + pg*256 + pb*TM_MAGIC_65536
628 } else { fb[idx] = fcol } } }
629 }
630 }
631 px = px + 1
632 }
633 py = py + 1
634 }
635 }
636 t = t + 1
637 }
638 return 0
639}
640// clear a z-buffer to +far
641func trimesh_zclear(zbuf: *i64, n: i64) -> i64 { var i: i64=0; while i<n { zbuf[i] = TM_MAGIC_2000000000; i=i+1 } return 0 }
642// ★F3 ANTI-ALIASING (SSAA): render at 2x resolution, box-downsample into fb -> smooth edges. fb must be pre-cleared to bg.
643func trimesh_render_aa(fb: *i64, W: i64, H: i64, yaw: i64, pitch: i64, camz: i64, focal: i64, smooth: i64) -> i64 {
644 let W2: i64 = W*2; let H2: i64 = H*2
645 let big: *i64 = sys_mmap(W2*H2*8) as *i64
646 let bz: *i64 = sys_mmap(W2*H2*8) as *i64
647 let bg: i64 = fb[0]
648 var i: i64 = 0
649 while i < W2*H2 { big[i] = bg; i = i+1 }
650 trimesh_zclear(bz, W2*H2)
651 trimesh_render(big, bz, W2, H2, yaw, pitch, camz, focal*2, smooth) // focal*2 keeps the 2x framing identical
652 var y: i64 = 0
653 while y < H {
654 var x: i64 = 0
655 while x < W {
656 let p0: i64 = big[(2*y)*W2 + 2*x]; let p1: i64 = big[(2*y)*W2 + 2*x+1]
657 let p2: i64 = big[(2*y+1)*W2 + 2*x]; let p3: i64 = big[(2*y+1)*W2 + 2*x+1]
658 let r: i64 = ((p0&255)+(p1&255)+(p2&255)+(p3&255))/4
659 let g: i64 = (((p0>>8)&255)+((p1>>8)&255)+((p2>>8)&255)+((p3>>8)&255))/4
660 let b: i64 = (((p0>>16)&255)+((p1>>16)&255)+((p2>>16)&255)+((p3>>16)&255))/4
661 fb[y*W + x] = r + g*256 + b*TM_MAGIC_65536
662 x = x + 1
663 }
664 y = y + 1
665 }
666 return 0
667}