nx_wasmvox3d.nx source
↩ module page · 375 lines · 15497 B
1// nx_wasmvox3d.nx -- TRUE 3D VOXEL world (per-voxel faces, not column boxes) so you can dig holes/tunnels and
2// stack blocks. Base-relative (same code runs native->PNG and wasm->browser). The world = a natural heightmap
3// PLUS a 2-bit-packed 3D edit grid (0=default, 1=placed-solid, 2=dug-air). Render draws only EXPOSED faces of
4// solid voxels (face to an air neighbor) -> tunnels/overhangs/floating blocks all work. Scoped small (48x48x18,
5// 192x144) for software-raster speed. Reuses the proven f32 + raster math. license_tier: ORIGINAL
6import "nx_f32_hw.nx"
7const O_MAGIC_65536: i64 = 65536
8
9const W: i64 = 192
10const H: i64 = 144
11const HW: i64 = 96
12const HH: i64 = 72
13const ZFAR: i64 = 1073741824
14const FOCAL: i64 = 150
15const WN: i64 = 48
16const YMAX: i64 = 18
17const WSEED: i64 = 7
18const WWATER: i64 = 3
19const WMAXH: i64 = 12
20const WLAT: i64 = 6
21const VDIST: i64 = 14
22
23const O_FB: i64 = 0
24const O_ZB: i64 = 221184 // W*H*8
25const O_R: i64 = 442368
26const O_RY: i64 = 442496
27const O_RX: i64 = 442624
28const O_V: i64 = 442752
29const O_RV: i64 = 442784
30const O_SX: i64 = 442816
31const O_SY: i64 = 442880
32const O_SD: i64 = 442944
33const O_SOK: i64 = 443008
34const O_PJ: i64 = 443072
35const O_HC: i64 = 443136 // per-column height cache, WN*WN i64
36const O_GRID: i64 = 461568 // 2-bit 3D edit grid: WN*WN*YMAX cells / 32 per i64
37
38func imin(a: i64, b: i64) -> i64 { if a < b { return a } return b }
39func imax(a: i64, b: i64) -> i64 { if a > b { return a } return b }
40func min3(a: i64, b: i64, c: i64) -> i64 { return imin(a, imin(b, c)) }
41func max3(a: i64, b: i64, c: i64) -> i64 { return imax(a, imax(b, c)) }
42func clamp255(v: i64) -> i64 { if v < 0 { return 0 } if v > 255 { return 255 } return v }
43func fratio(num: i64, den: i64) -> i64 { return f32_div(f32_of(num), f32_of(den)) }
44
45func wlat(ix: i64, iz: i64) -> i64 {
46 var n: i64 = (ix * 71 + iz * 191 + ix * iz * 7 + WSEED * 101) % 6
47 if n < 0 { n = n + 6 }
48 return n
49}
50// natural surface height of a column (no edits)
51func terrain_h0(gx: i64, gz: i64) -> i64 {
52 let bx: i64 = gx + 200
53 let bz: i64 = gz + 200
54 let ix: i64 = bx / WLAT
55 let iz: i64 = bz / WLAT
56 let fx: i64 = (bx % WLAT) * 256 / WLAT
57 let fz: i64 = (bz % WLAT) * 256 / WLAT
58 let h00: i64 = wlat(ix, iz)
59 let h10: i64 = wlat(ix + 1, iz)
60 let h01: i64 = wlat(ix, iz + 1)
61 let h11: i64 = wlat(ix + 1, iz + 1)
62 let a: i64 = h00 * 256 + (h10 - h00) * fx
63 let b: i64 = h01 * 256 + (h11 - h01) * fx
64 var h: i64 = (a * 256 + (b - a) * fz) / O_MAGIC_65536 + 1
65 if h < 1 { h = 1 }
66 if h > WMAXH { h = WMAXH }
67 return h
68}
69// 2-bit edit grid
70func grid_idx(gx: i64, gy: i64, gz: i64) -> i64 { return (gy * WN + gz) * WN + gx }
71func getcell(base: i64, gx: i64, gy: i64, gz: i64) -> i64 {
72 let idx: i64 = grid_idx(gx, gy, gz)
73 let p: *i64 = (base + O_GRID) as *i64
74 return (p[idx / 32] >> ((idx % 32) * 2)) & 3
75}
76func setcell(base: i64, gx: i64, gy: i64, gz: i64, v: i64) -> i64 {
77 let idx: i64 = grid_idx(gx, gy, gz)
78 let p: *i64 = (base + O_GRID) as *i64
79 let sh: i64 = (idx % 32) * 2
80 let cur: i64 = p[idx / 32]
81 p[idx / 32] = (cur - (cur & (3 << sh))) | ((v & 3) << sh)
82 return 0
83}
84// is (gx,gy,gz) solid? hcol = cached natural height of column (gx,gz); pass -1 to force a fresh terrain_h0.
85func solid_h(base: i64, gx: i64, gy: i64, gz: i64, hcol: i64) -> i64 {
86 if gy < 0 { return 1 }
87 if gy >= YMAX { return 0 }
88 if gx < 0 { return 0 }
89 if gx >= WN { return 0 }
90 if gz < 0 { return 0 }
91 if gz >= WN { return 0 }
92 let e: i64 = getcell(base, gx, gy, gz)
93 if e == 1 { return 1 }
94 if e == 2 { return 0 }
95 var h: i64 = hcol
96 if h < 0 { h = terrain_h0(gx, gz) }
97 if gy < h { return 1 }
98 return 0
99}
100func solid(base: i64, gx: i64, gy: i64, gz: i64) -> i64 {
101 // read the cached column height (valid for 0<=gx,gz<WN; else fresh)
102 if gx >= 0 { if gx < WN { if gz >= 0 { if gz < WN {
103 let hc: *i64 = (base + O_HC) as *i64
104 return solid_h(base, gx, gy, gz, hc[gz * WN + gx])
105 } } } }
106 return solid_h(base, gx, gy, gz, 0 - 1)
107}
108func voxel_color(gy: i64, top: i64) -> i64 {
109 if gy <= WWATER { return 64 + 116 * 256 + 204 * O_MAGIC_65536 } // water blue
110 if top == 1 {
111 if gy >= WMAXH - 1 { return 224 + 226 * 256 + 234 * O_MAGIC_65536 } // snow
112 return 96 + 172 * 256 + 76 * O_MAGIC_65536 // grass
113 }
114 if gy >= WMAXH - 1 { return 150 + 146 * 256 + 140 * O_MAGIC_65536 } // rock
115 if gy <= 2 { return 120 + 116 * 256 + 110 * O_MAGIC_65536 } // stone
116 return 134 + 98 * 256 + 60 * O_MAGIC_65536 // dirt
117}
118func shade(lnx: i64, lny: i64, lnz: i64) -> i64 {
119 var d: i64 = lnx * 2 + lny * 3 + lnz * 1
120 if d < 0 { d = 0 }
121 return clamp255(110 + d * 38)
122}
123func packc(col: i64, bright: i64) -> i64 {
124 let r: i64 = (col & 0xff) * bright / 255
125 let g: i64 = ((col >> 8) & 0xff) * bright / 255
126 let b: i64 = ((col >> 16) & 0xff) * bright / 255
127 return r + g * 256 + b * O_MAGIC_65536
128}
129func fill_z(base: i64, x0: i64, y0: i64, d0: i64, x1: i64, y1: i64, d1: i64, x2: i64, y2: i64, d2: i64, col: i64) -> i64 {
130 let fb: *i64 = (base + O_FB) as *i64
131 let zb: *i64 = (base + O_ZB) as *i64
132 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)
133 if area == 0 { return 0 }
134 let minx: i64 = imax(0, min3(x0, x1, x2))
135 let maxx: i64 = imin(W - 1, max3(x0, x1, x2))
136 let miny: i64 = imax(0, min3(y0, y1, y2))
137 let maxy: i64 = imin(H - 1, max3(y0, y1, y2))
138 var py: i64 = miny
139 while py <= maxy {
140 var px: i64 = minx
141 while px <= maxx {
142 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1)
143 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2)
144 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0)
145 var ins: i64 = 0
146 if area > 0 { if e0 >= 0 { if e1 >= 0 { if e2 >= 0 { ins = 1 } } } }
147 if area < 0 { if e0 <= 0 { if e1 <= 0 { if e2 <= 0 { ins = 1 } } } }
148 if ins == 1 {
149 let d: i64 = (d0 * e0 + d1 * e1 + d2 * e2) / area
150 let idx: i64 = py * W + px
151 if d < zb[idx] { zb[idx] = d; fb[idx] = col }
152 }
153 px = px + 1
154 }
155 py = py + 1
156 }
157 return 0
158}
159func project(base: i64, camx: i64, camy: i64, camz: i64, iwx: i64, iwy: i64, iwz: i64) -> i64 {
160 let v: *i64 = (base + O_V) as *i64
161 let r: *i64 = (base + O_RV) as *i64
162 let R: *i64 = (base + O_R) as *i64
163 let pj: *i64 = (base + O_PJ) as *i64
164 v[0] = f32_sub(f32_of(iwx), camx)
165 v[1] = f32_sub(f32_of(iwy), camy)
166 v[2] = f32_sub(f32_of(iwz), camz)
167 v[3] = f32_of(0)
168 m4_vec4(R, v, r)
169 let pz: i64 = r[2]
170 if (pz & 0x80000000) != 0 { pj[3] = 0; return 0 }
171 let zi: i64 = f32_int(f32_mul(pz, f32_of(64)))
172 if zi <= 0 { pj[3] = 0; return 0 }
173 pj[0] = HW + f32_int(f32_div(f32_mul(r[0], f32_of(FOCAL)), pz))
174 pj[1] = HH - f32_int(f32_div(f32_mul(r[1], f32_of(FOCAL)), pz))
175 pj[2] = zi
176 pj[3] = 1
177 return 0
178}
179func corners(base: i64, camx: i64, camy: i64, camz: i64, x0: i64, y0: i64, z0: i64) -> i64 {
180 let SX: *i64 = (base + O_SX) as *i64
181 let SY: *i64 = (base + O_SY) as *i64
182 let SD: *i64 = (base + O_SD) as *i64
183 let SOK: *i64 = (base + O_SOK) as *i64
184 let pj: *i64 = (base + O_PJ) as *i64
185 var k: i64 = 0
186 while k < 8 {
187 project(base, camx, camy, camz, x0 + (k & 1), y0 + ((k >> 1) & 1), z0 + ((k >> 2) & 1))
188 SX[k] = pj[0]; SY[k] = pj[1]; SD[k] = pj[2]; SOK[k] = pj[3]
189 k = k + 1
190 }
191 return 0
192}
193func face(base: i64, a: i64, b: i64, c: i64, dd: i64, lnx: i64, lny: i64, lnz: i64, col: i64) -> i64 {
194 let SX: *i64 = (base + O_SX) as *i64
195 let SY: *i64 = (base + O_SY) as *i64
196 let SD: *i64 = (base + O_SD) as *i64
197 let SOK: *i64 = (base + O_SOK) as *i64
198 if SOK[a] == 0 { return 0 }
199 if SOK[b] == 0 { return 0 }
200 if SOK[c] == 0 { return 0 }
201 if SOK[dd] == 0 { return 0 }
202 let fc: i64 = packc(col, shade(lnx, lny, lnz))
203 fill_z(base, SX[a], SY[a], SD[a], SX[b], SY[b], SD[b], SX[c], SY[c], SD[c], fc)
204 fill_z(base, SX[a], SY[a], SD[a], SX[c], SY[c], SD[c], SX[dd], SY[dd], SD[dd], fc)
205 return 0
206}
207// draw the exposed faces of solid voxel (gx,gy,gz)
208func draw_voxel(base: i64, camx: i64, camy: i64, camz: i64, gx: i64, gy: i64, gz: i64) -> i64 {
209 let up: i64 = solid(base, gx, gy + 1, gz)
210 let tcol: i64 = voxel_color(gy, 1 - up) // exposed top -> grass/surface tint
211 let scol: i64 = voxel_color(gy, 0)
212 corners(base, camx, camy, camz, gx, gy, gz)
213 if up == 0 { face(base, 2, 3, 7, 6, 0, 1, 0, tcol) }
214 if solid(base, gx + 1, gy, gz) == 0 { face(base, 1, 5, 7, 3, 1, 0, 0, scol) }
215 if solid(base, gx - 1, gy, gz) == 0 { face(base, 0, 2, 6, 4, 0 - 1, 0, 0, scol) }
216 if solid(base, gx, gy, gz + 1) == 0 { face(base, 4, 5, 7, 6, 0, 0, 1, scol) }
217 if solid(base, gx, gy, gz - 1) == 0 { face(base, 0, 1, 3, 2, 0, 0, 0 - 1, scol) }
218 return 0
219}
220func corners_sz(base: i64, camx: i64, camy: i64, camz: i64, x0: i64, y0: i64, z0: i64, sw: i64, sh: i64, sl: i64) -> i64 {
221 let SX: *i64 = (base + O_SX) as *i64
222 let SY: *i64 = (base + O_SY) as *i64
223 let SD: *i64 = (base + O_SD) as *i64
224 let SOK: *i64 = (base + O_SOK) as *i64
225 let pj: *i64 = (base + O_PJ) as *i64
226 var k: i64 = 0
227 while k < 8 {
228 project(base, camx, camy, camz, x0 + (k & 1) * sw, y0 + ((k >> 1) & 1) * sh, z0 + ((k >> 2) & 1) * sl)
229 SX[k] = pj[0]; SY[k] = pj[1]; SD[k] = pj[2]; SOK[k] = pj[3]
230 k = k + 1
231 }
232 return 0
233}
234func draw_solidbox(base: i64, camx: i64, camy: i64, camz: i64, x0: i64, y0: i64, z0: i64, sw: i64, sh: i64, sl: i64, col: i64) -> i64 {
235 corners_sz(base, camx, camy, camz, x0, y0, z0, sw, sh, sl)
236 face(base, 2, 3, 7, 6, 0, 1, 0, col)
237 face(base, 1, 5, 7, 3, 1, 0, 0, col)
238 face(base, 0, 2, 6, 4, 0 - 1, 0, 0, col)
239 face(base, 4, 5, 7, 6, 0, 0, 1, col)
240 face(base, 0, 1, 3, 2, 0, 0, 0 - 1, col)
241 return 0
242}
243func has_tree(gx: i64, gz: i64) -> i64 {
244 let h: i64 = terrain_h0(gx, gz)
245 if h <= WWATER { return 0 }
246 if h >= WMAXH - 1 { return 0 }
247 var n: i64 = (gx * 131 + gz * 197 + gx * gz * 7 + 13) % 17
248 if n < 0 { n = n + 17 }
249 if n == 0 { return 1 }
250 return 0
251}
252func draw_tree(base: i64, camx: i64, camy: i64, camz: i64, gx: i64, gz: i64) -> i64 {
253 let h: i64 = terrain_h0(gx, gz)
254 draw_solidbox(base, camx, camy, camz, gx, h, gz, 1, 3, 1, 92 + 64 * 256 + 38 * O_MAGIC_65536)
255 draw_solidbox(base, camx, camy, camz, gx - 1, h + 3, gz - 1, 3, 3, 3, 40 + 122 * 256 + 50 * O_MAGIC_65536)
256 return 0
257}
258func render_at(base: i64, camx: i64, camy: i64, camz: i64, cgx: i64, cgz: i64) -> i64 {
259 let fb: *i64 = (base + O_FB) as *i64
260 let zb: *i64 = (base + O_ZB) as *i64
261 let sky: i64 = 142 + 186 * 256 + 228 * O_MAGIC_65536
262 var i: i64 = 0
263 while i < W * H { fb[i] = sky; zb[i] = ZFAR; i = i + 1 }
264 // refresh the column-height cache for the world
265 let hc: *i64 = (base + O_HC) as *i64
266 var cz: i64 = 0
267 while cz < WN { var cx: i64 = 0; while cx < WN { hc[cz * WN + cx] = terrain_h0(cx, cz); cx = cx + 1 } cz = cz + 1 }
268 var gz: i64 = cgz - VDIST
269 while gz <= cgz + VDIST {
270 if gz >= 0 { if gz < WN {
271 var gx: i64 = cgx - VDIST
272 while gx <= cgx + VDIST {
273 if gx >= 0 { if gx < WN {
274 var gy: i64 = 0
275 while gy < YMAX {
276 if solid(base, gx, gy, gz) == 1 { draw_voxel(base, camx, camy, camz, gx, gy, gz) }
277 gy = gy + 1
278 }
279 } }
280 gx = gx + 1
281 }
282 } }
283 gz = gz + 1
284 }
285 // trees: decorative boxes after the voxel terrain (z-buffer occludes correctly)
286 var tgz: i64 = cgz - VDIST
287 while tgz <= cgz + VDIST {
288 if tgz >= 0 { if tgz < WN {
289 var tgx: i64 = cgx - VDIST
290 while tgx <= cgx + VDIST {
291 if tgx >= 0 { if tgx < WN {
292 if has_tree(tgx, tgz) == 1 { draw_tree(base, camx, camy, camz, tgx, tgz) }
293 } }
294 tgx = tgx + 1
295 }
296 } }
297 tgz = tgz + 1
298 }
299 return 0
300}
301func build_R(base: i64, ycos: i64, ysin: i64, pcos: i64, psin: i64) -> i64 {
302 let Ry: *i64 = (base + O_RY) as *i64
303 let Rx: *i64 = (base + O_RX) as *i64
304 let R: *i64 = (base + O_R) as *i64
305 m4_roty(fratio(ycos, 1000), fratio(ysin, 1000), Ry)
306 m4_rotx(fratio(pcos, 1000), f32_neg(fratio(psin, 1000)), Rx)
307 m4_mul(Rx, Ry, R)
308 return 0
309}
310func mem_bytes() -> i64 { return O_GRID + (WN * WN * YMAX / 32 + 4) * 8 }
311// surface height (highest solid voxel +1) at a column -- for spawn/collision in the shim
312func surface_y(base: i64, gx: i64, gz: i64) -> i64 {
313 var gy: i64 = YMAX - 1
314 while gy >= 0 { if solid(base, gx, gy, gz) == 1 { return gy + 1 } gy = gy - 1 }
315 return 0
316}
317// 3D-DDA raycast: returns the hit solid cell packed, plus the air cell just before it for placement.
318// out[0]=hit flag, out[1..3]=hit cell, out[4..6]=place cell (last air before hit).
319func raycast(base: i64, camx256: i64, camy256: i64, camz256: i64, dx1000: i64, dy1000: i64, dz1000: i64, out: *i64) -> i64 {
320 var px: i64 = camx256
321 var py: i64 = camy256
322 var pz: i64 = camz256
323 var ax: i64 = px / 256
324 var ay: i64 = py / 256
325 var az: i64 = pz / 256
326 var i: i64 = 0
327 while i < 140 {
328 let gx: i64 = px / 256
329 let gy: i64 = py / 256
330 let gz: i64 = pz / 256
331 if solid(base, gx, gy, gz) == 1 {
332 out[0] = 1; out[1] = gx; out[2] = gy; out[3] = gz; out[4] = ax; out[5] = ay; out[6] = az
333 return 0
334 }
335 ax = gx; ay = gy; az = gz
336 px = px + dx1000 * 38 / 1000
337 py = py + dy1000 * 38 / 1000
338 pz = pz + dz1000 * 38 / 1000
339 i = i + 1
340 }
341 out[0] = 0
342 return 0
343}
344// WASM exports
345func render(camx256: i64, camy256: i64, camz256: i64, ycos: i64, ysin: i64, pcos: i64, psin: i64) -> i64 {
346 build_R(0, ycos, ysin, pcos, psin)
347 let camx: i64 = f32_div(f32_of(camx256), f32_of(256))
348 let camy: i64 = f32_div(f32_of(camy256), f32_of(256))
349 let camz: i64 = f32_div(f32_of(camz256), f32_of(256))
350 render_at(0, camx, camy, camz, camx256 / 256, camz256 / 256)
351 return 0
352}
353func terrain_at(gx: i64, gz: i64) -> i64 {
354 let hc: *i64 = (0 + O_HC) as *i64
355 if gx >= 0 { if gx < WN { if gz >= 0 { if gz < WN { hc[gz * WN + gx] = terrain_h0(gx, gz) } } } }
356 return surface_y(0, gx, gz)
357}
358// break the solid voxel the camera is aiming at (dug = air)
359func break_at(camx256: i64, camy256: i64, camz256: i64, dx1000: i64, dy1000: i64, dz1000: i64) -> i64 {
360 let o: *i64 = (0 + O_PJ) as *i64
361 raycast(0, camx256, camy256, camz256, dx1000, dy1000, dz1000, o)
362 if o[0] == 1 { if o[2] >= 0 { if o[2] < YMAX { setcell(0, o[1], o[2], o[3], 2) } } }
363 return 0
364}
365// place a solid voxel in the air cell just before the hit
366func place_at(camx256: i64, camy256: i64, camz256: i64, dx1000: i64, dy1000: i64, dz1000: i64) -> i64 {
367 let o: *i64 = (0 + O_PJ) as *i64
368 raycast(0, camx256, camy256, camz256, dx1000, dy1000, dz1000, o)
369 if o[0] == 1 { if o[5] >= 0 { if o[5] < YMAX { setcell(0, o[4], o[5], o[6], 1) } } }
370 return 0
371}
372func fb_off() -> i64 { return O_FB }
373func ww() -> i64 { return W }
374func hh() -> i64 { return H }
375func main() -> i64 { return 0 }