#!/usr/bin/env python3
"""
Put the ENGINE's terrain next to the LOOKDEV's, same ground, same colours.
Every check in this session has produced a number, and a number has not once
told anybody what they were looking at. This writes one PNG: lookdev on the
left, engine on the right, difference on the far right, hypsometric tint shared
so the two are directly comparable by eye.
The engine half comes from map_dc_diffusion writing /tmp/engine_gpu_surface.f32
on load -- a 64^3 debug_gpu_density dispatch, so it is the GPU's own field, not
the CPU proxy that misled this session repeatedly.
python3 compare_engine_vs_lookdev.py
"""
import os, subprocess, sys
import numpy as np
DC = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "dc-terrain"))
HBLK = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "hyper-tps",
"assets", "terrain", "diffusion_gully_30km.hblk"))
ENG = "/tmp/engine_gpu_surface.f32"
SPAN, N = 7680.0, 64
CX, CZ = 53760.0, 38400.0
OX, OZ = CX - SPAN / 2.0, CZ - SPAN / 2.0
STEP = SPAN / float(N - 1)
def tint(h, lo, hi):
t = np.clip((h - lo) / max(hi - lo, 1e-6), 0, 1)
stops = np.array([[0.05,0.12,0.28],[0.20,0.35,0.45],[0.30,0.42,0.22],
[0.55,0.50,0.30],[0.60,0.55,0.52],[0.92,0.92,0.94]])
idx = t * (len(stops) - 1)
i0 = np.floor(idx).astype(int); i1 = np.minimum(i0 + 1, len(stops) - 1)
f = (idx - i0)[..., None]
rgb = stops[i0] * (1 - f) + stops[i1] * f
# hillshade so shape reads, not just altitude
gy, gx = np.gradient(h, STEP)
n = 1.0 / np.sqrt(gx*gx + gy*gy + 1.0)
shade = np.clip(0.45 + 0.75 * ((-gx - gy) * n * 0.5 + 0.5), 0, 1.35)
return np.clip(rgb * shade[..., None], 0, 1)
def main():
if not os.path.exists(ENG):
print(f"missing {ENG} — load map_dc_diffusion once; it writes this on load")
return 2
eng = np.fromfile(ENG, dtype="<f4", count=N*N).astype(np.float64).reshape(N, N)
miss = eng < -99998.0
print(f"engine: {N}x{N}, {(~miss).sum()} columns with a surface, "
f"{eng[~miss].min():.0f}..{eng[~miss].max():.0f} m")
out = "/tmp/lookdev_surface.f32"
r = subprocess.run(["/tmp/erosion_render4", "--heightmap", HBLK,
"--ox", str(OX), "--oz", str(OZ), "--n", str(N),
"--mpp", str(STEP), "--out", out], cwd=DC,
capture_output=True, text=True)
if r.returncode != 0:
print(r.stdout, r.stderr); return 1
look = np.fromfile(out, dtype="<f4", count=N*N).astype(np.float64).reshape(N, N)
print(f"lookdev: {look.min():.0f}..{look.max():.0f} m")
valid = ~miss
d = np.abs(eng - look)
print(f"\n max |engine - lookdev| = {d[valid].max():8.1f} m")
print(f" rms = {np.sqrt((d[valid]**2).mean()):8.1f} m")
lo = min(eng[valid].min(), look.min()); hi = max(eng[valid].max(), look.max())
a = tint(look, lo, hi); b = tint(np.where(miss, lo, eng), lo, hi)
dm = np.clip(d / max(d[valid].max(), 1e-6), 0, 1)
c = np.stack([dm, dm*0.3, 1.0-dm], -1)
gap = np.ones((N, 4, 3))
img = (np.concatenate([a, gap, b, gap, c], axis=1) * 255).astype(np.uint8)
try:
from PIL import Image
Image.fromarray(img).resize((img.shape[1]*6, img.shape[0]*6), Image.NEAREST)\
.save("/tmp/engine_vs_lookdev.png")
print("\n wrote /tmp/engine_vs_lookdev.png (lookdev | engine | difference)")
except ImportError:
img.tofile("/tmp/engine_vs_lookdev.raw")
print("\n no PIL; wrote raw RGB to /tmp/engine_vs_lookdev.raw")
return 0
if __name__ == "__main__":
sys.exit(main())