code wiki / (root) / nx_render_core.nx

nx_render_core.nx source

↩ module page · 518 lines · 23851 B

1// nx_render_core.nx -- Implements a fixed-point 3D rendering core with matrix operations and perspective projection for both native and WebAssembly targets. 2import "nx_vecmath.nx" 3const RC_MAGIC_40500: i64 = 40500 4const RC_MAGIC_65536: i64 = 65536 5const RC_MAGIC_16777216: i64 = 16777216 6const RC_MAGIC_4096: i64 = 4096 7const RC_MAGIC_31599: i64 = 31599 8const RC_MAGIC_29874: i64 = 29874 9const RC_MAGIC_31183: i64 = 31183 10const RC_MAGIC_29647: i64 = 29647 11const RC_MAGIC_5101: i64 = 5101 12const RC_MAGIC_29671: i64 = 29671 13const RC_MAGIC_31719: i64 = 31719 14const RC_MAGIC_9359: i64 = 9359 15const RC_MAGIC_31727: i64 = 31727 16const RC_MAGIC_29679: i64 = 29679 17// nx_render_core.nx -- HAL-FREE shared 3D render core: the pure (syscall-free) math extracted from the 18// certified nx_camera_q14 (Q14 mat4 camera) + nx_raster_triangle (Pineda z-buffered rasterizer) so it can 19// compile to BOTH native AND --target wat/wasm. The certified originals import nx_hal/nx_syscalls (whose 20// shims are @ifdef TARGET_X86_64-gated, with no wasm shim), so they cannot reach the browser; this core 21// has NO imports and uses i64 directly. Caller supplies all buffers (no sys_mmap), so a wasm organ can 22// back them with fixed linear-memory offsets. Math is a FAITHFUL transcription of the certified organs 23// (rule 15: extract shared primitive; later the originals may delegate here to dedup). license_tier: ORIGINAL 24// 25// Q14 fixed point throughout. Matrix = 16 i64 row-major. Vertex layout (4 i64): [x_px, y_px, z_q14, packed_rgba]. 26// Framebuffer = w*h i64, each holds u32 RGBA in the low 32 bits. Z-buffer = w*h i64 (Q14 depth, smaller=closer). 27 28const RC_Q: i64 = 16384 29const RC_M4_ELEMS: i64 = 16 30const RC_Z_FAR: i64 = 16384000 31 32// ===== 4x4 matrix builders (pure) ===== 33func rc_identity_4x4(out: *i64) -> *i64 { 34 var i: i64 = 0 35 while i < RC_M4_ELEMS { out[i] = 0; i = i + 1 } 36 out[0] = RC_Q 37 out[5] = RC_Q 38 out[10] = RC_Q 39 out[15] = RC_Q 40 return out 41} 42// translation: identity with right column (tx,ty,tz,1) 43func rc_translation_4x4(tx: i64, ty: i64, tz: i64, out: *i64) -> *i64 { 44 rc_identity_4x4(out) 45 out[3] = tx 46 out[7] = ty 47 out[11] = tz 48 return out 49} 50// perspective (right-handed, looking down -Z, OpenGL-style ndc). cos/sin of half-fov passed in as Q14 51// (the core is trig-free so the caller supplies them; a wasm organ fills a static trig table). 52// f = cos_h/sin_h ; out[0]=f/aspect, out[5]=f, out[10]=(f+n)/(n-f), out[11]=2fn/(n-f), out[14]=-Q 53func rc_perspective_cs(cos_h: i64, sin_h: i64, aspect_q14: i64, near_q14: i64, far_q14: i64, out: *i64) -> *i64 { 54 if sin_h == 0 { rc_identity_4x4(out); return out } 55 let f_q14: i64 = (cos_h * RC_Q) / sin_h 56 if aspect_q14 == 0 { rc_identity_4x4(out); return out } 57 let f_over_asp: i64 = (f_q14 * RC_Q) / aspect_q14 58 let nf_diff: i64 = near_q14 - far_q14 59 if nf_diff == 0 { rc_identity_4x4(out); return out } 60 let fpn: i64 = far_q14 + near_q14 61 let m10: i64 = (fpn * RC_Q) / nf_diff 62 let two_fn: i64 = 2 * far_q14 * near_q14 / RC_Q 63 let m11: i64 = (two_fn * RC_Q) / nf_diff 64 var i: i64 = 0 65 while i < RC_M4_ELEMS { out[i] = 0; i = i + 1 } 66 out[0] = f_over_asp 67 out[5] = f_q14 68 out[10] = m10 69 out[11] = m11 70 out[14] = 0 - RC_Q 71 return out 72} 73// sovereign fixed-point sin (Q14) via Bhaskara I's approximation -- NO table, NO JS trig (exact at 0/30/90, 74// ~0.2% error elsewhere; fine for a camera). sin(d)=4d(180-d)/(40500-d(180-d)) on [0,180]; sin(x)=-sin(x-180); 75// range-reduced mod 360. Enables free-camera rotation at arbitrary angles. 76func rc_sin_q14(deg: i64) -> i64 { 77 var d: i64 = deg % 360 78 if d < 0 { d = d + 360 } 79 var sign: i64 = 1 80 if d >= 180 { d = d - 180; sign = 0 - 1 } 81 let t: i64 = d * (180 - d) 82 return sign * (4 * t * RC_Q) / (RC_MAGIC_40500 - t) 83} 84func rc_cos_q14(deg: i64) -> i64 { return rc_sin_q14(deg + 90) } 85// out = a * b (row-major, /RC_Q after each dot to recover Q14). out must not alias a or b. 86func rc_mat4_mul(a: *i64, b: *i64, out: *i64) -> *i64 { 87 var r: i64 = 0 88 while r < 4 { 89 var c: i64 = 0 90 while c < 4 { 91 var sum: i64 = 0 92 var k: i64 = 0 93 while k < 4 { sum = sum + a[r * 4 + k] * b[k * 4 + c]; k = k + 1 } 94 out[r * 4 + c] = sum / RC_Q 95 c = c + 1 96 } 97 r = r + 1 98 } 99 return out 100} 101// out = M * v (column vector). out[i] = sum_k M[i,k]*v[k] / RC_Q 102func rc_mat4_vec4(m: *i64, v: *i64, out: *i64) -> *i64 { 103 var r: i64 = 0 104 while r < 4 { 105 var sum: i64 = 0 106 var k: i64 = 0 107 while k < 4 { sum = sum + m[r * 4 + k] * v[k]; k = k + 1 } 108 out[r] = sum / RC_Q 109 r = r + 1 110 } 111 return out 112} 113 114// ===== rasterizer (pure) ===== 115func rc_min3(a: i64, b: i64, c: i64) -> i64 { var m: i64 = a; if b < m { m = b } if c < m { m = c } return m } 116func rc_max3(a: i64, b: i64, c: i64) -> i64 { var m: i64 = a; if b > m { m = b } if c > m { m = c } return m } 117func rc_clamp(v: i64, lo: i64, hi: i64) -> i64 { if v < lo { return lo } if v > hi { return hi } return v } 118func rc_same_sign_3(a: i64, b: i64, c: i64) -> i64 { 119 if a >= 0 { if b >= 0 { if c >= 0 { return 1 } } } 120 if a <= 0 { if b <= 0 { if c <= 0 { return 1 } } } 121 return 0 122} 123// NOTE: returns i64 (the --target wat backend does not support void-returning calls -- a call to a 124// void fn emits a local.set with nothing on the stack and fails validation). Callers discard the 0. 125func rc_clear(fb: *i64, w: i64, h: i64, color: i64) -> i64 { 126 let n: i64 = w * h 127 var i: i64 = 0 128 while i < n { fb[i] = color; i = i + 1 } 129 return 0 130} 131func rc_zclear(zb: *i64, w: i64, h: i64) -> i64 { 132 let n: i64 = w * h 133 var i: i64 = 0 134 while i < n { zb[i] = RC_Z_FAR; i = i + 1 } 135 return 0 136} 137// rasterize one triangle (tri = 12 i64: 3 verts x [x,y,z_q14,rgba]) z-buffered, per-pixel barycentric 138// colour with alpha-as-brightness. faithful transcription of nx_raster_triangle. 139func rc_triangle(fb: *i64, zb: *i64, w: i64, h: i64, tri: *i64) -> i64 { 140 let x0: i64 = tri[0] 141 let y0: i64 = tri[1] 142 let z0: i64 = tri[2] 143 let c0: i64 = tri[3] 144 let x1: i64 = tri[4] 145 let y1: i64 = tri[5] 146 let z1: i64 = tri[6] 147 let c1: i64 = tri[7] 148 let x2: i64 = tri[8] 149 let y2: i64 = tri[9] 150 let z2: i64 = tri[10] 151 let c2: i64 = tri[11] 152 let r0: i64 = c0 % 256 153 let g0: i64 = (c0 / 256) % 256 154 let b0: i64 = (c0 / RC_MAGIC_65536) % 256 155 let a0: i64 = (c0 / RC_MAGIC_16777216) % 256 156 let r1c: i64 = c1 % 256 157 let g1c: i64 = (c1 / 256) % 256 158 let b1c: i64 = (c1 / RC_MAGIC_65536) % 256 159 let a1c: i64 = (c1 / RC_MAGIC_16777216) % 256 160 let r2c: i64 = c2 % 256 161 let g2c: i64 = (c2 / 256) % 256 162 let b2c: i64 = (c2 / RC_MAGIC_65536) % 256 163 let a2c: i64 = (c2 / RC_MAGIC_16777216) % 256 164 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) 165 if area == 0 { return 0 } 166 let min_x: i64 = rc_clamp(rc_min3(x0, x1, x2), 0, w - 1) 167 let max_x: i64 = rc_clamp(rc_max3(x0, x1, x2), 0, w - 1) 168 let min_y: i64 = rc_clamp(rc_min3(y0, y1, y2), 0, h - 1) 169 let max_y: i64 = rc_clamp(rc_max3(y0, y1, y2), 0, h - 1) 170 var py: i64 = min_y 171 while py <= max_y { 172 var px: i64 = min_x 173 while px <= max_x { 174 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1) 175 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2) 176 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0) 177 if rc_same_sign_3(e0, e1, e2) == 1 { 178 let z_num: i64 = e0 * z0 + e1 * z1 + e2 * z2 179 let z: i64 = z_num / area 180 let idx: i64 = py * w + px 181 if z < zb[idx] { 182 zb[idx] = z 183 let r: i64 = (e0 * r0 + e1 * r1c + e2 * r2c) / area 184 let g: i64 = (e0 * g0 + e1 * g1c + e2 * g2c) / area 185 let b: i64 = (e0 * b0 + e1 * b1c + e2 * b2c) / area 186 let a: i64 = (e0 * a0 + e1 * a1c + e2 * a2c) / area 187 let r_lit: i64 = (r * a) / 255 188 let g_lit: i64 = (g * a) / 255 189 let b_lit: i64 = (b * a) / 255 190 fb[idx] = r_lit + g_lit * 256 + b_lit * RC_MAGIC_65536 + 255 * RC_MAGIC_16777216 191 } 192 } 193 px = px + 1 194 } 195 py = py + 1 196 } 197 return 0 198} 199// FLAT z-buffered triangle (the PERF path for flat-shaded faces): edge-function coverage + z-test, writes ONE 200// precomputed colour -- NO per-pixel barycentric colour/light interpolation. ~1 divide/covered-pixel (z only) vs 201// rc_triangle's ~8 (z + r,g,b,a + 3 light) = far cheaper on a weak CPU. tri = 10 i64: [x0,y0,z0, x1,y1,z1, 202// x2,y2,z2, color]. Returns the covered-and-written pixel count (for benchmarking). Same coverage+z as rc_triangle. 203func rc_triangle_flat(fb: *i64, zb: *i64, w: i64, h: i64, tri: *i64) -> i64 { 204 let x0: i64 = tri[0] 205 let y0: i64 = tri[1] 206 let z0: i64 = tri[2] 207 let x1: i64 = tri[3] 208 let y1: i64 = tri[4] 209 let z1: i64 = tri[5] 210 let x2: i64 = tri[6] 211 let y2: i64 = tri[7] 212 let z2: i64 = tri[8] 213 let col: i64 = tri[9] 214 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) 215 if area == 0 { return 0 } 216 let min_x: i64 = rc_clamp(rc_min3(x0, x1, x2), 0, w - 1) 217 let max_x: i64 = rc_clamp(rc_max3(x0, x1, x2), 0, w - 1) 218 let min_y: i64 = rc_clamp(rc_min3(y0, y1, y2), 0, h - 1) 219 let max_y: i64 = rc_clamp(rc_max3(y0, y1, y2), 0, h - 1) 220 var cnt: i64 = 0 221 var py: i64 = min_y 222 while py <= max_y { 223 var px: i64 = min_x 224 while px <= max_x { 225 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1) 226 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2) 227 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0) 228 if rc_same_sign_3(e0, e1, e2) == 1 { 229 let z: i64 = (e0 * z0 + e1 * z1 + e2 * z2) / area 230 let idx: i64 = py * w + px 231 if z < zb[idx] { zb[idx] = z; fb[idx] = col; cnt = cnt + 1 } 232 } 233 px = px + 1 234 } 235 py = py + 1 236 } 237 return cnt 238} 239// integer sqrt (Newton) + integer pow (Q14) -- the per-pixel math the Phong rasterizer needs. 240func rc_isqrt(n: i64) -> i64 { return vm_isqrt(n) } 241func rc_pow_q14(base: i64, n: i64) -> i64 { 242 var r: i64 = RC_Q 243 var i: i64 = 0 244 while i < n { r = r * base / RC_Q; i = i + 1 } 245 return r 246} 247// PHONG z-buffered triangle: interpolates the per-vertex NORMAL per pixel (not the colour), normalises it, and 248// shades EACH pixel (Lambert diffuse + Blinn-Phong specular) -> a highlight that is sharp + correctly placed even 249// BETWEEN vertices, where Gouraud (vertex-interpolated colour) blurs or misses it. pb = 27 i64: [x0,y0,z0,x1,y1,z1, 250// x2,y2,z2, n0x,n0y,n0z,n1x,n1y,n1z,n2x,n2y,n2z, lx,ly,lz, vx,vy,vz, base_rgba, shininess, ambient, bump]. L,V unit Q14; bump!=0 perturbs the per-pixel normal (procedural bump-map surface detail). 251// Returns the covered-and-written pixel count. Same coverage+z as rc_triangle; only the shading is per-pixel. 252func rc_triangle_phong(fb: *i64, zb: *i64, w: i64, h: i64, pb: *i64) -> i64 { 253 let x0: i64=pb[0]; let y0: i64=pb[1]; let z0: i64=pb[2] 254 let x1: i64=pb[3]; let y1: i64=pb[4]; let z1: i64=pb[5] 255 let x2: i64=pb[6]; let y2: i64=pb[7]; let z2: i64=pb[8] 256 let n0x: i64=pb[9]; let n0y: i64=pb[10]; let n0z: i64=pb[11] 257 let n1x: i64=pb[12]; let n1y: i64=pb[13]; let n1z: i64=pb[14] 258 let n2x: i64=pb[15]; let n2y: i64=pb[16]; let n2z: i64=pb[17] 259 let lx: i64=pb[18]; let ly: i64=pb[19]; let lz: i64=pb[20] 260 let vx: i64=pb[21]; let vy: i64=pb[22]; let vz: i64=pb[23] 261 let base: i64=pb[24]; let shin: i64=pb[25]; let amb: i64=pb[26]; let bump: i64=pb[27] 262 let br0: i64=base%256; let bg0: i64=(base/256)%256; let bb0: i64=(base/RC_MAGIC_65536)%256 263 let hx: i64=lx+vx; let hy: i64=ly+vy; let hz: i64=lz+vz 264 let hm: i64=rc_isqrt(hx*hx + hy*hy + hz*hz) 265 var uhx: i64=0; var uhy: i64=0; var uhz: i64=0 266 if hm > 0 { uhx=hx*RC_Q/hm; uhy=hy*RC_Q/hm; uhz=hz*RC_Q/hm } 267 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) 268 if area == 0 { return 0 } 269 let min_x: i64 = rc_clamp(rc_min3(x0, x1, x2), 0, w - 1) 270 let max_x: i64 = rc_clamp(rc_max3(x0, x1, x2), 0, w - 1) 271 let min_y: i64 = rc_clamp(rc_min3(y0, y1, y2), 0, h - 1) 272 let max_y: i64 = rc_clamp(rc_max3(y0, y1, y2), 0, h - 1) 273 var cnt: i64 = 0 274 var py: i64 = min_y 275 while py <= max_y { 276 var px: i64 = min_x 277 while px <= max_x { 278 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1) 279 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2) 280 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0) 281 if rc_same_sign_3(e0, e1, e2) == 1 { 282 let z: i64 = (e0 * z0 + e1 * z1 + e2 * z2) / area 283 let idx: i64 = py * w + px 284 if z < zb[idx] { 285 zb[idx] = z 286 let nx: i64 = (e0*n0x + e1*n1x + e2*n2x) / area 287 let ny: i64 = (e0*n0y + e1*n1y + e2*n2y) / area 288 let nz: i64 = (e0*n0z + e1*n1z + e2*n2z) / area 289 let nm: i64 = rc_isqrt(nx*nx + ny*ny + nz*nz) 290 if nm > 0 { 291 var unx: i64 = nx*RC_Q/nm 292 var uny: i64 = ny*RC_Q/nm 293 var unz: i64 = nz*RC_Q/nm 294 if bump != 0 { 295 let dnx: i64 = ((px % 6) - 3) * bump 296 let dny: i64 = ((py % 6) - 3) * bump 297 let pnx: i64 = unx + dnx 298 let pny: i64 = uny + dny 299 let pm: i64 = rc_isqrt(pnx*pnx + pny*pny + unz*unz) 300 if pm > 0 { unx = pnx*RC_Q/pm; uny = pny*RC_Q/pm; unz = unz*RC_Q/pm } 301 } 302 var d: i64 = (unx*lx + uny*ly + unz*lz) / RC_Q 303 if d < 0 { d = 0 } 304 var bri: i64 = d 305 if bri < amb { bri = amb } 306 var sp: i64 = 0 307 let nh: i64 = (unx*uhx + uny*uhy + unz*uhz) / RC_Q 308 if nh > 0 { sp = rc_pow_q14(nh, shin) } 309 var rr: i64 = br0*bri/RC_Q + sp*255/RC_Q 310 var gg: i64 = bg0*bri/RC_Q + sp*255/RC_Q 311 var bb2: i64 = bb0*bri/RC_Q + sp*255/RC_Q 312 if rr > 255 { rr = 255 } 313 if gg > 255 { gg = 255 } 314 if bb2 > 255 { bb2 = 255 } 315 fb[idx] = rr + gg*256 + bb2*RC_MAGIC_65536 + 255*RC_MAGIC_16777216 316 cnt = cnt + 1 317 } 318 } 319 } 320 px = px + 1 321 } 322 py = py + 1 323 } 324 return cnt 325} 326// procedural per-texel surface detail: perturb a base RGB by a small deterministic hash of texel coords 327// (the minecraft-ish per-pixel block speckle). Returns packed RGB (no alpha); caller applies brightness. 328func rc_texel(r: i64, g: i64, b: i64, u: i64, v: i64) -> i64 { 329 let n: i64 = ((u * 7 + v * 13 + u * v * 5) % 7) - 3 // -3..3 330 var rr: i64 = r + n * 7 331 var gg: i64 = g + n * 7 332 var bb: i64 = b + n * 7 333 if rr < 0 { rr = 0 } 334 if rr > 255 { rr = 255 } 335 if gg < 0 { gg = 0 } 336 if gg > 255 { gg = 255 } 337 if bb < 0 { bb = 0 } 338 if bb > 255 { bb = 255 } 339 return rr + gg * 256 + bb * RC_MAGIC_65536 340} 341// TEXTURED z-buffered triangle. tri = 3 verts x [x, y, z, u, v] (15 i64). UV interpolated per-pixel 342// (barycentric, same weights as Z) -> procedural texel -> directional brightness folded in. base RGB + 343// brightness (0..255) are per-face constants. 344func rc_triangle_tex(fb: *i64, zb: *i64, w: i64, h: i64, tri: *i64, baseR: i64, baseG: i64, baseB: i64, bright: i64) -> i64 { 345 let x0: i64 = tri[0] 346 let y0: i64 = tri[1] 347 let z0: i64 = tri[2] 348 let u0: i64 = tri[3] 349 let v0: i64 = tri[4] 350 let x1: i64 = tri[5] 351 let y1: i64 = tri[6] 352 let z1: i64 = tri[7] 353 let u1: i64 = tri[8] 354 let v1: i64 = tri[9] 355 let x2: i64 = tri[10] 356 let y2: i64 = tri[11] 357 let z2: i64 = tri[12] 358 let u2: i64 = tri[13] 359 let v2: i64 = tri[14] 360 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) 361 if area == 0 { return 0 } 362 let min_x: i64 = rc_clamp(rc_min3(x0, x1, x2), 0, w - 1) 363 let max_x: i64 = rc_clamp(rc_max3(x0, x1, x2), 0, w - 1) 364 let min_y: i64 = rc_clamp(rc_min3(y0, y1, y2), 0, h - 1) 365 let max_y: i64 = rc_clamp(rc_max3(y0, y1, y2), 0, h - 1) 366 var py: i64 = min_y 367 while py <= max_y { 368 var px: i64 = min_x 369 while px <= max_x { 370 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1) 371 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2) 372 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0) 373 if rc_same_sign_3(e0, e1, e2) == 1 { 374 let z: i64 = (e0 * z0 + e1 * z1 + e2 * z2) / area 375 let idx: i64 = py * w + px 376 if z < zb[idx] { 377 zb[idx] = z 378 let u: i64 = (e0 * u0 + e1 * u1 + e2 * u2) / area 379 let v: i64 = (e0 * v0 + e1 * v1 + e2 * v2) / area 380 let tx: i64 = rc_texel(baseR, baseG, baseB, u, v) 381 let tr: i64 = tx % 256 382 let tg: i64 = (tx / 256) % 256 383 let tb: i64 = (tx / RC_MAGIC_65536) % 256 384 fb[idx] = (tr * bright) / 255 + ((tg * bright) / 255) * 256 + ((tb * bright) / 255) * RC_MAGIC_65536 + 255 * RC_MAGIC_16777216 385 } 386 } 387 px = px + 1 388 } 389 py = py + 1 390 } 391 return 0 392} 393// ===== ENGINE: data-driven scene rendering (game-agnostic) -- renders an ENTITY TABLE, not a hardcoded 394// world. This is the seed of a reusable engine: any game = a scene of entities (transform + box mesh + 395// colour). Caller provides fb/zb, an MVP, the entity table, and a >=52-i64 scratch buffer. ===== 396// draw a quad (4 projected corners by index) as 2 z-buffered triangles; skip if any corner is behind near. 397func rc_box_quad(fb: *i64, zb: *i64, sw: i64, sh: i64, corn: *i64, ia: i64, ib: i64, ic: i64, id: i64, col: i64, tri: *i64) -> i64 { 398 if corn[ia * 4 + 3] == 0 { return 0 } 399 if corn[ib * 4 + 3] == 0 { return 0 } 400 if corn[ic * 4 + 3] == 0 { return 0 } 401 if corn[id * 4 + 3] == 0 { return 0 } 402 tri[0] = corn[ia * 4]; tri[1] = corn[ia * 4 + 1]; tri[2] = corn[ia * 4 + 2]; tri[3] = col 403 tri[4] = corn[ib * 4]; tri[5] = corn[ib * 4 + 1]; tri[6] = corn[ib * 4 + 2]; tri[7] = col 404 tri[8] = corn[ic * 4]; tri[9] = corn[ic * 4 + 1]; tri[10] = corn[ic * 4 + 2]; tri[11] = col 405 rc_triangle(fb, zb, sw, sh, tri) 406 tri[0] = corn[ia * 4]; tri[1] = corn[ia * 4 + 1]; tri[2] = corn[ia * 4 + 2]; tri[3] = col 407 tri[4] = corn[ic * 4]; tri[5] = corn[ic * 4 + 1]; tri[6] = corn[ic * 4 + 2]; tri[7] = col 408 tri[8] = corn[id * 4]; tri[9] = corn[id * 4 + 1]; tri[10] = corn[id * 4 + 2]; tri[11] = col 409 rc_triangle(fb, zb, sw, sh, tri) 410 return 0 411} 412// project + z-raster one box entity (min-corner (x0,y0,z0), size (hx,hy,hz) in world units, colour col). 413func rc_draw_box(fb: *i64, zb: *i64, sw: i64, sh: i64, mvp: *i64, x0: i64, y0: i64, z0: i64, hx: i64, hy: i64, hz: i64, col: i64, hw: i64, hh: i64, scratch: *i64) -> i64 { 414 let corn: *i64 = scratch 415 let vtmp: *i64 = ((scratch as i64) + 32 * 8) as *i64 416 let ctmp: *i64 = ((scratch as i64) + 36 * 8) as *i64 417 let tri: *i64 = ((scratch as i64) + 40 * 8) as *i64 418 var i: i64 = 0 419 while i < 8 { 420 var dx: i64 = 0 421 if (i % 2) == 1 { dx = hx } 422 var dy: i64 = 0 423 if ((i / 2) % 2) == 1 { dy = hy } 424 var dz: i64 = 0 425 if ((i / 4) % 2) == 1 { dz = hz } 426 vtmp[0] = (x0 + dx) * RC_Q 427 vtmp[1] = (y0 + dy) * RC_Q 428 vtmp[2] = (z0 + dz) * RC_Q 429 vtmp[3] = RC_Q 430 rc_mat4_vec4(mvp, vtmp, ctmp) 431 let w: i64 = ctmp[3] 432 let s: *i64 = ((corn as i64) + i * 4 * 8) as *i64 433 if w < RC_MAGIC_4096 { s[3] = 0 } else { s[0] = hw + (ctmp[0] * hw) / w; s[1] = hh - (ctmp[1] * hh) / w; s[2] = w; s[3] = 1 } 434 i = i + 1 435 } 436 rc_box_quad(fb, zb, sw, sh, corn, 4, 5, 7, 6, col, tri) 437 rc_box_quad(fb, zb, sw, sh, corn, 0, 1, 3, 2, col, tri) 438 rc_box_quad(fb, zb, sw, sh, corn, 1, 5, 7, 3, col, tri) 439 rc_box_quad(fb, zb, sw, sh, corn, 0, 4, 6, 2, col, tri) 440 rc_box_quad(fb, zb, sw, sh, corn, 2, 3, 7, 6, col, tri) 441 rc_box_quad(fb, zb, sw, sh, corn, 0, 1, 5, 4, col, tri) 442 return 0 443} 444// render a whole SCENE: ent = nent entities, 7 i64 each [x0,y0,z0, hx,hy,hz, packed_color]. The engine is 445// game-agnostic -- pets3d (or any game) is just one scene fed to this. 446func rc_draw_scene(fb: *i64, zb: *i64, sw: i64, sh: i64, mvp: *i64, ent: *i64, nent: i64, hw: i64, hh: i64, scratch: *i64) -> i64 { 447 var e: i64 = 0 448 while e < nent { 449 let b: *i64 = ((ent as i64) + e * 7 * 8) as *i64 450 rc_draw_box(fb, zb, sw, sh, mvp, b[0], b[1], b[2], b[3], b[4], b[5], b[6], hw, hh, scratch) 451 e = e + 1 452 } 453 return 0 454} 455// ===== sovereign 2D UI primitives (reusable: HUD, editor overlays, the "2D over 3D" viewport chrome that 456// every DCC/engine tool needs). All draw packed-RGBA pixels into the framebuffer; NO JS UI logic. ===== 457func rc_pow8(r: i64) -> i64 { var v: i64 = 1; var k: i64 = 0; while k < r { v = v * 8; k = k + 1 } return v } 458func rc_pow10(r: i64) -> i64 { var v: i64 = 1; var k: i64 = 0; while k < r { v = v * 10; k = k + 1 } return v } 459// filled rectangle (bars, panels, backgrounds), viewport-clipped. 460func rc_fillrect(fb: *i64, fbw: i64, fbh: i64, x0: i64, y0: i64, rw: i64, rh: i64, col: i64) -> i64 { 461 var y: i64 = y0 462 while y < y0 + rh { 463 var x: i64 = x0 464 while x < x0 + rw { 465 if x >= 0 { if y >= 0 { if x < fbw { if y < fbh { fb[y * fbw + x] = col } } } } 466 x = x + 1 467 } 468 y = y + 1 469 } 470 return 0 471} 472// 3x5 bitmap glyph for a digit 0..9, packed as 5 rows of 3 bits (base-8 per row). 473func rc_font3x5(d: i64) -> i64 { 474 if d == 0 { return RC_MAGIC_31599 } 475 if d == 1 { return RC_MAGIC_29874 } 476 if d == 2 { return RC_MAGIC_31183 } 477 if d == 3 { return RC_MAGIC_29647 } 478 if d == 4 { return RC_MAGIC_5101 } 479 if d == 5 { return RC_MAGIC_29671 } 480 if d == 6 { return RC_MAGIC_31719 } 481 if d == 7 { return RC_MAGIC_9359 } 482 if d == 8 { return RC_MAGIC_31727 } 483 return RC_MAGIC_29679 484} 485// draw one digit glyph at (px,py), pixel size sc. 486func rc_draw_digit(fb: *i64, fbw: i64, fbh: i64, px: i64, py: i64, d: i64, col: i64, sc: i64) -> i64 { 487 let pat: i64 = rc_font3x5(d) 488 var r: i64 = 0 489 while r < 5 { 490 let rowbits: i64 = (pat / rc_pow8(r)) % 8 491 var c: i64 = 0 492 while c < 3 { 493 var bitval: i64 = 1 494 if c == 0 { bitval = 4 } 495 if c == 1 { bitval = 2 } 496 if (rowbits / bitval) % 2 == 1 { rc_fillrect(fb, fbw, fbh, px + c * sc, py + r * sc, sc, sc, col) } 497 c = c + 1 498 } 499 r = r + 1 500 } 501 return 0 502} 503// draw a non-negative integer in decimal at (px,py). 504func rc_draw_number(fb: *i64, fbw: i64, fbh: i64, px: i64, py: i64, val: i64, col: i64, sc: i64) -> i64 { 505 if val < 0 { return 0 } 506 var t: i64 = val 507 var nd: i64 = 1 508 while t >= 10 { t = t / 10; nd = nd + 1 } 509 var i: i64 = 0 510 while i < nd { 511 let dig: i64 = (val / rc_pow10(nd - 1 - i)) % 10 512 rc_draw_digit(fb, fbw, fbh, px + i * (4 * sc), py, dig, col, sc) 513 i = i + 1 514 } 515 return 0 516} 517func rc_pixel(fb: *i64, w: i64, x: i64, y: i64) -> i64 { return fb[y * w + x] } 518func rc_depth(zb: *i64, w: i64, x: i64, y: i64) -> i64 { return zb[y * w + x] }