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