code wiki / (root) / nx_meshview_wasm.nx

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) }