${name}
@@ -266,42 +265,16 @@ function getGraph(currentGraph) {
return newGraph;
}
-function drawHeights(heights) {
- const canvas = document.createElement("canvas");
- canvas.width = graph.cellsX;
- canvas.height = graph.cellsY;
- const ctx = canvas.getContext("2d");
- const imageData = ctx.createImageData(graph.cellsX, graph.cellsY);
-
- const scheme = getColorScheme(byId("heightmapSelectionColorScheme").value);
- const renderOcean = byId("heightmapSelectionRenderOcean").checked;
- const getHeight = height => (height < 20 ? (renderOcean ? height : 0) : height);
-
- for (let i = 0; i < heights.length; i++) {
- const color = scheme(1 - getHeight(heights[i]) / 100);
- const {r, g, b} = d3.color(color);
-
- const n = i * 4;
- imageData.data[n] = r;
- imageData.data[n + 1] = g;
- imageData.data[n + 2] = b;
- imageData.data[n + 3] = 255;
- }
-
- ctx.putImageData(imageData, 0, 0);
- return canvas.toDataURL("image/png");
-}
-
function drawTemplatePreview(id) {
const heights = HeightmapGenerator.fromTemplate(graph, id);
- const dataUrl = drawHeights(heights);
+ const dataUrl = getHeightmapPreview(heights);
const article = byId("heightmapSelection").querySelector(`[data-id="${id}"]`);
article.querySelector("img").src = dataUrl;
}
async function drawPrecreatedHeightmap(id) {
const heights = await HeightmapGenerator.fromPrecreated(graph, id);
- const dataUrl = drawHeights(heights);
+ const dataUrl = getHeightmapPreview(heights);
const article = byId("heightmapSelection").querySelector(`[data-id="${id}"]`);
article.querySelector("img").src = dataUrl;
}
@@ -337,3 +310,10 @@ function confirmHeightmapEdit() {
onConfirm: () => editHeightmap({mode: "erase", tool})
});
}
+
+function getHeightmapPreview(heights) {
+ const scheme = getColorScheme(byId("heightmapSelectionColorScheme").value);
+ const renderOcean = byId("heightmapSelectionRenderOcean").checked;
+ const dataUrl = drawHeights({heights, width: grid.cellsX, height: grid.cellsY, scheme, renderOcean});
+ return dataUrl;
+}
diff --git a/modules/ui/options.js b/modules/ui/options.js
index 863241f7..8149bc8e 100644
--- a/modules/ui/options.js
+++ b/modules/ui/options.js
@@ -702,7 +702,7 @@ function changeEra() {
}
async function openTemplateSelectionDialog() {
- const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.93.07");
+ const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.93.12");
HeightmapSelectionDialog.open();
}
diff --git a/modules/ui/style.js b/modules/ui/style.js
index 5217d9cb..77284913 100644
--- a/modules/ui/style.js
+++ b/modules/ui/style.js
@@ -3,7 +3,7 @@
// add available filters to lists
{
- const filters = Array.from(document.getElementById("filters").querySelectorAll("filter"));
+ const filters = Array.from(byId("filters").querySelectorAll("filter"));
const emptyOption = '';
const options = filters.map(filter => {
const id = filter.getAttribute("id");
@@ -12,8 +12,8 @@
});
const allOptions = emptyOption + options.join("");
- document.getElementById("styleFilterInput").innerHTML = allOptions;
- document.getElementById("styleStatesBodyFilter").innerHTML = allOptions;
+ byId("styleFilterInput").innerHTML = allOptions;
+ byId("styleStatesBodyFilter").innerHTML = allOptions;
}
// store some style inputs as options
@@ -47,7 +47,7 @@ const heightmapColorSchemes = {
};
// add color schemes to the lists
-document.getElementById("styleHeightmapScheme").innerHTML = Object.keys(heightmapColorSchemes)
+byId("styleHeightmapScheme").innerHTML = Object.keys(heightmapColorSchemes)
.map(scheme => ``)
.join("");
@@ -278,9 +278,9 @@ function selectStyleElement() {
if (sel === "ocean") {
styleOcean.style.display = "block";
styleOceanFill.value = styleOceanFillOutput.value = oceanLayers.select("#oceanBase").attr("fill");
- styleOceanPattern.value = document.getElementById("oceanicPattern")?.getAttribute("href");
+ styleOceanPattern.value = byId("oceanicPattern")?.getAttribute("href");
styleOceanPatternOpacity.value = styleOceanPatternOpacityOutput.value =
- document.getElementById("oceanicPattern").getAttribute("opacity") || 1;
+ byId("oceanicPattern").getAttribute("opacity") || 1;
outlineLayers.value = oceanLayers.attr("layers");
}
@@ -313,7 +313,7 @@ function selectStyleElement() {
// update group options
styleGroupSelect.options.length = 0; // remove all options
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(sel)) {
- const groups = document.getElementById(sel).querySelectorAll("g");
+ const groups = byId(sel).querySelectorAll("g");
groups.forEach(el => {
if (el.id === "burgLabels") return;
const option = new Option(`${el.id} (${el.childElementCount})`, el.id, false, false);
@@ -458,11 +458,11 @@ styleOceanFill.addEventListener("input", function () {
});
styleOceanPattern.addEventListener("change", function () {
- document.getElementById("oceanicPattern")?.setAttribute("href", this.value);
+ byId("oceanicPattern")?.setAttribute("href", this.value);
});
styleOceanPatternOpacity.addEventListener("input", function () {
- document.getElementById("oceanicPattern").setAttribute("opacity", this.value);
+ byId("oceanicPattern").setAttribute("opacity", this.value);
styleOceanPatternOpacityOutput.value = this.value;
});
@@ -477,6 +477,129 @@ styleHeightmapScheme.addEventListener("change", function () {
drawHeightmap();
});
+openCreateHeightmapSchemeButton.addEventListener("click", function () {
+ // start with current scheme
+ this.dataset.stops = terrs.attr("scheme").startsWith("#")
+ ? terrs.attr("scheme")
+ : (function () {
+ const scheme = heightmapColorSchemes[terrs.attr("scheme")];
+ return [0, 0.25, 0.5, 0.75, 1].map(scheme).map(toHEX).join(",");
+ })();
+
+ // render dialog base structure
+ alertMessage.innerHTML = /* html */ `
+ Define heightmap gradient colors from high to low altitude
+
+
+
+
`;
+
+ renderPreview();
+ renderStops();
+ renderGradient();
+
+ function renderPreview() {
+ const stops = openCreateHeightmapSchemeButton.dataset.stops.split(",");
+ const scheme = d3.scaleSequential(d3.interpolateRgbBasis(stops));
+
+ const preview = drawHeights({
+ heights: grid.cells.h,
+ width: grid.cellsX,
+ height: grid.cellsY,
+ scheme,
+ renderOcean: false
+ });
+
+ byId("heightmapSchemePreview").src = preview;
+ }
+
+ function renderStops() {
+ const stops = openCreateHeightmapSchemeButton.dataset.stops.split(",");
+
+ const colorInput = color =>
+ ``;
+ const removeStopButton = index =>
+ ``;
+ const addStopButton = () =>
+ ``;
+
+ const container = byId("heightmapSchemeStops");
+ container.innerHTML = stops
+ .map(
+ (stop, index) => `${colorInput(stop)}
+ ${index && index < stops.length - 1 ? removeStopButton(index) : ""}`
+ )
+ .join(addStopButton());
+
+ Array.from(container.querySelectorAll("input.stop")).forEach(
+ (input, index) =>
+ (input.oninput = function () {
+ stops[index] = this.value;
+ openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
+ renderPreview();
+ renderGradient();
+ })
+ );
+
+ Array.from(container.querySelectorAll("button.remove")).forEach(
+ button =>
+ (button.onclick = function () {
+ const index = +this.dataset.index;
+ stops.splice(index, 1);
+ openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
+ renderPreview();
+ renderStops();
+ renderGradient();
+ })
+ );
+
+ Array.from(container.querySelectorAll("button.add")).forEach(
+ (button, index) =>
+ (button.onclick = function () {
+ const middleColor = d3.interpolateRgb(stops[index], stops[index + 1])(0.5);
+ stops.splice(index + 1, 0, toHEX(middleColor));
+ openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
+ renderPreview();
+ renderStops();
+ renderGradient();
+ })
+ );
+ }
+
+ function renderGradient() {
+ const stops = openCreateHeightmapSchemeButton.dataset.stops;
+ byId("heightmapSchemeGradient").style.background = `linear-gradient(to right, ${stops})`;
+ }
+
+ function handleCreate() {
+ const stops = openCreateHeightmapSchemeButton.dataset.stops;
+ if (stops in heightmapColorSchemes) return tip("This scheme already exists", false, "error");
+
+ heightmapColorSchemes[stops] = d3.scaleSequential(d3.interpolateRgbBasis(stops.split(",")));
+ byId("styleHeightmapScheme").options.add(new Option(stops, stops, false, true));
+
+ terrs.attr("scheme", stops);
+ drawHeightmap();
+
+ handleClose();
+ }
+
+ function handleClose() {
+ $("#alert").dialog("close");
+ }
+
+ $("#alert").dialog({
+ resizable: false,
+ title: "Create heightmap color scheme",
+ width: "28em",
+ buttons: {
+ Create: handleCreate,
+ Cancel: handleClose
+ },
+ position: {my: "center top+150", at: "center top", of: "svg"}
+ });
+});
+
styleHeightmapTerracingInput.addEventListener("input", function () {
terrs.attr("terracing", this.value);
drawHeightmap();
@@ -801,7 +924,7 @@ function fetchTextureURL(url) {
INFO && console.log("Provided URL is", url);
const img = new Image();
img.onload = function () {
- const canvas = document.getElementById("texturePreview");
+ const canvas = byId("texturePreview");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
diff --git a/utils/graphUtils.js b/utils/graphUtils.js
index 780dc931..fdbe7bcb 100644
--- a/utils/graphUtils.js
+++ b/utils/graphUtils.js
@@ -325,7 +325,7 @@ function drawCellsValue(data) {
.text(d => d);
}
-// helper function non-used for the generation
+// helper function non-used for the main generation
function drawPolygons(data) {
const max = d3.max(data),
min = d3.min(data),
@@ -342,3 +342,28 @@ function drawPolygons(data) {
.attr("fill", d => scheme(d))
.attr("stroke", d => scheme(d));
}
+
+// draw raster heightmap preview (not used in main generation)
+function drawHeights({heights, width, height, scheme, renderOcean}) {
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+ const ctx = canvas.getContext("2d");
+ const imageData = ctx.createImageData(width, height);
+
+ const getHeight = height => (height < 20 ? (renderOcean ? height : 0) : height);
+
+ for (let i = 0; i < heights.length; i++) {
+ const color = scheme(1 - getHeight(heights[i]) / 100);
+ const {r, g, b} = d3.color(color);
+
+ const n = i * 4;
+ imageData.data[n] = r;
+ imageData.data[n + 1] = g;
+ imageData.data[n + 2] = b;
+ imageData.data[n + 3] = 255;
+ }
+
+ ctx.putImageData(imageData, 0, 0);
+ return canvas.toDataURL("image/png");
+}
diff --git a/versioning.js b/versioning.js
index 57ad9593..ec17f078 100644
--- a/versioning.js
+++ b/versioning.js
@@ -1,7 +1,7 @@
"use strict";
// version and caching control
-const version = "1.93.11"; // generator version, update each time
+const version = "1.93.12"; // generator version, update each time
{
document.title += " v" + version;
@@ -28,6 +28,7 @@ const version = "1.93.11"; // generator version, update each time
-
Latest changes:
+
- Ability to define custom heightmap color scheme
- New style preset Night and new heightmap color schemes
- Random encounter markers (integration with Deorum)
- Auto-load of the last saved map is now optional (see Onload behavior in Options)