nx_meshview_wasm.nx source
↩ module page · 1126 lines · 51080 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"
10import "nx_vecmath.nx"
11
12const MV_W: i64 = 64
13const MV_H: i64 = 64
14const MV_N: i64 = 4096 // 64*64
15const VQ: i64 = 16384 // Q14 one (== RC_Q)
16const DIST: i64 = 65536 // 4.0 Q14: push the model down -Z
17const FOVH: i64 = 30 // half field-of-view degrees
18const FARQ: i64 = 16384000 // far plane Q14
19const BG: i64 = 4280295456 // r32 g32 b32 a255
20const RED: i64 = 4278190335 // r255 g0 b0 a255
21const BLU: i64 = 4294901760 // r0 g0 b255 a255
22
23// fixed BYTE offsets into wasm linear memory (all 8-byte aligned; total < 67KB, inside the default memory).
24const O_FB: i64 = 0 // MV_N i64 framebuffer (-> 32768)
25const O_ZB: i64 = 32768 // MV_N i64 z-buffer (-> 65536)
26const O_PROJ: i64 = 65536 // 16 i64
27const O_ROTY: i64 = 65664
28const O_MV: i64 = 65792
29const O_TRANS: i64 = 65920
30const O_MVP: i64 = 66048
31const O_VB: i64 = 66176 // 4 i64 vertex scratch
32const O_CLIP: i64 = 66208 // 4 i64 clip scratch
33const O_SCR: i64 = 66240 // 6 verts * 4 i64 (-> 66432)
34const O_TRI: i64 = 66432 // 12 i64 tri scratch (-> 66528)
35const O_VERTS: i64 = 66528 // 6 verts * 4 i64 (-> 66720)
36const O_IDX: i64 = 66720 // 2 tris * 3 i64 (-> 66768)
37// --- lit cube (CAP-PBR visual) ---
38const O_CVERTS: i64 = 66768 // 8 cube verts * 3 i64 (-> 66960)
39const O_CTRIS: i64 = 66960 // 12 tris * 7 i64 [i0,i1,i2,nx,ny,nz,color] (-> 67632)
40const O_CSCR: i64 = 67632 // 8 verts * 4 i64 projected scratch (-> 67888)
41const C_HALF: i64 = 9830 // 0.6 * Q14 cube half-extent
42const C_BASE: i64 = 4292004040 // base material (200,200,210,255) -- shading reveals the form
43// --- skinned (bending) cube: CAP-SKELETAL-ANIM visual ---
44const O_SKIN: i64 = 67888 // 8 skinned verts * 4 i64 (-> 68144)
45const O_M0: i64 = 68144 // bone0 mat4 (-> 68272)
46const O_M1: i64 = 68272 // bone1 mat4 (-> 68400)
47const O_REST: i64 = 68400 // rest vec4 scratch (-> 68432)
48const O_STMP: i64 = 68432 // skin tmp vec4 scratch (-> 68464)
49// --- smooth (Gouraud) lit+specular SPHERE: CAP-PBR per-vertex-normal shading (kills the faceted look) ---
50const SP_RINGS: i64 = 8
51const SP_SECT: i64 = 12
52const SP_NV: i64 = 117 // (RINGS+1)*(SECT+1)
53const SP_NT: i64 = 192 // RINGS*SECT*2
54const SP_R: i64 = 9830 // radius 0.6*Q
55const SPHERE_BASE: i64 = 4293299280 // base material (80,140,230,255)
56const O_SPV: i64 = 68480 // sphere verts SP_NV*3 (-> 71288)
57const O_SPN: i64 = 71288 // sphere normals SP_NV*3 (-> 74096)
58const O_SPC: i64 = 74096 // sphere per-vertex colours SP_NV (-> 75032)
59const O_SPS: i64 = 75032 // sphere projected scr SP_NV*4 (-> 78776)
60const O_SPI: i64 = 78776 // sphere indices SP_NT*3 (-> 83384)
61const O_PBUF: i64 = 83392 // phong tri buffer 28 i64 (-> 83616)
62// --- generic loaded/torus mesh buffers (sized for <=256 verts / 512 tris) ---
63const O_MVV: i64 = 83616 // mesh verts 256*3 (-> 89760)
64const O_MVN: i64 = 89760 // mesh normals 256*3 (-> 95904)
65const O_MVC: i64 = 95904 // mesh colours 256 (-> 97952)
66const O_MVS: i64 = 97952 // mesh projected 256*4 (-> 106144)
67const O_MVI: i64 = 106144 // mesh indices 512*3 (-> 118432)
68const MESH_BASE: i64 = 4282158310 // mesh material (230,140,60,255) -- orange
69const TR_SECT: i64 = 12
70const TR_RING: i64 = 8
71const TR_NV: i64 = 96 // SECT*RING
72const TR_NT: i64 = 192 // SECT*RING*2
73const TR_MAJ: i64 = 7000 // major (centre) radius, Q14
74const TR_MIN: i64 = 3600 // tube radius, Q14 (chunky donut, still a clear hole)
75
76func mv_w() -> i64 { return MV_W }
77func mv_h() -> i64 { return MV_H }
78func mv_fb_off() -> i64 { return O_FB } // byte offset of the framebuffer (JS reads it stride-8)
79
80// bake the demo z-test scene (RED triangle nearer + BLUE triangle farther, same screen footprint) into memory.
81func mv_scene() -> i64 {
82 let v: *i64 = (O_VERTS as i64) as *i64
83 v[0]=0-13107; v[1]=0-13107; v[2]=8192; v[3]=RED
84 v[4]=13107; v[5]=0-13107; v[6]=8192; v[7]=RED
85 v[8]=0; v[9]=13107; v[10]=8192; v[11]=RED
86 v[12]=0-13107; v[13]=0-13107; v[14]=0-8192; v[15]=BLU
87 v[16]=13107; v[17]=0-13107; v[18]=0-8192; v[19]=BLU
88 v[20]=0; v[21]=13107; v[22]=0-8192; v[23]=BLU
89 let ix: *i64 = (O_IDX as i64) as *i64
90 ix[0]=0; ix[1]=1; ix[2]=2; ix[3]=3; ix[4]=4; ix[5]=5
91 return 0
92}
93
94// project one model vertex (Q14) through mvp -> scr[base..base+3] = [screen_x, screen_y, depth, visible].
95func mv_proj1(mvp: *i64, vx: i64, vy: i64, vz: i64, scr: *i64, base: i64) -> i64 {
96 let vb: *i64 = (O_VB as i64) as *i64
97 let cl: *i64 = (O_CLIP as i64) as *i64
98 vb[0]=vx; vb[1]=vy; vb[2]=vz; vb[3]=VQ
99 rc_mat4_vec4(mvp, vb, cl)
100 let cw: i64 = cl[3]
101 if cw <= 0 { scr[base+3]=0; return 0 }
102 scr[base+0] = (cl[0]*VQ/cw + VQ) * MV_W / (2*VQ)
103 scr[base+1] = (VQ - cl[1]*VQ/cw) * MV_H / (2*VQ)
104 scr[base+2] = cl[2]*VQ/cw + VQ
105 scr[base+3] = 1
106 return 0
107}
108
109// render the baked mesh at orbit angle (degrees). Returns triangles drawn. EXPORTED -> JS calls this per frame.
110func mv_render(angle: i64) -> i64 {
111 let fb: *i64 = (O_FB as i64) as *i64
112 let zb: *i64 = (O_ZB as i64) as *i64
113 rc_clear(fb, MV_W, MV_H, BG)
114 rc_zclear(zb, MV_W, MV_H)
115 mv_scene()
116 let proj: *i64 = (O_PROJ as i64) as *i64
117 let roty: *i64 = (O_ROTY as i64) as *i64
118 let mvm: *i64 = (O_MV as i64) as *i64
119 let trans: *i64 = (O_TRANS as i64) as *i64
120 let mvp: *i64 = (O_MVP as i64) as *i64
121 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
122 let cc: i64 = rc_cos_q14(angle)
123 let ss: i64 = rc_sin_q14(angle)
124 var z: i64 = 0
125 while z < 16 { roty[z]=0; z=z+1 }
126 roty[0]=cc; roty[2]=ss; roty[5]=VQ; roty[8]=0-ss; roty[10]=cc; roty[15]=VQ
127 rc_translation_4x4(0, 0, 0-DIST, trans)
128 rc_mat4_mul(trans, roty, mvm)
129 rc_mat4_mul(proj, mvm, mvp)
130 let verts: *i64 = (O_VERTS as i64) as *i64
131 let idx: *i64 = (O_IDX as i64) as *i64
132 let scr: *i64 = (O_SCR as i64) as *i64
133 var vi: i64 = 0
134 while vi < 6 { mv_proj1(mvp, verts[vi*4+0], verts[vi*4+1], verts[vi*4+2], scr, vi*4); vi=vi+1 }
135 let tri: *i64 = (O_TRI as i64) as *i64
136 var ti: i64 = 0
137 var drawn: i64 = 0
138 while ti < 2 {
139 let a: i64 = idx[ti*3+0]
140 let b: i64 = idx[ti*3+1]
141 let c: i64 = idx[ti*3+2]
142 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 {
143 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]; tri[3]=verts[a*4+3]
144 tri[4]=scr[b*4+0]; tri[5]=scr[b*4+1]; tri[6]=scr[b*4+2]; tri[7]=verts[b*4+3]
145 tri[8]=scr[c*4+0]; tri[9]=scr[c*4+1]; tri[10]=scr[c*4+2]; tri[11]=verts[c*4+3]
146 rc_triangle(fb, zb, MV_W, MV_H, tri)
147 drawn=drawn+1
148 } } }
149 ti=ti+1
150 }
151 return drawn
152}
153
154// EXPORTED accessors so the host (and the wasm gate) can read results without knowing the memory layout.
155func mv_pixel(i: i64) -> i64 { let fb: *i64 = (O_FB as i64) as *i64; return fb[i] }
156func 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 }
157
158// ===== CAP-PBR: lit cube (the visual payoff) -- inlined Lambert shading, mirrors nx_meshrender's mr_* =====
159func mv_dot3(ax: i64, ay: i64, az: i64, bx: i64, by: i64, bz: i64) -> i64 { return (ax*bx + ay*by + az*bz) / VQ }
160func 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 }
161func 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 }
162func 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 }
163func 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 }
164func 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 }
165func mv_cube_scene() -> i64 {
166 let v: *i64 = (O_CVERTS as i64) as *i64
167 let H: i64 = C_HALF
168 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)
169 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)
170 let t: *i64 = (O_CTRIS as i64) as *i64
171 let Q: i64 = VQ
172 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)
173 mv_sett(t,2, 4,6,5, 0,0,Q, C_BASE); mv_sett(t,3, 4,7,6, 0,0,Q, C_BASE)
174 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)
175 mv_sett(t,6, 1,5,6, Q,0,0, C_BASE); mv_sett(t,7, 1,6,2, Q,0,0, C_BASE)
176 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)
177 mv_sett(t,10, 3,2,6, 0,Q,0, C_BASE); mv_sett(t,11, 3,6,7, 0,Q,0, C_BASE)
178 return 0
179}
180// render the lit cube at orbit angle (degrees). Per-face Lambert shading (light from above-front), z-buffered. EXPORTED.
181func mv_render_cube(angle: i64) -> i64 {
182 let fb: *i64 = (O_FB as i64) as *i64
183 let zb: *i64 = (O_ZB as i64) as *i64
184 rc_clear(fb, MV_W, MV_H, BG)
185 rc_zclear(zb, MV_W, MV_H)
186 mv_cube_scene()
187 let proj: *i64 = (O_PROJ as i64) as *i64
188 let roty: *i64 = (O_ROTY as i64) as *i64
189 let mvm: *i64 = (O_MV as i64) as *i64
190 let trans: *i64 = (O_TRANS as i64) as *i64
191 let mvp: *i64 = (O_MVP as i64) as *i64
192 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
193 let cc: i64 = rc_cos_q14(angle)
194 let ss: i64 = rc_sin_q14(angle)
195 var z: i64 = 0
196 while z < 16 { roty[z]=0; z=z+1 }
197 roty[0]=cc; roty[2]=ss; roty[5]=VQ; roty[8]=0-ss; roty[10]=cc; roty[15]=VQ
198 rc_translation_4x4(0, 0, 0-DIST, trans)
199 rc_mat4_mul(trans, roty, mvm)
200 rc_mat4_mul(proj, mvm, mvp)
201 let verts: *i64 = (O_CVERTS as i64) as *i64
202 let scr: *i64 = (O_CSCR as i64) as *i64
203 var vi: i64 = 0
204 while vi < 8 { mv_proj1(mvp, verts[vi*3+0], verts[vi*3+1], verts[vi*3+2], scr, vi*4); vi=vi+1 }
205 let tri: *i64 = (O_TRI as i64) as *i64
206 let nrm: *i64 = (O_VB as i64) as *i64 // vbuf free after projection -> reuse for the rotated normal
207 let tris: *i64 = (O_CTRIS as i64) as *i64
208 let LX: i64 = 9459
209 let LY: i64 = 9459
210 let LZ: i64 = 9459 // unit light from upper-right-FRONT (camera side ~(0.577,0.577,0.577)) -> lights visible faces
211 var ti: i64 = 0
212 var drawn: i64 = 0
213 while ti < 12 {
214 let a: i64 = tris[ti*7+0]
215 let b: i64 = tris[ti*7+1]
216 let c: i64 = tris[ti*7+2]
217 var vis: i64 = 0
218 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
219 if vis==1 {
220 mv_rotn(cc, ss, tris[ti*7+3], tris[ti*7+4], tris[ti*7+5], nrm)
221 if nrm[2] > 0 { // BACK-FACE CULL: render only faces whose normal points at the camera (+Z)
222 let br: i64 = mv_lambert(nrm[0], nrm[1], nrm[2], LX, LY, LZ)
223 let col: i64 = mv_shade(tris[ti*7+6], br, 2048)
224 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
225 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
226 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
227 tri[9]=col
228 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
229 drawn=drawn+1
230 }
231 }
232 ti=ti+1
233 }
234 return drawn
235}
236
237// ===== CAP-SKELETAL-ANIM visual: a cube whose TOP HALF is bound to a bone that rotates -> the mesh BENDS (skinning live) =====
238func mv_rotz(deg: i64, out: *i64) -> i64 {
239 let c: i64 = rc_cos_q14(deg)
240 let s: i64 = rc_sin_q14(deg)
241 var i: i64 = 0
242 while i < 16 { out[i]=0; i=i+1 }
243 out[0]=c; out[1]=0-s; out[4]=s; out[5]=c; out[10]=VQ; out[15]=VQ
244 return 0
245}
246// 2-bone linear blend skinning: ob[obase..obase+3] = w0*(m0*rest) + w1*(m1*rest). (mirrors nx_meshrender mr_skin2.)
247func mv_skin2(rest: *i64, m0: *i64, w0: i64, m1: *i64, w1: i64, tmp: *i64, ob: *i64, obase: i64) -> i64 {
248 rc_mat4_vec4(m0, rest, tmp)
249 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
250 rc_mat4_vec4(m1, rest, tmp)
251 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
252 return 0
253}
254// 6 distinct face colours so the bend reads clearly (computed, no magic packed values).
255func mv_facecol(f: i64) -> i64 {
256 var r: i64=200; var g: i64=200; var b: i64=200
257 if f==0 { r=235; g=70; b=60 }
258 if f==1 { r=70; g=200; b=90 }
259 if f==2 { r=70; g=120; b=235 }
260 if f==3 { r=235; g=205; b=70 }
261 if f==4 { r=210; g=90; b=200 }
262 if f==5 { r=80; g=205; b=210 }
263 return r + g*256 + b*65536 + 255*16777216
264}
265// ===== skinned-normal lighting (mirrors nx_meshrender mr_isqrt/mr_face_lambert) =====
266// brightness from the ACTUAL (skinned/deformed) face geometry: the normal is the cross product of the live edges,
267// so lighting is CORRECT under deformation with NO pre-baked normal. The native math is proven by nx_skinnorm_gate (6/6).
268func mv_isqrt(n: i64) -> i64 { return vm_isqrt(n) }
269func 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 {
270 let e1x: i64=p1x-p0x; let e1y: i64=p1y-p0y; let e1z: i64=p1z-p0z
271 let e2x: i64=p2x-p0x; let e2y: i64=p2y-p0y; let e2z: i64=p2z-p0z
272 let nx: i64=e1y*e2z - e1z*e2y
273 let ny: i64=e1z*e2x - e1x*e2z
274 let nz: i64=e1x*e2y - e1y*e2x
275 let dotnl: i64=nx*lx + ny*ly + nz*lz
276 if dotnl <= 0 { return 0 }
277 let mag2: i64=nx*nx + ny*ny + nz*nz
278 if mag2 <= 0 { return 0 }
279 let m: i64=mv_isqrt(mag2)
280 if m <= 0 { return 0 }
281 return dotnl / m
282}
283// ===== CAP-PBR rung-2: Blinn-Phong specular (mirrors nx_meshrender mr_pow_q14/mr_face_specular) =====
284// proven natively by nx_spec_gate (6/6). Glossy highlight from the (skinned/deformed) face normal + half-vector.
285func mv_pow_q14(base: i64, n: i64) -> i64 {
286 var r: i64 = VQ
287 var i: i64 = 0
288 while i < n {
289 r = r * base / VQ
290 i = i + 1
291 }
292 return r
293}
294func 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 {
295 let e1x: i64=p1x-p0x; let e1y: i64=p1y-p0y; let e1z: i64=p1z-p0z
296 let e2x: i64=p2x-p0x; let e2y: i64=p2y-p0y; let e2z: i64=p2z-p0z
297 let nx: i64=e1y*e2z - e1z*e2y
298 let ny: i64=e1z*e2x - e1x*e2z
299 let nz: i64=e1x*e2y - e1y*e2x
300 let nmag2: i64=nx*nx + ny*ny + nz*nz
301 if nmag2 <= 0 { return 0 }
302 let nmag: i64=mv_isqrt(nmag2)
303 if nmag <= 0 { return 0 }
304 let hx: i64=lx+vx; let hy: i64=ly+vy; let hz: i64=lz+vz
305 let hmag2: i64=hx*hx + hy*hy + hz*hz
306 if hmag2 <= 0 { return 0 }
307 let hmag: i64=mv_isqrt(hmag2)
308 if hmag <= 0 { return 0 }
309 let unx: i64=nx*VQ/nmag; let uny: i64=ny*VQ/nmag; let unz: i64=nz*VQ/nmag
310 let uhx: i64=hx*VQ/hmag; let uhy: i64=hy*VQ/hmag; let uhz: i64=hz*VQ/hmag
311 let ndoth: i64=(unx*uhx + uny*uhy + unz*uhz) / VQ
312 if ndoth <= 0 { return 0 }
313 var nd: i64=ndoth
314 if nd > VQ { nd = VQ }
315 return mv_pow_q14(nd, shin)
316}
317// add a white specular highlight of intensity sp [0,VQ] onto a packed rgba (each channel clamped to 255).
318func mv_add_spec(rgba: i64, sp: i64) -> i64 {
319 let add: i64 = sp * 255 / VQ
320 var r: i64 = rgba % 256
321 var g: i64 = (rgba / 256) % 256
322 var b: i64 = (rgba / 65536) % 256
323 r = r + add; if r > 255 { r = 255 }
324 g = g + add; if g > 255 { g = 255 }
325 b = b + add; if b > 255 { b = 255 }
326 return r + g*256 + b*65536 + 255*16777216
327}
328// render the bending cube. frame -> joint bend angle (oscillates +-50deg); top verts (y>0) follow bone1=rotZ(bend),
329// bottom verts (y<0) stay on bone0=identity -> linear blend skinning deforms the cube. Fixed 3/4 view. z-buffered. EXPORTED.
330// shared skinning + projection for the 3 skincube renderers (flat/lit/spec): clears fb/zb, skins the 8 cube verts
331// by the frame's bend into O_SKIN, and projects them (fixed 25deg view) into O_CSCR. The renderers then differ ONLY
332// in the per-triangle shading (rule 15 DRY -- this was 3x duplicated setup).
333func mv_skincube_xform(frame: i64) -> i64 {
334 let fb: *i64 = (O_FB as i64) as *i64
335 let zb: *i64 = (O_ZB as i64) as *i64
336 rc_clear(fb, MV_W, MV_H, BG)
337 rc_zclear(zb, MV_W, MV_H)
338 mv_cube_scene()
339 let m0: *i64 = (O_M0 as i64) as *i64
340 let m1: *i64 = (O_M1 as i64) as *i64
341 rc_identity_4x4(m0)
342 let bend: i64 = rc_sin_q14(frame*3) * 50 / VQ // oscillate the joint +-50 degrees
343 mv_rotz(bend, m1)
344 let cv: *i64 = (O_CVERTS as i64) as *i64
345 let sk: *i64 = (O_SKIN as i64) as *i64
346 let rest: *i64 = (O_REST as i64) as *i64
347 let stmp: *i64 = (O_STMP as i64) as *i64
348 var vi: i64 = 0
349 while vi < 8 {
350 var w0: i64 = 0
351 if cv[vi*3+1] < 0 { w0 = VQ } // bottom half -> bone0 (static); top half -> bone1 (bend)
352 let w1: i64 = VQ - w0
353 rest[0]=cv[vi*3+0]; rest[1]=cv[vi*3+1]; rest[2]=cv[vi*3+2]; rest[3]=VQ
354 mv_skin2(rest, m0, w0, m1, w1, stmp, sk, vi*4)
355 vi=vi+1
356 }
357 let proj: *i64 = (O_PROJ as i64) as *i64
358 let roty: *i64 = (O_ROTY as i64) as *i64
359 let mvm: *i64 = (O_MV as i64) as *i64
360 let trans: *i64 = (O_TRANS as i64) as *i64
361 let mvp: *i64 = (O_MVP as i64) as *i64
362 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
363 let cc: i64 = rc_cos_q14(25)
364 let ss: i64 = rc_sin_q14(25)
365 var z: i64 = 0
366 while z < 16 { roty[z]=0; z=z+1 }
367 roty[0]=cc; roty[2]=ss; roty[5]=VQ; roty[8]=0-ss; roty[10]=cc; roty[15]=VQ
368 rc_translation_4x4(0, 0, 0-DIST, trans)
369 rc_mat4_mul(trans, roty, mvm)
370 rc_mat4_mul(proj, mvm, mvp)
371 let scr: *i64 = (O_CSCR as i64) as *i64
372 var pi: i64 = 0
373 while pi < 8 { mv_proj1(mvp, sk[pi*4+0], sk[pi*4+1], sk[pi*4+2], scr, pi*4); pi=pi+1 }
374 return 0
375}
376// render the bending cube, FLAT per-face colours. EXPORTED.
377func mv_render_skincube(frame: i64) -> i64 {
378 mv_skincube_xform(frame)
379 let fb: *i64 = (O_FB as i64) as *i64
380 let zb: *i64 = (O_ZB as i64) as *i64
381 let scr: *i64 = (O_CSCR as i64) as *i64
382 let tri: *i64 = (O_TRI as i64) as *i64
383 let tris: *i64 = (O_CTRIS as i64) as *i64
384 var ti: i64 = 0
385 var drawn: i64 = 0
386 while ti < 12 {
387 let a: i64 = tris[ti*7+0]
388 let b: i64 = tris[ti*7+1]
389 let c: i64 = tris[ti*7+2]
390 var vis: i64 = 0
391 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
392 if vis==1 {
393 let col: i64 = mv_facecol(ti/2)
394 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
395 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
396 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
397 tri[9]=col
398 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
399 drawn=drawn+1
400 }
401 ti=ti+1
402 }
403 return drawn
404}
405
406// CAP-SKELETAL-ANIM rung-2 visual: the SAME bending cube, now LIT from its SKINNED normals. Each frame the face
407// normal is recomputed (cross product of the live skinned edges) via mv_face_lambert, so the lighting is CORRECT
408// under the bend deformation -- no pre-baked normal (the gap the flat skincube left open). Verts are passed a,c,b
409// so the cross product yields the OUTWARD normal (the cube winding is inward). Light = upper-right-FRONT (model
410// space). mv_shade attenuates the face colour by the Lambert term (ambient floor 2048). z-buffered. EXPORTED.
411func mv_render_skincube_lit(frame: i64) -> i64 {
412 mv_skincube_xform(frame)
413 let fb: *i64 = (O_FB as i64) as *i64
414 let zb: *i64 = (O_ZB as i64) as *i64
415 let scr: *i64 = (O_CSCR as i64) as *i64
416 let sk: *i64 = (O_SKIN as i64) as *i64
417 let tri: *i64 = (O_TRI as i64) as *i64
418 let tris: *i64 = (O_CTRIS as i64) as *i64
419 let LX: i64 = 8192
420 let LY: i64 = 8192
421 let LZ: i64 = 11585
422 var ti: i64 = 0
423 var drawn: i64 = 0
424 while ti < 12 {
425 let a: i64 = tris[ti*7+0]
426 let b: i64 = tris[ti*7+1]
427 let c: i64 = tris[ti*7+2]
428 var vis: i64 = 0
429 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
430 if vis==1 {
431 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)
432 let col: i64 = mv_shade(mv_facecol(ti/2), br, 2048)
433 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
434 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
435 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
436 tri[9]=col
437 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
438 drawn=drawn+1
439 }
440 ti=ti+1
441 }
442 return drawn
443}
444
445// CAP-PBR rung-2 visual: the lit bending cube + a Blinn-Phong SPECULAR highlight (mv_face_specular over the skinned
446// normal + the model-space view dir) -> a GLOSSY cube. Specular recomputed per pose so the highlight tracks the bend.
447// Verts a,c,b for the OUTWARD normal (cube winding is inward); V = the camera dir in model space (undo the 25deg view
448// turn). z-buffered. EXPORTED.
449func mv_render_skincube_spec(frame: i64) -> i64 {
450 mv_skincube_xform(frame)
451 let fb: *i64 = (O_FB as i64) as *i64
452 let zb: *i64 = (O_ZB as i64) as *i64
453 let scr: *i64 = (O_CSCR as i64) as *i64
454 let sk: *i64 = (O_SKIN as i64) as *i64
455 let tri: *i64 = (O_TRI as i64) as *i64
456 let tris: *i64 = (O_CTRIS as i64) as *i64
457 let LX: i64 = 8192
458 let LY: i64 = 8192
459 let LZ: i64 = 11585
460 let VX: i64 = 0 - rc_sin_q14(25)
461 let VY: i64 = 0
462 let VZ: i64 = rc_cos_q14(25)
463 var ti: i64 = 0
464 var drawn: i64 = 0
465 while ti < 12 {
466 let a: i64 = tris[ti*7+0]
467 let b: i64 = tris[ti*7+1]
468 let c: i64 = tris[ti*7+2]
469 var vis: i64 = 0
470 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
471 if vis==1 {
472 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)
473 var col: i64 = mv_shade(mv_facecol(ti/2), br, 2048)
474 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)
475 col = mv_add_spec(col, sp)
476 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
477 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
478 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
479 tri[9]=col
480 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
481 drawn=drawn+1
482 }
483 ti=ti+1
484 }
485 return drawn
486}
487
488// ===== CAP-PBR rung-3: smooth Gouraud shading on a curved mesh (sphere) -- per-VERTEX unit normals + the
489// rc_triangle barycentric colour interpolation = a ROUND, smoothly-shaded ball (no facets). =====
490// specular from a unit normal directly (per-vertex Gouraud; no cross product needed -- the normal IS the geometry).
491func mv_normal_specular(nx: i64, ny: i64, nz: i64, lx: i64, ly: i64, lz: i64, vx: i64, vy: i64, vz: i64, shin: i64) -> i64 {
492 let hx: i64=lx+vx; let hy: i64=ly+vy; let hz: i64=lz+vz
493 let hmag2: i64=hx*hx + hy*hy + hz*hz
494 if hmag2 <= 0 { return 0 }
495 let hmag: i64=mv_isqrt(hmag2)
496 if hmag <= 0 { return 0 }
497 let uhx: i64=hx*VQ/hmag; let uhy: i64=hy*VQ/hmag; let uhz: i64=hz*VQ/hmag
498 let ndoth: i64=(nx*uhx + ny*uhy + nz*uhz) / VQ
499 if ndoth <= 0 { return 0 }
500 var nd: i64=ndoth
501 if nd > VQ { nd = VQ }
502 return mv_pow_q14(nd, shin)
503}
504// build the UV sphere: per-vertex unit normal (= position/R) + position + the triangle indices (poles -> a few
505// degenerate tris that raster nothing; harmless).
506func mv_sphere_scene() -> i64 {
507 let v: *i64 = (O_SPV as i64) as *i64
508 let nrm: *i64 = (O_SPN as i64) as *i64
509 var i: i64 = 0
510 while i <= SP_RINGS {
511 let lat: i64 = i * 180 / SP_RINGS
512 let clat: i64 = rc_cos_q14(lat)
513 let slat: i64 = rc_sin_q14(lat)
514 var j: i64 = 0
515 while j <= SP_SECT {
516 let lon: i64 = j * 360 / SP_SECT
517 let clon: i64 = rc_cos_q14(lon)
518 let slon: i64 = rc_sin_q14(lon)
519 let nxx: i64 = slat * clon / VQ
520 let nyy: i64 = clat
521 let nzz: i64 = slat * slon / VQ
522 let idx: i64 = i * (SP_SECT + 1) + j
523 nrm[idx*3+0]=nxx; nrm[idx*3+1]=nyy; nrm[idx*3+2]=nzz
524 v[idx*3+0]=nxx*SP_R/VQ; v[idx*3+1]=nyy*SP_R/VQ; v[idx*3+2]=nzz*SP_R/VQ
525 j = j + 1
526 }
527 i = i + 1
528 }
529 let id: *i64 = (O_SPI as i64) as *i64
530 var ti: i64 = 0
531 var ri: i64 = 0
532 while ri < SP_RINGS {
533 var si: i64 = 0
534 while si < SP_SECT {
535 let a: i64 = ri * (SP_SECT+1) + si
536 let bb: i64 = (ri+1) * (SP_SECT+1) + si
537 let cd: i64 = (ri+1) * (SP_SECT+1) + (si+1)
538 let dd: i64 = ri * (SP_SECT+1) + (si+1)
539 id[ti*3+0]=a; id[ti*3+1]=bb; id[ti*3+2]=cd; ti=ti+1
540 id[ti*3+0]=a; id[ti*3+1]=cd; id[ti*3+2]=dd; ti=ti+1
541 si = si + 1
542 }
543 ri = ri + 1
544 }
545 return 0
546}
547// prep: scene + static view mvp + per-vertex Gouraud colour (lit+spec, with the LIGHT orbited by `angle` so a glint
548// sweeps the static sphere) -> O_SPC, and projected verts -> O_SPS.
549// shared sphere geometry (angle-independent: the sphere is STATIC, only the light orbits): scene + static-view mvp
550// + project all verts -> O_SPS. Used by the Gouraud/flat prep AND the Phong renderer (rule 15 DRY).
551func mv_sphere_xform() -> i64 {
552 mv_sphere_scene()
553 let proj: *i64 = (O_PROJ as i64) as *i64
554 let trans: *i64 = (O_TRANS as i64) as *i64
555 let mvp: *i64 = (O_MVP as i64) as *i64
556 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
557 rc_translation_4x4(0, 0, 0-DIST, trans)
558 rc_mat4_mul(proj, trans, mvp)
559 let v: *i64 = (O_SPV as i64) as *i64
560 let scr: *i64 = (O_SPS as i64) as *i64
561 var k: i64 = 0
562 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 }
563 return 0
564}
565// ===== R4: GENERIC arbitrary-mesh renderer (the in-browser STL-viewer core) =====
566// renders ANY triangle mesh held in caller-supplied linear-memory buffers: per-vertex unit normals -> Gouraud lit+
567// spec colours, static view, light orbited by `angle`. vbuf/nbuf/cbuf/sbuf/ibuf = byte offsets; nverts/ntris =
568// counts; base = material rgba; mode 0 = smooth Gouraud (rc_triangle), mode 1 = flat (rc_triangle_flat). The sphere
569// + torus + (next) a loaded STL all route through THIS one function (rule 15 DRY). EXPORTED accessors below let a
570// host write geometry into the buffers. Returns triangles drawn.
571func mv_render_mesh(angle: i64, vbuf: i64, nbuf: i64, cbuf: i64, sbuf: i64, ibuf: i64, nverts: i64, ntris: i64, base: i64, mode: i64) -> i64 {
572 let fb: *i64 = (O_FB as i64) as *i64
573 let zb: *i64 = (O_ZB as i64) as *i64
574 rc_clear(fb, MV_W, MV_H, BG)
575 rc_zclear(zb, MV_W, MV_H)
576 let proj: *i64 = (O_PROJ as i64) as *i64
577 let trans: *i64 = (O_TRANS as i64) as *i64
578 let mvp: *i64 = (O_MVP as i64) as *i64
579 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
580 rc_translation_4x4(0, 0, 0-DIST, trans)
581 rc_mat4_mul(proj, trans, mvp)
582 let cc: i64 = rc_cos_q14(angle)
583 let ss: i64 = rc_sin_q14(angle)
584 let LX: i64 = (cc*8192 + ss*11585) / VQ
585 let LY: i64 = 8192
586 let LZ: i64 = ((0-ss)*8192 + cc*11585) / VQ
587 let v: *i64 = (vbuf as i64) as *i64
588 let nrm: *i64 = (nbuf as i64) as *i64
589 let col: *i64 = (cbuf as i64) as *i64
590 let scr: *i64 = (sbuf as i64) as *i64
591 var k: i64 = 0
592 while k < nverts {
593 let nxx: i64 = nrm[k*3+0]
594 let nyy: i64 = nrm[k*3+1]
595 let nzz: i64 = nrm[k*3+2]
596 let br: i64 = mv_lambert(nxx,nyy,nzz, LX,LY,LZ)
597 var c0: i64 = mv_shade(base, br, 2048)
598 let sp: i64 = mv_normal_specular(nxx,nyy,nzz, LX,LY,LZ, 0,0,VQ, 24)
599 c0 = mv_add_spec(c0, sp)
600 col[k] = c0
601 mv_proj1(mvp, v[k*3+0], v[k*3+1], v[k*3+2], scr, k*4)
602 k = k + 1
603 }
604 let id: *i64 = (ibuf as i64) as *i64
605 let tri: *i64 = (O_TRI as i64) as *i64
606 var ti: i64 = 0
607 var drawn: i64 = 0
608 while ti < ntris {
609 let a: i64 = id[ti*3+0]
610 let b: i64 = id[ti*3+1]
611 let c: i64 = id[ti*3+2]
612 var vis: i64 = 0
613 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
614 if vis==1 {
615 if mode==0 {
616 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]; tri[3]=col[a]
617 tri[4]=scr[b*4+0]; tri[5]=scr[b*4+1]; tri[6]=scr[b*4+2]; tri[7]=col[b]
618 tri[8]=scr[c*4+0]; tri[9]=scr[c*4+1]; tri[10]=scr[c*4+2]; tri[11]=col[c]
619 rc_triangle(fb, zb, MV_W, MV_H, tri)
620 }
621 if mode==1 {
622 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]
623 tri[3]=scr[b*4+0]; tri[4]=scr[b*4+1]; tri[5]=scr[b*4+2]
624 tri[6]=scr[c*4+0]; tri[7]=scr[c*4+1]; tri[8]=scr[c*4+2]
625 tri[9]=col[a]
626 rc_triangle_flat(fb, zb, MV_W, MV_H, tri)
627 }
628 drawn=drawn+1
629 }
630 ti=ti+1
631 }
632 return drawn
633}
634// byte offsets of the generic mesh buffers (a host writes geometry here, then calls mv_render_loaded). EXPORTED.
635func mv_meshv_off() -> i64 { return O_MVV }
636func mv_meshn_off() -> i64 { return O_MVN }
637func mv_meshi_off() -> i64 { return O_MVI }
638// build a low-poly UV TORUS (genus-1 -- a topology a cube/sphere can't fake) into the generic mesh buffers.
639func mv_torus_scene() -> i64 {
640 let v: *i64 = (O_MVV as i64) as *i64
641 let nrm: *i64 = (O_MVN as i64) as *i64
642 var i: i64 = 0
643 while i < TR_SECT {
644 let u: i64 = i * 360 / TR_SECT
645 let cu: i64 = rc_cos_q14(u)
646 let su: i64 = rc_sin_q14(u)
647 var j: i64 = 0
648 while j < TR_RING {
649 let vv: i64 = j * 360 / TR_RING
650 let cvv: i64 = rc_cos_q14(vv)
651 let svv: i64 = rc_sin_q14(vv)
652 let rr: i64 = TR_MAJ + TR_MIN * cvv / VQ
653 let idx: i64 = i * TR_RING + j
654 v[idx*3+0] = rr * cu / VQ
655 v[idx*3+1] = TR_MIN * svv / VQ
656 v[idx*3+2] = rr * su / VQ
657 nrm[idx*3+0] = cvv * cu / VQ
658 nrm[idx*3+1] = svv
659 nrm[idx*3+2] = cvv * su / VQ
660 j = j + 1
661 }
662 i = i + 1
663 }
664 let id: *i64 = (O_MVI as i64) as *i64
665 var ti: i64 = 0
666 var si: i64 = 0
667 while si < TR_SECT {
668 let si2: i64 = (si + 1) % TR_SECT
669 var ri: i64 = 0
670 while ri < TR_RING {
671 let ri2: i64 = (ri + 1) % TR_RING
672 let a: i64 = si * TR_RING + ri
673 let b: i64 = si2 * TR_RING + ri
674 let c: i64 = si2 * TR_RING + ri2
675 let d: i64 = si * TR_RING + ri2
676 id[ti*3+0]=a; id[ti*3+1]=b; id[ti*3+2]=c; ti=ti+1
677 id[ti*3+0]=a; id[ti*3+1]=c; id[ti*3+2]=d; ti=ti+1
678 ri = ri + 1
679 }
680 si = si + 1
681 }
682 return 0
683}
684// render the torus via the generic mesh renderer. EXPORTED.
685func mv_render_torus(angle: i64) -> i64 {
686 mv_torus_scene()
687 return mv_render_mesh(angle, O_MVV, O_MVN, O_MVC, O_MVS, O_MVI, TR_NV, TR_NT, MESH_BASE, 0)
688}
689// render whatever geometry a host has written into the generic buffers (nverts/ntris supplied). EXPORTED.
690func mv_render_loaded(angle: i64, nverts: i64, ntris: i64) -> i64 {
691 return mv_render_mesh(angle, O_MVV, O_MVN, O_MVC, O_MVS, O_MVI, nverts, ntris, MESH_BASE, 0)
692}
693// Track-E rung-1: INTERACTIVE ORBIT VIEWPORT. The CAMERA orbits the loaded mesh -- model rotated by `yaw` (about Y)
694// and pushed `dist` down -Z (zoom), so the object spins on screen + scales as the page drives yaw from mouse-drag
695// and dist from scroll. The per-vertex normal is rotated by yaw too, so the world-fixed light stays put while you
696// orbit (correct orbit-camera lighting). Gouraud, z-buffered. EXPORTED. (CLEANUP DEBT: the per-vertex shade+project
697// and raster loops mirror mv_render_mesh -> extract mv_shade_project_verts + mv_raster_indexed next, route both.)
698func mv_render_loaded_cam(yaw: i64, dist: i64, nverts: i64, ntris: i64) -> i64 {
699 let fb: *i64 = (O_FB as i64) as *i64
700 let zb: *i64 = (O_ZB as i64) as *i64
701 rc_clear(fb, MV_W, MV_H, BG)
702 rc_zclear(zb, MV_W, MV_H)
703 let proj: *i64 = (O_PROJ as i64) as *i64
704 let roty: *i64 = (O_ROTY as i64) as *i64
705 let mvm: *i64 = (O_MV as i64) as *i64
706 let trans: *i64 = (O_TRANS as i64) as *i64
707 let mvp: *i64 = (O_MVP as i64) as *i64
708 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), MV_W*VQ/MV_H, VQ, FARQ, proj)
709 let cc: i64 = rc_cos_q14(yaw)
710 let ss: i64 = rc_sin_q14(yaw)
711 var z: i64 = 0
712 while z < 16 { roty[z]=0; z=z+1 }
713 roty[0]=cc; roty[2]=ss; roty[5]=VQ; roty[8]=0-ss; roty[10]=cc; roty[15]=VQ
714 rc_translation_4x4(0, 0, 0-dist, trans)
715 rc_mat4_mul(trans, roty, mvm)
716 rc_mat4_mul(proj, mvm, mvp)
717 let LX: i64 = 8192
718 let LY: i64 = 8192
719 let LZ: i64 = 11585
720 let v: *i64 = (O_MVV as i64) as *i64
721 let nrm: *i64 = (O_MVN as i64) as *i64
722 let col: *i64 = (O_MVC as i64) as *i64
723 let scr: *i64 = (O_MVS as i64) as *i64
724 var k: i64 = 0
725 while k < nverts {
726 let rnx: i64 = (cc*nrm[k*3+0] + ss*nrm[k*3+2]) / VQ
727 let rny: i64 = nrm[k*3+1]
728 let rnz: i64 = ((0-ss)*nrm[k*3+0] + cc*nrm[k*3+2]) / VQ
729 let br: i64 = mv_lambert(rnx,rny,rnz, LX,LY,LZ)
730 var c0: i64 = mv_shade(MESH_BASE, br, 2048)
731 let sp: i64 = mv_normal_specular(rnx,rny,rnz, LX,LY,LZ, 0,0,VQ, 24)
732 c0 = mv_add_spec(c0, sp)
733 col[k] = c0
734 mv_proj1(mvp, v[k*3+0], v[k*3+1], v[k*3+2], scr, k*4)
735 k = k + 1
736 }
737 let id: *i64 = (O_MVI as i64) as *i64
738 let tri: *i64 = (O_TRI as i64) as *i64
739 var ti: i64 = 0
740 var drawn: i64 = 0
741 while ti < ntris {
742 let a: i64 = id[ti*3+0]
743 let b: i64 = id[ti*3+1]
744 let c: i64 = id[ti*3+2]
745 var vis: i64 = 0
746 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
747 if vis==1 {
748 tri[0]=scr[a*4+0]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]; tri[3]=col[a]
749 tri[4]=scr[b*4+0]; tri[5]=scr[b*4+1]; tri[6]=scr[b*4+2]; tri[7]=col[b]
750 tri[8]=scr[c*4+0]; tri[9]=scr[c*4+1]; tri[10]=scr[c*4+2]; tri[11]=col[c]
751 rc_triangle(fb, zb, MV_W, MV_H, tri)
752 drawn=drawn+1
753 }
754 ti=ti+1
755 }
756 return drawn
757}
758// smooth Gouraud sphere -- routes through the generic mesh renderer (mode 0). EXPORTED.
759func mv_render_sphere(angle: i64) -> i64 {
760 mv_sphere_scene()
761 return mv_render_mesh(angle, O_SPV, O_SPN, O_SPC, O_SPS, O_SPI, SP_NV, SP_NT, SPHERE_BASE, 0)
762}
763// flat sphere (mode 1) -- the gate's negative control proving smooth shading really interpolates. EXPORTED.
764func mv_render_sphere_flat(angle: i64) -> i64 {
765 mv_sphere_scene()
766 return mv_render_mesh(angle, O_SPV, O_SPN, O_SPC, O_SPS, O_SPI, SP_NV, SP_NT, SPHERE_BASE, 1)
767}
768
769// CAP-PBR rung-4: true PHONG sphere -- per-pixel normal interpolation + shading via rc_triangle_phong. Same sphere
770// + orbited light as Gouraud, but the highlight is computed at EVERY pixel -> sharp + correctly placed between
771// vertices. EXPORTED. The per-vertex NORMALS (not colours) feed the rasterizer; L,V,base,shin,amb ride in pb too.
772// shared per-pixel-Phong sphere draw. bump=0 -> smooth Phong; bump>0 -> procedural per-pixel normal perturbation
773// (a bump map: surface detail with NO extra geometry).
774func mv_sphere_phong_draw(angle: i64, bump: i64) -> i64 {
775 let fb: *i64 = (O_FB as i64) as *i64
776 let zb: *i64 = (O_ZB as i64) as *i64
777 rc_clear(fb, MV_W, MV_H, BG)
778 rc_zclear(zb, MV_W, MV_H)
779 mv_sphere_xform()
780 let cc: i64 = rc_cos_q14(angle)
781 let ss: i64 = rc_sin_q14(angle)
782 let nrm: *i64 = (O_SPN as i64) as *i64
783 let scr: *i64 = (O_SPS as i64) as *i64
784 let id: *i64 = (O_SPI as i64) as *i64
785 let pb: *i64 = (O_PBUF as i64) as *i64
786 pb[18] = (cc*8192 + ss*11585) / VQ
787 pb[19] = 8192
788 pb[20] = ((0-ss)*8192 + cc*11585) / VQ
789 pb[21] = 0; pb[22] = 0; pb[23] = VQ
790 pb[24] = SPHERE_BASE; pb[25] = 24; pb[26] = 2048; pb[27] = bump
791 var ti: i64 = 0
792 var drawn: i64 = 0
793 while ti < SP_NT {
794 let a: i64 = id[ti*3+0]
795 let b: i64 = id[ti*3+1]
796 let c: i64 = id[ti*3+2]
797 var vis: i64 = 0
798 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
799 if vis==1 {
800 pb[0]=scr[a*4+0]; pb[1]=scr[a*4+1]; pb[2]=scr[a*4+2]
801 pb[3]=scr[b*4+0]; pb[4]=scr[b*4+1]; pb[5]=scr[b*4+2]
802 pb[6]=scr[c*4+0]; pb[7]=scr[c*4+1]; pb[8]=scr[c*4+2]
803 pb[9]=nrm[a*3+0]; pb[10]=nrm[a*3+1]; pb[11]=nrm[a*3+2]
804 pb[12]=nrm[b*3+0]; pb[13]=nrm[b*3+1]; pb[14]=nrm[b*3+2]
805 pb[15]=nrm[c*3+0]; pb[16]=nrm[c*3+1]; pb[17]=nrm[c*3+2]
806 rc_triangle_phong(fb, zb, MV_W, MV_H, pb)
807 drawn=drawn+1
808 }
809 ti=ti+1
810 }
811 return drawn
812}
813// smooth Phong sphere (bump=0). EXPORTED.
814func mv_render_sphere_phong(angle: i64) -> i64 { return mv_sphere_phong_draw(angle, 0) }
815// CAP-PBR rung-5: bump-mapped sphere -- per-pixel procedural normal perturbation adds SURFACE DETAIL (a dimpled,
816// textured look) that catches the orbiting light, with NO extra geometry / no extra triangles. EXPORTED.
817func mv_render_sphere_nmap(angle: i64) -> i64 { return mv_sphere_phong_draw(angle, 1200) }
818
819// ============================== R5v2: DANCE VIEWER (ws=dance-motion 2026-08-10) ==============================
820// SOVEREIGN in-page player, v2 wire format 'NXV2': adds per-vertex SECOND joint + Q14 blend weight so the
821// skin BLENDS across joints (v1's rigid one-joint bind tore into spikes at real dance angles -- operator-
822// observed live), and raises the stage to 640x480. The page fetches the .nxdv blob, copies it VERBATIM to
823// dv_blob_off, and calls mv_dance_render(t_ms, yaw_deg, pitch_deg, dist_q14) per frame; blit O_DFB.
824// Blob image (fixed offsets ARE the wire format; emitter = nx_dance_emit, oracle = build_blob.js):
825// header 8 i64 [magic 'NXV2'=0x3256584E, nverts, ntris, nkeys, key_ms, njoint, 0, 0]
826// verts Q14 x3 | normals Q14 x3 | colours rgba | joint1 | joint2 | weight1 Q14 | indices x3
827// parent-slot x nj | local translations Q14 x3 | inverse-bind mat4 Q14 | tracks key-major [key][bone][xyzw]
828// REFUSES (-1) on bad magic / counts over capacity. Own carve, ZERO overlap with the legacy 64x64 paths.
829const DV_W: i64 = 640
830const DV_H: i64 = 480
831const DV_MAXV: i64 = 7000
832const DV_MAXT: i64 = 14000
833const DV_NJ: i64 = 11
834const DV_MAXK: i64 = 128
835const DV_MAGIC: i64 = 0x3256584E
836const DV_DIST_MIN: i64 = 16384
837const DV_DIST_MAX: i64 = 524288
838
839const O_DFB: i64 = 131072
840const O_DZB: i64 = 2588672
841const O_DHDR: i64 = 5046272
842const O_DV: i64 = 5046336
843const O_DN: i64 = 5214336
844const O_DC: i64 = 5382336
845const O_DJ: i64 = 5438336
846const O_DJ2: i64 = 5494336
847const O_DWT: i64 = 5550336
848const O_DI: i64 = 5606336
849const O_DPAR: i64 = 5942336
850const O_DJT: i64 = 5942424
851const O_DIBM: i64 = 5942688
852const O_DTRK: i64 = 5944096
853const O_DSK: i64 = 5976864
854const O_DS: i64 = 6200864
855const O_DCL: i64 = 6424864
856const O_DPAL: i64 = 6480864
857const O_DWLD: i64 = 6482272
858const O_DLOC: i64 = 6483680
859const O_DQ: i64 = 6483808
860const O_DRX: i64 = 6483840
861const O_DRY: i64 = 6483968
862const O_DROT: i64 = 6484096
863const O_DPRJ: i64 = 6484224
864const O_DMV: i64 = 6484352
865const O_DMVP: i64 = 6484480
866const O_DVB: i64 = 6484608
867const O_DVC: i64 = 6484640
868const O_DVD: i64 = 6484672
869const O_DVE: i64 = 6484704
870
871func dv_w() -> i64 { return DV_W }
872func dv_h() -> i64 { return DV_H }
873func dv_fb_off() -> i64 { return O_DFB }
874func dv_blob_off() -> i64 { return O_DHDR }
875func dv_blob_len() -> i64 { return O_DSK - O_DHDR }
876func dv_duration_ms() -> i64 {
877 let h: *i64 = (O_DHDR as i64) as *i64
878 return h[3] * h[4]
879}
880
881// Q14 quaternion (x,y,z,w in q[0..3]) + Q14 translation -> row-major Q14 mat4 (rc_mat4_vec4 convention:
882// rotation rows 0..2, translation in column 3, m[15]=VQ).
883func dv_quatmat(q: *i64, tx: i64, ty: i64, tz: i64, out: *i64) -> i64 {
884 let xx: i64 = q[0]*q[0]/VQ
885 let yy: i64 = q[1]*q[1]/VQ
886 let zz: i64 = q[2]*q[2]/VQ
887 let xy: i64 = q[0]*q[1]/VQ
888 let xz: i64 = q[0]*q[2]/VQ
889 let yz: i64 = q[1]*q[2]/VQ
890 let wx: i64 = q[3]*q[0]/VQ
891 let wy: i64 = q[3]*q[1]/VQ
892 let wz: i64 = q[3]*q[2]/VQ
893 out[0] = VQ - 2*(yy+zz)
894 out[1] = 2*(xy-wz)
895 out[2] = 2*(xz+wy)
896 out[3] = tx
897 out[4] = 2*(xy+wz)
898 out[5] = VQ - 2*(xx+zz)
899 out[6] = 2*(yz-wx)
900 out[7] = ty
901 out[8] = 2*(xz-wy)
902 out[9] = 2*(yz+wx)
903 out[10] = VQ - 2*(xx+yy)
904 out[11] = tz
905 out[12] = 0
906 out[13] = 0
907 out[14] = 0
908 out[15] = VQ
909 return 0
910}
911
912// sample bone (0..7) at t_ms: shortest-path nlerp between neighbouring keys, renormalized to Q14 via
913// mv_isqrt -- an un-normalized quat scales the skin matrix by |q|^2 and the body visibly breathes.
914func dv_sample(bone: i64, tms: i64, qo: *i64) -> i64 {
915 let h: *i64 = (O_DHDR as i64) as *i64
916 let nkeys: i64 = h[3]
917 let keyms: i64 = h[4]
918 let trk: *i64 = (O_DTRK as i64) as *i64
919 var k: i64 = tms / keyms
920 if k < 0 { k = 0 }
921 var f: i64 = 0
922 if k >= nkeys - 1 { k = nkeys - 1 }
923 else { f = (tms - k*keyms) * VQ / keyms }
924 let a: i64 = (k*8 + bone) * 4
925 var b: i64 = a
926 if k < nkeys - 1 { b = ((k+1)*8 + bone) * 4 }
927 let dot: i64 = trk[a]*trk[b] + trk[a+1]*trk[b+1] + trk[a+2]*trk[b+2] + trk[a+3]*trk[b+3]
928 var sg: i64 = 1
929 if dot < 0 { sg = 0 - 1 }
930 let g: i64 = VQ - f
931 qo[0] = (trk[a]*g + sg*trk[b]*f) / VQ
932 qo[1] = (trk[a+1]*g + sg*trk[b+1]*f) / VQ
933 qo[2] = (trk[a+2]*g + sg*trk[b+2]*f) / VQ
934 qo[3] = (trk[a+3]*g + sg*trk[b+3]*f) / VQ
935 let n2: i64 = qo[0]*qo[0] + qo[1]*qo[1] + qo[2]*qo[2] + qo[3]*qo[3]
936 let nn: i64 = mv_isqrt(n2)
937 if nn > 0 { qo[0]=qo[0]*VQ/nn; qo[1]=qo[1]*VQ/nn; qo[2]=qo[2]*VQ/nn; qo[3]=qo[3]*VQ/nn }
938 return 0
939}
940
941// joint palette at t_ms: world[slot] = world[parent] * (T(local) * R(track)); pal = world * IBM.
942// Slots are TOPOLOGICALLY ordered by the emitter (parent < child), so one forward pass suffices.
943func dv_palette(tms: i64) -> i64 {
944 let h: *i64 = (O_DHDR as i64) as *i64
945 let nj: i64 = h[5]
946 let par: *i64 = (O_DPAR as i64) as *i64
947 let jt: *i64 = (O_DJT as i64) as *i64
948 let wld: *i64 = (O_DWLD as i64) as *i64
949 let loc: *i64 = (O_DLOC as i64) as *i64
950 let q: *i64 = (O_DQ as i64) as *i64
951 var s: i64 = 0
952 while s < nj {
953 q[0]=0; q[1]=0; q[2]=0; q[3]=VQ
954 if s >= 1 { if s <= 8 { dv_sample(s-1, tms, q) } }
955 dv_quatmat(q, jt[s*3], jt[s*3+1], jt[s*3+2], loc)
956 let wbase: i64 = s*16
957 if par[s] < 0 {
958 var ci: i64 = 0
959 while ci < 16 { wld[wbase+ci] = loc[ci]; ci = ci + 1 }
960 } else {
961 rc_mat4_mul(((O_DWLD + par[s]*128) as i64) as *i64, loc, ((O_DWLD + s*128) as i64) as *i64)
962 }
963 rc_mat4_mul(((O_DWLD + s*128) as i64) as *i64, ((O_DIBM + s*128) as i64) as *i64, ((O_DPAL + s*128) as i64) as *i64)
964 s = s + 1
965 }
966 return 0
967}
968
969// project one Q14 world vertex through O_DMVP into the DANCE screen buffer (own scratch, own W/H).
970func dv_proj1(vx: i64, vy: i64, vz: i64, scr: *i64, base: i64) -> i64 {
971 let mvp: *i64 = (O_DMVP as i64) as *i64
972 let vb: *i64 = (O_DVB as i64) as *i64
973 let cl: *i64 = (O_DVC as i64) as *i64
974 vb[0]=vx; vb[1]=vy; vb[2]=vz; vb[3]=VQ
975 rc_mat4_vec4(mvp, vb, cl)
976 let cw: i64 = cl[3]
977 if cw <= 0 { scr[base+3]=0; return 0 }
978 scr[base+0] = (cl[0]*VQ/cw + VQ) * DV_W / (2*VQ)
979 scr[base+1] = (VQ - cl[1]*VQ/cw) * DV_H / (2*VQ)
980 scr[base+2] = cl[2]*VQ/cw + VQ
981 scr[base+3] = 1
982 return 0
983}
984
985// THE EXPORTED FRAME: render the loaded skinned being at t_ms with an orbit camera.
986// yaw/pitch in integer degrees, dist Q14 (16384 = 1.0; clamped). Returns triangles drawn, -1 REFUSED.
987func mv_dance_render(tms: i64, yaw: i64, pitch: i64, dist: i64) -> i64 {
988 let h: *i64 = (O_DHDR as i64) as *i64
989 if h[0] != DV_MAGIC { return 0 - 1 }
990 let nverts: i64 = h[1]
991 let ntris: i64 = h[2]
992 if nverts < 3 { return 0 - 1 }
993 if nverts > DV_MAXV { return 0 - 1 }
994 if ntris < 1 { return 0 - 1 }
995 if ntris > DV_MAXT { return 0 - 1 }
996 if h[3] < 2 { return 0 - 1 }
997 if h[3] > DV_MAXK { return 0 - 1 }
998 if h[4] < 1 { return 0 - 1 }
999 if h[5] < 1 { return 0 - 1 }
1000 if h[5] > DV_NJ { return 0 - 1 }
1001 var dd: i64 = dist
1002 if dd < DV_DIST_MIN { dd = DV_DIST_MIN }
1003 if dd > DV_DIST_MAX { dd = DV_DIST_MAX }
1004 let fb: *i64 = (O_DFB as i64) as *i64
1005 let zb: *i64 = (O_DZB as i64) as *i64
1006 rc_clear(fb, DV_W, DV_H, BG)
1007 rc_zclear(zb, DV_W, DV_H)
1008 dv_palette(tms)
1009 // camera: rot = rotX(pitch) * rotY(yaw); mv = T(0,0,-dist) * rot; mvp = proj * mv
1010 let rx: *i64 = (O_DRX as i64) as *i64
1011 let ry: *i64 = (O_DRY as i64) as *i64
1012 let rot: *i64 = (O_DROT as i64) as *i64
1013 let prj: *i64 = (O_DPRJ as i64) as *i64
1014 let mvm: *i64 = (O_DMV as i64) as *i64
1015 let trans: *i64 = (O_TRANS as i64) as *i64
1016 let mvp: *i64 = (O_DMVP as i64) as *i64
1017 var zi: i64 = 0
1018 while zi < 16 { rx[zi]=0; ry[zi]=0; zi=zi+1 }
1019 let cy: i64 = rc_cos_q14(yaw)
1020 let sy: i64 = rc_sin_q14(yaw)
1021 ry[0]=cy; ry[2]=sy; ry[5]=VQ; ry[8]=0-sy; ry[10]=cy; ry[15]=VQ
1022 let cp: i64 = rc_cos_q14(pitch)
1023 let sp: i64 = rc_sin_q14(pitch)
1024 rx[0]=VQ; rx[5]=cp; rx[6]=0-sp; rx[9]=sp; rx[10]=cp; rx[15]=VQ
1025 rc_mat4_mul(rx, ry, rot)
1026 rc_perspective_cs(rc_cos_q14(FOVH), rc_sin_q14(FOVH), DV_W*VQ/DV_H, VQ, FARQ, prj)
1027 rc_translation_4x4(0, 0, 0-dd, trans)
1028 rc_mat4_mul(trans, rot, mvm)
1029 rc_mat4_mul(prj, mvm, mvp)
1030 // skin: TWO-BONE blend, p = (w*M1 + (VQ-w)*M2) applied to rest pos and normal (w=0 kills translation
1031 // for the normal). Then camera rotation for view-space lighting -- light fixed (.5,.5,.707) Q14.
1032 let v: *i64 = (O_DV as i64) as *i64
1033 let nrm: *i64 = (O_DN as i64) as *i64
1034 let colv: *i64 = (O_DC as i64) as *i64
1035 let jv: *i64 = (O_DJ as i64) as *i64
1036 let j2v: *i64 = (O_DJ2 as i64) as *i64
1037 let wtv: *i64 = (O_DWT as i64) as *i64
1038 let sk: *i64 = (O_DSK as i64) as *i64
1039 let scr: *i64 = (O_DS as i64) as *i64
1040 let lit: *i64 = (O_DCL as i64) as *i64
1041 let vb: *i64 = (O_DVB as i64) as *i64
1042 let vc: *i64 = (O_DVC as i64) as *i64
1043 let vd: *i64 = (O_DVD as i64) as *i64
1044 let ve: *i64 = (O_DVE as i64) as *i64
1045 let LX: i64 = 8192
1046 let LY: i64 = 8192
1047 let LZ: i64 = 11585
1048 var k: i64 = 0
1049 while k < nverts {
1050 var j1: i64 = jv[k]
1051 if j1 < 0 { j1 = 0 }
1052 if j1 >= h[5] { j1 = 0 }
1053 var j2: i64 = j2v[k]
1054 if j2 < 0 { j2 = j1 }
1055 if j2 >= h[5] { j2 = j1 }
1056 var w1: i64 = wtv[k]
1057 if w1 < 0 { w1 = 0 }
1058 if w1 > VQ { w1 = VQ }
1059 let w2: i64 = VQ - w1
1060 let pm1: *i64 = ((O_DPAL + j1*128) as i64) as *i64
1061 let pm2: *i64 = ((O_DPAL + j2*128) as i64) as *i64
1062 vb[0]=v[k*3]; vb[1]=v[k*3+1]; vb[2]=v[k*3+2]; vb[3]=VQ
1063 rc_mat4_vec4(pm1, vb, vc)
1064 rc_mat4_vec4(pm2, vb, vd)
1065 sk[k*4] = (w1*vc[0] + w2*vd[0]) / VQ
1066 sk[k*4+1] = (w1*vc[1] + w2*vd[1]) / VQ
1067 sk[k*4+2] = (w1*vc[2] + w2*vd[2]) / VQ
1068 sk[k*4+3] = VQ
1069 vb[0]=nrm[k*3]; vb[1]=nrm[k*3+1]; vb[2]=nrm[k*3+2]; vb[3]=0
1070 rc_mat4_vec4(pm1, vb, vc)
1071 rc_mat4_vec4(pm2, vb, vd)
1072 ve[0] = (w1*vc[0] + w2*vd[0]) / VQ
1073 ve[1] = (w1*vc[1] + w2*vd[1]) / VQ
1074 ve[2] = (w1*vc[2] + w2*vd[2]) / VQ
1075 ve[3] = 0
1076 rc_mat4_vec4(rot, ve, vc)
1077 // two-sided lambert (abs dot): interior faces exposed by the bridge filter's honest holes
1078 // shade like skin instead of reading as black notches
1079 var br: i64 = mv_dot3(vc[0], vc[1], vc[2], LX, LY, LZ)
1080 if br < 0 { br = 0 - br }
1081 var c0: i64 = mv_shade(colv[k], br, 3072)
1082 let spq: i64 = mv_normal_specular(vc[0], vc[1], vc[2], LX, LY, LZ, 0, 0, VQ, 16)
1083 c0 = mv_add_spec(c0, spq)
1084 // hemispheric fill: cool sky from above -- a second light direction the flat clay read lacks
1085 let sky: i64 = (vc[1] + VQ) / 2
1086 var rr2: i64 = (c0 & 255) + sky*10/VQ
1087 var gg2: i64 = ((c0 >> 8) & 255) + sky*14/VQ
1088 var bb2: i64 = ((c0 >> 16) & 255) + sky*22/VQ
1089 if rr2 > 255 { rr2 = 255 }
1090 if gg2 > 255 { gg2 = 255 }
1091 if bb2 > 255 { bb2 = 255 }
1092 c0 = rr2 + gg2*256 + bb2*65536 + 4278190080
1093 lit[k] = c0
1094 dv_proj1(sk[k*4], sk[k*4+1], sk[k*4+2], scr, k*4)
1095 k = k + 1
1096 }
1097 let id: *i64 = (O_DI as i64) as *i64
1098 let tri: *i64 = (O_TRI as i64) as *i64
1099 var ti: i64 = 0
1100 var drawn: i64 = 0
1101 while ti < ntris {
1102 let a: i64 = id[ti*3]
1103 let b: i64 = id[ti*3+1]
1104 let c: i64 = id[ti*3+2]
1105 var ok: i64 = 1
1106 if a < 0 { ok = 0 }
1107 if a >= nverts { ok = 0 }
1108 if b < 0 { ok = 0 }
1109 if b >= nverts { ok = 0 }
1110 if c < 0 { ok = 0 }
1111 if c >= nverts { ok = 0 }
1112 if ok == 1 {
1113 var vis: i64 = 0
1114 if scr[a*4+3]==1 { if scr[b*4+3]==1 { if scr[c*4+3]==1 { vis=1 } } }
1115 if vis == 1 {
1116 tri[0]=scr[a*4]; tri[1]=scr[a*4+1]; tri[2]=scr[a*4+2]; tri[3]=lit[a]
1117 tri[4]=scr[b*4]; tri[5]=scr[b*4+1]; tri[6]=scr[b*4+2]; tri[7]=lit[b]
1118 tri[8]=scr[c*4]; tri[9]=scr[c*4+1]; tri[10]=scr[c*4+2]; tri[11]=lit[c]
1119 rc_triangle(fb, zb, DV_W, DV_H, tri)
1120 drawn = drawn + 1
1121 }
1122 }
1123 ti = ti + 1
1124 }
1125 return drawn
1126}