added topography

This commit is contained in:
barrulus 2025-09-04 16:21:22 +01:00
parent 21df872ca2
commit 2c3692f000
5 changed files with 151 additions and 1 deletions

View file

@ -494,6 +494,14 @@
>
<u>H</u>eightmap
</li>
<li
id="toggleTopography"
data-tip="Topographic contours: click to toggle, drag to raise or lower the layer"
data-shortcut="Q"
onclick="toggleTopography(event)"
>
Topo<u>g</u>raphy
</li>
<li
id="toggleBiomes"
data-tip="Biomes: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
@ -8205,5 +8213,6 @@
<script defer src="modules/renderers/draw-burg-labels.js?v=1.108.1"></script>
<script defer src="modules/renderers/draw-burg-icons.js?v=1.104.0"></script>
<script defer src="modules/renderers/draw-relief-icons.js?v=1.108.4"></script>
<script defer src="modules/renderers/draw-topography.js?v=1.108.4"></script>
</body>
</html>

View file

@ -50,6 +50,7 @@ let lakes = viewbox.append("g").attr("id", "lakes");
let landmass = viewbox.append("g").attr("id", "landmass");
let texture = viewbox.append("g").attr("id", "texture");
let terrs = viewbox.append("g").attr("id", "terrs");
let topography = viewbox.append("g").attr("id", "topography");
let biomes = viewbox.append("g").attr("id", "biomes");
let cells = viewbox.append("g").attr("id", "cells");
let gridOverlay = viewbox.append("g").attr("id", "gridOverlay");
@ -249,7 +250,7 @@ document.addEventListener("DOMContentLoaded", async () => {
hideLoading();
await checkLoadParameters();
}
restoreDefaultEvents; // apply default viewbox events
restoreDefaultEvents(); // apply default viewbox events
initiateAutosave();
});

View file

@ -0,0 +1,125 @@
"use strict";
// Draw topographic hillshade-style fill per grid cell
function drawTopography() {
TIME && console.time("drawTopography");
const group = d3.select("#topography");
group.selectAll("*").remove();
const {cells, points, features: gridFeatures} = grid;
// Colorimetry from user (positive altitudes, depressions, negative altitudes)
// Start positive ramp at #BAAE9A as requested
const POSITIVE_COLORS = [
"#BAAE9A",
"#AC9A7C",
"#AA8753",
"#B9985A",
"#C3A76B",
"#CAB982",
"#D3CA9D",
"#DED6A3",
"#E8E1B6",
"#EFEBC0",
"#E1E4B5",
"#D1D7AB",
"#BDCC96",
"#A8C68F",
"#94BF8B",
"#ACD0A5"
];
const NEGATIVE_COLORS = [
"#D8F2FE",
"#C6ECFF",
"#B9E3FF",
"#ACDBFB",
"#A1D2F7",
"#96C9F0",
"#8DC1EA",
"#84B9E3",
"#79B2DE",
"#71ABD8"
];
const DEPRESSIONS_COLOR = "#0978AB"; // lakes / inland depressions: Rivers, coasts, hydronyms color
const posInterp = d3.interpolateRgbBasis(POSITIVE_COLORS);
const negInterp = d3.interpolateRgbBasis(NEGATIVE_COLORS);
// Base color scheme mirrors heightmap scheme
// not used now; we fully replace with provided palette
// Lighting settings (can be overridden by attributes)
const azimuth = (+group.attr("azimuth") || 315) * (Math.PI / 180); // degrees → radians
const altitude = (+group.attr("altitude") || 45) * (Math.PI / 180);
const light = [Math.cos(azimuth) * Math.cos(altitude), Math.sin(azimuth) * Math.cos(altitude), Math.sin(altitude)];
const zScale = +group.attr("zscale") || 0.8; // vertical exaggeration for normals
let html = "";
for (const i of cells.i) {
const h = cells.h[i];
const isWater = h < 20;
const featureType = isWater && gridFeatures ? gridFeatures[cells.f[i]]?.type : null;
const isLake = isWater && featureType === "lake";
// Estimate gradient from neighbors
const [cx, cy] = points[i];
let gx = 0,
gy = 0,
wsum = 0;
for (const n of cells.c[i]) {
if (n < 0) continue;
const [nx, ny] = points[n];
const dx = nx - cx;
const dy = ny - cy;
const dist = Math.hypot(dx, dy) || 1;
const dh = (cells.h[n] - h) / 100; // normalize height difference
const w = 1 / dist; // weight close neighbors higher
gx += w * dh * (dx / dist);
gy += w * dh * (dy / dist);
wsum += w;
}
if (wsum) {
gx /= wsum;
gy /= wsum;
}
// Build surface normal and compute light intensity
const nx = -gx * zScale;
const ny = -gy * zScale;
const nz = 1;
const invLen = 1 / Math.hypot(nx, ny, nz);
const n0 = [nx * invLen, ny * invLen, nz * invLen];
const intensity = minmax(n0[0] * light[0] + n0[1] * light[1] + n0[2] * light[2], 0, 1);
// Base color from provided palettes
let baseColor;
if (isWater) {
if (isLake) baseColor = d3.color(DEPRESSIONS_COLOR);
else {
const t = minmax((20 - h) / 20, 0, 1); // shallow 0 → deep 1
baseColor = d3.color(negInterp(t));
}
} else {
const t = minmax((h - 20) / 80, 0, 1); // lowland 0 → highland 1
baseColor = d3.color(posInterp(t));
}
// Modulate lightness by intensity (weaker effect on water)
const hsl = d3.hsl(baseColor);
const k = isWater ? 0.4 : 0.8;
hsl.l = minmax(hsl.l * (0.6 + k * intensity), 0, 1);
const color = hsl.toString();
// Cell polygon
const poly = getGridPolygon(i)
.map(p => `${rn(p[0], 2)},${rn(p[1], 2)}`)
.join(" ");
html += `<polygon points="${poly}" fill="${color}" opacity="${(terrs.attr("opacity") || 1) * 1}" />`;
}
group.html(html);
TIME && console.timeEnd("drawTopography");
}

View file

@ -61,6 +61,7 @@ function handleKeyup(event) {
else if (key === "%") toggleAddMarker();
else if (code === "KeyX") toggleTexture();
else if (code === "KeyH") toggleHeight();
else if (code === "KeyQ") toggleTopography();
else if (code === "KeyB") toggleBiomes();
else if (code === "KeyE") toggleCells();
else if (code === "KeyG") toggleGrid();

View file

@ -186,6 +186,7 @@ function drawLayers() {
drawFeatures();
if (layerIsOn("toggleTexture")) drawTexture();
if (layerIsOn("toggleHeight")) drawHeightmap();
if (layerIsOn("toggleTopography")) drawTopography();
if (layerIsOn("toggleBiomes")) drawBiomes();
if (layerIsOn("toggleCells")) drawCells();
if (layerIsOn("toggleGrid")) drawGrid();
@ -229,6 +230,18 @@ function toggleHeight(event) {
}
}
function toggleTopography(event) {
const group = d3.select('#topography');
const hasContent = group.selectAll('*').size() > 0;
if (!hasContent) {
turnButtonOn('toggleTopography');
drawTopography();
} else {
turnButtonOff('toggleTopography');
group.selectAll('*').remove();
}
}
function toggleTemperature(event) {
if (!temperature.selectAll("*").size()) {
turnButtonOn("toggleTemperature");
@ -1015,6 +1028,7 @@ function moveLayer(event, ui) {
// define connection between option layer buttons and actual svg groups to move the element
function getLayer(id) {
if (id === "toggleHeight") return $("#terrs");
if (id === "toggleTopography") return $("#topography");
if (id === "toggleBiomes") return $("#biomes");
if (id === "toggleCells") return $("#cells");
if (id === "toggleGrid") return $("#gridOverlay");