code wiki / (root) / nx_render_core.nx

nx_render_core.nx source

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