mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
Submap live preview.
This commit is contained in:
parent
159c1aa3e3
commit
0d71f10f05
3 changed files with 137 additions and 36 deletions
|
|
@ -4370,8 +4370,8 @@
|
||||||
|
|
||||||
<div>Shift</div>
|
<div>Shift</div>
|
||||||
<div>
|
<div>
|
||||||
<label>X: <input id="submapShiftX" type="number" min="0" size="4" value="0" /></label>
|
<label>X: <input id="submapShiftX" type="number" size="4" value="0" /></label>
|
||||||
<label>Y: <input id="submapShiftY" type="number" min="0" size="4" value="0" /></label>
|
<label>Y: <input id="submapShiftY" type="number" size="4" value="0" /></label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>Rotate</div>
|
<div>Rotate</div>
|
||||||
|
|
@ -4395,6 +4395,7 @@
|
||||||
<label for="submapMirrorV" class="checkbox-label">vertically</label>
|
<label for="submapMirrorV" class="checkbox-label">vertically</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="submapPreview" style="border:1px solid black; margin: 1em auto; overflow:hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="submapOptionsDialog" style="display: none" class="dialog">
|
<div id="submapOptionsDialog" style="display: none" class="dialog">
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ async function saveTiles() {
|
||||||
|
|
||||||
// parse map svg to object url
|
// parse map svg to object url
|
||||||
async function getMapURL(type, options = {}) {
|
async function getMapURL(type, options = {}) {
|
||||||
const {debug = false, globe = false, noLabels = false, noWater = false, fullMap = false} = options;
|
const {debug = false, globe = false, noLabels = false, noWater = false, noScaleBar = false, noIce = false, fullMap = false} = options;
|
||||||
|
|
||||||
if (fullMap) drawScaleBar(1);
|
if (fullMap) drawScaleBar(1);
|
||||||
|
|
||||||
|
|
@ -164,6 +164,12 @@ async function getMapURL(type, options = {}) {
|
||||||
clone.select("#oceanBase").attr("opacity", 0);
|
clone.select("#oceanBase").attr("opacity", 0);
|
||||||
clone.select("#oceanPattern").attr("opacity", 0);
|
clone.select("#oceanPattern").attr("opacity", 0);
|
||||||
}
|
}
|
||||||
|
if (noScaleBar) {
|
||||||
|
clone.select("#scaleBar")?.remove()
|
||||||
|
}
|
||||||
|
if (noIce) {
|
||||||
|
clone.select("#ice")?.remove()
|
||||||
|
}
|
||||||
if (fullMap) {
|
if (fullMap) {
|
||||||
// reset transform to show the whole map
|
// reset transform to show the whole map
|
||||||
clone.attr("width", graphWidth).attr("height", graphHeight);
|
clone.attr("width", graphWidth).attr("height", graphHeight);
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,29 @@
|
||||||
// UI elements for submap generation
|
// UI elements for submap generation
|
||||||
|
|
||||||
window.UISubmap = (function () {
|
window.UISubmap = (function () {
|
||||||
document.getElementById("submapPointsInput").addEventListener("input", function () {
|
const byId = document.getElementById.bind(document);
|
||||||
const output = document.getElementById("submapPointsOutputFormatted");
|
byId("submapPointsInput").addEventListener("input", function () {
|
||||||
|
const output = byId("submapPointsOutputFormatted");
|
||||||
const cells = cellsDensityMap[+this.value] || 1000;
|
const cells = cellsDensityMap[+this.value] || 1000;
|
||||||
this.dataset.cells = cells;
|
this.dataset.cells = cells;
|
||||||
output.value = getCellsDensityValue(cells);
|
output.value = getCellsDensityValue(cells);
|
||||||
output.style.color = getCellsDensityColor(cells);
|
output.style.color = getCellsDensityColor(cells);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("submapScaleInput").addEventListener("input", function (event) {
|
byId("submapScaleInput").addEventListener("input", function (event) {
|
||||||
const exp = Math.pow(1.1, +event.target.value);
|
const exp = Math.pow(1.1, +event.target.value);
|
||||||
document.getElementById("submapScaleOutput").value = rn(exp,2);
|
byId("submapScaleOutput").value = rn(exp,2);
|
||||||
event.stopPropagation();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
byId("submapAngleInput").addEventListener("input", function (event) {
|
||||||
|
byId("submapAngleOutput").value = event.target.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const $previewBox = byId("submapPreview");
|
||||||
|
const $scaleInput = byId("submapScaleInput");
|
||||||
|
const $shiftX = byId("submapShiftX");
|
||||||
|
const $shiftY = byId("submapShiftY");
|
||||||
|
|
||||||
function openSubmapMenu() {
|
function openSubmapMenu() {
|
||||||
$("#submapOptionsDialog").dialog({
|
$("#submapOptionsDialog").dialog({
|
||||||
title: "Create a submap",
|
title: "Create a submap",
|
||||||
|
|
@ -34,21 +43,59 @@ window.UISubmap = (function () {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openResampleMenu() {
|
const getTransformInput = _ => ({
|
||||||
|
angle: (+byId("submapAngleInput").value / 180) * Math.PI,
|
||||||
|
shiftX: +byId("submapShiftX").value,
|
||||||
|
shiftY: +byId("submapShiftY").value,
|
||||||
|
ratio: +byId("submapScaleInput").value,
|
||||||
|
mirrorH: byId("submapMirrorH").checked,
|
||||||
|
mirrorV: byId("submapMirrorV").checked,
|
||||||
|
})
|
||||||
|
|
||||||
|
async function openResampleMenu() {
|
||||||
resetZoom(0);
|
resetZoom(0);
|
||||||
|
|
||||||
document.getElementById("submapAngleInput").value = 0;
|
byId("submapAngleInput").value = 0;
|
||||||
document.getElementById("submapAngleOutput").value = "0";
|
byId("submapAngleOutput").value = "0";
|
||||||
document.getElementById("submapScaleInput").value = 1;
|
byId("submapScaleOutput").value = 1;
|
||||||
document.getElementById("submapScaleOutput").value = 1;
|
byId("submapMirrorH").checked = false;
|
||||||
document.getElementById("submapShiftX").value = 0;
|
byId("submapMirrorV").checked = false;
|
||||||
document.getElementById("submapShiftY").value = 0;
|
$scaleInput.value = 0;
|
||||||
document.getElementById("submapMirrorH").checked = false;
|
$shiftX.value = 0;
|
||||||
document.getElementById("submapMirrorV").checked = false;
|
$shiftY.value = 0;
|
||||||
|
|
||||||
|
const previewScale = 400 / graphWidth;
|
||||||
|
const [w, h] = [400, graphHeight * previewScale];
|
||||||
|
$previewBox.style.width = w + 'px';
|
||||||
|
$previewBox.style.height = h + 'px';
|
||||||
|
$previewBox.style.position = 'relative';
|
||||||
|
|
||||||
|
// handle mouse input
|
||||||
|
const dispatchInput = e => e.dispatchEvent(new Event('input', {bubbles:true}));
|
||||||
|
|
||||||
|
// mouse wheel
|
||||||
|
$previewBox.onwheel = e => {
|
||||||
|
$scaleInput.value = $scaleInput.valueAsNumber - Math.sign(e.deltaY);
|
||||||
|
dispatchInput($scaleInput);
|
||||||
|
};
|
||||||
|
|
||||||
|
// mouse drag
|
||||||
|
let mouseIsDown = false, mouseX = 0, mouseY = 0;
|
||||||
|
$previewBox.onmousedown = e => [ mouseIsDown, mouseX, mouseY ] = [ true, $shiftX.value - e.clientX / previewScale, $shiftY.value - e.clientY / previewScale];
|
||||||
|
$previewBox.onmouseup = _ => mouseIsDown = false;
|
||||||
|
$previewBox.onmouseleave = _ => mouseIsDown = false;
|
||||||
|
$previewBox.onmousemove = e => {
|
||||||
|
if (!mouseIsDown) return;
|
||||||
|
e.preventDefault();
|
||||||
|
$shiftX.value = Math.round(mouseX + e.clientX / previewScale);
|
||||||
|
$shiftY.value = Math.round(mouseY + e.clientY / previewScale);
|
||||||
|
dispatchInput($shiftX);
|
||||||
|
// dispatchInput($shiftY); // not needed X bubbles anyway
|
||||||
|
};
|
||||||
|
|
||||||
$("#resampleDialog").dialog({
|
$("#resampleDialog").dialog({
|
||||||
title: "Resample map",
|
title: "Resample map",
|
||||||
width: "30em",
|
width: "430px",
|
||||||
resizable: false,
|
resizable: false,
|
||||||
position: {my: "center", at: "center", of: "svg"},
|
position: {my: "center", at: "center", of: "svg"},
|
||||||
buttons: {
|
buttons: {
|
||||||
|
|
@ -61,25 +108,72 @@ window.UISubmap = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// use double resolution for PNG to get sharper image
|
||||||
|
const $preview = await loadPreview($previewBox, w*2, h*2);
|
||||||
|
// could be done with SVG. Faster to load, slower to use.
|
||||||
|
// const $preview = await loadPreviewSVG($previewBox, w, h);
|
||||||
|
$preview.style.position = "absolute";
|
||||||
|
$preview.style.width = w + "px";
|
||||||
|
$preview.style.height = h + "px";
|
||||||
|
|
||||||
|
byId("resampleDialog").oninput = event => {
|
||||||
|
const { angle, shiftX, shiftY, ratio, mirrorH, mirrorV } = getTransformInput();
|
||||||
|
const scale = Math.pow(1.1,ratio);
|
||||||
|
const transformStyle = `
|
||||||
|
translate(${shiftX*previewScale}px, ${shiftY*previewScale}px)
|
||||||
|
scale(${mirrorH?-scale:scale}, ${mirrorV?-scale:scale})
|
||||||
|
rotate(${angle}rad)
|
||||||
|
`;
|
||||||
|
|
||||||
|
$preview.style.transform = transformStyle;
|
||||||
|
$preview.style['transform-origin'] = 'center';
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPreview($container, w, h) {
|
||||||
|
const url = await getMapURL("png", { globe: false, noWater: true, fullMap: true, noLabels: false, noScaleBar: true, noIce: true });
|
||||||
|
|
||||||
|
const link = document.createElement("a");
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
const img = new Image();
|
||||||
|
img.src = url;
|
||||||
|
img.onload = function () {
|
||||||
|
ctx.drawImage(img, 0, 0, w, h);
|
||||||
|
};
|
||||||
|
$container.textContent = '';
|
||||||
|
$container.appendChild(canvas);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently unused alternative to PNG version
|
||||||
|
async function loadPreviewSVG($container, w, h) {
|
||||||
|
$container.innerHTML = /*html*/`
|
||||||
|
<svg id="submapPreviewSVG" viewBox="0 0 ${graphWidth} ${graphHeight}">
|
||||||
|
<rect width="100%" height="100%" fill="${byId('styleOceanFill').value}" />
|
||||||
|
<rect fill="url(#oceanic)" width="100%" height="100%" />
|
||||||
|
<use href="#map"></use>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
return byId("submapPreviewSVG");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resample the whole map to different cell resolution or shape
|
// Resample the whole map to different cell resolution or shape
|
||||||
const resampleCurrentMap = debounce(function () {
|
const resampleCurrentMap = debounce(function () {
|
||||||
WARN && console.warn("Resampling current map");
|
WARN && console.warn("Resampling current map");
|
||||||
const cellNumId = +document.getElementById("submapPointsInput").value;
|
const cellNumId = +byId("submapPointsInput").value;
|
||||||
if (!cellsDensityMap[cellNumId]) return console.error("Unknown cell number!");
|
if (!cellsDensityMap[cellNumId]) return console.error("Unknown cell number!");
|
||||||
|
|
||||||
const angle = (+document.getElementById("submapAngleInput").value / 180) * Math.PI;
|
const { angle, shiftX, shiftY, ratio, mirrorH, mirrorV } = getTransformInput()
|
||||||
const shiftX = +document.getElementById("submapShiftX").value;
|
|
||||||
const shiftY = +document.getElementById("submapShiftY").value;
|
|
||||||
const ratio = +document.getElementById("submapScaleInput").value;
|
|
||||||
const mirrorH = document.getElementById("submapMirrorH").checked;
|
|
||||||
const mirrorV = document.getElementById("submapMirrorV").checked;
|
|
||||||
|
|
||||||
const [cx, cy] = [graphWidth / 2, graphHeight / 2];
|
const [cx, cy] = [graphWidth / 2, graphHeight / 2];
|
||||||
const rot = alfa => (x, y) => [(x - cx) * Math.cos(alfa) - (y - cy) * Math.sin(alfa) + cx, (y - cy) * Math.cos(alfa) + (x - cx) * Math.sin(alfa) + cy];
|
const rot = alfa => (x, y) => [(x - cx) * Math.cos(alfa) - (y - cy) * Math.sin(alfa) + cx, (y - cy) * Math.cos(alfa) + (x - cx) * Math.sin(alfa) + cy];
|
||||||
const scale = ratio => (x, y) => [(x-cx) * ratio + cx, (y-cy) * ratio + cy];
|
|
||||||
const shift = (dx, dy) => (x, y) => [x + dx, y + dy];
|
const shift = (dx, dy) => (x, y) => [x + dx, y + dy];
|
||||||
|
const scale = r => (x, y) => [(x-cx) * r + cx, (y-cy) * r + cy];
|
||||||
const flipH = (x, y) => [-x + 2 * cx, y];
|
const flipH = (x, y) => [-x + 2 * cx, y];
|
||||||
const flipV = (x, y) => [x, -y + 2 * cy];
|
const flipV = (x, y) => [x, -y + 2 * cy];
|
||||||
const app = (f, g) => (x, y) => f(...g(x, y));
|
const app = (f, g) => (x, y) => f(...g(x, y));
|
||||||
|
|
@ -89,13 +183,13 @@ window.UISubmap = (function () {
|
||||||
let inverse = id;
|
let inverse = id;
|
||||||
|
|
||||||
if (angle) [projection, inverse] = [rot(angle), rot(-angle)];
|
if (angle) [projection, inverse] = [rot(angle), rot(-angle)];
|
||||||
|
if (ratio) [projection, inverse] = [app(scale(Math.pow(1.1,ratio)), projection), app(inverse, scale(Math.pow(1.1,-ratio)))];
|
||||||
|
if (mirrorH) [projection, inverse] = [app(flipH, projection), app(inverse, flipH)];
|
||||||
|
if (mirrorV) [projection, inverse] = [app(flipV, projection), app(inverse, flipV)];
|
||||||
if (shiftX || shiftY) {
|
if (shiftX || shiftY) {
|
||||||
projection = app(shift(shiftX, shiftY), projection);
|
projection = app(shift(shiftX, shiftY), projection);
|
||||||
inverse = app(inverse, shift(-shiftX, -shiftY));
|
inverse = app(inverse, shift(-shiftX, -shiftY));
|
||||||
}
|
}
|
||||||
if (ratio) [projection, inverse] = [app(scale(Math.pow(1.1,ratio)), projection), app(inverse, scale(Math.pow(1.1,-ratio)))];
|
|
||||||
if (mirrorH) [projection, inverse] = [app(flipH, projection), app(inverse, flipH)];
|
|
||||||
if (mirrorV) [projection, inverse] = [app(flipV, projection), app(inverse, flipV)];
|
|
||||||
|
|
||||||
changeCellsDensity(cellNumId);
|
changeCellsDensity(cellNumId);
|
||||||
startResample({
|
startResample({
|
||||||
|
|
@ -115,7 +209,7 @@ window.UISubmap = (function () {
|
||||||
const generateSubmap = debounce(function () {
|
const generateSubmap = debounce(function () {
|
||||||
WARN && console.warn("Resampling current map");
|
WARN && console.warn("Resampling current map");
|
||||||
closeDialogs("#worldConfigurator, #options3d");
|
closeDialogs("#worldConfigurator, #options3d");
|
||||||
const checked = id => Boolean(document.getElementById(id).checked);
|
const checked = id => Boolean(byId(id).checked);
|
||||||
|
|
||||||
// Create projection func from current zoom extents
|
// Create projection func from current zoom extents
|
||||||
const [[x0, y0], [x1, y1]] = getViewBoxExtent();
|
const [[x0, y0], [x1, y1]] = getViewBoxExtent();
|
||||||
|
|
@ -136,14 +230,14 @@ window.UISubmap = (function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
// converting map position on the planet
|
// converting map position on the planet
|
||||||
const mapSizeOutput = document.getElementById("mapSizeOutput");
|
const mapSizeOutput = byId("mapSizeOutput");
|
||||||
const latitudeOutput = document.getElementById("latitudeOutput");
|
const latitudeOutput = byId("latitudeOutput");
|
||||||
const latN = 90 - ((180 - (mapSizeInput.value / 100) * 180) * latitudeOutput.value) / 100;
|
const latN = 90 - ((180 - (mapSizeInput.value / 100) * 180) * latitudeOutput.value) / 100;
|
||||||
const newLatN = latN - ((y0 / graphHeight) * mapSizeOutput.value * 180) / 100;
|
const newLatN = latN - ((y0 / graphHeight) * mapSizeOutput.value * 180) / 100;
|
||||||
mapSizeOutput.value /= scale;
|
mapSizeOutput.value /= scale;
|
||||||
latitudeOutput.value = ((90 - newLatN) / (180 - (mapSizeOutput.value / 100) * 180)) * 100;
|
latitudeOutput.value = ((90 - newLatN) / (180 - (mapSizeOutput.value / 100) * 180)) * 100;
|
||||||
document.getElementById("mapSizeInput").value = mapSizeOutput.value;
|
byId("mapSizeInput").value = mapSizeOutput.value;
|
||||||
document.getElementById("latitudeInput").value = latitudeOutput.value;
|
byId("latitudeInput").value = latitudeOutput.value;
|
||||||
|
|
||||||
// fix scale
|
// fix scale
|
||||||
distanceScaleInput.value = distanceScaleOutput.value = rn((distanceScale = distanceScaleOutput.value / scale), 2);
|
distanceScaleInput.value = distanceScaleOutput.value = rn((distanceScale = distanceScaleOutput.value / scale), 2);
|
||||||
|
|
@ -188,7 +282,7 @@ window.UISubmap = (function () {
|
||||||
|
|
||||||
function changeStyles(scale) {
|
function changeStyles(scale) {
|
||||||
// resize burgIcons
|
// resize burgIcons
|
||||||
const burgIcons = [...document.getElementById("burgIcons").querySelectorAll("g")];
|
const burgIcons = [...byId("burgIcons").querySelectorAll("g")];
|
||||||
for (const bi of burgIcons) {
|
for (const bi of burgIcons) {
|
||||||
const newRadius = rn(minmax(bi.getAttribute("size") * scale, 0.2, 10), 2);
|
const newRadius = rn(minmax(bi.getAttribute("size") * scale, 0.2, 10), 2);
|
||||||
changeRadius(newRadius, bi.id);
|
changeRadius(newRadius, bi.id);
|
||||||
|
|
@ -197,7 +291,7 @@ window.UISubmap = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// burglabels
|
// burglabels
|
||||||
const burgLabels = [...document.getElementById("burgLabels").querySelectorAll("g")];
|
const burgLabels = [...byId("burgLabels").querySelectorAll("g")];
|
||||||
for (const bl of burgLabels) {
|
for (const bl of burgLabels) {
|
||||||
const size = +bl.dataset["size"];
|
const size = +bl.dataset["size"];
|
||||||
bl.dataset["size"] = Math.max(rn((size + size / scale) / 2, 2), 1) * scale;
|
bl.dataset["size"] = Math.max(rn((size + size / scale) / 2, 2), 1) * scale;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue