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}