ludic/packages/ludic.render3d/place_rec.ludic
Orkuncakilkaya 8589a5a7b4 render3d: place_rec.ludic, the one encoder and decoder of a baked placement
prec_put / prec_get / prec_size (Physics' spec): PREC_FULL, 12 bytes, for trees, boulders and stones
(u16 x, z in 1/65536 of the chunk; u16 y in cm above y_base; u8 scale over the kind's lo..hi, yaw, seed,
wind), and PREC_PACKED, 7 bytes, for the stream kinds (56 bits, least significant first: x 12, z 12,
y 12 in cm, scale 6, yaw 6, seed 4, wind 4). An encode takes the cell a value falls in, a decode its
centre, so a round trip is stable; a record is read from a []byte at an offset, never a pointer.
stream_emit_baked and layer_chunk_put take (recs, at, fmt) and decode through it; the earlier sb7_*
pair is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:28:11 +03:00

68 lines
3.8 KiB
Text

# place_rec.ludic — a baked placement's record, the ONE encoder and decoder the bake, the ring, the
# physics and the renderer all use (so they cannot drift). Two formats: FULL, 12 bytes, for the fixed
# kinds (trees, boulders, stones); PACKED, 7 bytes, for the stream kinds (grass, flowers, willow). An
# encode takes the cell a value falls in and a decode its centre, so decoding and encoding again is
# stable. A record is read from a []byte at a byte offset (the game cannot index a pointer).
export const PREC_FULL: int = 0
export const PREC_PACKED: int = 1
export function prec_size(fmt: int) -> int {
if fmt == PREC_PACKED { return 7 }
return 12
}
function prec_q(v: float, steps: int) -> int { return min(max(int(Math.floor(v * float(steps))), 0), steps - 1) }
function prec_r(v: float, top: int) -> int { return min(max(int(Math.floor(v * float(top) + 0.5)), 0), top) }
function prec_wrap(yaw: float) -> float {
let t = 2.0 * PI
return yaw - Math.floor(yaw / t) * t
}
# one instance in world units into b at byte `at`, against its chunk (x0, z0, size, y_base) and kind (lo, hi)
export function prec_put(fmt: int, b: []byte, at: int, x: float, y: float, z: float, scale: float, yaw: float, seed: float, wind: float, x0: float, z0: float, size: float, y_base: float, lo: float, hi: float) -> void {
var sn = 0.0
if hi > lo { sn = (scale - lo) / (hi - lo) }
if fmt == PREC_PACKED {
let qx = prec_q((x - x0) / size, 4096)
let qz = prec_q((z - z0) / size, 4096)
let qy = min(max(int(Math.floor((y - y_base) * 100.0 + 0.5)), 0), 4095)
let lw = qx | (qz << 12) | ((qy & 255) << 24)
let hw = ((qy >> 8) & 15) | (prec_r(sn, 63) << 4) | ((prec_r(prec_wrap(yaw) / (2.0 * PI), 64) & 63) << 10) | (prec_r(seed, 15) << 16) | (prec_r(wind, 15) << 20)
b[at] = lw & 255; b[at + 1] = (lw >> 8) & 255; b[at + 2] = (lw >> 16) & 255; b[at + 3] = (lw >> 24) & 255
b[at + 4] = hw & 255; b[at + 5] = (hw >> 8) & 255; b[at + 6] = (hw >> 16) & 255
return
}
let qx = prec_q((x - x0) / size, 65536)
let qz = prec_q((z - z0) / size, 65536)
let qy = min(max(int(Math.floor((y - y_base) * 100.0 + 0.5)), 0), 65535)
b[at] = qx & 255; b[at + 1] = qx >> 8; b[at + 2] = qz & 255; b[at + 3] = qz >> 8
b[at + 4] = qy & 255; b[at + 5] = qy >> 8
b[at + 6] = prec_r(sn, 255); b[at + 7] = prec_r(prec_wrap(yaw) / (2.0 * PI), 256) & 255
b[at + 8] = prec_r(seed, 255); b[at + 9] = prec_r(wind, 255); b[at + 10] = 0; b[at + 11] = 0
}
# record i of the run at byte `at` into out[0 .. 8], the layers' instance layout:
# x, y, z, scale, sin yaw, cos yaw, seed, wind
export function prec_get(fmt: int, b: []byte, at: int, i: int, x0: float, z0: float, size: float, y_base: float, lo: float, hi: float, out: floats) -> void {
let o = at + i * prec_size(fmt)
var qx = 0; var qz = 0; var qy = 0; var qs = 0; var qa = 0; var qe = 0; var qw = 0
var xs = 65536.0; var ss = 255.0; var as = 256.0; var es = 255.0
if fmt == PREC_PACKED {
let lw = b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24)
let hw = b[o + 4] | (b[o + 5] << 8) | (b[o + 6] << 16)
qx = lw & 4095; qz = (lw >> 12) & 4095; qy = ((lw >> 24) & 255) | ((hw & 15) << 8)
qs = (hw >> 4) & 63; qa = (hw >> 10) & 63; qe = (hw >> 16) & 15; qw = (hw >> 20) & 15
xs = 4096.0; ss = 63.0; as = 64.0; es = 15.0
} else {
qx = b[o] | (b[o + 1] << 8); qz = b[o + 2] | (b[o + 3] << 8); qy = b[o + 4] | (b[o + 5] << 8)
qs = b[o + 6]; qa = b[o + 7]; qe = b[o + 8]; qw = b[o + 9]
}
let yaw = float(qa) / as * 2.0 * PI
out[0] = x0 + (float(qx) + 0.5) * size / xs
out[1] = y_base + float(qy) * 0.01
out[2] = z0 + (float(qz) + 0.5) * size / xs
out[3] = lo + float(qs) / ss * (hi - lo)
out[4] = Math.sin(yaw); out[5] = Math.cos(yaw)
out[6] = float(qe) / es; out[7] = float(qw) / es
}