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