Render textured aerial maps and support legacy recovery links
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as THREE from "/three.js";
|
||||
import { buildTerrain } from "./terrain-mesh.js";
|
||||
import { firstTextureFrame } from "./block-colors.mjs";
|
||||
import { mapDimensions } from "./map-view.mjs";
|
||||
// This renderer consumes snapshots, never connects to a Minecraft server.
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
@@ -21,10 +22,12 @@ window.rendererInfo = {
|
||||
: "unavailable",
|
||||
};
|
||||
scene.background = new THREE.Color("#95c4eb");
|
||||
scene.add(new THREE.HemisphereLight("#e9f3ff", "#73704e", 2.0));
|
||||
const ambient = new THREE.HemisphereLight("#e9f3ff", "#73704e", 2.0);
|
||||
scene.add(ambient);
|
||||
const sun = new THREE.DirectionalLight("#fff4d6", 2.2);
|
||||
sun.position.set(-40, 90, -30);
|
||||
scene.add(sun);
|
||||
scene.add(sun.target);
|
||||
const loader = new THREE.TextureLoader(),
|
||||
jsonCache = new Map(),
|
||||
textureCache = new Map(),
|
||||
@@ -142,7 +145,17 @@ function selectModels(state, props) {
|
||||
.filter(Boolean)
|
||||
.map((p) => p.split("=")),
|
||||
);
|
||||
if (match(c, props)) return [Array.isArray(v) ? v[0] : v];
|
||||
// Some exports omit default properties (for example snowy=false on a
|
||||
// grass block). Match every property we do have, so the block remains
|
||||
// visible instead of silently disappearing from the terrain and map.
|
||||
if (
|
||||
Object.entries(c).every(
|
||||
([name, value]) =>
|
||||
props[name] === undefined ||
|
||||
String(value).split("|").includes(props[name]),
|
||||
)
|
||||
)
|
||||
return [Array.isArray(v) ? v[0] : v];
|
||||
}
|
||||
}
|
||||
return (state.multipart || [])
|
||||
@@ -282,9 +295,10 @@ window.renderFace = (p, face, size) => {
|
||||
renderScene(camera);
|
||||
return renderer.domElement.toDataURL("image/webp", 0.85);
|
||||
};
|
||||
window.renderMap = (size) => {
|
||||
window.renderMap = () => {
|
||||
const b = window.terrainBounds;
|
||||
renderer.setSize(size, size);
|
||||
const { width, height } = mapDimensions(b);
|
||||
renderer.setSize(width, height);
|
||||
const camera = new THREE.OrthographicCamera(
|
||||
-(b.maxX - b.minX) / 2,
|
||||
(b.maxX - b.minX) / 2,
|
||||
@@ -293,10 +307,60 @@ window.renderMap = (size) => {
|
||||
0.1,
|
||||
1024,
|
||||
);
|
||||
camera.position.set((b.minX + b.maxX) / 2, 512, (b.minZ + b.maxZ) / 2);
|
||||
camera.position.set((b.minX + b.maxX) / 2, 320, (b.minZ + b.maxZ) / 2);
|
||||
camera.up.set(0, 0, -1);
|
||||
camera.lookAt(camera.position.x, 0, camera.position.z);
|
||||
renderScene(camera, true);
|
||||
return renderer.domElement.toDataURL("image/webp", 0.9);
|
||||
const background = scene.background;
|
||||
const previousSunPosition = sun.position.clone();
|
||||
const previousSunTarget = sun.target.position.clone();
|
||||
const previousAmbient = ambient.intensity;
|
||||
const previousSunIntensity = sun.intensity;
|
||||
const centerX = camera.position.x;
|
||||
const centerZ = camera.position.z;
|
||||
const radius = Math.hypot(b.maxX - b.minX, b.maxZ - b.minZ) / 2 + 256;
|
||||
// The panorama renderer keeps its broad, shadowless lighting. For the map,
|
||||
// an angled sun makes ridges, cliffs and buildings cast readable shadows.
|
||||
ambient.intensity = 1.25;
|
||||
sun.intensity = 2.2;
|
||||
sun.position.set(centerX - 220, 300, centerZ - 180);
|
||||
sun.target.position.set(centerX, 64, centerZ);
|
||||
sun.castShadow = true;
|
||||
sun.shadow.mapSize.set(4096, 4096);
|
||||
sun.shadow.camera.left = -radius;
|
||||
sun.shadow.camera.right = radius;
|
||||
sun.shadow.camera.top = radius;
|
||||
sun.shadow.camera.bottom = -radius;
|
||||
sun.shadow.camera.near = 1;
|
||||
sun.shadow.camera.far = radius * 4;
|
||||
sun.shadow.camera.updateProjectionMatrix();
|
||||
sun.shadow.bias = -0.0004;
|
||||
sun.shadow.normalBias = 0.03;
|
||||
renderer.shadowMap.enabled = true;
|
||||
renderer.shadowMap.type = THREE.PCFShadowMap;
|
||||
for (const mesh of window.terrainMeshes) {
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
}
|
||||
scene.background = new THREE.Color("#ddc79f");
|
||||
try {
|
||||
renderScene(camera, true);
|
||||
return {
|
||||
data: renderer.domElement.toDataURL("image/webp", 0.92),
|
||||
width,
|
||||
height,
|
||||
};
|
||||
} finally {
|
||||
renderer.shadowMap.enabled = false;
|
||||
sun.castShadow = false;
|
||||
for (const mesh of window.terrainMeshes) {
|
||||
mesh.castShadow = false;
|
||||
mesh.receiveShadow = false;
|
||||
}
|
||||
sun.position.copy(previousSunPosition);
|
||||
sun.target.position.copy(previousSunTarget);
|
||||
ambient.intensity = previousAmbient;
|
||||
sun.intensity = previousSunIntensity;
|
||||
scene.background = background;
|
||||
}
|
||||
};
|
||||
window.rendererReady = true;
|
||||
|
||||
Reference in New Issue
Block a user