# ground_density_write.ludic — a map's painted ground densities (one PNG per layer: R the chance a cell keeps # one, G an optional scale) cut into the terrain's 64 m tiles as LGD2, the format the build-time bake writes # (maroon-lake's tools/bake/ground_density.py): an all-zero tile is nothing and a one-value tile a word, the # scale is kept only where it varies, and the density is held to 6 bits and the scale to 4. This is a dev # build's own copy, made when the bake is missing: the same bytes the bake decodes to, never deflated. const GD_MAGIC: int = 843335500 # "LGD2" read as a little-endian u32: 0x3244474C const GD_VERSION: int = 2 const GD_HEADW: int = 136 # words: magic, version, n, side, layers, 3 spare, (channels, index) x 64 const GD_LAYERS: int = 64 const GD_R_BITS: int = 6 const GD_G_BITS: int = 4 # v (0..255) held to `bits`, a byte again - the integer arithmetic the bake uses, so both agree to the bit function gd_q(v: int, bits: int) -> int { let top = (1 << bits) - 1 let q = (v * top + 127) / 255 return (q * 255 + top / 2) / top } # the layers' PNGs written to f from `base` (n tiles a side of `side` texels); the payload's length, or -1 @alloc_ok("a dev build's map load: the densities cut once") function gd_write_payload(render3d_st: mut Render3dState, f: pointer, base: int, pngs: []string, n: int, side: int) -> int { let layers = min(len(pngs), GD_LAYERS) let head = words(GD_HEADW) for k in 0 .. GD_HEADW { head[k] = 0 } let idx = words(max(layers * n * n * 2, 1)) for k in 0 .. len(idx) { idx[k] = 0 } let ib = GD_HEADW * 4 file_write(f, data_of(head), ib) file_write(f, data_of(idx), layers * n * n * 8) var at = ib + layers * n * n * 8 let tile = buffer(side * side * 2) for l in 0 .. layers { let px = png_decode_bytes(render3d_st, pngs[l], null) if px == null or render3d_st.tex_w != n * side or render3d_st.tex_h != n * side { gd_say_bad(pngs[l], n * side); return -1 } let c = render3d_st.tex_channels var ch = 1 if c >= 2 and gd_g_varies(px, c, n * side) { ch = 2 } head[8 + l * 2] = ch; head[9 + l * 2] = ib + l * n * n * 8 at = gd_write_layer(f, px, c, ch, n, side, tile, idx, l * n * n * 2, at) free(px) } free(tile) head[0] = GD_MAGIC; head[1] = GD_VERSION; head[2] = n; head[3] = side; head[4] = layers file_seek(f, base, 0) file_write(f, data_of(head), ib) file_write(f, data_of(idx), layers * n * n * 8) file_seek(f, base + at, 0) free(head); free(idx) return at } # does the PNG's G vary anywhere (else the layer keeps R alone) function gd_g_varies(px: []byte, c: int, w: int) -> bool { let g0 = px[1] for i in 0 .. w * w { if px[i * c + 1] != g0 { return true } } return false } # one layer's tiles: each quantized, then nothing (all zero), a word (one value) or its bytes; the next offset function gd_write_layer(f: pointer, px: []byte, c: int, ch: int, n: int, side: int, tile: []byte, idx: words, i0: int, at0: int) -> int { let w = n * side var at = at0 for j in 0 .. n { for i in 0 .. n { var same = true for r in 0 .. side { for k in 0 .. side { let o = ((j * side + r) * w + i * side + k) * c let p = (r * side + k) * ch tile[p] = gd_q(px[o], GD_R_BITS) if ch == 2 { tile[p + 1] = gd_q(px[o + 1], GD_G_BITS) } if tile[p] != tile[0] or (ch == 2 and tile[p + 1] != tile[1]) { same = false } } } let e = i0 + (j * n + i) * 2 if same { var g = 0 if ch == 2 { g = tile[1] } if tile[0] != 0 or g != 0 { idx[e] = 1; idx[e + 1] = tile[0] | (g << 8) } } else { file_write(f, data_of(tile), side * side * ch) idx[e] = at; idx[e + 1] = side * side * ch at += side * side * ch } } } return at } # The bake's own file from a build without Python (ludic.base's LBAK header, as terrain_tiles_bake writes it): # stored tiles, which the reader takes as it takes the deflated ones @alloc_ok("a bake: once, at build time") export function ground_density_bake(render3d_st: mut Render3dState, path: string, key: string, version: int, inputs_hash: long, pngs: []string) -> bool { Fs.mkdir(Path.dir(path)) let f = file_open(path, "wb") if f == null { return false } let base = 32 + (len(key) + 1 + 7) / 8 * 8 let head = buffer(base) for i in 0 .. base { head[i] = 0 } file_write(f, data_of(head), base) let n = gd_write_payload(render3d_st, f, base, pngs, TERRAIN_RES / TT_TEX, GD_SIDE) let p = data_of(head) Vk.put_i32(p, 0, BAKE_MAGIC); Vk.put_i32(p, 4, 1); Vk.put_i32(p, 8, version); Vk.put_i32(p, 12, base) Vk.put_i64(p, 16, inputs_hash) let nl: long = max(n, 0) Vk.put_i64(p, 24, nl) for i in 0 .. len(key) { head[32 + i] = key[i] } file_seek(f, 0, 0) file_write(f, data_of(head), base) file_close(f) free(head) return n >= 0 } function gd_say_bad(path: string, want: int) -> void { print(`r3d: ground: {path} is missing or not {want} x {want}; the densities are not written`) }