code wiki / (root) / nx_trimesh.nx

nx_trimesh.nx source

↩ module page · 774 lines · 45206 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" 10import "nx_vecmath.nx" 11import "nx_relief_lib.nx" 12const TM_MAGIC_1024: i64 = 1024 13const TM_MAGIC_1000000000: i64 = 1000000000 14const TM_MAGIC_374761393: i64 = 374761393 15const TM_MAGIC_668265263: i64 = 668265263 16const TM_MAGIC_1610612741: i64 = 1610612741 17const TM_MAGIC_1274126177: i64 = 1274126177 18const TM_MAGIC_1048576: i64 = 1048576 19const TM_MAGIC_4096: i64 = 4096 20const TM_MAGIC_1618: i64 = 1618 21const TM_MAGIC_65536: i64 = 65536 22const TM_MAGIC_1300: i64 = 1300 23const TM_MAGIC_2400: i64 = 2400 24const TM_MAGIC_3000: i64 = 3000 25const TM_MAGIC_100000: i64 = 100000 26const TM_MAGIC_90000: i64 = 90000 27const TM_MAGIC_65535: i64 = 65535 28const TM_MAGIC_37777: i64 = 37777 29const TM_MAGIC_91131: i64 = 91131 30const TM_MAGIC_6144: i64 = 6144 31const TM_MAGIC_81111: i64 = 81111 32const TM_MAGIC_2000000000: i64 = 2000000000 33 34// ★CAPACITY IS DERIVED, NOT TYPED. Surface nets emits at most one vertex per grid cell, so a mesh from 35// an N^3 grid cannot exceed N^3 vertices -- but the surface is 2-DIMENSIONAL, so the real bound is the 36// number of cells the surface passes through, which scales as N^2. TM_GRID is the largest grid any 37// caller polygonizes; everything else follows from it, with a safety factor for a folded surface that 38// crosses a cell column more than once. Change the grid and the caps move with it. 39// (The previous 48k was typed, and silently truncated a skull at 2mm cells -- the organ then reported a 40// bounding box measured from an INCOMPLETE mesh, a wrong number that looks like a right one.) 41const TM_GRID: i64 = 256 // max polygonizer grid resolution supported 42const TM_FOLD: i64 = 16 // surface-crossings per cell column, worst case. MEASURED 43 // 2026-08-10: 6 truncated a real skull voxelization at 1mm -- a 44 // two-sided thin shell with sinuses, orbits and a tooth row crosses 45 // a column far more than 6 times (786,432-tri cap hit, half the 46 // skull dropped, overflow=1 the only witness). Same failure the 47 // comment above already records for the typed 48k. 48const TM_VCAP: i64 = TM_GRID*TM_GRID*TM_FOLD 49const TM_TCAP: i64 = TM_VCAP*2 // surface nets emits ~2 triangles per vertex 50// ★ACTIVE capacity, chosen by the CALLER. TM_VCAP/TM_TCAP above are the POLYGONIZER's worst case, and 51// a 256^3 surface-nets run genuinely needs them -- but because capacity was a COMPILE-TIME const, 52// every other consumer was forced to pay that floor: 13 arrays = 136 MiB. A skinned character is 53// 14,164 verts / 28,092 tris = 1.83 MiB, so the fixed cap overcharged it 74x, and that overcharge put 54// the estate's ONLY 3D rasterizer out of reach of every memory-bounded target. The wasm world page is 55// the sharp case: it cannot call sys_mmap AT ALL (sys_mmap is __syscall(SYS_MMAP,...) and wasm has no 56// syscalls), which is exactly why the nx_wasm_* modules address everything as base+offset. 57// These two hold whatever the ACTIVE allocation is; 0 means "not yet allocated", so the accessors 58// fall back to the const and every existing caller is unaffected. 59static TM_VCAPN: i64 60static TM_TCAPN: i64 61func tm_vcap() -> i64 { if TM_VCAPN == 0 { return TM_VCAP } return TM_VCAPN } 62func tm_tcap() -> i64 { if TM_TCAPN == 0 { return TM_TCAP } return TM_TCAPN } 63static TM_VX: i64 // model-space vert x,y,z (3 arrays interleaved via ptrs) 64static TM_VY: i64 65static TM_VZ: i64 66static TM_TA: i64 // tri vert index a,b,c + colour (4 arrays) 67static TM_TB: i64 68static TM_TC: i64 69static TM_TCOL: i64 70static TM_VNX: i64 // ★F3: per-vertex model-space normal (for Gouraud smooth shading) 71static TM_VNY: i64 72static TM_VNZ: i64 73static TM_VCOL: i64 // per-vertex colour (for multi-material meshes, e.g. the meshed anatomy) 74static TM_NV: i64 75static TM_NT: i64 76static TM_OVF: i64 // set to 1 if a vertex/triangle add hit the capacity (so callers detect truncation) 77func tm_ovf() -> i64 { return TM_OVF } 78static TM_SPEC: i64 // ★F3 Blinn-Phong specular strength (0 = off/matte; ~110 = shiny). opt-in, zero-cost when 0. 79func tm_set_spec(v: i64) -> i64 { TM_SPEC = v; return 0 } 80static TM_TEX: i64 // ★F3 procedural 3D solid-noise texture: lattice scale in units (0 = off). opt-in. 81func tm_set_tex(scale: i64) -> i64 { TM_TEX = scale; return 0 } 82// ★Q2 SUN SHADOWS (2026-07-12, the quality ladder): a world-space orthographic SHADOW MAP. Bake once per scene 83// (model space -- camera-independent, so one bake serves every view), then the pixel path darkens fragments whose 84// sun-depth is behind the nearest blocker. Opt-in: tm_set_sun + tm_shadow_bake + tm_set_shadow(1); zero-cost off. 85const TM_SHRES: i64 = 576 86static TM_SUNX: i64 // direction TOWARD the sun, normalized *1024 87static TM_SUNY: i64 88static TM_SUNZ: i64 89static TM_SHON: i64 // 1 = apply the baked map in the pixel path 90static TM_SH: i64 // the map: TM_SHRES^2 i64 sun-depths (max = nearest blocker) 91static TM_SRX: i64 // ortho basis R (*1024) 92static TM_SRY: i64 93static TM_SRZ: i64 94static TM_SUX: i64 // ortho basis U (*1024) 95static TM_SUY: i64 96static TM_SUZ: i64 97static TM_SHA0: i64 // a-min / b-min / spans (world units) -> texel mapping 98static TM_SHB0: i64 99static TM_SHSA: i64 100static TM_SHSB: i64 101func tm_set_sun(x: i64, y: i64, z: i64) -> i64 { 102 let l: i64 = tm_isqrt(x*x + y*y + z*z) + 1 103 TM_SUNX = x*TM_MAGIC_1024/l 104 TM_SUNY = y*TM_MAGIC_1024/l 105 TM_SUNZ = z*TM_MAGIC_1024/l 106 return 0 107} 108func tm_set_shadow(v: i64) -> i64 { TM_SHON = v; return 0 } 109func tm_shadow_bake() -> i64 { 110 if TM_SH == 0 { TM_SH = sys_mmap(TM_SHRES*TM_SHRES*8) as i64 } 111 let sh: *i64 = TM_SH as *i64 112 var i: i64 = 0 113 while i < TM_SHRES*TM_SHRES { sh[i] = 0 - TM_MAGIC_1000000000; i = i + 1 } 114 // ortho basis from the sun dir: R = S x up (fallback x-axis), U = S x R 115 var rx: i64 = TM_SUNZ 116 var ry: i64 = 0 117 var rz: i64 = 0 - TM_SUNX 118 var rl: i64 = tm_isqrt(rx*rx + rz*rz) 119 if rl < 40 { rx = TM_MAGIC_1024; ry = 0; rz = 0; rl = TM_MAGIC_1024 } 120 rx = rx*TM_MAGIC_1024/rl; rz = rz*TM_MAGIC_1024/rl 121 let ux: i64 = (TM_SUNY*rz - TM_SUNZ*ry)/TM_MAGIC_1024 122 let uy: i64 = (TM_SUNZ*rx - TM_SUNX*rz)/TM_MAGIC_1024 123 let uz: i64 = (TM_SUNX*ry - TM_SUNY*rx)/TM_MAGIC_1024 124 TM_SRX = rx; TM_SRY = ry; TM_SRZ = rz 125 TM_SUX = ux; TM_SUY = uy; TM_SUZ = uz 126 // project all verts to (a,b,d) sun space 127 let px: *i64 = TM_VX as *i64 128 let py: *i64 = TM_VY as *i64 129 let pz: *i64 = TM_VZ as *i64 130 let sa: *i64 = sys_mmap(TM_NV*8) as *i64 131 let sb: *i64 = sys_mmap(TM_NV*8) as *i64 132 let sd: *i64 = sys_mmap(TM_NV*8) as *i64 133 var amin: i64 = TM_MAGIC_1000000000 134 var amax: i64 = 0-TM_MAGIC_1000000000 135 var bmin: i64 = TM_MAGIC_1000000000 136 var bmax: i64 = 0-TM_MAGIC_1000000000 137 i = 0 138 while i < TM_NV { 139 let a: i64 = (px[i]*rx + py[i]*ry + pz[i]*rz)/TM_MAGIC_1024 140 let b: i64 = (px[i]*ux + py[i]*uy + pz[i]*uz)/TM_MAGIC_1024 141 let d: i64 = (px[i]*TM_SUNX + py[i]*TM_SUNY + pz[i]*TM_SUNZ)/TM_MAGIC_1024 142 sa[i] = a; sb[i] = b; sd[i] = d 143 if a < amin { amin = a } if a > amax { amax = a } 144 if b < bmin { bmin = b } if b > bmax { bmax = b } 145 i = i + 1 146 } 147 TM_SHA0 = amin 148 TM_SHB0 = bmin 149 TM_SHSA = amax - amin + 1 150 TM_SHSB = bmax - bmin + 1 151 // rasterize every triangle into the map, keeping MAX sun-depth (nearest blocker) 152 let TA: *i64 = TM_TA as *i64 153 let TB: *i64 = TM_TB as *i64 154 let TC: *i64 = TM_TC as *i64 155 var t: i64 = 0 156 while t < TM_NT { 157 let i0: i64 = TA[t]; let i1: i64 = TB[t]; let i2: i64 = TC[t] 158 let ax: i64 = (sa[i0]-amin)*TM_SHRES/TM_SHSA; let ay: i64 = (sb[i0]-bmin)*TM_SHRES/TM_SHSB 159 let bx: i64 = (sa[i1]-amin)*TM_SHRES/TM_SHSA; let by: i64 = (sb[i1]-bmin)*TM_SHRES/TM_SHSB 160 let cx: i64 = (sa[i2]-amin)*TM_SHRES/TM_SHSA; let cy: i64 = (sb[i2]-bmin)*TM_SHRES/TM_SHSB 161 let area: i64 = (bx-ax)*(cy-ay) - (by-ay)*(cx-ax) 162 if area != 0 { 163 var minx: i64 = tm_min3(ax,bx,cx); var maxx: i64 = tm_max3(ax,bx,cx) 164 var miny: i64 = tm_min3(ay,by,cy); var maxy: i64 = tm_max3(ay,by,cy) 165 if minx < 0 { minx = 0 } if miny < 0 { miny = 0 } 166 if maxx > TM_SHRES-1 { maxx = TM_SHRES-1 } if maxy > TM_SHRES-1 { maxy = TM_SHRES-1 } 167 var qy: i64 = miny 168 while qy <= maxy { 169 var qx: i64 = minx 170 while qx <= maxx { 171 let e0: i64 = (cx-bx)*(qy-by) - (cy-by)*(qx-bx) 172 let e1: i64 = (ax-cx)*(qy-cy) - (ay-cy)*(qx-cx) 173 let e2: i64 = (bx-ax)*(qy-ay) - (by-ay)*(qx-ax) 174 var inside: i64 = 0 175 if area > 0 { if e0>=0 { if e1>=0 { if e2>=0 { inside=1 } } } } 176 else { if e0<=0 { if e1<=0 { if e2<=0 { inside=1 } } } } 177 if inside == 1 { 178 let dd: i64 = (e0*sd[i0] + e1*sd[i1] + e2*sd[i2]) / area 179 let mi: i64 = qy*TM_SHRES + qx 180 if dd > sh[mi] { sh[mi] = dd } 181 } 182 qx = qx + 1 183 } 184 qy = qy + 1 185 } 186 } 187 t = t + 1 188 } 189 return 0 190} 191// shadow factor at a model-space point: 1024 = lit, else attenuated. Bias scales with texel size (acne guard). 192func tm_shadow_at(wx: i64, wy: i64, wz: i64) -> i64 { 193 let a: i64 = (wx*TM_SRX + wy*TM_SRY + wz*TM_SRZ)/TM_MAGIC_1024 194 let b: i64 = (wx*TM_SUX + wy*TM_SUY + wz*TM_SUZ)/TM_MAGIC_1024 195 let tx: i64 = (a - TM_SHA0)*TM_SHRES/TM_SHSA 196 let ty: i64 = (b - TM_SHB0)*TM_SHRES/TM_SHSB 197 if tx < 0 { return TM_MAGIC_1024 } 198 if ty < 0 { return TM_MAGIC_1024 } 199 if tx > TM_SHRES-1 { return TM_MAGIC_1024 } 200 if ty > TM_SHRES-1 { return TM_MAGIC_1024 } 201 let d: i64 = (wx*TM_SUNX + wy*TM_SUNY + wz*TM_SUNZ)/TM_MAGIC_1024 202 let sh: *i64 = TM_SH as *i64 203 var bias: i64 = TM_SHSA/TM_SHRES*3 204 if bias < 22 { bias = 22 } 205 if sh[ty*TM_SHRES + tx] > d + bias { return 340 } 206 return TM_MAGIC_1024 207} 208static TM_IMG: i64 // ★F3 IMAGE texture (packed w*h*3 RGB) sampled via per-vertex UV. 0 = off. 209static TM_IMGW: i64 210static TM_IMGH: i64 211func tm_set_image(rgb: *u8, w: i64, h: i64) -> i64 { TM_IMG = rgb as i64; TM_IMGW = w; TM_IMGH = h; return 0 } 212static TM_NRM: i64 // ★R4 NORMAL map (packed w*h*3, tangent-space, 128=zero). 0 = off. 213static TM_NRMW: i64 214static TM_NRMH: i64 215const TM_NMID: i64 = 128 216const TM_NSCL: i64 = 127 217func tm_set_normalmap(rgb: *u8, w: i64, h: i64) -> i64 { TM_NRM = rgb as i64; TM_NRMW = w; TM_NRMH = h; return 0 } 218// Build a deterministic orthonormal basis from a unit normal and rotate a tangent-space normal into it. 219// Method: Duff et al. 2017, "Building an Orthonormal Basis, Revisited" -- branchless, and well conditioned 220// EVERYWHERE because the sign trick keeps the denominator in [1,2] instead of collapsing near the pole. 221// ★WHY A UV-FREE BASIS IS CORRECT HERE, and where it would not be: our relief is ISOTROPIC band-limited 222// noise, and an isotropic detail normal has no preferred tangent direction, so any consistent basis gives 223// a statistically identical result. An ANISOTROPIC or authored map (real skin follows Langer's lines) 224// WOULD need UV-aligned tangents from mesh derivatives. That is a named rung, not an oversight. 225// All vectors are fixed-point with TM_MAGIC_4096 representing 1.0. 226func tm_perturb_normal(nx: i64, ny: i64, nz: i64, tx: i64, ty: i64, tz: i64, out3: *i64) -> i64 { 227 let S: i64 = TM_MAGIC_4096 228 var nl: i64 = tm_isqrt(nx*nx + ny*ny + nz*nz) 229 if nl < 1 { nl = 1 } 230 let ux: i64 = nx*S/nl 231 let uy: i64 = ny*S/nl 232 let uz: i64 = nz*S/nl 233 var sg: i64 = 1 234 if uz < 0 { sg = 0 - 1 } 235 let den: i64 = sg*S + uz 236 if den == 0 { out3[0] = ux; out3[1] = uy; out3[2] = uz; return 0 } 237 let aa: i64 = 0 - (S*S)/den 238 let bb: i64 = ux*uy/S*aa/S 239 let t0: i64 = S + sg*ux/S*ux*aa/S 240 let t1: i64 = sg*bb 241 let t2: i64 = 0 - sg*ux 242 let b0: i64 = bb 243 let b1: i64 = sg*S + uy*uy/S*aa/S 244 let b2: i64 = 0 - uy 245 let px: i64 = (tx*t0 + ty*b0 + tz*ux)/S 246 let py: i64 = (tx*t1 + ty*b1 + tz*uy)/S 247 let pz: i64 = (tx*t2 + ty*b2 + tz*uz)/S 248 var pl: i64 = tm_isqrt(px*px + py*py + pz*pz) 249 if pl < 1 { pl = 1 } 250 out3[0] = px*S/pl 251 out3[1] = py*S/pl 252 out3[2] = pz*S/pl 253 return 0 254} 255static TM_VU: i64 // per-vertex UV, Q16 (65536 = 1.0) 256static TM_VV: i64 257func 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 } 258// ★DELEGATES to nx_relief_lib, the ONE OWNER of this field since 2026-08-25. The body moved VERBATIM, 259// so every caller -- the texture baker, nx_worldpipe, the engine gates -- is behaviourally identical BY 260// CONSTRUCTION, not by a second copy that happens to agree today. 261func tm_hash3(a: i64, b: i64, c: i64) -> i64 { return rlf_hash3(a, b, c) } 262// trilinear value noise at 3D position, lattice scale S. Returns 0..1023 (smooth). Offset keeps floor-division positive. 263// ★DELEGATES to nx_relief_lib (ONE OWNER, 2026-08-25). Body moved VERBATIM. 264func tm_noise3(x: i64, y: i64, z: i64, S: i64) -> i64 { return rlf_noise3(x, y, z, S) } 265 266func tm_isqrt(v: i64) -> i64 { return vm_isqrt(v) } 267func tm_min(a: i64, b: i64) -> i64 { if a<b {return a} return b } 268func tm_max(a: i64, b: i64) -> i64 { if a>b {return a} return b } 269func tm_min3(a: i64, b: i64, c: i64) -> i64 { return tm_min(tm_min(a,b),c) } 270func tm_max3(a: i64, b: i64, c: i64) -> i64 { return tm_max(tm_max(a,b),c) } 271 272func tm_reset() -> i64 { 273 if TM_VX == 0 { 274 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 275 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 276 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 277 TM_VCOL = sys_mmap(TM_VCAP*8) as i64 278 TM_VU = sys_mmap(TM_VCAP*8) as i64; TM_VV = sys_mmap(TM_VCAP*8) as i64 279 } 280 TM_VCAPN = TM_VCAP; TM_TCAPN = TM_TCAP 281 TM_NV = 0; TM_NT = 0; TM_OVF = 0 282 return 0 283} 284// ★ARENA BINDING -- THE SAME RASTERIZER, NO SYSCALLS. Points the 13 arrays at consecutive slices of 285// memory the CALLER already owns, so a wasm module (base+offset, no mmap) or any memory-bounded 286// target can drive trimesh_render UNCHANGED. That function already takes fb and zbuf as PARAMETERS, 287// so vertex STORAGE was the only thing tying this organ to Linux -- binding it is all that was 288// missing. ONE rasterizer with two allocation strategies, never a second copy to drift. 289// Layout: 9 vertex arrays then 4 triangle arrays, 8-byte words, in exactly the order below. 290// Size the arena with tm_bind_bytes(nv, nt) -- do not hand-compute it at the call site. 291func tm_bind_bytes(nv: i64, nt: i64) -> i64 { return nv*8*9 + nt*8*4 } 292func tm_bind(mem: i64, nv: i64, nt: i64) -> i64 { 293 if mem == 0 { return 3 } 294 if nv <= 0 { return 3 } 295 if nt <= 0 { return 3 } 296 var o: i64 = mem 297 TM_VX = o; o = o + nv*8 298 TM_VY = o; o = o + nv*8 299 TM_VZ = o; o = o + nv*8 300 TM_VNX = o; o = o + nv*8 301 TM_VNY = o; o = o + nv*8 302 TM_VNZ = o; o = o + nv*8 303 TM_VCOL = o; o = o + nv*8 304 TM_VU = o; o = o + nv*8 305 TM_VV = o; o = o + nv*8 306 TM_TA = o; o = o + nt*8 307 TM_TB = o; o = o + nt*8 308 TM_TC = o; o = o + nt*8 309 TM_TCOL = o; o = o + nt*8 310 TM_VCAPN = nv 311 TM_TCAPN = nt 312 TM_NV = 0; TM_NT = 0; TM_OVF = 0 313 return 0 314} 315func tm_vcol(idx: i64, col: i64) -> i64 { let p: *i64 = TM_VCOL as *i64; p[idx] = col; return 0 } 316// NOTE: declare the pointer up front -- inline-cast-index READ `(TM_VX as *i64)[i]` miscompiles to the base pointer. 317func 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 } 318func 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 } 319func tm_getvcol(idx: i64) -> i64 { let p: *i64=TM_VCOL as *i64; return p[idx] } 320// ★read-only accessors so a caller can EXPORT what surface_nets built (e.g. write NXMSH2 and then look 321// at it). Without these the polygonizer could fill these buffers and nothing could get the geometry 322// back out, which is how meshes got produced that nobody could view. 323func tm_vx(i: i64) -> i64 { let p: *i64=TM_VX as *i64; return p[i] } 324func tm_vy(i: i64) -> i64 { let p: *i64=TM_VY as *i64; return p[i] } 325func tm_vz(i: i64) -> i64 { let p: *i64=TM_VZ as *i64; return p[i] } 326func tm_vnx(i: i64) -> i64 { let p: *i64=TM_VNX as *i64; return p[i] } 327func tm_vny(i: i64) -> i64 { let p: *i64=TM_VNY as *i64; return p[i] } 328func tm_vnz(i: i64) -> i64 { let p: *i64=TM_VNZ as *i64; return p[i] } 329func tm_ta(i: i64) -> i64 { let p: *i64=TM_TA as *i64; return p[i] } 330func tm_tb(i: i64) -> i64 { let p: *i64=TM_TB as *i64; return p[i] } 331func tm_tc(i: i64) -> i64 { let p: *i64=TM_TC as *i64; return p[i] } 332func 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 } 333// translate (and optionally uniform-scale, mil=1000 -> 1.0) vertices [from,to) -- scene placement of a loaded asset 334func tm_place(from: i64, to: i64, dx: i64, dy: i64, dz: i64, mil: i64) -> i64 { 335 let px: *i64 = TM_VX as *i64; let py: *i64 = TM_VY as *i64; let pz: *i64 = TM_VZ as *i64 336 var i: i64 = from 337 while i < to { 338 px[i] = px[i]*mil/1000 + dx 339 py[i] = py[i]*mil/1000 + dy 340 pz[i] = pz[i]*mil/1000 + dz 341 i = i + 1 342 } 343 return 0 344} 345// ★F3: compute per-vertex normals = normalized sum of adjacent FACE normals (model space). Enables Gouraud smooth shading. 346func tm_compute_normals() -> i64 { 347 let nx: *i64 = TM_VNX as *i64; let ny: *i64 = TM_VNY as *i64; let nz: *i64 = TM_VNZ as *i64 348 let vx: *i64 = TM_VX as *i64; let vy: *i64 = TM_VY as *i64; let vz: *i64 = TM_VZ as *i64 349 var i: i64 = 0 350 while i < TM_NV { nx[i]=0; ny[i]=0; nz[i]=0; i=i+1 } 351 let TA: *i64 = TM_TA as *i64; let TB: *i64 = TM_TB as *i64; let TC: *i64 = TM_TC as *i64 352 var t: i64 = 0 353 while t < TM_NT { 354 let a: i64 = TA[t]; let b: i64 = TB[t]; let c: i64 = TC[t] 355 let ux: i64 = vx[b]-vx[a]; let uy: i64 = vy[b]-vy[a]; let uz: i64 = vz[b]-vz[a] 356 let wx: i64 = vx[c]-vx[a]; let wy: i64 = vy[c]-vy[a]; let wz: i64 = vz[c]-vz[a] 357 var fnx: i64 = uy*wz-uz*wy; var fny: i64 = uz*wx-ux*wz; var fnz: i64 = ux*wy-uy*wx 358 let fl: i64 = tm_isqrt(fnx*fnx+fny*fny+fnz*fnz) + 1 // unit-ish (256) so big tris don't dominate the average 359 fnx = fnx*256/fl; fny = fny*256/fl; fnz = fnz*256/fl 360 nx[a]=nx[a]+fnx; ny[a]=ny[a]+fny; nz[a]=nz[a]+fnz 361 nx[b]=nx[b]+fnx; ny[b]=ny[b]+fny; nz[b]=nz[b]+fnz 362 nx[c]=nx[c]+fnx; ny[c]=ny[c]+fny; nz[c]=nz[c]+fnz 363 t = t + 1 364 } 365 i = 0 366 while i < TM_NV { 367 let l: i64 = tm_isqrt(nx[i]*nx[i]+ny[i]*ny[i]+nz[i]*nz[i]) + 1 368 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 369 i = i + 1 370 } 371 return 0 372} 373func tm_vert(x: i64, y: i64, z: i64) -> i64 { 374 if TM_NV >= tm_vcap() { TM_OVF = 1; return tm_vcap()-1 } // capacity guard: never write OOB 375 let i: i64 = TM_NV 376 let px: *i64 = TM_VX as *i64; let py: *i64 = TM_VY as *i64; let pz: *i64 = TM_VZ as *i64 377 px[i] = x; py[i] = y; pz[i] = z 378 TM_NV = TM_NV + 1 379 return i 380} 381// add a vertex WITH an explicit per-vertex normal (for isosurface: SDF-gradient normals, winding-independent smooth shading) 382func tm_vert_n(x: i64, y: i64, z: i64, nx: i64, ny: i64, nz: i64) -> i64 { 383 if TM_NV >= tm_vcap() { TM_OVF = 1; return tm_vcap()-1 } // capacity guard 384 let i: i64 = TM_NV 385 let px: *i64 = TM_VX as *i64; let py: *i64 = TM_VY as *i64; let pz: *i64 = TM_VZ as *i64 386 let qx: *i64 = TM_VNX as *i64; let qy: *i64 = TM_VNY as *i64; let qz: *i64 = TM_VNZ as *i64 387 px[i]=x; py[i]=y; pz[i]=z; qx[i]=nx; qy[i]=ny; qz[i]=nz 388 TM_NV = TM_NV + 1 389 return i 390} 391func tm_tri(a: i64, b: i64, c: i64, col: i64) -> i64 { 392 if TM_NT >= tm_tcap() { TM_OVF = 1; return tm_tcap()-1 } // capacity guard 393 let i: i64 = TM_NT 394 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 395 pa[i] = a; pb[i] = b; pc[i] = c; pcol[i] = col 396 TM_NT = TM_NT + 1 397 return i 398} 399func 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 } 400func tm_nt() -> i64 { return TM_NT } 401func tm_nv() -> i64 { return TM_NV } 402 403// ---- primitive mesh builders (append at center cx,cy,cz, half-size s, colour col) ---- 404func tm_cube(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 { 405 let b: i64 = TM_NV 406 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- 407 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+ 408 tm_quad(b+4,b+5,b+6,b+7,col) // front 409 tm_quad(b+1,b+0,b+3,b+2,col) // back 410 tm_quad(b+0,b+4,b+7,b+3,col) // left 411 tm_quad(b+5,b+1,b+2,b+6,col) // right 412 tm_quad(b+3,b+7,b+6,b+2,col) // top 413 tm_quad(b+0,b+1,b+5,b+4,col) // bottom 414 return 0 415} 416func tm_tetra(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 { 417 let b: i64 = TM_NV 418 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 419 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) 420 return 0 421} 422func tm_octa(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 { 423 let b: i64 = TM_NV 424 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 425 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 426 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 427 return 0 428} 429func tm_pyramid(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 { 430 let b: i64 = TM_NV 431 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 432 tm_vert(cx,cy+s,cz) // apex 433 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 434 tm_quad(b+0,b+3,b+2,b+1,col) // base 435 return 0 436} 437func tm_prism(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 { // triangular prism (extruded triangle along z) 438 let b: i64 = TM_NV 439 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 440 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 441 tm_tri(b+3,b+4,b+5,col); tm_tri(b+1,b+0,b+2,col) // caps 442 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 443 return 0 444} 445// icosahedron (12 verts via golden ratio phi~1.618, integer *1000). 20 triangular faces -- the iconic polyhedron. 446func tm_icosa(cx: i64, cy: i64, cz: i64, s: i64, col: i64) -> i64 { 447 let b: i64 = TM_NV 448 let p: i64 = s*TM_MAGIC_1618/1000 449 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) 450 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) 451 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) 452 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) 453 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) 454 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) 455 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) 456 return 0 457} 458// triangulated UV-sphere (rings x segs) -- shows even a ROUND shape is now real triangles, not an SDF blob. 459func tm_uvsphere(cx: i64, cy: i64, cz: i64, s: i64, rings: i64, segs: i64, col: i64) -> i64 { 460 let b: i64 = TM_NV 461 var r: i64 = 0 462 while r <= rings { 463 let theta: i64 = IT_PI * r / rings // 0..PI (lat) 464 let st: i64 = it_sin4096(theta); let ct: i64 = it_cos4096(theta) 465 var sg: i64 = 0 466 while sg <= segs { 467 let phi: i64 = 2*IT_PI * sg / segs // 0..2PI (lon) 468 let sp: i64 = it_sin4096(phi); let cp: i64 = it_cos4096(phi) 469 let x: i64 = cx + s*st/TM_MAGIC_4096*cp/TM_MAGIC_4096 470 let y: i64 = cy + s*ct/TM_MAGIC_4096 471 let z: i64 = cz + s*st/TM_MAGIC_4096*sp/TM_MAGIC_4096 472 tm_vert(x,y,z) 473 sg = sg + 1 474 } 475 r = r + 1 476 } 477 let w: i64 = segs+1 478 r = 0 479 while r < rings { 480 var sg: i64 = 0 481 while sg < segs { 482 let v0: i64 = b + r*w + sg 483 let v1: i64 = b + r*w + sg+1 484 let v2: i64 = b + (r+1)*w + sg 485 let v3: i64 = b + (r+1)*w + sg+1 486 tm_quad(v0,v1,v3,v2,col) 487 sg = sg + 1 488 } 489 r = r + 1 490 } 491 return 0 492} 493// ★compose a RECOGNIZABLE thing from primitives: a low-poly humanoid (boxes) -- the shape kernel applied to a figure, 494// the angular counterpart to the ellipsoid being. Proves we can build real things from triangles. 495func tm_figure(cx: i64, cy: i64, cz: i64, col: i64) -> i64 { 496 let skin: i64 = 214 + 170*256 + 150*TM_MAGIC_65536 497 tm_cube(cx, cy+300, cz, 78, skin) // head 498 tm_cube(cx, cy+120, cz, 96, col) // torso (taller via two stacked -> keep one for low-poly) 499 tm_cube(cx, cy-10, cz, 88, col) // lower torso 500 tm_cube(cx-150, cy+150, cz, 42, skin) // L upper arm 501 tm_cube(cx+150, cy+150, cz, 42, skin) // R upper arm 502 tm_cube(cx-150, cy-30, cz, 40, skin) // L forearm 503 tm_cube(cx+150, cy-30, cz, 40, skin) // R forearm 504 tm_cube(cx-56, cy-230, cz, 52, col) // L thigh 505 tm_cube(cx+56, cy-230, cz, 52, col) // R thigh 506 tm_cube(cx-56, cy-410, cz, 48, col) // L shin 507 tm_cube(cx+56, cy-410, cz, 48, col) // R shin 508 return 0 509} 510 511// ---- the rasterizer: transform -> project -> barycentric fill + Z-BUFFER + flat per-face shading ---- 512// fb, zbuf sized W*H. yaw/pitch it4096. camz shifts model into +Z. Background left as-is by caller. 513func trimesh_render(fb: *i64, zbuf: *i64, W: i64, H: i64, yaw: i64, pitch: i64, camz: i64, focal: i64, smooth: i64) -> i64 { 514 let cyw: i64 = it_cos4096(yaw); let syw: i64 = it_sin4096(yaw) 515 let cpt: i64 = it_cos4096(pitch); let spt: i64 = it_sin4096(pitch) 516 // light dir (view space), upper-left-front 517 let lx: i64 = 0-TM_MAGIC_1300; let ly: i64 = TM_MAGIC_2400; let lz: i64 = 0-TM_MAGIC_3000 518 let llen: i64 = tm_isqrt(lx*lx+ly*ly+lz*lz) + 1 519 // per-vertex projected screen + view-space 520 let SX: *i64 = sys_mmap(TM_NV*8) as *i64 521 let SY: *i64 = sys_mmap(TM_NV*8) as *i64 522 let VX: *i64 = sys_mmap(TM_NV*8) as *i64 523 let VY: *i64 = sys_mmap(TM_NV*8) as *i64 524 let VZ: *i64 = sys_mmap(TM_NV*8) as *i64 525 let mvx: *i64 = TM_VX as *i64; let mvy: *i64 = TM_VY as *i64; let mvz: *i64 = TM_VZ as *i64 526 var i: i64 = 0 527 while i < TM_NV { 528 let x: i64 = mvx[i]; let y: i64 = mvy[i]; let z: i64 = mvz[i] 529 let x1: i64 = (x*cyw + z*syw)/TM_MAGIC_4096 530 let z1: i64 = (0-x*syw + z*cyw)/TM_MAGIC_4096 531 let y2: i64 = (y*cpt - z1*spt)/TM_MAGIC_4096 532 let z2: i64 = (y*spt + z1*cpt)/TM_MAGIC_4096 533 let vz: i64 = z2 + camz 534 VX[i] = x1; VY[i] = y2; VZ[i] = vz 535 if vz > 20 { SX[i] = W/2 + focal*x1/vz; SY[i] = H/2 - focal*y2/vz } 536 else { SX[i] = 0-TM_MAGIC_100000; SY[i] = 0-TM_MAGIC_100000 } 537 i = i + 1 538 } 539 // ★F3 GOURAUD: per-vertex Lambert (rotate model normal into view space, dot with light). fx1024, 0..1024. 540 // ★F3 BLINN-PHONG: per-vertex specular = (N.H)^16 * TM_SPEC, H = halfway(light, view); view dir toward camera = -z. 541 let LAMV: *i64 = sys_mmap(TM_NV*8) as *i64 542 let SPECV: *i64 = sys_mmap(TM_NV*8) as *i64 543 // view-space per-vertex normals, kept so the fragment stage can interpolate a REAL normal for 544 // normal mapping. Allocated once per render, never inside the pixel loop. 545 let NVXV: *i64 = sys_mmap(TM_NV*8) as *i64 546 let NVYV: *i64 = sys_mmap(TM_NV*8) as *i64 547 let NVZV: *i64 = sys_mmap(TM_NV*8) as *i64 548 let PN3: *i64 = sys_mmap(3*8) as *i64 549 let hx: i64 = lx*TM_MAGIC_1024/llen 550 let hy: i64 = ly*TM_MAGIC_1024/llen 551 let hz: i64 = lz*TM_MAGIC_1024/llen - TM_MAGIC_1024 // + Vhat=(0,0,-1) 552 let hlen: i64 = tm_isqrt(hx*hx + hy*hy + hz*hz) + 1 553 if smooth >= 1 { // needed for BOTH smooth==1 (Gouraud) and smooth==2 (per-vertex colour) 554 let mnx: *i64 = TM_VNX as *i64; let mny: *i64 = TM_VNY as *i64; let mnz: *i64 = TM_VNZ as *i64 555 var q: i64 = 0 556 while q < TM_NV { 557 let mx: i64 = mnx[q]; let my: i64 = mny[q]; let mz: i64 = mnz[q] 558 let nx1: i64 = (mx*cyw + mz*syw)/TM_MAGIC_4096 559 let nz1: i64 = (0-mx*syw + mz*cyw)/TM_MAGIC_4096 560 let nvy: i64 = (my*cpt - nz1*spt)/TM_MAGIC_4096 561 let nvz: i64 = (my*spt + nz1*cpt)/TM_MAGIC_4096 562 let nvx: i64 = nx1 563 var lam: i64 = (nvx*lx + nvy*ly + nvz*lz) / (4*llen) // |n|=TM_MAGIC_4096 -> /(TM_MAGIC_4096*llen/TM_MAGIC_1024)=/(4*llen) 564 if lam < 0 { lam = 0 } 565 if lam > TM_MAGIC_1024 { lam = TM_MAGIC_1024 } 566 NVXV[q] = nvx 567 NVYV[q] = nvy 568 NVZV[q] = nvz 569 LAMV[q] = lam 570 var sv: i64 = 0 571 if TM_SPEC > 0 { 572 var sc: i64 = (nvx*hx + nvy*hy + nvz*hz) / (4*hlen) // N.H, fx1024 (|N|=TM_MAGIC_4096) 573 if sc < 0 { sc = 0 } 574 if sc > TM_MAGIC_1024 { sc = TM_MAGIC_1024 } 575 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 576 sv = TM_SPEC*c16/TM_MAGIC_1024 577 } 578 SPECV[q] = sv 579 q = q + 1 580 } 581 } 582 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 583 let VCOL: *i64 = TM_VCOL as *i64 584 var t: i64 = 0 585 while t < TM_NT { 586 let i0: i64 = TA[t]; let i1: i64 = TB[t]; let i2: i64 = TC[t] 587 let ax: i64 = SX[i0]; let ay: i64 = SY[i0] 588 let bx: i64 = SX[i1]; let by: i64 = SY[i1] 589 let cx: i64 = SX[i2]; let cy: i64 = SY[i2] 590 var skip: i64 = 0 591 if ax < 0-TM_MAGIC_90000 { skip = 1 } if bx < 0-TM_MAGIC_90000 { skip = 1 } if cx < 0-TM_MAGIC_90000 { skip = 1 } 592 let area: i64 = (bx-ax)*(cy-ay) - (by-ay)*(cx-ax) 593 if area == 0 { skip = 1 } 594 if skip == 0 { 595 // face normal (view space), oriented toward the camera (-Z) 596 let ux: i64 = VX[i1]-VX[i0]; let uy: i64 = VY[i1]-VY[i0]; let uz: i64 = VZ[i1]-VZ[i0] 597 let wx: i64 = VX[i2]-VX[i0]; let wy: i64 = VY[i2]-VY[i0]; let wz: i64 = VZ[i2]-VZ[i0] 598 var nx: i64 = uy*wz - uz*wy; var ny: i64 = uz*wx - ux*wz; var nz: i64 = ux*wy - uy*wx 599 if nz > 0 { nx = 0-nx; ny = 0-ny; nz = 0-nz } 600 let nl: i64 = tm_isqrt(nx*nx+ny*ny+nz*nz) + 1 601 var lam: i64 = (nx*lx + ny*ly + nz*lz) / (nl*llen/TM_MAGIC_1024) 602 if lam < 0 { lam = 0 } 603 let col: i64 = TCOL[t] 604 let br: i64 = col&255; let bg: i64 = (col>>8)&255; let bb: i64 = (col>>16)&255 605 let sh: i64 = 42 + 86*lam/TM_MAGIC_1024 606 var cr: i64 = br*sh/128; var cg: i64 = bg*sh/128; var cb: i64 = bb*sh/128 607 if cr>255 {cr=255} if cg>255 {cg=255} if cb>255 {cb=255} 608 let fcol: i64 = cr + cg*256 + cb*TM_MAGIC_65536 609 // per-vertex colours (smooth==2 multi-material meshes) 610 let k0: i64 = VCOL[i0]; let k1: i64 = VCOL[i1]; let k2: i64 = VCOL[i2] 611 let k0r: i64 = k0&255; let k0g: i64 = (k0>>8)&255; let k0b: i64 = (k0>>16)&255 612 let k1r: i64 = k1&255; let k1g: i64 = (k1>>8)&255; let k1b: i64 = (k1>>16)&255 613 let k2r: i64 = k2&255; let k2g: i64 = (k2>>8)&255; let k2b: i64 = (k2>>16)&255 614 // per-vertex UVs (Q16) for image-texture sampling 615 let TVU: *i64 = TM_VU as *i64 616 let TVV: *i64 = TM_VV as *i64 617 let u0: i64 = TVU[i0]; let u1: i64 = TVU[i1]; let u2: i64 = TVU[i2] 618 let w0: i64 = TVV[i0]; let w1: i64 = TVV[i1]; let w2: i64 = TVV[i2] 619 // bbox (clamped) 620 var minx: i64 = tm_min3(ax,bx,cx); var maxx: i64 = tm_max3(ax,bx,cx) 621 var miny: i64 = tm_min3(ay,by,cy); var maxy: i64 = tm_max3(ay,by,cy) 622 if minx < 0 { minx = 0 } if miny < 0 { miny = 0 } 623 if maxx > W-1 { maxx = W-1 } if maxy > H-1 { maxy = H-1 } 624 var py: i64 = miny 625 while py <= maxy { 626 var px: i64 = minx 627 while px <= maxx { 628 let e0: i64 = (cx-bx)*(py-by) - (cy-by)*(px-bx) // weight of vertex i0 629 let e1: i64 = (ax-cx)*(py-cy) - (ay-cy)*(px-cx) // weight of i1 630 let e2: i64 = (bx-ax)*(py-ay) - (by-ay)*(px-ax) // weight of i2 631 var inside: i64 = 0 632 if area > 0 { if e0>=0 { if e1>=0 { if e2>=0 { inside=1 } } } } 633 else { if e0<=0 { if e1<=0 { if e2<=0 { inside=1 } } } } 634 if inside == 1 { 635 let zz: i64 = (e0*VZ[i0] + e1*VZ[i1] + e2*VZ[i2]) / area // interpolated view depth 636 let idx: i64 = py*W + px 637 if zz < zbuf[idx] { 638 zbuf[idx] = zz 639 if smooth == 1 { 640 var l2: i64 = (e0*LAMV[i0] + e1*LAMV[i1] + e2*LAMV[i2]) / area // interpolated (Gouraud) 641 if l2 < 0 { l2 = 0 } 642 var sp2: i64 = (e0*SPECV[i0] + e1*SPECV[i1] + e2*SPECV[i2]) / area // specular highlight 643 if sp2 < 0 { sp2 = 0 } 644 if TM_SHON == 1 { // ★Q2 sun shadow 645 let wx2: i64 = (e0*mvx[i0] + e1*mvx[i1] + e2*mvx[i2]) / area 646 let wy2: i64 = (e0*mvy[i0] + e1*mvy[i1] + e2*mvy[i2]) / area 647 let wz2: i64 = (e0*mvz[i0] + e1*mvz[i1] + e2*mvz[i2]) / area 648 let sf2: i64 = tm_shadow_at(wx2, wy2, wz2) 649 l2 = l2*sf2/TM_MAGIC_1024 650 sp2 = sp2*sf2/TM_MAGIC_1024 651 } 652 let s2: i64 = 42 + 86*l2/TM_MAGIC_1024 653 var r2: i64 = br*s2/128 + sp2; var g2: i64 = bg*s2/128 + sp2; var b2: i64 = bb*s2/128 + sp2 654 if r2>255 {r2=255} if g2>255 {g2=255} if b2>255 {b2=255} 655 fb[idx] = r2 + g2*256 + b2*TM_MAGIC_65536 656 } else { if smooth == 2 { 657 var l3: i64 = (e0*LAMV[i0] + e1*LAMV[i1] + e2*LAMV[i2]) / area // Gouraud shade 658 if l3 < 0 { l3 = 0 } 659 var sp3: i64 = (e0*SPECV[i0] + e1*SPECV[i1] + e2*SPECV[i2]) / area 660 if sp3 < 0 { sp3 = 0 } 661 var mr: i64 = (e0*k0r + e1*k1r + e2*k2r) / area // interpolated per-vertex colour 662 var mg: i64 = (e0*k0g + e1*k1g + e2*k2g) / area 663 var mb: i64 = (e0*k0b + e1*k1b + e2*k2b) / area 664 if TM_IMG != 0 { // ★IMAGE texture via interpolated UV (REPEAT wrap) 665 var iu: i64 = ((e0*u0 + e1*u1 + e2*u2) / area) & TM_MAGIC_65535 666 var iv: i64 = ((e0*w0 + e1*w1 + e2*w2) / area) & TM_MAGIC_65535 667 let tx: i64 = iu*TM_IMGW/TM_MAGIC_65536 668 let ty: i64 = iv*TM_IMGH/TM_MAGIC_65536 669 let timg: *u8 = TM_IMG as *u8 670 let toff: i64 = (ty*TM_IMGW + tx)*3 671 mr = timg[toff] as i64 672 mg = timg[toff+1] as i64 673 mb = timg[toff+2] as i64 674 } 675 if TM_NRM != 0 { // ★R4 NORMAL MAP: real per-fragment normal 676 let ju: i64 = ((e0*u0 + e1*u1 + e2*u2) / area) & TM_MAGIC_65535 677 let jv: i64 = ((e0*w0 + e1*w1 + e2*w2) / area) & TM_MAGIC_65535 678 let nsx: i64 = ju*TM_NRMW/TM_MAGIC_65536 679 let nsy: i64 = jv*TM_NRMH/TM_MAGIC_65536 680 let nimg: *u8 = TM_NRM as *u8 681 let noff: i64 = (nsy*TM_NRMW + nsx)*3 682 let etx: i64 = ((nimg[noff] as i64) - TM_NMID)*TM_MAGIC_4096/TM_NSCL 683 let ety: i64 = ((nimg[noff+1] as i64) - TM_NMID)*TM_MAGIC_4096/TM_NSCL 684 let etz: i64 = ((nimg[noff+2] as i64) - TM_NMID)*TM_MAGIC_4096/TM_NSCL 685 let fnx: i64 = (e0*NVXV[i0] + e1*NVXV[i1] + e2*NVXV[i2]) / area 686 let fny: i64 = (e0*NVYV[i0] + e1*NVYV[i1] + e2*NVYV[i2]) / area 687 let fnz: i64 = (e0*NVZV[i0] + e1*NVZV[i1] + e2*NVZV[i2]) / area 688 tm_perturb_normal(fnx, fny, fnz, etx, ety, etz, PN3) 689 var lm: i64 = (PN3[0]*lx + PN3[1]*ly + PN3[2]*lz) / (4*llen) 690 if lm < 0 { lm = 0 } 691 if lm > TM_MAGIC_1024 { lm = TM_MAGIC_1024 } 692 l3 = lm 693 } 694 var needw: i64 = 0 695 if TM_TEX > 0 { needw = 1 } 696 if TM_SHON == 1 { needw = 1 } 697 if needw == 1 { 698 let wx: i64 = (e0*mvx[i0] + e1*mvx[i1] + e2*mvx[i2]) / area 699 let wy: i64 = (e0*mvy[i0] + e1*mvy[i1] + e2*mvy[i2]) / area 700 let wz: i64 = (e0*mvz[i0] + e1*mvz[i1] + e2*mvz[i2]) / area 701 if TM_TEX > 0 { 702 // ★Q1 MULTI-OCTAVE ALBEDO (not one grey mottle): 3 octaves of brightness 703 // + a decorrelated HUE octave (warm/cool tonal variation) -- the natural 704 // self-similar detail the photoreal judge (ns_assess MSCN/scale) measures. 705 let n1: i64 = tm_noise3(wx, wy, wz, TM_TEX as i64) 706 let n2: i64 = tm_noise3(wx, wy+TM_MAGIC_37777, wz, TM_TEX/3+1) 707 let n3: i64 = tm_noise3(wx+TM_MAGIC_91131, wy, wz, TM_TEX/9+1) 708 let tf: i64 = 620 + (n1*3 + n2*2 + n3)*470/TM_MAGIC_6144 // 0.60..1.06 709 let hue: i64 = tm_noise3(wx, wy+TM_MAGIC_81111, wz, TM_TEX*2) - 512 // warm/cool +- 710 mr = mr*tf/TM_MAGIC_1024 + hue*22/512 711 mg = mg*tf/TM_MAGIC_1024 + hue*8/512 712 mb = mb*tf/TM_MAGIC_1024 - hue*20/512 713 if mr < 0 { mr = 0 } 714 if mg < 0 { mg = 0 } 715 if mb < 0 { mb = 0 } 716 } 717 if TM_SHON == 1 { // ★Q2 sun shadow 718 let sf: i64 = tm_shadow_at(wx, wy, wz) 719 l3 = l3*sf/TM_MAGIC_1024 720 sp3 = sp3*sf/TM_MAGIC_1024 721 } 722 } 723 let s3: i64 = 42 + 86*l3/TM_MAGIC_1024 724 var r3: i64 = mr*s3/128 + sp3; var g3: i64 = mg*s3/128 + sp3; var b3: i64 = mb*s3/128 + sp3 725 if r3>255 {r3=255} if g3>255 {g3=255} if b3>255 {b3=255} 726 fb[idx] = r3 + g3*256 + b3*TM_MAGIC_65536 727 } else { if smooth == 3 { 728 // UNLIT per-vertex colour (radiosity display: the vertex colour IS the lighting) 729 var pr: i64 = (e0*k0r + e1*k1r + e2*k2r) / area 730 var pg: i64 = (e0*k0g + e1*k1g + e2*k2g) / area 731 var pb: i64 = (e0*k0b + e1*k1b + e2*k2b) / area 732 if pr<0 {pr=0} if pg<0 {pg=0} if pb<0 {pb=0} 733 if pr>255 {pr=255} if pg>255 {pg=255} if pb>255 {pb=255} 734 fb[idx] = pr + pg*256 + pb*TM_MAGIC_65536 735 } else { fb[idx] = fcol } } } 736 } 737 } 738 px = px + 1 739 } 740 py = py + 1 741 } 742 } 743 t = t + 1 744 } 745 return 0 746} 747// clear a z-buffer to +far 748func trimesh_zclear(zbuf: *i64, n: i64) -> i64 { var i: i64=0; while i<n { zbuf[i] = TM_MAGIC_2000000000; i=i+1 } return 0 } 749// ★F3 ANTI-ALIASING (SSAA): render at 2x resolution, box-downsample into fb -> smooth edges. fb must be pre-cleared to bg. 750func trimesh_render_aa(fb: *i64, W: i64, H: i64, yaw: i64, pitch: i64, camz: i64, focal: i64, smooth: i64) -> i64 { 751 let W2: i64 = W*2; let H2: i64 = H*2 752 let big: *i64 = sys_mmap(W2*H2*8) as *i64 753 let bz: *i64 = sys_mmap(W2*H2*8) as *i64 754 let bg: i64 = fb[0] 755 var i: i64 = 0 756 while i < W2*H2 { big[i] = bg; i = i+1 } 757 trimesh_zclear(bz, W2*H2) 758 trimesh_render(big, bz, W2, H2, yaw, pitch, camz, focal*2, smooth) // focal*2 keeps the 2x framing identical 759 var y: i64 = 0 760 while y < H { 761 var x: i64 = 0 762 while x < W { 763 let p0: i64 = big[(2*y)*W2 + 2*x]; let p1: i64 = big[(2*y)*W2 + 2*x+1] 764 let p2: i64 = big[(2*y+1)*W2 + 2*x]; let p3: i64 = big[(2*y+1)*W2 + 2*x+1] 765 let r: i64 = ((p0&255)+(p1&255)+(p2&255)+(p3&255))/4 766 let g: i64 = (((p0>>8)&255)+((p1>>8)&255)+((p2>>8)&255)+((p3>>8)&255))/4 767 let b: i64 = (((p0>>16)&255)+((p1>>16)&255)+((p2>>16)&255)+((p3>>16)&255))/4 768 fb[y*W + x] = r + g*256 + b*TM_MAGIC_65536 769 x = x + 1 770 } 771 y = y + 1 772 } 773 return 0 774}