The tiles' photograph side is 0 when the map has none: no photograph or coarse photograph sections, no decode, ter_o answers 0, and no coarse photograph texture - as such a map has always drawn. The cut and terrain_tiles_bake no longer require one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
203 lines
7.9 KiB
Text
203 lines
7.9 KiB
Text
# terrain_ltt2_write.ludic — the terrain as THE quantized tiles every reader answers from (plan 26 of
|
|
# maroon-lake): heights as u16 over each tile's own minimum and step, normals octahedral 8 + 8, the
|
|
# photograph RGB8, and the coarse level. Written by the build-time bake and, in a dev build with no bake,
|
|
# by the cut; either way the whole copy is put back to the quantized values, so the build's own
|
|
# queries, the physics, the placements' bake and every machine read the same numbers.
|
|
|
|
const LT_MAGIC: int = 844383308 # "LTT2"
|
|
const LT_VERSION: int = 1
|
|
const LT_HEAD: int = 128 # 32 words
|
|
|
|
# the payload at the file's current place (its start is `base`); its length. Header written last.
|
|
@alloc_ok("a map being made or baked: the tiles written once")
|
|
function ltt2_write(render3d_st: mut Render3dState, f: pointer, base: int) -> int {
|
|
let n = TERRAIN_RES / TT_TEX
|
|
# a map with no photograph (Lamar's ground is painted by its materials) has no photograph sections
|
|
var side = 0
|
|
if render3d_st.ter_ortho_px != null { side = render3d_st.ter_ortho_w / n }
|
|
let head = words(32)
|
|
for k in 0 .. 32 { head[k] = 0 }
|
|
file_write(f, data_of(head), LT_HEAD)
|
|
var at = LT_HEAD
|
|
head[8] = at; at += ltt2_write_heights(render3d_st, f, n)
|
|
let nrm = ltt2_normals_back(render3d_st)
|
|
head[10] = at; at += ltt2_write_normals(f, nrm, n)
|
|
head[11] = at
|
|
if side > 0 { at += ltt2_write_ortho(render3d_st, f, n, side) }
|
|
head[12] = at; at += ltt2_write_coarse_h(render3d_st, f)
|
|
head[13] = at; at += ltt2_write_coarse_n(f, nrm)
|
|
free(nrm)
|
|
head[14] = at
|
|
if side > 0 { at += ltt2_write_coarse_o(render3d_st, f) }
|
|
head[0] = LT_MAGIC; head[1] = LT_VERSION; head[2] = TT_TEX; head[3] = n; head[4] = side
|
|
head[5] = TERRAIN_RES; head[6] = render3d_st.ter_ortho_w; head[7] = TT_COARSE; head[15] = at
|
|
file_seek(f, base, 0)
|
|
file_write(f, data_of(head), LT_HEAD)
|
|
file_seek(f, base + at, 0)
|
|
free(head)
|
|
return at
|
|
}
|
|
|
|
# each tile's (min, step) table, then its heights as u16; the whole copy put back to what they decode to
|
|
@alloc_ok("a map being made or baked: one tile's scratch and the table, freed after")
|
|
function ltt2_write_heights(render3d_st: mut Render3dState, f: pointer, n: int) -> int {
|
|
let tab = floats(n * n * 2)
|
|
let h = render3d_st.ter_heights
|
|
for j in 0 .. n {
|
|
for i in 0 .. n {
|
|
var lo = 1000000.0; var hi = -1000000.0
|
|
for r in 0 .. TT_TEX { for k in 0 .. TT_TEX { let v = h[(j * TT_TEX + r) * TERRAIN_RES + i * TT_TEX + k]; lo = Math.min(lo, v); hi = Math.max(hi, v) } }
|
|
tab[(j * n + i) * 2] = lo; tab[(j * n + i) * 2 + 1] = (hi - lo) / 65535.0
|
|
}
|
|
}
|
|
file_write(f, data_of(tab), n * n * 8)
|
|
let b = buffer(TT_TEX * TT_TEX * 2)
|
|
for j in 0 .. n {
|
|
for i in 0 .. n {
|
|
let lo = tab[(j * n + i) * 2]; let step = tab[(j * n + i) * 2 + 1]
|
|
for r in 0 .. TT_TEX {
|
|
for k in 0 .. TT_TEX {
|
|
let at = (j * TT_TEX + r) * TERRAIN_RES + i * TT_TEX + k
|
|
var q = 0
|
|
if step > 0.0 { q = min(max(int(Math.floor((h[at] - lo) / step + 0.5)), 0), 65535) }
|
|
h[at] = lo + float(q) * step
|
|
b[(r * TT_TEX + k) * 2] = q & 255; b[(r * TT_TEX + k) * 2 + 1] = q >> 8
|
|
}
|
|
}
|
|
file_write(f, data_of(b), TT_TEX * TT_TEX * 2)
|
|
}
|
|
}
|
|
free(b)
|
|
free(tab)
|
|
return n * n * 8 + n * n * TT_TEX * TT_TEX * 2
|
|
}
|
|
|
|
# the baked normals read back once (RG16F: two halves a word)
|
|
@alloc_ok("a map being made or baked: the normals read back once, freed by the caller")
|
|
function ltt2_normals_back(render3d_st: mut Render3dState) -> words {
|
|
let all = words(TERRAIN_RES * TERRAIN_RES)
|
|
gpu_tex_bind(render3d_st, GPU_TEX2D, render3d_st.ter_normal_tex)
|
|
gpu_pixel_store(render3d_st, GL_PACK_ALIGNMENT, 4)
|
|
gpu_tex_read(render3d_st, GPU_TEX2D, GL_RG, GL_HALF_FLOAT, data_of(all))
|
|
return all
|
|
}
|
|
|
|
@alloc_ok("a map being made or baked: one tile's scratch, freed after")
|
|
function ltt2_write_normals(f: pointer, nrm: words, n: int) -> int {
|
|
let b = buffer(TT_TEX * TT_TEX * 2)
|
|
for j in 0 .. n {
|
|
for i in 0 .. n {
|
|
for r in 0 .. TT_TEX {
|
|
for k in 0 .. TT_TEX {
|
|
let w = nrm[(j * TT_TEX + r) * TERRAIN_RES + i * TT_TEX + k]
|
|
let e = lt_oct_enc(lt_unhalf(w & 0xFFFF), lt_unhalf((w >> 16) & 0xFFFF))
|
|
b[(r * TT_TEX + k) * 2] = e & 255; b[(r * TT_TEX + k) * 2 + 1] = e >> 8
|
|
}
|
|
}
|
|
file_write(f, data_of(b), TT_TEX * TT_TEX * 2)
|
|
}
|
|
}
|
|
free(b)
|
|
return n * n * TT_TEX * TT_TEX * 2
|
|
}
|
|
|
|
@alloc_ok("a map being made or baked: one tile's scratch, freed after")
|
|
function ltt2_write_ortho(render3d_st: Render3dState, f: pointer, n: int, side: int) -> int {
|
|
let b = buffer(side * side * 3)
|
|
let w = render3d_st.ter_ortho_w
|
|
let c = render3d_st.ter_ortho_c
|
|
let px = render3d_st.ter_ortho_px
|
|
for j in 0 .. n {
|
|
for i in 0 .. n {
|
|
for r in 0 .. side {
|
|
for k in 0 .. side {
|
|
let o = ((j * side + r) * w + i * side + k) * c
|
|
let d = (r * side + k) * 3
|
|
b[d] = px[o]; b[d + 1] = px[o + 1]; b[d + 2] = px[o + 2]
|
|
}
|
|
}
|
|
file_write(f, data_of(b), side * side * 3)
|
|
}
|
|
}
|
|
free(b)
|
|
return n * n * side * side * 3
|
|
}
|
|
|
|
# the coarse heights: the mean of each 2 x 2 of the quantized ones (kept in tt_coarse too)
|
|
function ltt2_write_coarse_h(render3d_st: mut Render3dState, f: pointer) -> int {
|
|
tt_make_coarse(render3d_st)
|
|
file_write(f, data_of(render3d_st.tt_coarse), TT_COARSE * TT_COARSE * 4)
|
|
return TT_COARSE * TT_COARSE * 4
|
|
}
|
|
|
|
# the coarse normals: each 2 x 2's mean direction, octahedral
|
|
@alloc_ok("a map being made or baked: one row's scratch, freed after")
|
|
function ltt2_write_coarse_n(f: pointer, nrm: words) -> int {
|
|
let k = TERRAIN_RES / TT_COARSE
|
|
let b = buffer(TT_COARSE * 2)
|
|
for j in 0 .. TT_COARSE {
|
|
for i in 0 .. TT_COARSE {
|
|
var sx = 0.0; var sz = 0.0
|
|
for bb in 0 .. k { for a in 0 .. k { let w = nrm[(j * k + bb) * TERRAIN_RES + i * k + a]; sx = sx + lt_unhalf(w & 0xFFFF); sz = sz + lt_unhalf((w >> 16) & 0xFFFF) } }
|
|
let e = lt_oct_enc(sx / float(k * k), sz / float(k * k))
|
|
b[i * 2] = e & 255; b[i * 2 + 1] = e >> 8
|
|
}
|
|
file_write(f, data_of(b), TT_COARSE * 2)
|
|
}
|
|
free(b)
|
|
return TT_COARSE * TT_COARSE * 2
|
|
}
|
|
|
|
# the coarse photograph: each block's mean colour, RGB8
|
|
@alloc_ok("a map being made or baked: one row's scratch, freed after")
|
|
function ltt2_write_coarse_o(render3d_st: Render3dState, f: pointer) -> int {
|
|
let w = render3d_st.ter_ortho_w
|
|
let k = max(w / TT_COARSE, 1)
|
|
let c = render3d_st.ter_ortho_c
|
|
let px = render3d_st.ter_ortho_px
|
|
let b = buffer(TT_COARSE * 3)
|
|
for j in 0 .. TT_COARSE {
|
|
for i in 0 .. TT_COARSE {
|
|
for ch in 0 .. 3 {
|
|
var s = 0
|
|
for bb in 0 .. k { for a in 0 .. k { s += px[((min(j * k + bb, w - 1)) * w + min(i * k + a, w - 1)) * c + ch] } }
|
|
b[i * 3 + ch] = (s + k * k / 2) / (k * k)
|
|
}
|
|
}
|
|
file_write(f, data_of(b), TT_COARSE * 3)
|
|
}
|
|
free(b)
|
|
return TT_COARSE * TT_COARSE * 3
|
|
}
|
|
|
|
# a normal's x and z (y up, rebuilt) as octahedral 8 + 8 bits, and back
|
|
function lt_oct_enc(x: float, z: float) -> int {
|
|
let y = Math.sqrt(Math.max(1.0 - x * x - z * z, 0.0))
|
|
let s = Math.abs(x) + y + Math.abs(z)
|
|
var px = 0.0; var pz = 0.0
|
|
if s > 0.0 { px = x / s; pz = z / s }
|
|
let qx = min(max(int(Math.floor((px * 0.5 + 0.5) * 255.0 + 0.5)), 0), 255)
|
|
let qz = min(max(int(Math.floor((pz * 0.5 + 0.5) * 255.0 + 0.5)), 0), 255)
|
|
return qx | (qz << 8)
|
|
}
|
|
# the decoded normal's x (want_z false) or z
|
|
function lt_oct_dec(e: int, want_z: bool) -> float {
|
|
let px = float(e & 255) / 255.0 * 2.0 - 1.0
|
|
let pz = float((e >> 8) & 255) / 255.0 * 2.0 - 1.0
|
|
let y = Math.max(1.0 - Math.abs(px) - Math.abs(pz), 0.0)
|
|
let l = Math.sqrt(px * px + y * y + pz * pz)
|
|
if l <= 0.0 { return 0.0 }
|
|
if want_z { return pz / l }
|
|
return px / l
|
|
}
|
|
|
|
# an IEEE half's bits as a float
|
|
function lt_unhalf(h: int) -> float {
|
|
let s = (h >> 15) & 1
|
|
let e = (h >> 10) & 31
|
|
let m = h & 1023
|
|
var v = 0.0
|
|
if e == 0 { v = float(m) / 16777216.0 } else { v = float_from_bits(((e - 15 + 127) << 23) | (m << 13)) }
|
|
if s == 1 { return -v }
|
|
return v
|
|
}
|