diff --git a/index.css b/index.css
index 1cb69877..58e92533 100644
--- a/index.css
+++ b/index.css
@@ -1303,11 +1303,6 @@ div.slider .ui-slider-handle {
color: white;
}
-#brushPower,
-#brushRadius {
- width: 12em;
-}
-
#rescaleHigher,
#rescaleLower,
#rescaleModifier {
diff --git a/index.html b/index.html
index 946c53d1..ae7345ee 100644
--- a/index.html
+++ b/index.html
@@ -108,7 +108,7 @@
}
-
+
@@ -3836,6 +3836,12 @@
+
+
@@ -3848,6 +3854,7 @@
min="1"
max="99"
value="25"
+ style="width: 13em"
/>
+
-
+
@@ -7804,7 +7836,7 @@
-
+
@@ -7834,7 +7866,7 @@
-
+
@@ -7864,7 +7896,7 @@
-
+
diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js
index 66696953..5a69337e 100644
--- a/modules/heightmap-generator.js
+++ b/modules/heightmap-generator.js
@@ -8,7 +8,7 @@ window.HeightmapGenerator = (function () {
const setGraph = graph => {
const {cellsDesired, cells, points} = graph;
- heights = cells.h || createTypedArray({maxValue: 100, length: points.length});
+ heights = cells.h ? Uint8Array.from(cells.h) : createTypedArray({maxValue: 100, length: points.length});
blobPower = getBlobPower(cellsDesired);
linePower = getLinePower(cellsDesired);
grid = graph;
@@ -198,7 +198,8 @@ window.HeightmapGenerator = (function () {
}
};
- const addRange = (count, height, rangeX, rangeY) => {
+ // fromCell, toCell are options cell ids
+ const addRange = (count, height, rangeX, rangeY, startCell, endCell) => {
count = getNumberInRange(count);
while (count > 0) {
addOneRange();
@@ -209,23 +210,27 @@ window.HeightmapGenerator = (function () {
const used = new Uint8Array(heights.length);
let h = lim(getNumberInRange(height));
- // find start and end points
- const startX = getPointInRange(rangeX, graphWidth);
- const startY = getPointInRange(rangeY, graphHeight);
+ if (rangeX && rangeY) {
+ // find start and end points
+ const startX = getPointInRange(rangeX, graphWidth);
+ const startY = getPointInRange(rangeY, graphHeight);
- let dist = 0,
- limit = 0,
- endX,
- endY;
- do {
- endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1;
- endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15;
- dist = Math.abs(endY - startY) + Math.abs(endX - startX);
- limit++;
- } while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50);
+ let dist = 0,
+ limit = 0,
+ endX,
+ endY;
+
+ do {
+ endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1;
+ endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15;
+ dist = Math.abs(endY - startY) + Math.abs(endX - startX);
+ limit++;
+ } while ((dist < graphWidth / 8 || dist > graphWidth / 3) && limit < 50);
+
+ startCell = findGridCell(startX, startY, grid);
+ endCell = findGridCell(endX, endY, grid);
+ }
- const startCell = findGridCell(startX, startY, grid);
- const endCell = findGridCell(endX, endY, grid);
let range = getRange(startCell, endCell);
// get main ridge
@@ -286,7 +291,7 @@ window.HeightmapGenerator = (function () {
}
};
- const addTrough = (count, height, rangeX, rangeY) => {
+ const addTrough = (count, height, rangeX, rangeY, startCell, endCell) => {
count = getNumberInRange(count);
while (count > 0) {
addOneTrough();
@@ -297,30 +302,33 @@ window.HeightmapGenerator = (function () {
const used = new Uint8Array(heights.length);
let h = lim(getNumberInRange(height));
- // find start and end points
- let limit = 0,
- startX,
- startY,
- start,
- dist = 0,
- endX,
- endY;
- do {
- startX = getPointInRange(rangeX, graphWidth);
- startY = getPointInRange(rangeY, graphHeight);
- start = findGridCell(startX, startY, grid);
- limit++;
- } while (heights[start] < 20 && limit < 50);
+ if (rangeX && rangeY) {
+ // find start and end points
+ let limit = 0,
+ startX,
+ startY,
+ dist = 0,
+ endX,
+ endY;
+ do {
+ startX = getPointInRange(rangeX, graphWidth);
+ startY = getPointInRange(rangeY, graphHeight);
+ startCell = findGridCell(startX, startY, grid);
+ limit++;
+ } while (heights[startCell] < 20 && limit < 50);
- limit = 0;
- do {
- endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1;
- endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15;
- dist = Math.abs(endY - startY) + Math.abs(endX - startX);
- limit++;
- } while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50);
+ limit = 0;
+ do {
+ endX = Math.random() * graphWidth * 0.8 + graphWidth * 0.1;
+ endY = Math.random() * graphHeight * 0.7 + graphHeight * 0.15;
+ dist = Math.abs(endY - startY) + Math.abs(endX - startX);
+ limit++;
+ } while ((dist < graphWidth / 8 || dist > graphWidth / 2) && limit < 50);
- let range = getRange(start, findGridCell(endX, endY, grid));
+ endCell = findGridCell(endX, endY, grid);
+ }
+
+ let range = getRange(startCell, endCell);
// get main ridge
function getRange(cur, end) {
@@ -388,8 +396,12 @@ window.HeightmapGenerator = (function () {
const vert = direction === "vertical";
const startX = vert ? Math.floor(Math.random() * graphWidth * 0.4 + graphWidth * 0.3) : 5;
const startY = vert ? 5 : Math.floor(Math.random() * graphHeight * 0.4 + graphHeight * 0.3);
- const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5;
- const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
+ const endX = vert
+ ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2)
+ : graphWidth - 5;
+ const endY = vert
+ ? graphHeight - 5
+ : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
const start = findGridCell(startX, startY, grid);
const end = findGridCell(endX, endY, grid);
diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js
index f72afd13..7f83354a 100644
--- a/modules/ui/heightmap-editor.js
+++ b/modules/ui/heightmap-editor.js
@@ -130,6 +130,12 @@ function editHeightmap(options) {
// move radius circle if drag mode is active
const pressed = byId("brushesButtons").querySelector("button.pressed");
if (!pressed) return;
+
+ if (pressed.id === "brushLine") {
+ debug.select("line").attr("x2", x).attr("y2", y);
+ return;
+ }
+
moveCircle(x, y, brushRadius.valueAsNumber, "#333");
}
@@ -181,7 +187,6 @@ function editHeightmap(options) {
else if (mode === "risk") restoreRiskedData();
// restore initial layers
- //viewbox.select("#heights").remove();
byId("heights").remove();
turnButtonOff("toggleHeight");
document
@@ -494,10 +499,8 @@ function editHeightmap(options) {
selection.forEach(function (i) {
let cell = viewbox.select("#heights").select("#cell" + i);
- if (!ocean && grid.cells.h[i] < 20) {
- cell.remove();
- return;
- }
+ if (!ocean && grid.cells.h[i] < 20) return cell.remove();
+
if (!cell.size())
cell = viewbox
.select("#heights")
@@ -594,24 +597,81 @@ function editHeightmap(options) {
function exitBrushMode() {
const pressed = document.querySelector("#brushesButtons > button.pressed");
- if (!pressed) return;
- pressed.classList.remove("pressed");
+ if (pressed) pressed.classList.remove("pressed");
- viewbox.style("cursor", "default").on(".drag", null);
+ viewbox.style("cursor", "default").on(".drag", null).on("click", clicked);
+ debug.selectAll(".lineCircle").remove();
removeCircle();
+
byId("brushesSliders").style.display = "none";
+ byId("lineSlider").style.display = "none";
}
const dragBrushThrottled = throttle(dragBrush, 100);
- function toggleBrushMode(e) {
- if (e.target.classList.contains("pressed")) {
+
+ function toggleBrushMode(event) {
+ if (event.target.classList.contains("pressed")) {
exitBrushMode();
return;
}
+
exitBrushMode();
- byId("brushesSliders").style.display = "block";
- e.target.classList.add("pressed");
- viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragBrushThrottled));
+ event.target.classList.add("pressed");
+
+ if (event.target.id === "brushLine") {
+ byId("lineSlider").style.display = "block";
+ viewbox.style("cursor", "crosshair").on("click", placeLinearFeature);
+ } else {
+ byId("brushesSliders").style.display = "block";
+ viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragBrushThrottled));
+ }
+ }
+
+ function placeLinearFeature() {
+ const [x, y] = d3.mouse(this);
+ const toCell = findGridCell(x, y, grid);
+
+ const lineCircle = debug.selectAll(".lineCircle");
+ if (!lineCircle.size()) {
+ // first click: add 1st control point
+ debug.append("line").attr("id", "brushCircle").attr("x1", x).attr("y1", y).attr("x2", x).attr("y2", y);
+
+ debug
+ .append("circle")
+ .attr("data-cell", toCell)
+ .attr("class", "lineCircle")
+ .attr("r", 6)
+ .attr("cx", x)
+ .attr("cy", y)
+ .attr("fill", "yellow")
+ .attr("stroke", "#333")
+ .attr("stroke-width", 2);
+ return;
+ }
+
+ // second click: execute operation and remove control points
+ const fromCell = +lineCircle.attr("data-cell");
+ debug.selectAll("*").remove();
+
+ const power = byId("linePower").valueAsNumber;
+ if (power === 0) return tip("Power should not be zero", false, "error");
+
+ const heights = grid.cells.h;
+ const operation = power > 0 ? HeightmapGenerator.addRange : HeightmapGenerator.addTrough;
+ HeightmapGenerator.setGraph(grid);
+ operation("1", String(Math.abs(power)), null, null, fromCell, toCell);
+ const changedHeights = HeightmapGenerator.getHeights();
+
+ let selection = [];
+ for (let i = 0; i < heights.length; i++) {
+ if (changedHeights[i] === heights[i]) continue;
+ if (changeOnlyLand.checked && heights[i] < 20) continue;
+ heights[i] = changedHeights[i];
+ selection.push(i);
+ }
+
+ mockHeightmapSelection(selection);
+ updateHistory();
}
function dragBrush() {
@@ -632,37 +692,44 @@ function editHeightmap(options) {
d3.event.on("end", updateHeightmap);
}
- function changeHeightForSelection(s, start) {
+ function changeHeightForSelection(selection, start) {
const power = brushPower.valueAsNumber;
+
const interpolate = d3.interpolateRound(power, 1);
const land = changeOnlyLand.checked;
const lim = v => minmax(v, land ? 20 : 0, 100);
- const h = grid.cells.h;
+ const heights = grid.cells.h;
const brush = document.querySelector("#brushesButtons > button.pressed").id;
- if (brush === "brushRaise") s.forEach(i => (h[i] = h[i] < 20 ? 20 : lim(h[i] + power)));
+ if (brush === "brushRaise") selection.forEach(i => (heights[i] = heights[i] < 20 ? 20 : lim(heights[i] + power)));
else if (brush === "brushElevate")
- s.forEach((i, d) => (h[i] = lim(h[i] + interpolate(d / Math.max(s.length - 1, 1)))));
- else if (brush === "brushLower") s.forEach(i => (h[i] = lim(h[i] - power)));
+ selection.forEach(
+ (i, d) => (heights[i] = lim(heights[i] + interpolate(d / Math.max(selection.length - 1, 1))))
+ );
+ else if (brush === "brushLower") selection.forEach(i => (heights[i] = lim(heights[i] - power)));
else if (brush === "brushDepress")
- s.forEach((i, d) => (h[i] = lim(h[i] - interpolate(d / Math.max(s.length - 1, 1)))));
- else if (brush === "brushAlign") s.forEach(i => (h[i] = lim(h[start])));
+ selection.forEach(
+ (i, d) => (heights[i] = lim(heights[i] - interpolate(d / Math.max(selection.length - 1, 1))))
+ );
+ else if (brush === "brushAlign") selection.forEach(i => (heights[i] = lim(heights[start])));
else if (brush === "brushSmooth")
- s.forEach(
+ selection.forEach(
i =>
- (h[i] = rn(
- (d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) +
- h[i] * (10 - power) +
+ (heights[i] = rn(
+ (d3.mean(grid.cells.c[i].filter(i => (land ? heights[i] >= 20 : 1)).map(c => heights[c])) +
+ heights[i] * (10 - power) +
0.6) /
(11 - power),
1
))
);
else if (brush === "brushDisrupt")
- s.forEach(i => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
+ selection.forEach(
+ i => (heights[i] = heights[i] < 15 ? heights[i] : lim(heights[i] + power / 1.6 - Math.random() * power))
+ );
- mockHeightmapSelection(s);
- // updateHistory(); uncomment to update history every step
+ mockHeightmapSelection(selection);
+ // updateHistory(); uncomment to update history on every step
}
function changeOnlyLandClick(e) {
diff --git a/modules/ui/hotkeys.js b/modules/ui/hotkeys.js
index d8fcaaf8..f7443469 100644
--- a/modules/ui/hotkeys.js
+++ b/modules/ui/hotkeys.js
@@ -89,13 +89,13 @@ function handleKeyup(event) {
else if (code === "KeyI") toggleIcons();
else if (code === "KeyM") toggleMilitary();
else if (code === "KeyK") toggleMarkers();
- else if (code === "Equal") toggleRulers();
+ else if (code === "Equal" && !customization) toggleRulers();
else if (code === "Slash") toggleScaleBar();
else if (code === "ArrowLeft") zoom.translateBy(svg, 10, 0);
else if (code === "ArrowRight") zoom.translateBy(svg, -10, 0);
else if (code === "ArrowUp") zoom.translateBy(svg, 0, 10);
else if (code === "ArrowDown") zoom.translateBy(svg, 0, -10);
- else if (key === "+" || key === "-") pressNumpadSign(key);
+ else if (key === "+" || key === "-" || key === "=") handleSizeChange(key);
else if (key === "0") resetZoom(1000);
else if (key === "1") zoom.scaleTo(svg, 1);
else if (key === "2") zoom.scaleTo(svg, 2);
@@ -117,11 +117,12 @@ function allowHotkeys() {
return true;
}
-function pressNumpadSign(key) {
- const change = key === "+" ? 1 : -1;
+// "+", "-" and "=" keys on numpad. "=" is for "+" on Mac
+function handleSizeChange(key) {
let brush = null;
if (document.getElementById("brushRadius")?.offsetParent) brush = document.getElementById("brushRadius");
+ else if (document.getElementById("linePower")?.offsetParent) brush = document.getElementById("linePower");
else if (document.getElementById("biomesManuallyBrush")?.offsetParent)
brush = document.getElementById("biomesManuallyBrush");
else if (document.getElementById("statesManuallyBrush")?.offsetParent)
@@ -135,6 +136,7 @@ function pressNumpadSign(key) {
brush = document.getElementById("religionsManuallyBrush");
if (brush) {
+ const change = key === "-" ? -5 : 5;
const value = minmax(+brush.value + change, +brush.min, +brush.max);
brush.value = document.getElementById(brush.id + "Number").value = value;
return;
diff --git a/utils/probabilityUtils.js b/utils/probabilityUtils.js
index 7759e330..b0234251 100644
--- a/utils/probabilityUtils.js
+++ b/utils/probabilityUtils.js
@@ -56,7 +56,7 @@ function biased(min, max, ex) {
// get number from string in format "1-3" or "2" or "0.5"
function getNumberInRange(r) {
if (typeof r !== "string") {
- ERROR && console.error("The value should be a string", r);
+ ERROR && console.error("Range value should be a string", r);
return 0;
}
if (!isNaN(+r)) return ~~r + +P(r - ~~r);
diff --git a/versioning.js b/versioning.js
index ea45eff7..6dac46df 100644
--- a/versioning.js
+++ b/versioning.js
@@ -1,7 +1,7 @@
"use strict";
// version and caching control
-const version = "1.87.15"; // generator version, update each time
+const version = "1.88.00"; // generator version, update each time
{
document.title += " v" + version;
@@ -28,6 +28,7 @@ const version = "1.87.15"; // generator version, update each time
Latest changes:
+ - Heightmap brushes: linear edit option
- Data Charts screen
- Сultures and religions can have multiple parents in hierarchy tree
- Heightmap selection screen