nx_voxel_world.nx source
↩ module page · 365 lines · 14921 B
1// nx_voxel_world.nx -- PARAMETERIZED sovereign 3D-world renderer: a game-SPEC (DATA) -> a rendered world.
2// Style is a spec field, NOT a hardcode -- the engine is not locked to one look: SP_STYLE 0=realism (textured,
3// smooth light), 1=toon (banded light + silhouette outline = anime/cel), 2=flat (untextured facets = artsy
4// low-poly). Geometry here is voxel terrain, but the rasterizer (fill_ztex) draws arbitrary textured triangles,
5// so other geometry (meshes/characters) plugs into the same path. Composes nx_f32_hw. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_f32_hw.nx"
8const SP_MAGIC_65536: i64 = 65536
9
10const W: i64 = 320
11const H: i64 = 240
12const HW: i64 = 160
13const HH: i64 = 120
14const TN: i64 = 4
15const ZFAR: i64 = 1073741824
16const SP_SEED: i64 = 0
17const SP_N: i64 = 1
18const SP_WATER: i64 = 2
19const SP_MAXH: i64 = 3
20const SP_LAT: i64 = 4
21const SP_PSIN: i64 = 5
22const SP_PCOS: i64 = 6
23const SP_YSIN: i64 = 7
24const SP_YCOS: i64 = 8
25const SP_PUSH: i64 = 9
26const SP_FOCAL: i64 = 10
27const SP_STYLE: i64 = 11
28const SP_CAMX: i64 = 12 // first-person camera position (f32 bit-patterns)
29const SP_CAMY: i64 = 13
30const SP_CAMZ: i64 = 14
31const SP_FPS: i64 = 15 // 1 = first-person (subtract cam pos, no push); 0 = orbit (default)
32const STYLE_REAL: i64 = 0
33const STYLE_TOON: i64 = 1
34const STYLE_FLAT: i64 = 2
35
36func world_pixels() -> i64 { return W * H }
37func world_fb_bytes() -> i64 { return W * H * 8 }
38func world_sky() -> i64 { return 142 + 186 * 256 + 228 * SP_MAGIC_65536 }
39
40func imin(a: i64, b: i64) -> i64 { if a < b { return a } return b }
41func imax(a: i64, b: i64) -> i64 { if a > b { return a } return b }
42func iabs(a: i64) -> i64 { if a < 0 { return 0 - a } return a }
43func min3(a: i64, b: i64, c: i64) -> i64 { return imin(a, imin(b, c)) }
44func max3(a: i64, b: i64, c: i64) -> i64 { return imax(a, imax(b, c)) }
45func clamp255(v: i64) -> i64 { if v < 0 { return 0 } if v > 255 { return 255 } return v }
46func same_sign3(a: i64, b: i64, c: i64) -> i64 {
47 if a >= 0 { if b >= 0 { if c >= 0 { return 1 } } }
48 if a <= 0 { if b <= 0 { if c <= 0 { return 1 } } }
49 return 0
50}
51func fratio(num: i64, den: i64) -> i64 { return f32_div(f32_of(num), f32_of(den)) }
52
53func lat(ix: i64, iz: i64, seed: i64) -> i64 {
54 var n: i64 = (ix * 71 + iz * 191 + ix * iz * 7 + seed * 101) % 6
55 if n < 0 { n = n + 6 }
56 return n
57}
58func terrain_h(sp: *i64, gx: i64, gz: i64) -> i64 {
59 let L: i64 = sp[SP_LAT]
60 let sd: i64 = sp[SP_SEED]
61 let bx: i64 = gx + 200
62 let bz: i64 = gz + 200
63 let ix: i64 = bx / L
64 let iz: i64 = bz / L
65 let fx: i64 = (bx % L) * 256 / L
66 let fz: i64 = (bz % L) * 256 / L
67 let h00: i64 = lat(ix, iz, sd)
68 let h10: i64 = lat(ix + 1, iz, sd)
69 let h01: i64 = lat(ix, iz + 1, sd)
70 let h11: i64 = lat(ix + 1, iz + 1, sd)
71 let a: i64 = h00 * 256 + (h10 - h00) * fx
72 let b: i64 = h01 * 256 + (h11 - h01) * fx
73 var h: i64 = (a * 256 + (b - a) * fz) / SP_MAGIC_65536 + 1
74 if h < 1 { h = 1 }
75 if h > sp[SP_MAXH] { h = sp[SP_MAXH] }
76 return h
77}
78func hN(sp: *i64, gx: i64, gz: i64) -> i64 {
79 if gx < 0 { return 0 }
80 if gz < 0 { return 0 }
81 if gx >= sp[SP_N] { return 0 }
82 if gz >= sp[SP_N] { return 0 }
83 return terrain_h(sp, gx, gz)
84}
85func top_color(sp: *i64, h: i64) -> i64 {
86 if h <= sp[SP_WATER] { return 64 + 116 * 256 + 204 * SP_MAGIC_65536 }
87 if h >= sp[SP_MAXH] { return 236 + 238 * 256 + 245 * SP_MAGIC_65536 }
88 if h == sp[SP_MAXH] - 1 { return 122 + 118 * 256 + 112 * SP_MAGIC_65536 }
89 return 96 + 172 * 256 + 76 * SP_MAGIC_65536
90}
91func side_color(sp: *i64, h: i64) -> i64 {
92 if h <= sp[SP_WATER] { return 58 + 104 * 256 + 188 * SP_MAGIC_65536 }
93 if h >= sp[SP_MAXH] - 1 { return 110 + 106 * 256 + 100 * SP_MAGIC_65536 }
94 return 134 + 98 * 256 + 60 * SP_MAGIC_65536
95}
96// per-pixel colour: realism adds per-texel speckle; toon/flat use the flat band colour.
97func texel_col(style: i64, baseR: i64, baseG: i64, baseB: i64, u: i64, v: i64, bright: i64) -> i64 {
98 var n: i64 = 0
99 if style == STYLE_REAL { n = ((u * 7 + v * 13 + u * v * 5) % 7) - 3 }
100 let r: i64 = clamp255(baseR + n * 8)
101 let g: i64 = clamp255(baseG + n * 8)
102 let b: i64 = clamp255(baseB + n * 8)
103 return ((r * bright) / 255) + ((g * bright) / 255) * 256 + ((b * bright) / 255) * SP_MAGIC_65536
104}
105// toon: hard 2-tone (lit / shadow) cel shading.
106func toon_band(b: i64) -> i64 {
107 if b < 150 { return 105 }
108 return 235
109}
110func fill_ztex(fb: *i64, zb: *i64, t: *i64, br: i64, bg: i64, bb: i64, bright: i64, style: i64) -> i64 {
111 let x0: i64 = t[0]; let y0: i64 = t[1]; let d0: i64 = t[2]; let u0: i64 = t[3]; let v0: i64 = t[4]
112 let x1: i64 = t[5]; let y1: i64 = t[6]; let d1: i64 = t[7]; let u1: i64 = t[8]; let v1: i64 = t[9]
113 let x2: i64 = t[10]; let y2: i64 = t[11]; let d2: i64 = t[12]; let u2: i64 = t[13]; let v2: i64 = t[14]
114 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)
115 if area == 0 { return 0 }
116 let minx: i64 = imax(0, min3(x0, x1, x2))
117 let maxx: i64 = imin(W - 1, max3(x0, x1, x2))
118 let miny: i64 = imax(0, min3(y0, y1, y2))
119 let maxy: i64 = imin(H - 1, max3(y0, y1, y2))
120 var py: i64 = miny
121 while py <= maxy {
122 var px: i64 = minx
123 while px <= maxx {
124 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1)
125 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2)
126 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0)
127 if same_sign3(e0, e1, e2) == 1 {
128 let d: i64 = (e0 * d0 + e1 * d1 + e2 * d2) / area
129 let idx: i64 = py * W + px
130 if d < zb[idx] {
131 zb[idx] = d
132 let u: i64 = (e0 * u0 + e1 * u1 + e2 * u2) / area
133 let v: i64 = (e0 * v0 + e1 * v1 + e2 * v2) / area
134 fb[idx] = texel_col(style, br, bg, bb, u, v, bright)
135 }
136 }
137 px = px + 1
138 }
139 py = py + 1
140 }
141 return 0
142}
143func project(sp: *i64, R: *i64, iwx: i64, iwy: i64, iwz: i64, out: *i64) -> i64 {
144 let v: *i64 = sys_mmap(32) as *i64
145 let r: *i64 = sys_mmap(32) as *i64
146 if sp[SP_FPS] == 1 {
147 v[0] = f32_sub(f32_of(iwx), sp[SP_CAMX])
148 v[1] = f32_sub(f32_of(iwy), sp[SP_CAMY])
149 v[2] = f32_sub(f32_of(iwz), sp[SP_CAMZ])
150 } else {
151 v[0] = f32_sub(f32_of(iwx), fratio(sp[SP_N], 2))
152 v[1] = f32_sub(f32_of(iwy), f32_of(2))
153 v[2] = f32_sub(f32_of(iwz), fratio(sp[SP_N], 2))
154 }
155 v[3] = f32_of(0)
156 m4_vec4(R, v, r)
157 var pz: i64 = f32_add(r[2], f32_of(sp[SP_PUSH]))
158 if sp[SP_FPS] == 1 { pz = r[2] }
159 if (pz & 0x80000000) != 0 { out[3] = 0; return 0 }
160 let zi: i64 = f32_int(f32_mul(pz, f32_of(64)))
161 if zi <= 0 { out[3] = 0; return 0 }
162 out[0] = HW + f32_int(f32_div(f32_mul(r[0], f32_of(sp[SP_FOCAL])), pz))
163 out[1] = HH - f32_int(f32_div(f32_mul(r[1], f32_of(sp[SP_FOCAL])), pz))
164 out[2] = zi
165 out[3] = 1
166 return 0
167}
168func shade_local(lnx: i64, lny: i64, lnz: i64) -> i64 {
169 var d: i64 = lnx * 2 + lny * 3 + lnz * 1
170 if d < 0 { d = 0 }
171 return clamp255(110 + d * 38)
172}
173func draw_face(fb: *i64, zb: *i64, sx: *i64, sy: *i64, sd: *i64, sok: *i64,
174 a: i64, b: i64, c: i64, dd: i64, lnx: i64, lny: i64, lnz: i64, col: i64, style: i64) -> i64 {
175 if sok[a] == 0 { return 0 }
176 if sok[b] == 0 { return 0 }
177 if sok[c] == 0 { return 0 }
178 if sok[dd] == 0 { return 0 }
179 var bright: i64 = shade_local(lnx, lny, lnz)
180 if style == STYLE_TOON { bright = toon_band(bright) }
181 let br: i64 = col & 0xff
182 let bg: i64 = (col >> 8) & 0xff
183 let bb: i64 = (col >> 16) & 0xff
184 let t: *i64 = sys_mmap(15 * 8) as *i64
185 t[0] = sx[a]; t[1] = sy[a]; t[2] = sd[a]; t[3] = 0; t[4] = 0
186 t[5] = sx[b]; t[6] = sy[b]; t[7] = sd[b]; t[8] = TN; t[9] = 0
187 t[10] = sx[c]; t[11] = sy[c]; t[12] = sd[c]; t[13] = TN; t[14] = TN
188 fill_ztex(fb, zb, t, br, bg, bb, bright, style)
189 t[0] = sx[a]; t[1] = sy[a]; t[2] = sd[a]; t[3] = 0; t[4] = 0
190 t[5] = sx[c]; t[6] = sy[c]; t[7] = sd[c]; t[8] = TN; t[9] = TN
191 t[10] = sx[dd]; t[11] = sy[dd]; t[12] = sd[dd]; t[13] = 0; t[14] = TN
192 fill_ztex(fb, zb, t, br, bg, bb, bright, style)
193 return 0
194}
195func draw_col(sp: *i64, fb: *i64, zb: *i64, R: *i64, gx: i64, gz: i64) -> i64 {
196 let st: i64 = sp[SP_STYLE]
197 let h: i64 = terrain_h(sp, gx, gz)
198 let sx: *i64 = sys_mmap(8 * 8) as *i64
199 let sy: *i64 = sys_mmap(8 * 8) as *i64
200 let sd: *i64 = sys_mmap(8 * 8) as *i64
201 let sok: *i64 = sys_mmap(8 * 8) as *i64
202 var k: i64 = 0
203 while k < 8 {
204 let dx: i64 = k & 1
205 let dy: i64 = (k >> 1) & 1
206 let dz: i64 = (k >> 2) & 1
207 let o: *i64 = sys_mmap(32) as *i64
208 project(sp, R, gx + dx, dy * h, gz + dz, o)
209 sx[k] = o[0]; sy[k] = o[1]; sd[k] = o[2]; sok[k] = o[3]
210 k = k + 1
211 }
212 let tc: i64 = top_color(sp, h)
213 let sc: i64 = side_color(sp, h)
214 draw_face(fb, zb, sx, sy, sd, sok, 2, 3, 7, 6, 0, 1, 0, tc, st)
215 if hN(sp, gx + 1, gz) < h { draw_face(fb, zb, sx, sy, sd, sok, 1, 5, 7, 3, 1, 0, 0, sc, st) }
216 if hN(sp, gx - 1, gz) < h { draw_face(fb, zb, sx, sy, sd, sok, 0, 4, 6, 2, 0 - 1, 0, 0, sc, st) }
217 if hN(sp, gx, gz + 1) < h { draw_face(fb, zb, sx, sy, sd, sok, 4, 5, 7, 6, 0, 0, 1, sc, st) }
218 if hN(sp, gx, gz - 1) < h { draw_face(fb, zb, sx, sy, sd, sok, 0, 1, 3, 2, 0, 0, 0 - 1, sc, st) }
219 return 0
220}
221// draw an arbitrary world-space box (the entity/object primitive: trees, props, mobs, buildings).
222func draw_obox(sp: *i64, fb: *i64, zb: *i64, R: *i64, x0: i64, y0: i64, z0: i64, hx: i64, hy: i64, hz: i64, col: i64, style: i64) -> i64 {
223 let sx: *i64 = sys_mmap(8 * 8) as *i64
224 let sy: *i64 = sys_mmap(8 * 8) as *i64
225 let sd: *i64 = sys_mmap(8 * 8) as *i64
226 let sok: *i64 = sys_mmap(8 * 8) as *i64
227 var k: i64 = 0
228 while k < 8 {
229 let dx: i64 = (k & 1) * hx
230 let dy: i64 = ((k >> 1) & 1) * hy
231 let dz: i64 = ((k >> 2) & 1) * hz
232 let o: *i64 = sys_mmap(32) as *i64
233 project(sp, R, x0 + dx, y0 + dy, z0 + dz, o)
234 sx[k] = o[0]; sy[k] = o[1]; sd[k] = o[2]; sok[k] = o[3]
235 k = k + 1
236 }
237 draw_face(fb, zb, sx, sy, sd, sok, 2, 3, 7, 6, 0, 1, 0, col, style)
238 draw_face(fb, zb, sx, sy, sd, sok, 1, 5, 7, 3, 1, 0, 0, col, style)
239 draw_face(fb, zb, sx, sy, sd, sok, 0, 4, 6, 2, 0 - 1, 0, 0, col, style)
240 draw_face(fb, zb, sx, sy, sd, sok, 4, 5, 7, 6, 0, 0, 1, col, style)
241 draw_face(fb, zb, sx, sy, sd, sok, 0, 1, 3, 2, 0, 0, 0 - 1, col, style)
242 return 0
243}
244// sparse deterministic trees on grass columns (not water/rock/snow).
245func has_tree(sp: *i64, gx: i64, gz: i64) -> i64 {
246 let h: i64 = terrain_h(sp, gx, gz)
247 if h <= sp[SP_WATER] { return 0 }
248 if h >= sp[SP_MAXH] - 1 { return 0 }
249 var n: i64 = (gx * 131 + gz * 197 + gx * gz * 7 + 13) % 19
250 if n < 0 { n = n + 19 }
251 if n == 0 { return 1 }
252 return 0
253}
254func draw_tree(sp: *i64, fb: *i64, zb: *i64, R: *i64, gx: i64, gz: i64, style: i64) -> i64 {
255 let h: i64 = terrain_h(sp, gx, gz)
256 draw_obox(sp, fb, zb, R, gx, h, gz, 1, 3, 1, 110 + 74 * 256 + 44 * SP_MAGIC_65536, style) // trunk
257 draw_obox(sp, fb, zb, R, gx - 1, h + 3, gz - 1, 3, 2, 3, 56 + 132 * 256 + 58 * SP_MAGIC_65536, style) // canopy
258 return 0
259}
260// toon silhouette/edge outline: black where depth jumps (incl. against sky).
261func outline_pass(fb: *i64, zb: *i64) -> i64 {
262 var y: i64 = 1
263 while y < H - 1 {
264 var x: i64 = 1
265 while x < W - 1 {
266 let i: i64 = y * W + x
267 let d: i64 = zb[i]
268 if d < ZFAR {
269 var edge: i64 = 0
270 if zb[i - 1] == ZFAR { edge = 1 }
271 if zb[i + 1] == ZFAR { edge = 1 }
272 if zb[i - W] == ZFAR { edge = 1 }
273 if zb[i + W] == ZFAR { edge = 1 }
274 if iabs(d - zb[i - 1]) > 14 { edge = 1 }
275 if iabs(d - zb[i + 1]) > 14 { edge = 1 }
276 if iabs(d - zb[i - W]) > 14 { edge = 1 }
277 if iabs(d - zb[i + W]) > 14 { edge = 1 }
278 if edge == 1 { fb[i] = 0 }
279 }
280 x = x + 1
281 }
282 y = y + 1
283 }
284 return 0
285}
286func put_u32le(b: *u8, o: i64, v: i64) -> i64 {
287 b[o] = (v & 0xff) as u8
288 b[o + 1] = ((v >> 8) & 0xff) as u8
289 b[o + 2] = ((v >> 16) & 0xff) as u8
290 b[o + 3] = ((v >> 24) & 0xff) as u8
291 return 0
292}
293func write_bmp(fb: *i64, path: *u8) -> i64 {
294 let pix: i64 = W * H * 3
295 let total: i64 = 54 + pix
296 let b: *u8 = sys_mmap(total) as *u8
297 b[0] = 66 as u8
298 b[1] = 77 as u8
299 put_u32le(b, 2, total)
300 put_u32le(b, 10, 54)
301 put_u32le(b, 14, 40)
302 put_u32le(b, 18, W)
303 put_u32le(b, 22, H)
304 b[26] = 1 as u8
305 b[28] = 24 as u8
306 put_u32le(b, 34, pix)
307 var o: i64 = 54
308 var ry: i64 = 0
309 while ry < H {
310 let syrow: i64 = H - 1 - ry
311 var x: i64 = 0
312 while x < W {
313 let c: i64 = fb[syrow * W + x]
314 b[o] = ((c >> 16) & 0xff) as u8
315 b[o + 1] = ((c >> 8) & 0xff) as u8
316 b[o + 2] = (c & 0xff) as u8
317 o = o + 3
318 x = x + 1
319 }
320 ry = ry + 1
321 }
322 let fd: i64 = sys_openat_wr(path, 0x1a4)
323 if fd < 0 { return 0 - 1 }
324 sys_write(fd, b, total)
325 sys_close(fd)
326 return 0
327}
328// build the camera matrix from the spec (yaw/pitch). Shared so entities use the same camera as the terrain.
329func build_cam(sp: *i64, R: *i64) -> i64 {
330 let Ry: *i64 = sys_mmap(128) as *i64
331 let Rx: *i64 = sys_mmap(128) as *i64
332 m4_roty(fratio(sp[SP_YCOS], 1000), fratio(sp[SP_YSIN], 1000), Ry)
333 m4_rotx(fratio(sp[SP_PCOS], 1000), f32_neg(fratio(sp[SP_PSIN], 1000)), Rx)
334 m4_mul(Rx, Ry, R)
335 return 0
336}
337// expose the projector + terrain height so a caller can place entities on the terrain in the same camera.
338func world_project(sp: *i64, R: *i64, iwx: i64, iwy: i64, iwz: i64, out: *i64) -> i64 { return project(sp, R, iwx, iwy, iwz, out) }
339func world_height(sp: *i64, gx: i64, gz: i64) -> i64 { return terrain_h(sp, gx, gz) }
340func world_obox(sp: *i64, fb: *i64, zb: *i64, R: *i64, x0: i64, y0: i64, z0: i64, hx: i64, hy: i64, hz: i64, col: i64, style: i64) -> i64 {
341 return draw_obox(sp, fb, zb, R, x0, y0, z0, hx, hy, hz, col, style)
342}
343func render_world(sp: *i64, fb: *i64, zb: *i64) -> i64 {
344 let sky: i64 = world_sky()
345 var i: i64 = 0
346 while i < W * H {
347 fb[i] = sky
348 zb[i] = ZFAR
349 i = i + 1
350 }
351 let R: *i64 = sys_mmap(128) as *i64
352 build_cam(sp, R)
353 var gz: i64 = 0
354 while gz < sp[SP_N] {
355 var gx: i64 = 0
356 while gx < sp[SP_N] {
357 draw_col(sp, fb, zb, R, gx, gz)
358 if has_tree(sp, gx, gz) == 1 { draw_tree(sp, fb, zb, R, gx, gz, sp[SP_STYLE]) }
359 gx = gx + 1
360 }
361 gz = gz + 1
362 }
363 if sp[SP_STYLE] == STYLE_TOON { outline_pass(fb, zb) }
364 return 0
365}