Render textured aerial maps and support legacy recovery links

This commit is contained in:
2026-10-05 18:11:12 +05:00
parent dcc3779262
commit 2cbaf45340
12 changed files with 226 additions and 35 deletions

View File

@@ -1,7 +1,7 @@
import { readFile, mkdir } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { readFile, mkdir, mkdtemp, copyFile, rm } from "node:fs/promises";
import { dirname, resolve, sep } from "node:path";
import { gunzipSync } from "node:zlib";
import { renderSurfaceMap } from "../renderer/surface-map.mjs";
import { renderSnapshot } from "../renderer/render.mjs";
const [input, output] = process.argv.slice(2);
if (!input || !output)
@@ -13,8 +13,17 @@ const terrain = JSON.parse(
maxOutputLength: 256 * 1024 * 1024,
}).toString(),
);
await mkdir(dirname(resolve(output)), { recursive: true });
const report = await renderSurfaceMap(terrain, output);
console.log(
`Rendered ${report.width} × ${report.height} map cells from ${report.columns.toLocaleString()} surface columns: ${output}`,
);
const parent = dirname(resolve(output));
await mkdir(parent, { recursive: true });
const temporary = await mkdtemp(resolve(parent, ".minegeo-map-"));
try {
const report = await renderSnapshot(terrain, [], temporary);
await copyFile(resolve(temporary, "map.webp"), resolve(output));
console.log(
`Rendered ${report.map.width} × ${report.map.height} aerial map from Y=${report.map.cameraY}: ${output}`,
);
} finally {
if (!temporary.startsWith(parent + sep))
throw new Error("Refusing to remove a map directory outside its parent");
await rm(temporary, { recursive: true, force: true });
}

View File

@@ -22,7 +22,9 @@ for (let x = -24; x < 24; x++) {
z,
x > 12 && z < -12
? "minecraft:water[level=0]"
: "minecraft:grass_block[snowy=false]",
: x < -12
? "minecraft:grass_block"
: "minecraft:grass_block[snowy=false]",
]);
}
}
@@ -91,6 +93,32 @@ assert.equal(
false,
);
await assertTerrainImage(await readFile(`${output}/map.webp`), "Surface map");
const mapMeta = await sharp(`${output}/map.webp`).metadata();
assert.equal(mapMeta.width, report.map.width);
assert.equal(mapMeta.height, report.map.height);
assert.equal(report.map.cameraY, 320);
const { data: mapPixels, info: mapInfo } = await sharp(`${output}/map.webp`)
.raw()
.toBuffer({ resolveWithObject: true });
const westGrass = (24 * mapInfo.width + 24) * mapInfo.channels;
assert.ok(
mapPixels[westGrass + 1] > mapPixels[westGrass],
"blocks without explicit default properties must appear on the aerial map",
);
const mapPixel = (x, z) => {
const pixelX = Math.floor(((x - snapshot.bounds.minX + 0.5) / 48) * mapInfo.width);
const pixelY = Math.floor(((z - snapshot.bounds.minZ + 0.5) / 48) * mapInfo.height);
const offset = (pixelY * mapInfo.width + pixelX) * mapInfo.channels;
return mapPixels.subarray(offset, offset + 3);
};
if (!process.argv[2]) {
const shadedGrass = mapPixel(10, 9);
const sunlitGrass = mapPixel(10, -9);
assert.ok(
sunlitGrass[1] - shadedGrass[1] > 20,
"the raised building must cast a visible shadow on the aerial map",
);
}
for (const point of points) {
for (const face of ["px", "nx", "py", "ny", "pz", "nz"]) {
const metadata = await sharp(