nx_meshview_wasm.nx source
↩ module page · 828 lines · 39091 B
1// nx_meshview_wasm.nx -- R3: the sovereign general-mesh renderer EMITTED to WASM (runs in ANY browser via OUR
2// software rasterizer -- zero WebGL, zero NVIDIA, zero Emscripten). Composes the HAL-free nx_render_core
3// (z-buffered Pineda raster + Q14 perspective/mat4/Bhaskara-trig) over a loaded vertex+index mesh held at fixed
4// linear-memory offsets (the proven nx_pets3d_wasm/nx_wasmcube wasm memory model: NO sys_mmap -- wasm has no
5// syscalls). The browser's ONLY job is to call mv_render(angle) and blit the framebuffer to a <canvas>; ALL
6// transform/projection/rasterization is sovereign Nishi. Mirrors the native nx_meshrender logic so the wasm
7// build is bit-faithful to native (proven by nx_meshview_wasm_gate via nx_wasm_vm). Compiles through the
8// SOVEREIGN chain: nx_compile_wat -> nx_wat_compiler -> .wasm. license_tier: ORIGINAL
9import "nx_render_core.nx"
10
11const MV_W: i64 = 64
12const MV_H: i64 = 64
13const MV_N: i64 = 4096 // 64*64
14const VQ: i64 = 16384 // Q14 one (== RC_Q)
15const DIST: i64 = 65536 // 4.0 Q14: push the model down -Z
16const FOVH: i64 = 30 // half field-of-view degrees
17const FARQ: i64 = 16384000 // far plane Q14
18const BG: i64 = 4280295456 // r32 g32 b32 a255
19const RED: i64 = 4278190335 // r255 g0 b0 a255
20const BLU: i64 = 4294901760 // r0 g0 b255 a255
21
22// fixed BYTE offsets into wasm linear memory (all 8-byte aligned; total < 67KB, inside the default memory).
23const O_FB: i64 = 0 // MV_N i64 framebuffer (-> 32768)
24const O_ZB: i64 = 32768 // MV_N i64 z-buffer (-> 65536)
25const O_PROJ: i64 = 65536 // 16 i64
26const O_ROTY: i64 = 65664
27const O_MV: i64 = 65792
28const O_TRANS: i64 = 65920
29const O_MVP: i64 = 66048
30const O_VB: i64 = 66176 // 4 i64 vertex scratch
31const O_CLIP: i64 = 66208 // 4 i64 clip scratch
32const O_SCR: i64 = 66240 // 6 verts * 4 i64 (-> 66432)
33const O_TRI: i64 = 66432 // 12 i64 tri scratch (-> 66528)
34const O_VERTS: i64 = 66528 // 6 verts * 4 i64 (-> 66720)
35const O_IDX: i64 = 66720 // 2 tris * 3 i64 (-> 66768)
36// --- lit cube (CAP-PBR visual) ---
37const O_CVERTS: i64 = 66768 // 8 cube verts * 3 i64 (-> 66960)
38const O_CTRIS: i64 = 66960 // 12 tris * 7 i64 [i0,i1,i2,nx,ny,nz,color] (-> 67632)
39const O_CSCR: i64 = 67632 // 8 verts * 4 i64 projected scratch (-> 67888)
40const C_HALF: i64 = 9830 // 0.6 * Q14 cube half-extent
41const C_BASE: i64 = 4292004040 // base material (200,200,210,255) -- shading reveals the form
42// --- skinned (bending) cube: CAP-SKELETAL-ANIM visual ---
43const O_SKIN: i64 = 67888 // 8 skinned verts * 4 i64 (-> 68144)
44const O_M0: i64 = 68144 // bone0 mat4 (-> 68272)
45const O_M1: i64 = 68272 // bone1 mat4 (-> 68400)
46const O_REST: i64 = 68400 // rest vec4 scratch (-> 68432)
47const O_STMP: i64 = 68432 // skin tmp vec4 scratch (-> 68464)
48// --- smooth (Gouraud) lit+specular SPHERE: CAP-PBR per-vertex-normal shading (kills the faceted look) ---
49const SP_RINGS: i64 = 8
50const SP_SECT: i64 = 12
51const SP_NV: i64 = 117 // (RINGS+1)*(SECT+1)
52const SP_NT: i64 = 192 // RINGS*SECT*2
53const SP_R: i64 = 9830 // radius 0.6*Q
54const SPHERE_BASE: i64 = 4293299280 // base material (80,140,230,255)
55const O_SPV: i64 = 68480 // sphere verts SP_NV*3 (-> 71288)
56const O_SPN: i64 = 71288 // sphere normals SP_NV*3 (-> 74096)
57const O_SPC: i64 = 74096 // sphere per-vertex colours SP_NV (-> 75032)
58const O_SPS: i64 = 75032 // sphere projected scr SP_NV*4 (-> 78776)
59const O_SPI: i64 = 78776 // sphere indices SP_NT*3 (-> 83384)
60const O_PBUF: i64 = 83392 // phong tri buffer 28 i64 (-> 83616)
61// --- generic loaded/torus mesh buffers (sized for <=256 verts / 512 tris) ---
62const O_MVV: i64 = 83616 // mesh verts 256*3 (-> 89760)
63const O_MVN: i64 = 89760 // mesh normals 256*3 (-> 95904)
64const O_MVC: i64 = 95904 // mesh colours 256 (-> 97952)
65const O_MVS: i64 = 97952 // mesh projected 256*4 (-> 106144)
66const O_MVI: i64 = 106144 // mesh indices 512*3 (-> 118432)
67const MESH_BASE: i64 = 4282158310 // mesh material (230,140,60,255) -- orange
68const TR_SECT: i64 = 12
69const TR_RING: i64 = 8
70const TR_NV: i64 = 96 // SECT*RING
71const TR_NT: i64 = 192 // SECT*RING*2
72const TR_MAJ: i64 = 7000 // major (centre) radius, Q14
73const TR_MIN: i64 = 3600 // tube radius, Q14 (chunky donut, still a clear hole)
74
75func mv_w() -> i64 { return MV_W }
76func mv_h() -> i64 { return MV_H }
77func mv_fb_off() -> i64 { return O_FB } // byte offset of the framebuffer (JS reads it stride-8)
78
79// bake the demo z-test scene (RED triangle nearer + BLUE triangle farther, same screen footprint) into memory.
80func mv_scene() -> i64 {
81 let v: *i64 = (O_VERTS as i64) as *i64
82 v[0]=0-13107; v[1]=0-13107; v[2]=8192; v[3]=RED
83 v[4]=13107; v[5]=0-13107; v[6]=8192; v[7]=RED
84 v[8]=0; v[9]=13107; v[10]=8192; v[11]=RED
85 v[12]=0-13107; v[13]=0-13107; v[14]=0-8192; v[15]=BLU
86 v[16]=13107; v[17]=0-13107; v[18]=0-8192; v[19]=BLU
87 v[20]=0; v[21]=13107; v[22]=0-8192; v[23]=BLU
88 let ix: *i64 = (O_IDX as i64) as *i64
89 ix[0]=0; ix[1]=1; ix[2]=2; ix[3]=3; ix[4]=4; ix[5]=5
90 return 0
91}
92
93// project one model vertex (Q14) through mvp -> scr[base..base+3] = [screen_x, screen_y, depth, visible].
94func mv_proj1(mvp: *i64, vx: i64, vy: i64, vz: i64, scr: *i64, base: i64) -> i64 {
95 let vb: *i64 = (O_VB as i64) as *i64
96 let cl: *i64 = (O_CLIP as i64) as *i64
97 vb[0]=vx; vb[1]=vy; vb[2]=vz; vb[3]=VQ
98 rc_mat4_vec4(mvp, vb, cl)
99 let cw: i64 = cl[3]
100 if cw <= 0 { scr[base+3]=0; return 0 }
101 scr[base+0] = (cl[0]*VQ/cw + VQ) * MV_W / (2*VQ)
102 scr[base+1] = (VQ - cl[1]*VQ/cw) * MV_H / (2*VQ)
103 scr[base+2] = cl[2]*VQ/cw + VQ
104 scr[base+3] = 1
105 return 0
106}
107
108// render the baked mesh at orbit angle (degrees). Returns triangles drawn. EXPORTED -> JS calls this per frame.
109func mv_render(angle: i64) -> i64 {
110 let fb: *i64 = (O_FB as i64) as *i64
111 let zb: *i64 = (O_ZB as i64) as *i64
112 rc_clear(fb, MV_W, MV_H, BG)
113 rc_zclear(zb, MV_W, MV_H)
114 mv_scene()
115 let proj: *i64 = (O_PROJ as i64) as *i64
116 let roty: *i64 = (O_ROTY as i64) as *i64
117 let mvm: *i64 = (O_MV as i64) as *i64
118 let trans: *i64 = (O_TRANS as i64) as *i64
119 let mvp: *i64 = (O_MVP as i64) as *i64
120 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
121 let cc: i64 = rc_cos_q14(angle)
122 let ss: i64 = rc_sin_q14(angle)
123 var z: i64 = 0
124 while z < 16 { roty[z]=0; z=z+1 }
125 roty[0]=cc; roty[2]=ss; roty[5]=VQ; roty[8]=0-ss; roty[10]=cc; roty[15]=VQ
126 rc_translation_4x4(0, 0, 0-DIST, trans)
127 rc_mat4_mul(trans, roty, mvm)
128 rc_mat4_mul(proj, mvm, mvp)
129 let verts: *i64 = (O_VERTS as i64) as *i64
130 let idx: *i64 = (O_IDX as i64) as *i64
131 let scr: *i64 = (O_SCR as i64) as *i64
132 var vi: i64 = 0
133 while vi < 6 { mv_proj1(mvp, verts[vi*4+0], verts[vi*4+1], verts[vi*4+2], scr, vi*4); vi=vi+1 }
134 let tri: *i64 = (O_TRI as i64) as *i64
135 var ti: i64 = 0
136 var drawn: i64 = 0
137 while ti < 2 {
138 let a: i64 = idx[ti*3+0]
139 let b: i64 = idx[ti*3+1]
140 let c: i64 = idx[ti*3+2]
141 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 {
142 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]; tri[3]=verts[a*4+3]
143 tri[4]=scr[b*4+0]; tri[5]=scr[b*4+1]; tri[6]=scr[b*4+2]; tri[7]=verts[b*4+3]
144 tri[8]=scr[c*4+0]; tri[9]=scr[c*4+1]; tri[10]=scr[c*4+2]; tri[11]=verts[c*4+3]
145 rc_triangle(fb, zb, MV_W, MV_H, tri)
146 drawn=drawn+1
147 } } }
148 ti=ti+1
149 }
150 return drawn
151}
152
153// EXPORTED accessors so the host (and the wasm gate) can read results without knowing the memory layout.
154func mv_pixel(i: i64) -> i64 { let fb: *i64 = (O_FB as i64) as *i64; return fb[i] }
155func mv_coverage() -> i64 { let fb: *i64 = (O_FB as i64) as *i64; var k: i64=0; var i: i64=0; while i<MV_N { if fb[i]!=BG {k=k+1} i=i+1 } return k }
156
157// ===== CAP-PBR: lit cube (the visual payoff) -- inlined Lambert shading, mirrors nx_meshrender's mr_* =====
158func mv_dot3(ax: i64, ay: i64, az: i64, bx: i64, by: i64, bz: i64) -> i64 { return (ax*bx + ay*by + az*bz) / VQ }
159func mv_lambert(nx: i64, ny: i64, nz: i64, lx: i64, ly: i64, lz: i64) -> i64 { let d: i64 = mv_dot3(nx,ny,nz,lx,ly,lz); if d < 0 { return 0 } return d }
160func mv_rotn(c: i64, s: i64, nx: i64, ny: i64, nz: i64, out: *i64) -> i64 { out[0] = (c*nx + s*nz)/VQ; out[1] = ny; out[2] = ((0-s)*nx + c*nz)/VQ; return 0 }
161func mv_shade(rgba: i64, b: i64, amb: i64) -> i64 { var bb: i64 = b; if bb < amb { bb = amb } let r: i64 = (rgba%256)*bb/VQ; let g: i64 = ((rgba/256)%256)*bb/VQ; let bl: i64 = ((rgba/65536)%256)*bb/VQ; return r + g*256 + bl*65536 + 255*16777216 }
162func mv_setv(p: *i64, k: i64, x: i64, y: i64, z: i64) -> i64 { p[k*3]=x; p[k*3+1]=y; p[k*3+2]=z; return 0 }
163func mv_sett(p: *i64, k: i64, a: i64, b: i64, c: i64, nx: i64, ny: i64, nz: i64, col: i64) -> i64 { p[k*7]=a; p[k*7+1]=b; p[k*7+2]=c; p[k*7+3]=nx; p[k*7+4]=ny; p[k*7+5]=nz; p[k*7+6]=col; return 0 }
164func mv_cube_scene() -> i64 {
165 let v: *i64 = (O_CVERTS as i64) as *i64
166 let H: i64 = C_HALF
167 mv_setv(v,0, 0-H,0-H,0-H); mv_setv(v,1, H,0-H,0-H); mv_setv(v,2, H,H,0-H); mv_setv(v,3, 0-H,H,0-H)
168 mv_setv(v,4, 0-H,0-H,H); mv_setv(v,5, H,0-H,H); mv_setv(v,6, H,H,H); mv_setv(v,7, 0-H,H,H)
169 let t: *i64 = (O_CTRIS as i64) as *i64
170 let Q: i64 = VQ
171 mv_sett(t,0, 0,1,2, 0,0,0-Q, C_BASE); mv_sett(t,1, 0,2,3, 0,0,0-Q, C_BASE)
172 mv_sett(t,2, 4,6,5, 0,0,Q, C_BASE); mv_sett(t,3, 4,7,6, 0,0,Q, C_BASE)
173 mv_sett(t,4, 0,3,7, 0-Q,0,0, C_BASE); mv_sett(t,5, 0,7,4, 0-Q,0,0, C_BASE)
174 mv_sett(t,6, 1,5,6, Q,0,0, C_BASE); mv_sett(t,7, 1,6,2, Q,0,0, C_BASE)
175 mv_sett(t,8, 0,4,5, 0,0-Q,0, C_BASE); mv_sett(t,9, 0,5,1, 0,0-Q,0, C_BASE)
176 mv_sett(t,10, 3,2,6, 0,Q,0, C_BASE); mv_sett(t,11, 3,6,7, 0,Q,0, C_BASE)
177 return 0
178}
179// render the lit cube at orbit angle (degrees). Per-face Lambert shading (light from above-front), z-buffered. EXPORTED.
180func mv_render_cube(angle: i64) -> i64 {
181 let fb: *i64 = (O_FB as i64) as *i64
182 let zb: *i64 = (O_ZB as i64) as *i64
183 rc_clear(fb, MV_W, MV_H, BG)
184 rc_zclear(zb, MV_W, MV_H)
185 mv_cube_scene()
186 let proj: *i64 = (O_PROJ as i64) as *i64
187 let roty: *i64 = (O_ROTY as i64) as *i64
188 let mvm: *i64 = (O_MV as i64) as *i64
189 let trans: *i64 = (O_TRANS as i64) as *i64
190 let mvp: *i64 = (O_MVP as i64) as *i64
191 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
192 let cc: i64 = rc_cos_q14(angle)
193 let ss: i64 = rc_sin_q14(angle)
194 var z: i64 = 0
195 while z < 16 { roty[z]=0; z=z+1 }
196 roty[0]=cc; roty[2]=ss; roty[5]=VQ; roty[8]=0-ss; roty[10]=cc; roty[15]=VQ
197 rc_translation_4x4(0, 0, 0-DIST, trans)
198 rc_mat4_mul(trans, roty, mvm)
199 rc_mat4_mul(proj, mvm, mvp)
200 let verts: *i64 = (O_CVERTS as i64) as *i64
201 let scr: *i64 = (O_CSCR as i64) as *i64
202 var vi: i64 = 0
203 while vi < 8 { mv_proj1(mvp, verts[vi*3+0], verts[vi*3+1], verts[vi*3+2], scr, vi*4); vi=vi+1 }
204 let tri: *i64 = (O_TRI as i64) as *i64
205 let nrm: *i64 = (O_VB as i64) as *i64 // vbuf free after projection -> reuse for the rotated normal
206 let tris: *i64 = (O_CTRIS as i64) as *i64
207 let LX: i64 = 9459
208 let LY: i64 = 9459
209 let LZ: i64 = 9459 // unit light from upper-right-FRONT (camera side ~(0.577,0.577,0.577)) -> lights visible faces
210 var ti: i64 = 0
211 var drawn: i64 = 0
212 while ti < 12 {
213 let a: i64 = tris[ti*7+0]
214 let b: i64 = tris[ti*7+1]
215 let c: i64 = tris[ti*7+2]
216 var vis: i64 = 0
217 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
218 if vis==1 {
219 mv_rotn(cc, ss, tris[ti*7+3], tris[ti*7+4], tris[ti*7+5], nrm)
220 if nrm[2] > 0 { // BACK-FACE CULL: render only faces whose normal points at the camera (+Z)
221 let br: i64 = mv_lambert(nrm[0], nrm[1], nrm[2], LX, LY, LZ)
222 let col: i64 = mv_shade(tris[ti*7+6], br, 2048)
223 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
224 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
225 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
226 tri[9]=col
227 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
228 drawn=drawn+1
229 }
230 }
231 ti=ti+1
232 }
233 return drawn
234}
235
236// ===== CAP-SKELETAL-ANIM visual: a cube whose TOP HALF is bound to a bone that rotates -> the mesh BENDS (skinning live) =====
237func mv_rotz(deg: i64, out: *i64) -> i64 {
238 let c: i64 = rc_cos_q14(deg)
239 let s: i64 = rc_sin_q14(deg)
240 var i: i64 = 0
241 while i < 16 { out[i]=0; i=i+1 }
242 out[0]=c; out[1]=0-s; out[4]=s; out[5]=c; out[10]=VQ; out[15]=VQ
243 return 0
244}
245// 2-bone linear blend skinning: ob[obase..obase+3] = w0*(m0*rest) + w1*(m1*rest). (mirrors nx_meshrender mr_skin2.)
246func mv_skin2(rest: *i64, m0: *i64, w0: i64, m1: *i64, w1: i64, tmp: *i64, ob: *i64, obase: i64) -> i64 {
247 rc_mat4_vec4(m0, rest, tmp)
248 ob[obase+0]=w0*tmp[0]/VQ; ob[obase+1]=w0*tmp[1]/VQ; ob[obase+2]=w0*tmp[2]/VQ; ob[obase+3]=w0*tmp[3]/VQ
249 rc_mat4_vec4(m1, rest, tmp)
250 ob[obase+0]=ob[obase+0]+w1*tmp[0]/VQ; ob[obase+1]=ob[obase+1]+w1*tmp[1]/VQ; ob[obase+2]=ob[obase+2]+w1*tmp[2]/VQ; ob[obase+3]=ob[obase+3]+w1*tmp[3]/VQ
251 return 0
252}
253// 6 distinct face colours so the bend reads clearly (computed, no magic packed values).
254func mv_facecol(f: i64) -> i64 {
255 var r: i64=200; var g: i64=200; var b: i64=200
256 if f==0 { r=235; g=70; b=60 }
257 if f==1 { r=70; g=200; b=90 }
258 if f==2 { r=70; g=120; b=235 }
259 if f==3 { r=235; g=205; b=70 }
260 if f==4 { r=210; g=90; b=200 }
261 if f==5 { r=80; g=205; b=210 }
262 return r + g*256 + b*65536 + 255*16777216
263}
264// ===== skinned-normal lighting (mirrors nx_meshrender mr_isqrt/mr_face_lambert) =====
265// brightness from the ACTUAL (skinned/deformed) face geometry: the normal is the cross product of the live edges,
266// so lighting is CORRECT under deformation with NO pre-baked normal. The native math is proven by nx_skinnorm_gate (6/6).
267func mv_isqrt(n: i64) -> i64 {
268 if n <= 0 { return 0 }
269 var x: i64 = n
270 if n > 1 { x = n / 2 }
271 var i: i64 = 0
272 while i < 64 {
273 let xn: i64 = (x + n / x) / 2
274 if xn >= x { return x }
275 x = xn
276 i = i + 1
277 }
278 return x
279}
280func mv_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 {
281 let e1x: i64=p1x-p0x; let e1y: i64=p1y-p0y; let e1z: i64=p1z-p0z
282 let e2x: i64=p2x-p0x; let e2y: i64=p2y-p0y; let e2z: i64=p2z-p0z
283 let nx: i64=e1y*e2z - e1z*e2y
284 let ny: i64=e1z*e2x - e1x*e2z
285 let nz: i64=e1x*e2y - e1y*e2x
286 let dotnl: i64=nx*lx + ny*ly + nz*lz
287 if dotnl <= 0 { return 0 }
288 let mag2: i64=nx*nx + ny*ny + nz*nz
289 if mag2 <= 0 { return 0 }
290 let m: i64=mv_isqrt(mag2)
291 if m <= 0 { return 0 }
292 return dotnl / m
293}
294// ===== CAP-PBR rung-2: Blinn-Phong specular (mirrors nx_meshrender mr_pow_q14/mr_face_specular) =====
295// proven natively by nx_spec_gate (6/6). Glossy highlight from the (skinned/deformed) face normal + half-vector.
296func mv_pow_q14(base: i64, n: i64) -> i64 {
297 var r: i64 = VQ
298 var i: i64 = 0
299 while i < n {
300 r = r * base / VQ
301 i = i + 1
302 }
303 return r
304}
305func mv_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 {
306 let e1x: i64=p1x-p0x; let e1y: i64=p1y-p0y; let e1z: i64=p1z-p0z
307 let e2x: i64=p2x-p0x; let e2y: i64=p2y-p0y; let e2z: i64=p2z-p0z
308 let nx: i64=e1y*e2z - e1z*e2y
309 let ny: i64=e1z*e2x - e1x*e2z
310 let nz: i64=e1x*e2y - e1y*e2x
311 let nmag2: i64=nx*nx + ny*ny + nz*nz
312 if nmag2 <= 0 { return 0 }
313 let nmag: i64=mv_isqrt(nmag2)
314 if nmag <= 0 { return 0 }
315 let hx: i64=lx+vx; let hy: i64=ly+vy; let hz: i64=lz+vz
316 let hmag2: i64=hx*hx + hy*hy + hz*hz
317 if hmag2 <= 0 { return 0 }
318 let hmag: i64=mv_isqrt(hmag2)
319 if hmag <= 0 { return 0 }
320 let unx: i64=nx*VQ/nmag; let uny: i64=ny*VQ/nmag; let unz: i64=nz*VQ/nmag
321 let uhx: i64=hx*VQ/hmag; let uhy: i64=hy*VQ/hmag; let uhz: i64=hz*VQ/hmag
322 let ndoth: i64=(unx*uhx + uny*uhy + unz*uhz) / VQ
323 if ndoth <= 0 { return 0 }
324 var nd: i64=ndoth
325 if nd > VQ { nd = VQ }
326 return mv_pow_q14(nd, shin)
327}
328// add a white specular highlight of intensity sp [0,VQ] onto a packed rgba (each channel clamped to 255).
329func mv_add_spec(rgba: i64, sp: i64) -> i64 {
330 let add: i64 = sp * 255 / VQ
331 var r: i64 = rgba % 256
332 var g: i64 = (rgba / 256) % 256
333 var b: i64 = (rgba / 65536) % 256
334 r = r + add; if r > 255 { r = 255 }
335 g = g + add; if g > 255 { g = 255 }
336 b = b + add; if b > 255 { b = 255 }
337 return r + g*256 + b*65536 + 255*16777216
338}
339// render the bending cube. frame -> joint bend angle (oscillates +-50deg); top verts (y>0) follow bone1=rotZ(bend),
340// bottom verts (y<0) stay on bone0=identity -> linear blend skinning deforms the cube. Fixed 3/4 view. z-buffered. EXPORTED.
341// shared skinning + projection for the 3 skincube renderers (flat/lit/spec): clears fb/zb, skins the 8 cube verts
342// by the frame's bend into O_SKIN, and projects them (fixed 25deg view) into O_CSCR. The renderers then differ ONLY
343// in the per-triangle shading (rule 15 DRY -- this was 3x duplicated setup).
344func mv_skincube_xform(frame: i64) -> i64 {
345 let fb: *i64 = (O_FB as i64) as *i64
346 let zb: *i64 = (O_ZB as i64) as *i64
347 rc_clear(fb, MV_W, MV_H, BG)
348 rc_zclear(zb, MV_W, MV_H)
349 mv_cube_scene()
350 let m0: *i64 = (O_M0 as i64) as *i64
351 let m1: *i64 = (O_M1 as i64) as *i64
352 rc_identity_4x4(m0)
353 let bend: i64 = rc_sin_q14(frame*3) * 50 / VQ // oscillate the joint +-50 degrees
354 mv_rotz(bend, m1)
355 let cv: *i64 = (O_CVERTS as i64) as *i64
356 let sk: *i64 = (O_SKIN as i64) as *i64
357 let rest: *i64 = (O_REST as i64) as *i64
358 let stmp: *i64 = (O_STMP as i64) as *i64
359 var vi: i64 = 0
360 while vi < 8 {
361 var w0: i64 = 0
362 if cv[vi*3+1] < 0 { w0 = VQ } // bottom half -> bone0 (static); top half -> bone1 (bend)
363 let w1: i64 = VQ - w0
364 rest[0]=cv[vi*3+0]; rest[1]=cv[vi*3+1]; rest[2]=cv[vi*3+2]; rest[3]=VQ
365 mv_skin2(rest, m0, w0, m1, w1, stmp, sk, vi*4)
366 vi=vi+1
367 }
368 let proj: *i64 = (O_PROJ as i64) as *i64
369 let roty: *i64 = (O_ROTY as i64) as *i64
370 let mvm: *i64 = (O_MV as i64) as *i64
371 let trans: *i64 = (O_TRANS as i64) as *i64
372 let mvp: *i64 = (O_MVP as i64) as *i64
373 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
374 let cc: i64 = rc_cos_q14(25)
375 let ss: i64 = rc_sin_q14(25)
376 var z: i64 = 0
377 while z < 16 { roty[z]=0; z=z+1 }
378 roty[0]=cc; roty[2]=ss; roty[5]=VQ; roty[8]=0-ss; roty[10]=cc; roty[15]=VQ
379 rc_translation_4x4(0, 0, 0-DIST, trans)
380 rc_mat4_mul(trans, roty, mvm)
381 rc_mat4_mul(proj, mvm, mvp)
382 let scr: *i64 = (O_CSCR as i64) as *i64
383 var pi: i64 = 0
384 while pi < 8 { mv_proj1(mvp, sk[pi*4+0], sk[pi*4+1], sk[pi*4+2], scr, pi*4); pi=pi+1 }
385 return 0
386}
387// render the bending cube, FLAT per-face colours. EXPORTED.
388func mv_render_skincube(frame: i64) -> i64 {
389 mv_skincube_xform(frame)
390 let fb: *i64 = (O_FB as i64) as *i64
391 let zb: *i64 = (O_ZB as i64) as *i64
392 let scr: *i64 = (O_CSCR as i64) as *i64
393 let tri: *i64 = (O_TRI as i64) as *i64
394 let tris: *i64 = (O_CTRIS as i64) as *i64
395 var ti: i64 = 0
396 var drawn: i64 = 0
397 while ti < 12 {
398 let a: i64 = tris[ti*7+0]
399 let b: i64 = tris[ti*7+1]
400 let c: i64 = tris[ti*7+2]
401 var vis: i64 = 0
402 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
403 if vis==1 {
404 let col: i64 = mv_facecol(ti/2)
405 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
406 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
407 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
408 tri[9]=col
409 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
410 drawn=drawn+1
411 }
412 ti=ti+1
413 }
414 return drawn
415}
416
417// CAP-SKELETAL-ANIM rung-2 visual: the SAME bending cube, now LIT from its SKINNED normals. Each frame the face
418// normal is recomputed (cross product of the live skinned edges) via mv_face_lambert, so the lighting is CORRECT
419// under the bend deformation -- no pre-baked normal (the gap the flat skincube left open). Verts are passed a,c,b
420// so the cross product yields the OUTWARD normal (the cube winding is inward). Light = upper-right-FRONT (model
421// space). mv_shade attenuates the face colour by the Lambert term (ambient floor 2048). z-buffered. EXPORTED.
422func mv_render_skincube_lit(frame: i64) -> i64 {
423 mv_skincube_xform(frame)
424 let fb: *i64 = (O_FB as i64) as *i64
425 let zb: *i64 = (O_ZB as i64) as *i64
426 let scr: *i64 = (O_CSCR as i64) as *i64
427 let sk: *i64 = (O_SKIN as i64) as *i64
428 let tri: *i64 = (O_TRI as i64) as *i64
429 let tris: *i64 = (O_CTRIS as i64) as *i64
430 let LX: i64 = 8192
431 let LY: i64 = 8192
432 let LZ: i64 = 11585
433 var ti: i64 = 0
434 var drawn: i64 = 0
435 while ti < 12 {
436 let a: i64 = tris[ti*7+0]
437 let b: i64 = tris[ti*7+1]
438 let c: i64 = tris[ti*7+2]
439 var vis: i64 = 0
440 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
441 if vis==1 {
442 let br: i64 = mv_face_lambert(sk[a*4+0],sk[a*4+1],sk[a*4+2], sk[c*4+0],sk[c*4+1],sk[c*4+2], sk[b*4+0],sk[b*4+1],sk[b*4+2], LX,LY,LZ)
443 let col: i64 = mv_shade(mv_facecol(ti/2), br, 2048)
444 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
445 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
446 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
447 tri[9]=col
448 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
449 drawn=drawn+1
450 }
451 ti=ti+1
452 }
453 return drawn
454}
455
456// CAP-PBR rung-2 visual: the lit bending cube + a Blinn-Phong SPECULAR highlight (mv_face_specular over the skinned
457// normal + the model-space view dir) -> a GLOSSY cube. Specular recomputed per pose so the highlight tracks the bend.
458// Verts a,c,b for the OUTWARD normal (cube winding is inward); V = the camera dir in model space (undo the 25deg view
459// turn). z-buffered. EXPORTED.
460func mv_render_skincube_spec(frame: i64) -> i64 {
461 mv_skincube_xform(frame)
462 let fb: *i64 = (O_FB as i64) as *i64
463 let zb: *i64 = (O_ZB as i64) as *i64
464 let scr: *i64 = (O_CSCR as i64) as *i64
465 let sk: *i64 = (O_SKIN as i64) as *i64
466 let tri: *i64 = (O_TRI as i64) as *i64
467 let tris: *i64 = (O_CTRIS as i64) as *i64
468 let LX: i64 = 8192
469 let LY: i64 = 8192
470 let LZ: i64 = 11585
471 let VX: i64 = 0 - rc_sin_q14(25)
472 let VY: i64 = 0
473 let VZ: i64 = rc_cos_q14(25)
474 var ti: i64 = 0
475 var drawn: i64 = 0
476 while ti < 12 {
477 let a: i64 = tris[ti*7+0]
478 let b: i64 = tris[ti*7+1]
479 let c: i64 = tris[ti*7+2]
480 var vis: i64 = 0
481 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
482 if vis==1 {
483 let br: i64 = mv_face_lambert(sk[a*4+0],sk[a*4+1],sk[a*4+2], sk[c*4+0],sk[c*4+1],sk[c*4+2], sk[b*4+0],sk[b*4+1],sk[b*4+2], LX,LY,LZ)
484 var col: i64 = mv_shade(mv_facecol(ti/2), br, 2048)
485 let sp: i64 = mv_face_specular(sk[a*4+0],sk[a*4+1],sk[a*4+2], sk[c*4+0],sk[c*4+1],sk[c*4+2], sk[b*4+0],sk[b*4+1],sk[b*4+2], LX,LY,LZ, VX,VY,VZ, 16)
486 col = mv_add_spec(col, sp)
487 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
488 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
489 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
490 tri[9]=col
491 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
492 drawn=drawn+1
493 }
494 ti=ti+1
495 }
496 return drawn
497}
498
499// ===== CAP-PBR rung-3: smooth Gouraud shading on a curved mesh (sphere) -- per-VERTEX unit normals + the
500// rc_triangle barycentric colour interpolation = a ROUND, smoothly-shaded ball (no facets). =====
501// specular from a unit normal directly (per-vertex Gouraud; no cross product needed -- the normal IS the geometry).
502func mv_normal_specular(nx: i64, ny: i64, nz: i64, lx: i64, ly: i64, lz: i64, vx: i64, vy: i64, vz: i64, shin: i64) -> i64 {
503 let hx: i64=lx+vx; let hy: i64=ly+vy; let hz: i64=lz+vz
504 let hmag2: i64=hx*hx + hy*hy + hz*hz
505 if hmag2 <= 0 { return 0 }
506 let hmag: i64=mv_isqrt(hmag2)
507 if hmag <= 0 { return 0 }
508 let uhx: i64=hx*VQ/hmag; let uhy: i64=hy*VQ/hmag; let uhz: i64=hz*VQ/hmag
509 let ndoth: i64=(nx*uhx + ny*uhy + nz*uhz) / VQ
510 if ndoth <= 0 { return 0 }
511 var nd: i64=ndoth
512 if nd > VQ { nd = VQ }
513 return mv_pow_q14(nd, shin)
514}
515// build the UV sphere: per-vertex unit normal (= position/R) + position + the triangle indices (poles -> a few
516// degenerate tris that raster nothing; harmless).
517func mv_sphere_scene() -> i64 {
518 let v: *i64 = (O_SPV as i64) as *i64
519 let nrm: *i64 = (O_SPN as i64) as *i64
520 var i: i64 = 0
521 while i <= SP_RINGS {
522 let lat: i64 = i * 180 / SP_RINGS
523 let clat: i64 = rc_cos_q14(lat)
524 let slat: i64 = rc_sin_q14(lat)
525 var j: i64 = 0
526 while j <= SP_SECT {
527 let lon: i64 = j * 360 / SP_SECT
528 let clon: i64 = rc_cos_q14(lon)
529 let slon: i64 = rc_sin_q14(lon)
530 let nxx: i64 = slat * clon / VQ
531 let nyy: i64 = clat
532 let nzz: i64 = slat * slon / VQ
533 let idx: i64 = i * (SP_SECT + 1) + j
534 nrm[idx*3+0]=nxx; nrm[idx*3+1]=nyy; nrm[idx*3+2]=nzz
535 v[idx*3+0]=nxx*SP_R/VQ; v[idx*3+1]=nyy*SP_R/VQ; v[idx*3+2]=nzz*SP_R/VQ
536 j = j + 1
537 }
538 i = i + 1
539 }
540 let id: *i64 = (O_SPI as i64) as *i64
541 var ti: i64 = 0
542 var ri: i64 = 0
543 while ri < SP_RINGS {
544 var si: i64 = 0
545 while si < SP_SECT {
546 let a: i64 = ri * (SP_SECT+1) + si
547 let bb: i64 = (ri+1) * (SP_SECT+1) + si
548 let cd: i64 = (ri+1) * (SP_SECT+1) + (si+1)
549 let dd: i64 = ri * (SP_SECT+1) + (si+1)
550 id[ti*3+0]=a; id[ti*3+1]=bb; id[ti*3+2]=cd; ti=ti+1
551 id[ti*3+0]=a; id[ti*3+1]=cd; id[ti*3+2]=dd; ti=ti+1
552 si = si + 1
553 }
554 ri = ri + 1
555 }
556 return 0
557}
558// prep: scene + static view mvp + per-vertex Gouraud colour (lit+spec, with the LIGHT orbited by `angle` so a glint
559// sweeps the static sphere) -> O_SPC, and projected verts -> O_SPS.
560// shared sphere geometry (angle-independent: the sphere is STATIC, only the light orbits): scene + static-view mvp
561// + project all verts -> O_SPS. Used by the Gouraud/flat prep AND the Phong renderer (rule 15 DRY).
562func mv_sphere_xform() -> i64 {
563 mv_sphere_scene()
564 let proj: *i64 = (O_PROJ as i64) as *i64
565 let trans: *i64 = (O_TRANS as i64) as *i64
566 let mvp: *i64 = (O_MVP as i64) as *i64
567 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
568 rc_translation_4x4(0, 0, 0-DIST, trans)
569 rc_mat4_mul(proj, trans, mvp)
570 let v: *i64 = (O_SPV as i64) as *i64
571 let scr: *i64 = (O_SPS as i64) as *i64
572 var k: i64 = 0
573 while k < SP_NV { mv_proj1(mvp, v[k*3+0], v[k*3+1], v[k*3+2], scr, k*4); k=k+1 }
574 return 0
575}
576// ===== R4: GENERIC arbitrary-mesh renderer (the in-browser STL-viewer core) =====
577// renders ANY triangle mesh held in caller-supplied linear-memory buffers: per-vertex unit normals -> Gouraud lit+
578// spec colours, static view, light orbited by `angle`. vbuf/nbuf/cbuf/sbuf/ibuf = byte offsets; nverts/ntris =
579// counts; base = material rgba; mode 0 = smooth Gouraud (rc_triangle), mode 1 = flat (rc_triangle_flat). The sphere
580// + torus + (next) a loaded STL all route through THIS one function (rule 15 DRY). EXPORTED accessors below let a
581// host write geometry into the buffers. Returns triangles drawn.
582func mv_render_mesh(angle: i64, vbuf: i64, nbuf: i64, cbuf: i64, sbuf: i64, ibuf: i64, nverts: i64, ntris: i64, base: i64, mode: i64) -> i64 {
583 let fb: *i64 = (O_FB as i64) as *i64
584 let zb: *i64 = (O_ZB as i64) as *i64
585 rc_clear(fb, MV_W, MV_H, BG)
586 rc_zclear(zb, MV_W, MV_H)
587 let proj: *i64 = (O_PROJ as i64) as *i64
588 let trans: *i64 = (O_TRANS as i64) as *i64
589 let mvp: *i64 = (O_MVP as i64) as *i64
590 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
591 rc_translation_4x4(0, 0, 0-DIST, trans)
592 rc_mat4_mul(proj, trans, mvp)
593 let cc: i64 = rc_cos_q14(angle)
594 let ss: i64 = rc_sin_q14(angle)
595 let LX: i64 = (cc*8192 + ss*11585) / VQ
596 let LY: i64 = 8192
597 let LZ: i64 = ((0-ss)*8192 + cc*11585) / VQ
598 let v: *i64 = (vbuf as i64) as *i64
599 let nrm: *i64 = (nbuf as i64) as *i64
600 let col: *i64 = (cbuf as i64) as *i64
601 let scr: *i64 = (sbuf as i64) as *i64
602 var k: i64 = 0
603 while k < nverts {
604 let nxx: i64 = nrm[k*3+0]
605 let nyy: i64 = nrm[k*3+1]
606 let nzz: i64 = nrm[k*3+2]
607 let br: i64 = mv_lambert(nxx,nyy,nzz, LX,LY,LZ)
608 var c0: i64 = mv_shade(base, br, 2048)
609 let sp: i64 = mv_normal_specular(nxx,nyy,nzz, LX,LY,LZ, 0,0,VQ, 24)
610 c0 = mv_add_spec(c0, sp)
611 col[k] = c0
612 mv_proj1(mvp, v[k*3+0], v[k*3+1], v[k*3+2], scr, k*4)
613 k = k + 1
614 }
615 let id: *i64 = (ibuf as i64) as *i64
616 let tri: *i64 = (O_TRI as i64) as *i64
617 var ti: i64 = 0
618 var drawn: i64 = 0
619 while ti < ntris {
620 let a: i64 = id[ti*3+0]
621 let b: i64 = id[ti*3+1]
622 let c: i64 = id[ti*3+2]
623 var vis: i64 = 0
624 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
625 if vis==1 {
626 if mode==0 {
627 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]; tri[3]=col[a]
628 tri[4]=scr[b*4+0]; tri[5]=scr[b*4+1]; tri[6]=scr[b*4+2]; tri[7]=col[b]
629 tri[8]=scr[c*4+0]; tri[9]=scr[c*4+1]; tri[10]=scr[c*4+2]; tri[11]=col[c]
630 rc_triangle(fb, zb, MV_W, MV_H, tri)
631 }
632 if mode==1 {
633 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
634 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
635 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
636 tri[9]=col[a]
637 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
638 }
639 drawn=drawn+1
640 }
641 ti=ti+1
642 }
643 return drawn
644}
645// byte offsets of the generic mesh buffers (a host writes geometry here, then calls mv_render_loaded). EXPORTED.
646func mv_meshv_off() -> i64 { return O_MVV }
647func mv_meshn_off() -> i64 { return O_MVN }
648func mv_meshi_off() -> i64 { return O_MVI }
649// build a low-poly UV TORUS (genus-1 -- a topology a cube/sphere can't fake) into the generic mesh buffers.
650func mv_torus_scene() -> i64 {
651 let v: *i64 = (O_MVV as i64) as *i64
652 let nrm: *i64 = (O_MVN as i64) as *i64
653 var i: i64 = 0
654 while i < TR_SECT {
655 let u: i64 = i * 360 / TR_SECT
656 let cu: i64 = rc_cos_q14(u)
657 let su: i64 = rc_sin_q14(u)
658 var j: i64 = 0
659 while j < TR_RING {
660 let vv: i64 = j * 360 / TR_RING
661 let cvv: i64 = rc_cos_q14(vv)
662 let svv: i64 = rc_sin_q14(vv)
663 let rr: i64 = TR_MAJ + TR_MIN * cvv / VQ
664 let idx: i64 = i * TR_RING + j
665 v[idx*3+0] = rr * cu / VQ
666 v[idx*3+1] = TR_MIN * svv / VQ
667 v[idx*3+2] = rr * su / VQ
668 nrm[idx*3+0] = cvv * cu / VQ
669 nrm[idx*3+1] = svv
670 nrm[idx*3+2] = cvv * su / VQ
671 j = j + 1
672 }
673 i = i + 1
674 }
675 let id: *i64 = (O_MVI as i64) as *i64
676 var ti: i64 = 0
677 var si: i64 = 0
678 while si < TR_SECT {
679 let si2: i64 = (si + 1) % TR_SECT
680 var ri: i64 = 0
681 while ri < TR_RING {
682 let ri2: i64 = (ri + 1) % TR_RING
683 let a: i64 = si * TR_RING + ri
684 let b: i64 = si2 * TR_RING + ri
685 let c: i64 = si2 * TR_RING + ri2
686 let d: i64 = si * TR_RING + ri2
687 id[ti*3+0]=a; id[ti*3+1]=b; id[ti*3+2]=c; ti=ti+1
688 id[ti*3+0]=a; id[ti*3+1]=c; id[ti*3+2]=d; ti=ti+1
689 ri = ri + 1
690 }
691 si = si + 1
692 }
693 return 0
694}
695// render the torus via the generic mesh renderer. EXPORTED.
696func mv_render_torus(angle: i64) -> i64 {
697 mv_torus_scene()
698 return mv_render_mesh(angle, O_MVV, O_MVN, O_MVC, O_MVS, O_MVI, TR_NV, TR_NT, MESH_BASE, 0)
699}
700// render whatever geometry a host has written into the generic buffers (nverts/ntris supplied). EXPORTED.
701func mv_render_loaded(angle: i64, nverts: i64, ntris: i64) -> i64 {
702 return mv_render_mesh(angle, O_MVV, O_MVN, O_MVC, O_MVS, O_MVI, nverts, ntris, MESH_BASE, 0)
703}
704// Track-E rung-1: INTERACTIVE ORBIT VIEWPORT. The CAMERA orbits the loaded mesh -- model rotated by `yaw` (about Y)
705// and pushed `dist` down -Z (zoom), so the object spins on screen + scales as the page drives yaw from mouse-drag
706// and dist from scroll. The per-vertex normal is rotated by yaw too, so the world-fixed light stays put while you
707// orbit (correct orbit-camera lighting). Gouraud, z-buffered. EXPORTED. (CLEANUP DEBT: the per-vertex shade+project
708// and raster loops mirror mv_render_mesh -> extract mv_shade_project_verts + mv_raster_indexed next, route both.)
709func mv_render_loaded_cam(yaw: i64, dist: i64, nverts: i64, ntris: i64) -> i64 {
710 let fb: *i64 = (O_FB as i64) as *i64
711 let zb: *i64 = (O_ZB as i64) as *i64
712 rc_clear(fb, MV_W, MV_H, BG)
713 rc_zclear(zb, MV_W, MV_H)
714 let proj: *i64 = (O_PROJ as i64) as *i64
715 let roty: *i64 = (O_ROTY as i64) as *i64
716 let mvm: *i64 = (O_MV as i64) as *i64
717 let trans: *i64 = (O_TRANS as i64) as *i64
718 let mvp: *i64 = (O_MVP as i64) as *i64
719 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
720 let cc: i64 = rc_cos_q14(yaw)
721 let ss: i64 = rc_sin_q14(yaw)
722 var z: i64 = 0
723 while z < 16 { roty[z]=0; z=z+1 }
724 roty[0]=cc; roty[2]=ss; roty[5]=VQ; roty[8]=0-ss; roty[10]=cc; roty[15]=VQ
725 rc_translation_4x4(0, 0, 0-dist, trans)
726 rc_mat4_mul(trans, roty, mvm)
727 rc_mat4_mul(proj, mvm, mvp)
728 let LX: i64 = 8192
729 let LY: i64 = 8192
730 let LZ: i64 = 11585
731 let v: *i64 = (O_MVV as i64) as *i64
732 let nrm: *i64 = (O_MVN as i64) as *i64
733 let col: *i64 = (O_MVC as i64) as *i64
734 let scr: *i64 = (O_MVS as i64) as *i64
735 var k: i64 = 0
736 while k < nverts {
737 let rnx: i64 = (cc*nrm[k*3+0] + ss*nrm[k*3+2]) / VQ
738 let rny: i64 = nrm[k*3+1]
739 let rnz: i64 = ((0-ss)*nrm[k*3+0] + cc*nrm[k*3+2]) / VQ
740 let br: i64 = mv_lambert(rnx,rny,rnz, LX,LY,LZ)
741 var c0: i64 = mv_shade(MESH_BASE, br, 2048)
742 let sp: i64 = mv_normal_specular(rnx,rny,rnz, LX,LY,LZ, 0,0,VQ, 24)
743 c0 = mv_add_spec(c0, sp)
744 col[k] = c0
745 mv_proj1(mvp, v[k*3+0], v[k*3+1], v[k*3+2], scr, k*4)
746 k = k + 1
747 }
748 let id: *i64 = (O_MVI as i64) as *i64
749 let tri: *i64 = (O_TRI as i64) as *i64
750 var ti: i64 = 0
751 var drawn: i64 = 0
752 while ti < ntris {
753 let a: i64 = id[ti*3+0]
754 let b: i64 = id[ti*3+1]
755 let c: i64 = id[ti*3+2]
756 var vis: i64 = 0
757 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
758 if vis==1 {
759 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]; tri[3]=col[a]
760 tri[4]=scr[b*4+0]; tri[5]=scr[b*4+1]; tri[6]=scr[b*4+2]; tri[7]=col[b]
761 tri[8]=scr[c*4+0]; tri[9]=scr[c*4+1]; tri[10]=scr[c*4+2]; tri[11]=col[c]
762 rc_triangle(fb, zb, MV_W, MV_H, tri)
763 drawn=drawn+1
764 }
765 ti=ti+1
766 }
767 return drawn
768}
769// smooth Gouraud sphere -- routes through the generic mesh renderer (mode 0). EXPORTED.
770func mv_render_sphere(angle: i64) -> i64 {
771 mv_sphere_scene()
772 return mv_render_mesh(angle, O_SPV, O_SPN, O_SPC, O_SPS, O_SPI, SP_NV, SP_NT, SPHERE_BASE, 0)
773}
774// flat sphere (mode 1) -- the gate's negative control proving smooth shading really interpolates. EXPORTED.
775func mv_render_sphere_flat(angle: i64) -> i64 {
776 mv_sphere_scene()
777 return mv_render_mesh(angle, O_SPV, O_SPN, O_SPC, O_SPS, O_SPI, SP_NV, SP_NT, SPHERE_BASE, 1)
778}
779
780// CAP-PBR rung-4: true PHONG sphere -- per-pixel normal interpolation + shading via rc_triangle_phong. Same sphere
781// + orbited light as Gouraud, but the highlight is computed at EVERY pixel -> sharp + correctly placed between
782// vertices. EXPORTED. The per-vertex NORMALS (not colours) feed the rasterizer; L,V,base,shin,amb ride in pb too.
783// shared per-pixel-Phong sphere draw. bump=0 -> smooth Phong; bump>0 -> procedural per-pixel normal perturbation
784// (a bump map: surface detail with NO extra geometry).
785func mv_sphere_phong_draw(angle: i64, bump: i64) -> i64 {
786 let fb: *i64 = (O_FB as i64) as *i64
787 let zb: *i64 = (O_ZB as i64) as *i64
788 rc_clear(fb, MV_W, MV_H, BG)
789 rc_zclear(zb, MV_W, MV_H)
790 mv_sphere_xform()
791 let cc: i64 = rc_cos_q14(angle)
792 let ss: i64 = rc_sin_q14(angle)
793 let nrm: *i64 = (O_SPN as i64) as *i64
794 let scr: *i64 = (O_SPS as i64) as *i64
795 let id: *i64 = (O_SPI as i64) as *i64
796 let pb: *i64 = (O_PBUF as i64) as *i64
797 pb[18] = (cc*8192 + ss*11585) / VQ
798 pb[19] = 8192
799 pb[20] = ((0-ss)*8192 + cc*11585) / VQ
800 pb[21] = 0; pb[22] = 0; pb[23] = VQ
801 pb[24] = SPHERE_BASE; pb[25] = 24; pb[26] = 2048; pb[27] = bump
802 var ti: i64 = 0
803 var drawn: i64 = 0
804 while ti < SP_NT {
805 let a: i64 = id[ti*3+0]
806 let b: i64 = id[ti*3+1]
807 let c: i64 = id[ti*3+2]
808 var vis: i64 = 0
809 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
810 if vis==1 {
811 pb[0]=scr[a*4+0]; pb[1]=scr[a*4+1]; pb[2]=scr[a*4+2]
812 pb[3]=scr[b*4+0]; pb[4]=scr[b*4+1]; pb[5]=scr[b*4+2]
813 pb[6]=scr[c*4+0]; pb[7]=scr[c*4+1]; pb[8]=scr[c*4+2]
814 pb[9]=nrm[a*3+0]; pb[10]=nrm[a*3+1]; pb[11]=nrm[a*3+2]
815 pb[12]=nrm[b*3+0]; pb[13]=nrm[b*3+1]; pb[14]=nrm[b*3+2]
816 pb[15]=nrm[c*3+0]; pb[16]=nrm[c*3+1]; pb[17]=nrm[c*3+2]
817 rc_triangle_phong(fb, zb, MV_W, MV_H, pb)
818 drawn=drawn+1
819 }
820 ti=ti+1
821 }
822 return drawn
823}
824// smooth Phong sphere (bump=0). EXPORTED.
825func mv_render_sphere_phong(angle: i64) -> i64 { return mv_sphere_phong_draw(angle, 0) }
826// CAP-PBR rung-5: bump-mapped sphere -- per-pixel procedural normal perturbation adds SURFACE DETAIL (a dimpled,
827// textured look) that catches the orbiting light, with NO extra geometry / no extra triangles. EXPORTED.
828func mv_render_sphere_nmap(angle: i64) -> i64 { return mv_sphere_phong_draw(angle, 1200) }