This commit is contained in:
Azgaar 2020-05-17 23:59:08 +03:00
parent f9457060ef
commit 7410c9e0b4
4 changed files with 106 additions and 77 deletions

View file

@ -1028,10 +1028,10 @@ div#regimentSelectorBody > div > div {
}
.color-div {
width: 2.5em;
width: 3em;
height: 1em;
display: inline-block;
margin: .1em .2em;
margin: 0 .16em;
border: 1px #c5c5c5 groove;
cursor: pointer;
}

View file

@ -1883,8 +1883,8 @@
<button data-tip="Display brushes panel" id="paintBrushes">Paint Brushes</button>
<button data-tip="Open template editor" id="applyTemplate" style="display: none">Template Editor</button>
<button data-tip="Open Image Converter" id="convertImage" style="display: none">Image Converter</button>
<button data-tip="Render heightmap data as a small monochrome image" id="heightmapPreview">Heightmap Preview</button>
<button data-tip="Preview heightmap in 3D scene" id="heightmap3DView">3D</button>
<button data-tip="Render heightmap data as a small monochrome image" id="heightmapPreview">Preview</button>
<button data-tip="Preview heightmap in 3D scene" id="heightmap3DView">3D scene</button>
</div>
<div id="customizeOptions">
@ -2674,17 +2674,18 @@
<div id="convertImageButtons">
<button id="convertImageLoad" data-tip="Load image to convert" class="icon-upload"></button>
<button id="convertAutoLum" data-tip="Auto-assign colors based on liminosity (good to monochrome images)" class="icon-adjust"></button>
<button id="convertAutoHue" data-tip="Auto-assign colors based on hue (good to colored images)" class="icon-brush"></button>
<button id="convertAutoLum" data-tip="Auto-assign colors based on liminosity (good for monochrome images)" class="icon-adjust"></button>
<button id="convertAutoHue" data-tip="Auto-assign colors based on hue (good for colored images)" class="icon-paint-roller"></button>
<button id="convertAutoFMG" data-tip="Auto-assign colors using generator scheme (for exported colored heightmaps)" class="icon-layer-group"></button>
<button id="convertColorsButton" data-tip="Set maximum number of colors" class="icon-signal"></button>
<input id="convertColors" value="128" style="display: none"/>
<input id="convertColors" value="100" style="display: none"/>
<button id="convertComplete" data-tip="Complete the conversion. All unassigned colors will be considered as ocean" class="icon-check"></button>
<button id="convertCancel" data-tip="Cancel the conversion. Previous heightmap will be restored" class="icon-cancel"></button>
</div>
<div data-tip="Set opacity of the loaded image" style="padding-top: 4px"><i>Overlay opacity:</i><br>
<input id="convertOverlay" type="range" min=0 max=1 step=.01 value=0 style="width: 11.5em">
<input id="convertOverlayNumber" type="number" min=0 max=1 step=.01 value=0 style="width: 3.5em">
<div data-tip="Set opacity of the loaded image" style="padding-top: .4em"><i>Overlay opacity:</i><br>
<input id="convertOverlay" type="range" min=0 max=1 step=.01 value=0 style="width: 12.6em">
<input id="convertOverlayNumber" type="number" min=0 max=1 step=.01 value=0 style="width: 4.2em">
</div>
<div data-tip="Select a color below and assign a height value for it" id="colorsSelect" style="display: none">
@ -2695,11 +2696,11 @@
</div>
<div data-tip="Select a color to re-assign the height value" id="colorsAssigned" style="display: none">
<i>Assigned colors: </i><br>
<i>Assigned colors (<span id="colorsAssignedNumber"></span>):</i><br>
</div>
<div data-tip="Select a color to assign a height value" id="colorsUnassigned" style="display: none">
<i>Unassigned colors: </i><br>
<i>Unassigned colors (<span id="colorsUnassignedNumber"></span>):</i><br>
</div>
</div>

View file

@ -3,13 +3,13 @@
function editHeightmap() {
void function selectEditMode() {
alertMessage.innerHTML = `<span>Heightmap is a core element on which all other data (rivers, burgs, states etc) is based.
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.</span>
<p>You can also <i>keep</i> all the data, but you won't be able to change the coastline.</p>
<p>If you need to change the coastline and keep the data, you may try the <i>risk</i> edit option.
The data will be restored as much as possible, but the coastline change can cause unexpected fluctuations and errors.</p>
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>`;
alertMessage.innerHTML = `Heightmap is a core element on which all other data (rivers, burgs, states etc) is based.
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
$("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em",
buttons: {
@ -61,9 +61,9 @@ function editHeightmap() {
changeOnlyLand.checked = false;
}
// hide convert and template buttons for the Keep mode
applyTemplate.style.display = type === "keep" ? "none" : "inline-block";
convertImage.style.display = type === "keep" ? "none" : "inline-block";
// show convert and template buttons for Erase mode only
applyTemplate.style.display = type === "erase" ? "inline-block" : "none";
convertImage.style.display = type === "erase" ? "inline-block" : "none";
// hide erosion checkbox if mode is Keep
changeHeightsBox.style.display = type === "keep" ? "none" : "inline-block";
@ -963,10 +963,11 @@ function editHeightmap() {
function openImageConverter() {
if ($("#imageConverter").is(":visible")) return;
imageToLoad.click();
closeDialogs("#imageConverter");
$("#imageConverter").dialog({
title: "Image Converter", maxHeight: svgHeight*.75, minHeight: "auto", width: "19.5em", resizable: false,
title: "Image Converter", maxHeight: svgHeight*.8, minHeight: "auto", width: "20em",
position: {my: "right top", at: "right-10 top+10", of: "svg"},
beforeClose: closeImageConverter
});
@ -978,15 +979,9 @@ function editHeightmap() {
canvas.height = graphHeight;
document.body.insertBefore(canvas, optionsContainer);
const img = new Image;
img.id = "image";
img.style.display = "none";
document.body.appendChild(img);
setOverlayOpacity(0);
document.getElementById("convertImageLoad").classList.add("glow"); // add glow effect
tip('Image Converter is opened. Upload the image and assign height value for each of the colors', true, "warn"); // main tip
clearMainTip();
tip('Image Converter is opened. Upload image and assign height value for each color', false, "warn"); // main tip
// remove all heights
grid.cells.h = new Uint8Array(grid.cells.i.length);
@ -1001,7 +996,7 @@ function editHeightmap() {
d3.select("#imageConverterPalette").selectAll("div").data(d3.range(101))
.enter().append("div").attr("data-color", i => i)
.style("background-color", i => color(1-(i < 20 ? i-5 : i) / 100))
.style("width", i => i < 20 || i > 70 ? ".2em" : ".1em")
.style("width", i => i < 40 || i > 68 ? ".2em" : ".1em")
.on("touchmove mousemove", showPalleteHeight).on("click", assignHeight);
}()
@ -1010,6 +1005,7 @@ function editHeightmap() {
document.getElementById("imageToLoad").addEventListener("change", loadImage);
document.getElementById("convertAutoLum").addEventListener("click", () => autoAssing("lum"));
document.getElementById("convertAutoHue").addEventListener("click", () => autoAssing("hue"));
document.getElementById("convertAutoFMG").addEventListener("click", () => autoAssing("scheme"));
document.getElementById("convertColorsButton").addEventListener("click", setConvertColorsNumber);
document.getElementById("convertComplete").addEventListener("click", applyConversion);
document.getElementById("convertCancel").addEventListener("click", cancelConversion);
@ -1030,12 +1026,12 @@ function editHeightmap() {
this.value = ""; // reset input value to get triggered if the file is re-uploaded
const reader = new FileReader();
const img = new Image;
img.onload = function() {
const ctx = document.getElementById("canvas").getContext("2d");
ctx.drawImage(img, 0, 0, graphWidth, graphHeight);
heightsFromImage(+convertColors.value);
resetZoom();
convertImageLoad.classList.remove("glow");
};
reader.onloadend = () => img.src = reader.result;
@ -1043,37 +1039,35 @@ function editHeightmap() {
}
function heightsFromImage(count) {
const ctx = document.getElementById("canvas").getContext("2d");
const sourceImage = document.getElementById("canvas");
const sampleCanvas = document.createElement("canvas");
sampleCanvas.width = grid.cellsX;
sampleCanvas.height = grid.cellsY;
sampleCanvas.getContext('2d').drawImage(sourceImage, 0, 0, grid.cellsX, grid.cellsY);
const q = new RgbQuant({colors:count});
q.sample(ctx);
const data = q.reduce(ctx);
q.sample(sampleCanvas);
const data = q.reduce(sampleCanvas);
const pallete = q.palette(true);
viewbox.select("#heights").selectAll("*").remove();
d3.select("#imageConverter").selectAll("div.color-div").remove();
colorsSelect.style.display = "block";
colorsUnassigned.style.display = "block";
colorsAssigned.style.display = "none";
let usedColors = new Set();
let gridColors = grid.points.map(p => {
const x = Math.floor(p[0]-.01), y = Math.floor(p[1]-.01);
const i = (x + y * graphWidth) * 4;
const r = data[i], g = data[i+1], b = data[i+2];
usedColors.add(`rgb(${r},${g},${b})`);
return [r, g, b];
});
sampleCanvas.remove(); // no need to keep
viewbox.select("#heights").selectAll("polygon").data(grid.cells.i).join("polygon")
.attr("points", d => getGridPolygon(d))
.attr("id", d => "cell"+d).attr("fill", d => `rgb(${gridColors[d].join(",")})`)
.attr("points", d => getGridPolygon(d)).attr("id", d => "cell"+d)
.attr("fill", d => `rgb(${data[d*4]}, ${data[d*4+1]}, ${data[d*4+2]})`)
.on("click", mapClicked);
const unassigned = [...usedColors].sort((a, b) => d3.lab(a).l - d3.lab(b).l);
d3.select("#colorsUnassigned").selectAll("div").data(unassigned).enter().append("div")
const colors = pallete.map(p => `rgb(${p[0]}, ${p[1]}, ${p[2]})`);
d3.select("#colorsUnassigned").selectAll("div").data(colors).enter().append("div")
.attr("data-color", i => i).style("background-color", i => i)
.attr("class", "color-div").on("click", colorClicked);
convertColors.value = unassigned.length;
document.getElementById("colorsUnassignedNumber").innerHTML = colors.length;
}
function mapClicked() {
@ -1123,34 +1117,60 @@ function editHeightmap() {
if (selectedColor.parentNode.id === "colorsUnassigned") {
colorsAssigned.appendChild(selectedColor);
colorsAssigned.style.display = "block";
document.getElementById("colorsUnassignedNumber").innerHTML = colorsUnassigned.childElementCount - 2;
document.getElementById("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2;
}
}
// auto assign color based on luminosity or hue
function autoAssing(type) {
const unassigned = colorsUnassigned.querySelectorAll("div");
if (!unassigned.length) {tip("No unassigned colors. Please load an image and click the button again", false, "error"); return;}
let unassigned = colorsUnassigned.querySelectorAll("div");
if (!unassigned.length) {
heightsFromImage(+convertColors.value);
unassigned = colorsUnassigned.querySelectorAll("div");
if (!unassigned.length) {
tip("No unassigned colors. Please load an image and click the button again", false, "error");
return;
}
}
const assinged = []; // assigned heights
const getHeightByHue = function(color) {
let hue = d3.hsl(color).h;
if (hue > 300) hue -= 360;
if (hue > 170) return Math.abs(hue-250) / 3 |0; // water
return Math.abs(hue-250+20) / 3 |0; // land
}
const getHeightByLum = function(color) {
let lum = d3.lab(color).l;
if (lum < 13) return lum / 13 * 20 |0; // water
return lum|0; // land
}
const scheme = d3.range(101).map(i => getColor(i, color()));
const hues = scheme.map(rgb => d3.hsl(rgb).h|0);
const getHeightByScheme = function(color) {
let height = scheme.indexOf(color);
if (height !== -1) return height; // exact match
const hue = d3.hsl(color).h;
const closest = hues.reduce((prev, curr) => (Math.abs(curr - hue) < Math.abs(prev - hue) ? curr : prev));
return hues.indexOf(closest);
}
const assinged = []; // store assigned heights
unassigned.forEach(el => {
const colorFrom = el.dataset.color;
const lab = d3.lab(colorFrom);
const normalized = type === "hue" ? rn(normalize(lab.b + lab.a / 2, -50, 200), 2) : rn(normalize(lab.l, -15, 100), 2);
let heightTo = rn(normalized * 100);
if (assinged[heightTo] && heightTo < 100) heightTo += 1; // if height is already added, try increased one
if (assinged[heightTo] && heightTo < 100) heightTo += 1; // if height is already added, try increased one
if (assinged[heightTo] && heightTo > 3) heightTo -= 3; // if increased one is also added, try decreased one
if (assinged[heightTo] && heightTo > 1) heightTo -= 1; // if increased one is also added, try decreased one
const clr = el.dataset.color;
const height = type === "hue" ? getHeightByHue(clr) : type === "lum" ? getHeightByLum(clr) : getHeightByScheme(clr);
const colorTo = color(1 - (height < 20 ? (height-5) / 100 : height / 100));
viewbox.select("#heights").selectAll("polygon[fill='" + clr + "']").attr("fill", colorTo).attr("data-height", height);
const colorTo = color(1 - (heightTo < 20 ? (heightTo-5)/100 : heightTo/100));
viewbox.select("#heights").selectAll("polygon[fill='" + colorFrom + "']").attr("fill", colorTo).attr("data-height", heightTo);
if (assinged[heightTo]) {el.remove(); return;} // if color is already added, remove it
if (assinged[height]) {el.remove(); return;} // if color is already added, remove it
el.style.backgroundColor = el.dataset.color = colorTo;
el.dataset.height = heightTo;
el.dataset.height = height;
colorsAssigned.appendChild(el);
assinged[heightTo] = true;
assinged[height] = true;
});
// sort assigned colors by height
@ -1160,10 +1180,11 @@ function editHeightmap() {
colorsAssigned.style.display = "block";
colorsUnassigned.style.display = "none";
document.getElementById("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2;
}
function setConvertColorsNumber() {
prompt(`Please set maximum number of colors. <br>An actual number is lower and depends on color scheme`,
prompt(`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`,
{default:+convertColors.value, step:1, min:3, max:255}, number => {
convertColors.value = number;
heightsFromImage(number);
@ -1176,6 +1197,11 @@ function editHeightmap() {
}
function applyConversion() {
if (colorsAssigned.childElementCount < 3) {
tip("Please do the assignment first", false, "error");
return;
}
viewbox.select("#heights").selectAll("polygon").each(function() {
const height = +this.dataset.height || 0;
const i = +this.id.slice(4);
@ -1195,9 +1221,7 @@ function editHeightmap() {
function restoreImageConverterState() {
const canvas = document.getElementById("canvas");
if (canvas) canvas.remove(); else return;
const img = document.getElementById("image");
if (img) img.remove(); else return;
if (canvas) canvas.remove();
d3.select("#imageConverter").selectAll("div.color-div").remove();
colorsAssigned.style.display = "none";
@ -1206,12 +1230,18 @@ function editHeightmap() {
viewbox.style("cursor", "default").on(".drag", null);
tip('Heightmap edit mode is active. Click on "Exit Customization" to finalize the heightmap', true);
$("#imageConverter").dialog("destroy");
openBrushesPanel();
}
function closeImageConverter(event) {
event.preventDefault();
event.stopPropagation();
alertMessage.innerHTML = 'Are you sure you want to close the Image Converter? Click "Cancel" to geck back to convertion. Click "Complete" to apply the conversion. Click "Close" to exit conversion mode and restore previous heightmap';
alertMessage.innerHTML = `
Are you sure you want to close the Image Converter?
Click "Cancel" to geck back to convertion.
Click "Complete" to apply the conversion.
Click "Close" to exit conversion mode and restore previous heightmap`;
$("#alert").dialog({resizable: false, title: "Close Image Converter",
buttons: {
Cancel: function() {

View file

@ -103,9 +103,7 @@ function editRoute(onClick) {
const l = elSelected.node().getTotalLength();
routeLength.innerHTML = rn(l * distanceScaleInput.value) + " " + distanceUnitInput.value;
if (modules.elevation) {
showEPForRoute(elSelected.node());
}
if (modules.elevation) showEPForRoute(elSelected.node());
}
function showElevationProfile() {