code wiki / (root) / nx_wasmfps.nx

nx_wasmfps.nx source

↩ module page · 290 lines · 12386 B

1// nx_wasmfps.nx -- FIRST-PERSON voxel world renderer, base-relative so the SAME code runs native (base = an 2// mmap'd region -> PNG, eyeball-verifiable) and in WASM (base = 0 -> linear memory). Z-buffered, perspective, 3// distance-culled, with trees; realism directional shading. Camera has a world POSITION (you walk/look), not 4// orbit. JS owns trig + camera state and calls render(); wasm owns the world + the hardware-f32 math. 5// World params are constants (fixed world, moving camera). license_tier: ORIGINAL 6import "nx_f32_hw.nx" 7const O_MAGIC_65536: i64 = 65536 8 9const W: i64 = 200 10const H: i64 = 150 11const HW: i64 = 100 12const HH: i64 = 75 13const ZFAR: i64 = 1073741824 14const FOCAL: i64 = 150 15const WN: i64 = 96 // world is WN x WN columns 16const WSEED: i64 = 7 17const WWATER: i64 = 3 18const WMAXH: i64 = 14 19const WLAT: i64 = 6 20const VDIST: i64 = 26 // only draw columns within this Manhattan range of the camera 21 22const O_FB: i64 = 0 23const O_ZB: i64 = 240000 // W*H*8 24const O_R: i64 = 480000 25const O_RY: i64 = 480128 26const O_RX: i64 = 480256 27const O_V: i64 = 480384 28const O_RV: i64 = 480416 29const O_SX: i64 = 480448 30const O_SY: i64 = 480512 31const O_SD: i64 = 480576 32const O_SOK: i64 = 480640 33const O_PJ: i64 = 480704 // project out: x,y,d,ok 34const O_DELTA: i64 = 481024 // per-column height edits, WN*WN i64 (block break/place) 35 36func imin(a: i64, b: i64) -> i64 { if a < b { return a } return b } 37func imax(a: i64, b: i64) -> i64 { if a > b { return a } return b } 38func iabs(a: i64) -> i64 { if a < 0 { return 0 - a } return a } 39func min3(a: i64, b: i64, c: i64) -> i64 { return imin(a, imin(b, c)) } 40func max3(a: i64, b: i64, c: i64) -> i64 { return imax(a, imax(b, c)) } 41func clamp255(v: i64) -> i64 { if v < 0 { return 0 } if v > 255 { return 255 } return v } 42func fratio(num: i64, den: i64) -> i64 { return f32_div(f32_of(num), f32_of(den)) } 43 44func wlat(ix: i64, iz: i64) -> i64 { 45 var n: i64 = (ix * 71 + iz * 191 + ix * iz * 7 + WSEED * 101) % 6 46 if n < 0 { n = n + 6 } 47 return n 48} 49func terrain_h(base: i64, gx: i64, gz: i64) -> i64 { 50 let bx: i64 = gx + 200 51 let bz: i64 = gz + 200 52 let ix: i64 = bx / WLAT 53 let iz: i64 = bz / WLAT 54 let fx: i64 = (bx % WLAT) * 256 / WLAT 55 let fz: i64 = (bz % WLAT) * 256 / WLAT 56 let h00: i64 = wlat(ix, iz) 57 let h10: i64 = wlat(ix + 1, iz) 58 let h01: i64 = wlat(ix, iz + 1) 59 let h11: i64 = wlat(ix + 1, iz + 1) 60 let a: i64 = h00 * 256 + (h10 - h00) * fx 61 let b: i64 = h01 * 256 + (h11 - h01) * fx 62 var h: i64 = (a * 256 + (b - a) * fz) / O_MAGIC_65536 + 1 63 if h < 1 { h = 1 } 64 if h > WMAXH { h = WMAXH } 65 if gx >= 0 { if gx < WN { if gz >= 0 { if gz < WN { 66 let dl: *i64 = (base + O_DELTA) as *i64 67 h = h + dl[gz * WN + gx] 68 } } } } 69 if h < 1 { h = 1 } 70 if h > 30 { h = 30 } 71 return h 72} 73func hN(base: i64, gx: i64, gz: i64) -> i64 { return terrain_h(base, gx, gz) } 74func top_color(h: i64) -> i64 { 75 if h <= WWATER { return 64 + 116 * 256 + 204 * O_MAGIC_65536 } 76 if h >= WMAXH { return 236 + 238 * 256 + 245 * O_MAGIC_65536 } 77 if h == WMAXH - 1 { return 150 + 146 * 256 + 140 * O_MAGIC_65536 } 78 return 96 + 172 * 256 + 76 * O_MAGIC_65536 79} 80func side_color(h: i64) -> i64 { 81 if h <= WWATER { return 58 + 104 * 256 + 188 * O_MAGIC_65536 } 82 if h >= WMAXH - 1 { return 138 + 134 * 256 + 128 * O_MAGIC_65536 } 83 return 134 + 98 * 256 + 60 * O_MAGIC_65536 84} 85func shade(lnx: i64, lny: i64, lnz: i64) -> i64 { 86 var d: i64 = lnx * 2 + lny * 3 + lnz * 1 87 if d < 0 { d = 0 } 88 return clamp255(110 + d * 38) 89} 90func packc(col: i64, bright: i64) -> i64 { 91 let r: i64 = (col & 0xff) * bright / 255 92 let g: i64 = ((col >> 8) & 0xff) * bright / 255 93 let b: i64 = ((col >> 16) & 0xff) * bright / 255 94 return r + g * 256 + b * O_MAGIC_65536 95} 96// z-buffered flat-colour triangle (barycentric, depth interpolated). col is final packed RGB. 97func 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 { 98 let fb: *i64 = (base + O_FB) as *i64 99 let zb: *i64 = (base + O_ZB) as *i64 100 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) 101 if area == 0 { return 0 } 102 let minx: i64 = imax(0, min3(x0, x1, x2)) 103 let maxx: i64 = imin(W - 1, max3(x0, x1, x2)) 104 let miny: i64 = imax(0, min3(y0, y1, y2)) 105 let maxy: i64 = imin(H - 1, max3(y0, y1, y2)) 106 var py: i64 = miny 107 while py <= maxy { 108 var px: i64 = minx 109 while px <= maxx { 110 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1) 111 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2) 112 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0) 113 var inside: i64 = 0 114 if area > 0 { if e0 >= 0 { if e1 >= 0 { if e2 >= 0 { inside = 1 } } } } 115 if area < 0 { if e0 <= 0 { if e1 <= 0 { if e2 <= 0 { inside = 1 } } } } 116 if inside == 1 { 117 let d: i64 = (d0 * e0 + d1 * e1 + d2 * e2) / area 118 let idx: i64 = py * W + px 119 if d < zb[idx] { zb[idx] = d; fb[idx] = col } 120 } 121 px = px + 1 122 } 123 py = py + 1 124 } 125 return 0 126} 127// project world (iwx,iwy,iwz) relative to camera (camx,camy,camz f32) through R -> O_PJ[x,y,d,ok] 128func project(base: i64, camx: i64, camy: i64, camz: i64, iwx: i64, iwy: i64, iwz: i64) -> i64 { 129 let v: *i64 = (base + O_V) as *i64 130 let r: *i64 = (base + O_RV) as *i64 131 let R: *i64 = (base + O_R) as *i64 132 let pj: *i64 = (base + O_PJ) as *i64 133 v[0] = f32_sub(f32_of(iwx), camx) 134 v[1] = f32_sub(f32_of(iwy), camy) 135 v[2] = f32_sub(f32_of(iwz), camz) 136 v[3] = f32_of(0) 137 m4_vec4(R, v, r) 138 let pz: i64 = r[2] 139 if (pz & 0x80000000) != 0 { pj[3] = 0; return 0 } 140 let zi: i64 = f32_int(f32_mul(pz, f32_of(64))) 141 if zi <= 0 { pj[3] = 0; return 0 } 142 pj[0] = HW + f32_int(f32_div(f32_mul(r[0], f32_of(FOCAL)), pz)) 143 pj[1] = HH - f32_int(f32_div(f32_mul(r[1], f32_of(FOCAL)), pz)) 144 pj[2] = zi 145 pj[3] = 1 146 return 0 147} 148// project the 8 corners of an axis box at (x0,y0,z0) size (sxw,syw,szw) into the SX/SY/SD/SOK scratch arrays 149func box_corners(base: i64, camx: i64, camy: i64, camz: i64, x0: i64, y0: i64, z0: i64, sw: i64, sh: i64, sl: i64) -> i64 { 150 let SX: *i64 = (base + O_SX) as *i64 151 let SY: *i64 = (base + O_SY) as *i64 152 let SD: *i64 = (base + O_SD) as *i64 153 let SOK: *i64 = (base + O_SOK) as *i64 154 let pj: *i64 = (base + O_PJ) as *i64 155 var k: i64 = 0 156 while k < 8 { 157 let dx: i64 = (k & 1) * sw 158 let dy: i64 = ((k >> 1) & 1) * sh 159 let dz: i64 = ((k >> 2) & 1) * sl 160 project(base, camx, camy, camz, x0 + dx, y0 + dy, z0 + dz) 161 SX[k] = pj[0]; SY[k] = pj[1]; SD[k] = pj[2]; SOK[k] = pj[3] 162 k = k + 1 163 } 164 return 0 165} 166func draw_face(base: i64, a: i64, b: i64, c: i64, dd: i64, lnx: i64, lny: i64, lnz: i64, col: i64) -> i64 { 167 let SX: *i64 = (base + O_SX) as *i64 168 let SY: *i64 = (base + O_SY) as *i64 169 let SD: *i64 = (base + O_SD) as *i64 170 let SOK: *i64 = (base + O_SOK) as *i64 171 if SOK[a] == 0 { return 0 } 172 if SOK[b] == 0 { return 0 } 173 if SOK[c] == 0 { return 0 } 174 if SOK[dd] == 0 { return 0 } 175 let fc: i64 = packc(col, shade(lnx, lny, lnz)) 176 fill_z(base, SX[a], SY[a], SD[a], SX[b], SY[b], SD[b], SX[c], SY[c], SD[c], fc) 177 fill_z(base, SX[a], SY[a], SD[a], SX[c], SY[c], SD[c], SX[dd], SY[dd], SD[dd], fc) 178 return 0 179} 180func draw_col(base: i64, camx: i64, camy: i64, camz: i64, gx: i64, gz: i64) -> i64 { 181 let h: i64 = terrain_h(base, gx, gz) 182 box_corners(base, camx, camy, camz, gx, 0, gz, 1, h, 1) 183 draw_face(base, 2, 3, 7, 6, 0, 1, 0, top_color(h)) 184 if hN(base, gx + 1, gz) < h { draw_face(base, 1, 5, 7, 3, 1, 0, 0, side_color(h)) } 185 if hN(base, gx - 1, gz) < h { draw_face(base, 0, 2, 6, 4, 0 - 1, 0, 0, side_color(h)) } 186 if hN(base, gx, gz + 1) < h { draw_face(base, 4, 5, 7, 6, 0, 0, 1, side_color(h)) } 187 if hN(base, gx, gz - 1) < h { draw_face(base, 0, 1, 3, 2, 0, 0, 0 - 1, side_color(h)) } 188 return 0 189} 190func has_tree(base: i64, gx: i64, gz: i64) -> i64 { 191 let h: i64 = terrain_h(base, gx, gz) 192 if h <= WWATER { return 0 } 193 if h >= WMAXH - 1 { return 0 } 194 var n: i64 = (gx * 131 + gz * 197 + gx * gz * 7 + 13) % 17 195 if n < 0 { n = n + 17 } 196 if n == 0 { return 1 } 197 return 0 198} 199func draw_box(base: i64, camx: i64, camy: i64, camz: i64, x0: i64, y0: i64, z0: i64, sw: i64, sh: i64, sl: i64, col: i64) -> i64 { 200 box_corners(base, camx, camy, camz, x0, y0, z0, sw, sh, sl) 201 draw_face(base, 2, 3, 7, 6, 0, 1, 0, col) 202 draw_face(base, 1, 5, 7, 3, 1, 0, 0, col) 203 draw_face(base, 0, 2, 6, 4, 0 - 1, 0, 0, col) 204 draw_face(base, 4, 5, 7, 6, 0, 0, 1, col) 205 draw_face(base, 0, 1, 3, 2, 0, 0, 0 - 1, col) 206 return 0 207} 208func draw_tree(base: i64, camx: i64, camy: i64, camz: i64, gx: i64, gz: i64) -> i64 { 209 let h: i64 = terrain_h(base, gx, gz) 210 draw_box(base, camx, camy, camz, gx, h, gz, 1, 3, 1, 92 + 64 * 256 + 38 * O_MAGIC_65536) // trunk (brown) 211 draw_box(base, camx, camy, camz, gx - 1, h + 3, gz - 1, 3, 3, 3, 40 + 122 * 256 + 50 * O_MAGIC_65536) // leaves (green) 212 return 0 213} 214func render_at(base: i64, camx: i64, camy: i64, camz: i64, cgx: i64, cgz: i64) -> i64 { 215 let fb: *i64 = (base + O_FB) as *i64 216 let zb: *i64 = (base + O_ZB) as *i64 217 let sky: i64 = 142 + 186 * 256 + 228 * O_MAGIC_65536 218 var i: i64 = 0 219 while i < W * H { fb[i] = sky; zb[i] = ZFAR; i = i + 1 } 220 var gz: i64 = cgz - VDIST 221 while gz <= cgz + VDIST { 222 if gz >= 0 { if gz < WN { 223 var gx: i64 = cgx - VDIST 224 while gx <= cgx + VDIST { 225 if gx >= 0 { if gx < WN { 226 draw_col(base, camx, camy, camz, gx, gz) 227 if has_tree(base, gx, gz) == 1 { draw_tree(base, camx, camy, camz, gx, gz) } 228 } } 229 gx = gx + 1 230 } 231 } } 232 gz = gz + 1 233 } 234 return 0 235} 236func build_R(base: i64, ycos: i64, ysin: i64, pcos: i64, psin: i64) -> i64 { 237 let Ry: *i64 = (base + O_RY) as *i64 238 let Rx: *i64 = (base + O_RX) as *i64 239 let R: *i64 = (base + O_R) as *i64 240 m4_roty(fratio(ycos, 1000), fratio(ysin, 1000), Ry) 241 m4_rotx(fratio(pcos, 1000), f32_neg(fratio(psin, 1000)), Rx) 242 m4_mul(Rx, Ry, R) 243 return 0 244} 245func mem_bytes() -> i64 { return O_DELTA + WN * WN * 8 + 64 } 246// WASM export: camera pos in 1/256 fixed-point ints, yaw/pitch cos/sin in 1/1000 ints (JS owns trig). 247func render(camx256: i64, camy256: i64, camz256: i64, ycos: i64, ysin: i64, pcos: i64, psin: i64) -> i64 { 248 build_R(0, ycos, ysin, pcos, psin) 249 let camx: i64 = f32_div(f32_of(camx256), f32_of(256)) 250 let camy: i64 = f32_div(f32_of(camy256), f32_of(256)) 251 let camz: i64 = f32_div(f32_of(camz256), f32_of(256)) 252 render_at(0, camx, camy, camz, camx256 / 256, camz256 / 256) 253 return 0 254} 255func terrain_at(gx: i64, gz: i64) -> i64 { return terrain_h(0, gx, gz) } 256// block edit: adjust a column's height delta (base-relative so the native gate can verify edits by PNG) 257func edit_delta(base: i64, gx: i64, gz: i64, change: i64) -> i64 { 258 if gx < 0 { return 0 } 259 if gx >= WN { return 0 } 260 if gz < 0 { return 0 } 261 if gz >= WN { return 0 } 262 let dl: *i64 = (base + O_DELTA) as *i64 263 dl[gz * WN + gx] = dl[gz * WN + gx] + change 264 return 0 265} 266func break_block(gx: i64, gz: i64) -> i64 { return edit_delta(0, gx, gz, 0 - 1) } 267func place_block(gx: i64, gz: i64) -> i64 { return edit_delta(0, gx, gz, 1) } 268// raycast from camera along (dir/1000); return the first column hit packed (gx*65536)+gz, or -1 269func pick(camx256: i64, camy256: i64, camz256: i64, dx1000: i64, dy1000: i64, dz1000: i64) -> i64 { 270 var px: i64 = camx256 271 var py: i64 = camy256 272 var pz: i64 = camz256 273 var i: i64 = 0 274 while i < 90 { 275 px = px + dx1000 * 48 / 1000 276 py = py + dy1000 * 48 / 1000 277 pz = pz + dz1000 * 48 / 1000 278 let gx: i64 = px / 256 279 let gz: i64 = pz / 256 280 if gx >= 0 { if gx < WN { if gz >= 0 { if gz < WN { 281 if py / 256 < terrain_h(0, gx, gz) { return (gx * O_MAGIC_65536) + gz } 282 } } } } 283 i = i + 1 284 } 285 return 0 - 1 286} 287func fb_off() -> i64 { return O_FB } 288func ww() -> i64 { return W } 289func hh() -> i64 { return H } 290func main() -> i64 { return 0 }