code wiki / (root) / nx_meshrender.nx

nx_meshrender.nx source

↩ module page · 198 lines · 10362 B

1// nx_meshrender.nx -- LIB: SOVEREIGN GENERAL TRIANGLE-MESH RENDERER (the keystone rung above nx_render_core). 2// Transforms + projects + z-buffer-rasterizes an ARBITRARY loaded triangle mesh (CAD STL / game asset) through 3// OUR OWN pipeline -- no OpenGL, no WebGL, no 3rd-party graphics. render_core gives the z-buffered triangle 4// primitive + Q14 perspective/mat4/sovereign-trig; this composes them over a vertex+index buffer so any mesh 5// renders, z-correct, in software (rule 15/22: compose, re-implement nothing). HAL-FREE (no syscalls, all 6// buffers caller-supplied) so the IDENTICAL code compiles to --target wat/wasm -> the mesh renders in ANY 7// browser via OUR sovereign WASM (the "emit, don't use 3rd party" path that nx_wasmcube/nx_pets3d_wasm proved). 8// license_tier: ORIGINAL 9// 10// Vertex layout (stride 4 i64): [x_q14, y_q14, z_q14, packed_rgba]. Index layout (stride 3 i64): [i0,i1,i2]. 11// Caller supplies scratch: proj/roty/mv/trans/mvp (16 i64 each), vbuf/clipbuf (4 i64), scr (nverts*4 i64: 12// [screen_x, screen_y, depth_q14, visible]), tribuf (12 i64). fb/zb = w*h i64. Returns triangles drawn. 13import "nx_render_core.nx" 14const MR_MAGIC_65536: i64 = 65536 15const MR_MAGIC_16777216: i64 = 16777216 16 17const MR_NEAR: i64 = 16384 // 1.0 in Q14 (near plane) 18const MR_FAR: i64 = 16384000 // 1000.0 in Q14 (far plane) 19 20// Y-rotation matrix (Q14, row-major) into out[16]. Uses render_core's sovereign fixed-point trig (no JS/table). 21func mr_roty(deg: i64, out: *i64) -> i64 { 22 let c: i64 = rc_cos_q14(deg) 23 let s: i64 = rc_sin_q14(deg) 24 var i: i64 = 0 25 while i < 16 { out[i] = 0; i = i + 1 } 26 out[0] = c 27 out[2] = s 28 out[5] = RC_Q 29 out[8] = 0 - s 30 out[10] = c 31 out[15] = RC_Q 32 return 0 33} 34 35// Project one model vertex through mvp -> screen (col,row), depth (Q14, smaller=closer), visibility. 36// Writes scr[base+0..3] = [screen_x, screen_y, depth, visible(1/0)]. vbuf/clipbuf are 4-i64 scratch. 37func mr_project(mvp: *i64, vx: i64, vy: i64, vz: i64, w: i64, h: i64, vbuf: *i64, clipbuf: *i64, scr: *i64, base: i64) -> i64 { 38 vbuf[0] = vx 39 vbuf[1] = vy 40 vbuf[2] = vz 41 vbuf[3] = RC_Q 42 rc_mat4_vec4(mvp, vbuf, clipbuf) 43 let cw: i64 = clipbuf[3] 44 if cw <= 0 { scr[base + 3] = 0; return 0 } // behind camera / at-or-past the near plane -> not visible 45 let ndcx: i64 = clipbuf[0] * RC_Q / cw 46 let ndcy: i64 = clipbuf[1] * RC_Q / cw 47 let ndcz: i64 = clipbuf[2] * RC_Q / cw 48 scr[base + 0] = (ndcx + RC_Q) * w / (2 * RC_Q) // NDC [-1,1] -> [0,w] 49 scr[base + 1] = (RC_Q - ndcy) * h / (2 * RC_Q) // NDC y up -> screen row down 50 scr[base + 2] = ndcz + RC_Q // [-1,1] -> [0,2Q]; smaller=closer; zclear far >> this 51 scr[base + 3] = 1 52 return 0 53} 54 55// ===== CAP-PBR rung-1: Lambert diffuse directional shading (per-vertex UNIT normals, Q14) ===== 56// dot of two Q14 vectors / RC_Q -> for UNIT vectors this equals cos(angle) in Q14. 57func mr_dot3(ax: i64, ay: i64, az: i64, bx: i64, by: i64, bz: i64) -> i64 { return (ax * bx + ay * by + az * bz) / RC_Q } 58// Lambert term: max(0, dot(N,L)) for unit N,L -> [0,RC_Q]. Back-faces (dot<0) go dark = no negative light. 59func mr_lambert(nx: i64, ny: i64, nz: i64, lx: i64, ly: i64, lz: i64) -> i64 { let d: i64 = mr_dot3(nx, ny, nz, lx, ly, lz); if d < 0 { return 0 } return d } 60// rotate a normal by a Y-rotation (cos c, sin s in Q14) into out[0..2] -> the WORLD normal as the object orbits. 61func mr_rotn(c: i64, s: i64, nx: i64, ny: i64, nz: i64, out: *i64) -> i64 { out[0] = (c * nx + s * nz) / RC_Q; out[1] = ny; out[2] = ((0 - s) * nx + c * nz) / RC_Q; return 0 } 62// shade a packed rgba by brightness b in [0,RC_Q] with an ambient floor amb in [0,RC_Q]; alpha kept 255. 63func mr_shade(rgba: i64, b: i64, amb: i64) -> i64 { 64 var bb: i64 = b 65 if bb < amb { bb = amb } 66 let r: i64 = (rgba % 256) * bb / RC_Q 67 let g: i64 = ((rgba / 256) % 256) * bb / RC_Q 68 let bl: i64 = ((rgba / MR_MAGIC_65536) % 256) * bb / RC_Q 69 return r + g * 256 + bl * MR_MAGIC_65536 + 255 * MR_MAGIC_16777216 70} 71 72// ===== CAP-SKELETAL-ANIM rung-1: linear blend skinning (2-bone core) ===== 73// skinned = w0*(M0*rest) + w1*(M1*rest), all Q14 (weights sum to RC_Q). rest=(x,y,z,RC_Q) homogeneous; M0/M1 = Q14 74// mat4 bone transforms (render_core builders: rc_identity/rc_translation/mr_roty); tmp[4] scratch; out[4] = the 75// skinned homogeneous position. Reuses rc_mat4_vec4 (rule 15). N-bone = the same accumulate over more (M_i,w_i) 76// pairs -- 2 influences is the rung-1 core (most vertices bind to <=4 bones). 77func mr_skin2(rest: *i64, m0: *i64, w0: i64, m1: *i64, w1: i64, tmp: *i64, out: *i64) -> i64 { 78 rc_mat4_vec4(m0, rest, tmp) 79 out[0] = w0 * tmp[0] / RC_Q 80 out[1] = w0 * tmp[1] / RC_Q 81 out[2] = w0 * tmp[2] / RC_Q 82 out[3] = w0 * tmp[3] / RC_Q 83 rc_mat4_vec4(m1, rest, tmp) 84 out[0] = out[0] + w1 * tmp[0] / RC_Q 85 out[1] = out[1] + w1 * tmp[1] / RC_Q 86 out[2] = out[2] + w1 * tmp[2] / RC_Q 87 out[3] = out[3] + w1 * tmp[3] / RC_Q 88 return 0 89} 90 91// ===== skinned normals: lighting from the ACTUAL (deformed) geometry ===== 92// sovereign integer sqrt (Newton). returns floor(sqrt(n)) for n>=0. 93func mr_isqrt(n: i64) -> i64 { 94 if n <= 0 { return 0 } 95 var x: i64 = n 96 if n > 1 { x = n / 2 } 97 var i: i64 = 0 98 while i < 64 { 99 let xn: i64 = (x + n / x) / 2 100 if xn >= x { return x } 101 x = xn 102 i = i + 1 103 } 104 return x 105} 106// face normal from 3 (possibly SKINNED/deformed) positions -> Lambert brightness [0,RC_Q] with UNIT light L (Q14). 107// N = (p1-p0) x (p2-p0); brightness = max(0, dot(N,L)) / |N|. Lighting derives from the real geometry, so it is 108// CORRECT under skinning deformation (no pre-baked normal). Q14 in -> Q14 brightness out; mr_isqrt for |N|. 109func mr_face_lambert(p0x: i64, p0y: i64, p0z: i64, p1x: i64, p1y: i64, p1z: i64, p2x: i64, p2y: i64, p2z: i64, lx: i64, ly: i64, lz: i64) -> i64 { 110 let e1x: i64=p1x-p0x; let e1y: i64=p1y-p0y; let e1z: i64=p1z-p0z 111 let e2x: i64=p2x-p0x; let e2y: i64=p2y-p0y; let e2z: i64=p2z-p0z 112 let nx: i64=e1y*e2z - e1z*e2y 113 let ny: i64=e1z*e2x - e1x*e2z 114 let nz: i64=e1x*e2y - e1y*e2x 115 let dotnl: i64=nx*lx + ny*ly + nz*lz 116 if dotnl <= 0 { return 0 } 117 let mag2: i64=nx*nx + ny*ny + nz*nz 118 if mag2 <= 0 { return 0 } 119 let m: i64=mr_isqrt(mag2) 120 if m <= 0 { return 0 } 121 return dotnl / m 122} 123 124// ===== CAP-PBR rung-2: Blinn-Phong specular (the glossy highlight from the half-vector) ===== 125// integer power in Q14: base,result in [0,RC_Q]; pow(b,0)=RC_Q, pow(b,n)=b^n. Higher n = sharper/tighter highlight. 126func mr_pow_q14(base: i64, n: i64) -> i64 { 127 var r: i64 = RC_Q 128 var i: i64 = 0 129 while i < n { 130 r = r * base / RC_Q 131 i = i + 1 132 } 133 return r 134} 135// Blinn-Phong specular term [0,RC_Q] from the (skinned/deformed) face geometry: N=(p1-p0)x(p2-p0), 136// H=unit(L+V) (L=light dir, V=view dir, both unit Q14), spec=max(0,dot(unitN,H))^shininess. Normalises N and H to 137// unit Q14 FIRST (bounds the integer range), then the power sharpens the highlight. Like mr_face_lambert it derives 138// from the real geometry, so the highlight is CORRECT under deformation. Small (unit-ish) Q14 coords (no overflow). 139func mr_face_specular(p0x: i64, p0y: i64, p0z: i64, p1x: i64, p1y: i64, p1z: i64, p2x: i64, p2y: i64, p2z: i64, lx: i64, ly: i64, lz: i64, vx: i64, vy: i64, vz: i64, shin: i64) -> i64 { 140 let e1x: i64=p1x-p0x; let e1y: i64=p1y-p0y; let e1z: i64=p1z-p0z 141 let e2x: i64=p2x-p0x; let e2y: i64=p2y-p0y; let e2z: i64=p2z-p0z 142 let nx: i64=e1y*e2z - e1z*e2y 143 let ny: i64=e1z*e2x - e1x*e2z 144 let nz: i64=e1x*e2y - e1y*e2x 145 let nmag2: i64=nx*nx + ny*ny + nz*nz 146 if nmag2 <= 0 { return 0 } 147 let nmag: i64=mr_isqrt(nmag2) 148 if nmag <= 0 { return 0 } 149 let hx: i64=lx+vx; let hy: i64=ly+vy; let hz: i64=lz+vz 150 let hmag2: i64=hx*hx + hy*hy + hz*hz 151 if hmag2 <= 0 { return 0 } 152 let hmag: i64=mr_isqrt(hmag2) 153 if hmag <= 0 { return 0 } 154 let unx: i64=nx*RC_Q/nmag; let uny: i64=ny*RC_Q/nmag; let unz: i64=nz*RC_Q/nmag 155 let uhx: i64=hx*RC_Q/hmag; let uhy: i64=hy*RC_Q/hmag; let uhz: i64=hz*RC_Q/hmag 156 let ndoth: i64=(unx*uhx + uny*uhy + unz*uhz) / RC_Q 157 if ndoth <= 0 { return 0 } 158 var nd: i64=ndoth 159 if nd > RC_Q { nd = RC_Q } 160 return mr_pow_q14(nd, shin) 161} 162 163// Render the whole mesh. angle_deg orbits the model about Y; dist pushes it down -Z (Q14); fov_half_deg = half 164// the vertical field of view. Clears fb to bg + zb to far, builds MVP = Proj * (Translate * RotY), projects every 165// vertex, then z-buffer-rasterizes every triangle whose 3 vertices are visible. Returns triangles drawn. 166func mr_render(verts: *i64, nverts: i64, idx: *i64, ntris: i64, proj: *i64, roty: *i64, mv: *i64, trans: *i64, mvp: *i64, vbuf: *i64, clipbuf: *i64, scr: *i64, tribuf: *i64, fb: *i64, zb: *i64, w: i64, h: i64, angle_deg: i64, dist: i64, fov_half_deg: i64, bg: i64) -> i64 { 167 rc_clear(fb, w, h, bg) 168 rc_zclear(zb, w, h) 169 let cosh: i64 = rc_cos_q14(fov_half_deg) 170 let sinh: i64 = rc_sin_q14(fov_half_deg) 171 let aspect: i64 = w * RC_Q / h 172 rc_perspective_cs(cosh, sinh, aspect, MR_NEAR, MR_FAR, proj) 173 mr_roty(angle_deg, roty) 174 rc_translation_4x4(0, 0, 0 - dist, trans) 175 rc_mat4_mul(trans, roty, mv) // mv = Translate * RotY (model -> view) 176 rc_mat4_mul(proj, mv, mvp) // mvp = Proj * mv (model -> clip) 177 var vi: i64 = 0 178 while vi < nverts { 179 mr_project(mvp, verts[vi * 4 + 0], verts[vi * 4 + 1], verts[vi * 4 + 2], w, h, vbuf, clipbuf, scr, vi * 4) 180 vi = vi + 1 181 } 182 var ti: i64 = 0 183 var drawn: i64 = 0 184 while ti < ntris { 185 let a: i64 = idx[ti * 3 + 0] 186 let b: i64 = idx[ti * 3 + 1] 187 let c: i64 = idx[ti * 3 + 2] 188 if scr[a * 4 + 3] == 1 { if scr[b * 4 + 3] == 1 { if scr[c * 4 + 3] == 1 { 189 tribuf[0] = scr[a * 4 + 0]; tribuf[1] = scr[a * 4 + 1]; tribuf[2] = scr[a * 4 + 2]; tribuf[3] = verts[a * 4 + 3] 190 tribuf[4] = scr[b * 4 + 0]; tribuf[5] = scr[b * 4 + 1]; tribuf[6] = scr[b * 4 + 2]; tribuf[7] = verts[b * 4 + 3] 191 tribuf[8] = scr[c * 4 + 0]; tribuf[9] = scr[c * 4 + 1]; tribuf[10] = scr[c * 4 + 2]; tribuf[11] = verts[c * 4 + 3] 192 rc_triangle(fb, zb, w, h, tribuf) 193 drawn = drawn + 1 194 } } } 195 ti = ti + 1 196 } 197 return drawn 198}