Fantasy-Map-Generator/modules/ui/transform-tool.js

204 lines
5.9 KiB
JavaScript

"use strict";
async function openTransformTool() {
const width = Math.min(400, window.innerWidth * 0.5);
const previewScale = width / graphWidth;
const height = graphHeight * previewScale;
let mouseIsDown = false;
let mouseX = 0;
let mouseY = 0;
resetInputs();
loadPreview();
$("#transformTool").dialog({
title: "Transform map",
resizable: false,
position: {my: "center", at: "center", of: "svg"},
buttons: {
Transform: function () {
closeDialogs();
transformMap();
},
Cancel: function () {
$(this).dialog("close");
}
}
});
if (modules.openTransformTool) return;
modules.openTransformTool = true;
// add listeners
byId("transformToolBody").on("input", handleInput);
byId("transformPreview")
.on("mousedown", handleMousedown)
.on("mouseup", _ => (mouseIsDown = false))
.on("mousemove", handleMousemove)
.on("wheel", handleWheel);
async function loadPreview() {
byId("transformPreview").style.width = width + "px";
byId("transformPreview").style.height = height + "px";
const options = {noWater: true, fullMap: true, noLabels: true, noScaleBar: true, noVignette: true, noIce: true};
const url = await getMapURL("png", options);
const SCALE = 4;
const img = new Image();
img.src = url;
img.onload = function () {
const $canvas = byId("transformPreviewCanvas");
$canvas.style.width = width + "px";
$canvas.style.height = height + "px";
$canvas.width = width * SCALE;
$canvas.height = height * SCALE;
$canvas.getContext("2d").drawImage(img, 0, 0, width * SCALE, height * SCALE);
};
}
function resetInputs() {
byId("transformAngleInput").value = 0;
byId("transformAngleOutput").value = "0";
byId("transformMirrorH").checked = false;
byId("transformMirrorV").checked = false;
byId("transformScaleInput").value = 0;
byId("transformScaleResult").value = 1;
byId("transformShiftX").value = 0;
byId("transformShiftY").value = 0;
handleInput();
updateCellsNumber(byId("pointsInput").value);
byId("transformPointsInput").oninput = e => updateCellsNumber(e.target.value);
function updateCellsNumber(value) {
byId("transformPointsInput").value = value;
const cells = cellsDensityMap[value];
byId("transformPointsInput").dataset.cells = cells;
const output = byId("transformPointsFormatted");
output.value = cells / 1000 + "K";
output.style.color = getCellsDensityColor(cells);
}
}
function handleInput() {
const angle = (+byId("transformAngleInput").value / 180) * Math.PI;
const shiftX = +byId("transformShiftX").value;
const shiftY = +byId("transformShiftY").value;
const mirrorH = byId("transformMirrorH").checked;
const mirrorV = byId("transformMirrorV").checked;
const EXP = 1.0965;
const scale = rn(EXP ** +byId("transformScaleInput").value, 2); // [0.1, 10]x
byId("transformScaleResult").value = scale;
byId("transformPreviewCanvas").style.transform = `
translate(${shiftX * previewScale}px, ${shiftY * previewScale}px)
scale(${mirrorH ? -scale : scale}, ${mirrorV ? -scale : scale})
rotate(${angle}rad)
`;
}
function handleMousedown(e) {
mouseIsDown = true;
const shiftX = +byId("transformShiftX").value;
const shiftY = +byId("transformShiftY").value;
mouseX = shiftX - e.clientX / previewScale;
mouseY = shiftY - e.clientY / previewScale;
}
function handleMousemove(e) {
if (!mouseIsDown) return;
e.preventDefault();
byId("transformShiftX").value = Math.round(mouseX + e.clientX / previewScale);
byId("transformShiftY").value = Math.round(mouseY + e.clientY / previewScale);
handleInput();
}
function handleWheel(e) {
const $scaleInput = byId("transformScaleInput");
$scaleInput.value = $scaleInput.valueAsNumber - Math.sign(e.deltaY);
handleInput();
}
function transformMap() {
INFO && console.group("transformMap");
const transformPointsValue = byId("transformPointsInput").value;
const globalPointsValue = byId("pointsInput").value;
if (transformPointsValue !== globalPointsValue) changeCellsDensity(transformPointsValue);
const [projection, inverse] = getProjection();
applyGraphSize();
fitMapToScreen();
resetZoom(0);
undraw();
Resample.process({projection, inverse, scale: 1});
drawLayers();
INFO && console.groupEnd("transformMap");
}
function getProjection() {
const centerX = graphWidth / 2;
const centerY = graphHeight / 2;
const shiftX = +byId("transformShiftX").value;
const shiftY = +byId("transformShiftY").value;
const angle = (+byId("transformAngleInput").value / 180) * Math.PI;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const scale = +byId("transformScaleResult").value;
const mirrorH = byId("transformMirrorH").checked;
const mirrorV = byId("transformMirrorV").checked;
function project(x, y) {
// center the point
x -= centerX;
y -= centerY;
// apply scale
if (scale !== 1) {
x *= scale;
y *= scale;
}
// apply rotation
if (angle) [x, y] = [x * cos - y * sin, x * sin + y * cos];
// apply mirroring
if (mirrorH) x = -x;
if (mirrorV) y = -y;
// uncenter the point and apply shift
return [x + centerX + shiftX, y + centerY + shiftY];
}
function inverse(x, y) {
// undo shift and center the point
x -= centerX + shiftX;
y -= centerY + shiftY;
// undo mirroring
if (mirrorV) y = -y;
if (mirrorH) x = -x;
// undo rotation
if (angle !== 0) [x, y] = [x * cos + y * sin, -x * sin + y * cos];
// undo scale
if (scale !== 1) {
x /= scale;
y /= scale;
}
// uncenter the point
return [x + centerX, y + centerY];
}
return [project, inverse];
}
}