Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into urquhart-routes

This commit is contained in:
Azgaar 2024-07-29 15:40:28 +02:00
commit c4370774e4
27 changed files with 423 additions and 294 deletions

View file

@ -848,10 +848,23 @@ export function resolveVersionConflicts(version) {
}
if (version < 1.98) {
// v1.98.00 changed routes generation algorithm and data format
// v1.98.00 changed compass layer and rose element id
const rose = compass.select("use");
rose.attr("xlink:href", "#defs-compass-rose");
if (!compass.selectAll("*").size()) {
compass.style("display", "none");
compass.append("use").attr("xlink:href", "#defs-compass-rose");
shiftCompass();
}
}
if (version < 1.99) {
// v1.99.00 changed routes generation algorithm and data format
// 1. cells.road => cells.routes and now it an object of objects {i1: {i2: routeId, i3: routeId}}
// 2. cells.crossroad is removed
// 3. pack.routes is added as an array of objects
// 4. rendering is changed
// v1.98.00 changed compass layer and rose element id
}
}

View file

@ -94,7 +94,8 @@ function getSettings() {
populationRate: populationRate,
urbanization: urbanization,
mapSize: mapSizeOutput.value,
latitudeO: latitudeOutput.value,
latitude: latitudeOutput.value,
longitude: longitudeOutput.value,
prec: precOutput.value,
options: options,
mapName: mapName.value,

View file

@ -87,7 +87,7 @@ async function exportToPngTiles() {
imgSchema.src = urlSchema;
await loadImage(imgSchema);
status.innerHTML = "Drawing schema...";
status.innerHTML = "Rendering schema...";
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
const blob = await canvasToBlob(canvas, "image/png");
ctx.clearRect(0, 0, canvas.width, canvas.height);
@ -95,9 +95,9 @@ async function exportToPngTiles() {
// download tiles
const url = await getMapURL("tiles", {fullMap: true});
const tilesX = +byId("tileColsInput").value;
const tilesY = +byId("tileRowsInput").value;
const scale = +byId("tileScaleInput").value;
const tilesX = +byId("tileColsOutput").value || 2;
const tilesY = +byId("tileRowsOutput").value || 2;
const scale = +byId("tileScaleOutput").value || 1;
const tolesTotal = tilesX * tilesY;
const tileW = (graphWidth / tilesX) | 0;
@ -113,11 +113,17 @@ async function exportToPngTiles() {
await loadImage(img);
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
function getRowLabel(row) {
const first = row >= alphabet.length ? alphabet[Math.floor(row / alphabet.length) - 1] : "";
const last = alphabet[row % alphabet.length];
return first + last;
}
for (let y = 0, row = 0, id = 1; y + tileH <= graphHeight; y += tileH, row++) {
const rowName = alphabet[row % alphabet.length];
const rowName = getRowLabel(row);
for (let x = 0, cell = 1; x + tileW <= graphWidth; x += tileW, cell++, id++) {
status.innerHTML = `Drawing tile ${rowName}${cell} (${id} of ${tolesTotal})...`;
status.innerHTML = `Rendering tile ${rowName}${cell} (${id} of ${tolesTotal})...`;
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
const blob = await canvasToBlob(canvas, "image/png");
ctx.clearRect(0, 0, canvas.width, canvas.height);
@ -295,7 +301,7 @@ async function getMapURL(type, options) {
// add wind rose
if (cloneEl.getElementById("compass")) {
const rose = svgDefs.getElementById("rose");
const rose = svgDefs.getElementById("defs-compass-rose");
if (rose) cloneDefs.appendChild(rose.cloneNode(true));
}

View file

@ -242,6 +242,7 @@ async function parseLoadedData(data, mapVersion) {
if (settings[22]) stylePreset.value = settings[22];
if (settings[23]) rescaleLabels.checked = +settings[23];
if (settings[24]) urbanDensity = urbanDensityInput.value = urbanDensityOutput.value = +settings[24];
if (settings[25]) longitudeInput.value = longitudeOutput.value = minmax(settings[25] || 50, 0, 100);
})();
void (function applyOptionsToUI() {
@ -458,7 +459,7 @@ async function parseLoadedData(data, mapVersion) {
{
// dynamically import and run auto-update script
const versionNumber = parseFloat(params[0]);
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.97.04");
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.98.00");
resolveVersionConflicts(versionNumber);
}

View file

@ -67,7 +67,8 @@ function prepareMapData() {
+hideLabels.checked,
stylePreset.value,
+rescaleLabels.checked,
urbanDensity
urbanDensity,
longitudeOutput.value
].join("|");
const coords = JSON.stringify(mapCoordinates);
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");

View file

@ -290,7 +290,7 @@ function toDMS(coord, c) {
const minutes = Math.floor(minutesNotTruncated);
const seconds = Math.floor((minutesNotTruncated - minutes) * 60);
const cardinal = c === "lat" ? (coord >= 0 ? "N" : "S") : coord >= 0 ? "E" : "W";
return degrees + " + minutes + " " + seconds + "″ " + cardinal;
return degrees + " + minutes + "" + seconds + "″" + cardinal;
}
// get surface elevation

View file

@ -1070,27 +1070,29 @@ function drawStates() {
const bodyData = body.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
const gapData = gap.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
const haloData = halo.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
const bodyString = bodyData.map(d => `<path id="state${d[1]}" d="${d[0]}" fill="${d[2]}" stroke="none"/>`).join("");
const gapString = gapData.map(d => `<path id="state-gap${d[1]}" d="${d[0]}" fill="none" stroke="${d[2]}"/>`).join("");
const clipString = bodyData
.map(d => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`)
.join("");
const haloString = haloData
.map(
d =>
`<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${
d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666"
}"/>`
)
.join("");
statesBody.html(bodyString + gapString);
defs.select("#statePaths").html(clipString);
statesHalo.html(haloString);
// connect vertices to chain
const isOptimized = shapeRendering.value === "optimizeSpeed";
if (!isOptimized) {
const haloData = halo.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
const haloString = haloData
.map(d => {
const stroke = d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666";
return `<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${stroke}"/>`;
})
.join("");
statesHalo.html(haloString);
const clipString = bodyData
.map(d => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`)
.join("");
defs.select("#statePaths").html(clipString);
}
function connectVertices(start, state) {
const chain = []; // vertices chain to form a path
const getType = c => {
@ -1525,10 +1527,6 @@ function toggleCompass(event) {
if (!layerIsOn("toggleCompass")) {
turnButtonOn("toggleCompass");
$("#compass").fadeIn();
if (!compass.selectAll("*").size()) {
compass.append("use").attr("xlink:href", "#rose");
shiftCompass();
}
if (event && isCtrlClick(event)) editStyle("compass");
} else {
if (event && isCtrlClick(event)) {

View file

@ -644,17 +644,16 @@ function randomizeCultureSet() {
function setRendering(value) {
viewbox.attr("shape-rendering", value);
// if (value === "optimizeSpeed") {
// // block some styles
// coastline.select("#sea_island").style("filter", "none");
// statesHalo.style("display", "none");
// emblems.style("opacity", 1);
// } else {
// // remove style block
// coastline.select("#sea_island").style("filter", null);
// statesHalo.style("display", null);
// emblems.style("opacity", null);
// }
if (value === "optimizeSpeed") {
// block some styles
coastline.select("#sea_island").style("filter", "none");
statesHalo.style("display", "none");
} else {
// remove style block
coastline.select("#sea_island").style("filter", null);
statesHalo.style("display", null);
if (pack.cells && statesHalo.selectAll("*").size() === 0) drawStates();
}
}
// generate current year and era name
@ -902,9 +901,9 @@ function updateTilesOptions() {
}
const tileSize = byId("tileSize");
const tilesX = +byId("tileColsOutput").value;
const tilesY = +byId("tileRowsOutput").value;
const scale = +byId("tileScaleOutput").value;
const tilesX = +byId("tileColsOutput").value || 2;
const tilesY = +byId("tileRowsOutput").value || 2;
const scale = +byId("tileScaleOutput").value || 1;
// calculate size
const sizeX = graphWidth * scale * tilesX;
@ -921,11 +920,16 @@ function updateTilesOptions() {
const tileH = (graphHeight / tilesY) | 0;
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
function getRowLabel(row) {
const first = row >= alphabet.length ? alphabet[Math.floor(row / alphabet.length) - 1] : "";
const last = alphabet[row % alphabet.length];
return first + last;
}
for (let y = 0, row = 0; y + tileH <= graphHeight; y += tileH, row++) {
for (let x = 0, column = 1; x + tileW <= graphWidth; x += tileW, column++) {
rects.push(`<rect x=${x} y=${y} width=${tileW} height=${tileH} />`);
const label = alphabet[row % alphabet.length] + column;
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${label}</text>`);
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${getRowLabel(row)}${column}</text>`);
}
}

View file

@ -63,7 +63,7 @@ async function getStylePreset(desiredPreset) {
async function fetchSystemPreset(preset) {
try {
const res = await fetch(`./styles/${preset}.json`);
const res = await fetch(`./styles/${preset}.json?v=${version}`);
return await res.json();
} catch (err) {
throw new Error("Cannot fetch style preset", preset);
@ -198,7 +198,7 @@ function addStylePreset() {
"mask"
],
"#compass": ["opacity", "transform", "filter", "mask", "shape-rendering"],
"#rose": ["transform"],
"#compass > use": ["transform"],
"#relig": ["opacity", "stroke", "stroke-width", "filter"],
"#cults": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#landmass": ["opacity", "fill", "filter"],

View file

@ -5,18 +5,17 @@ function editWorld() {
title: "Configure World",
resizable: false,
width: "minmax(40em, 85vw)",
buttons: {
"Whole World": () => applyWorldPreset(100, 50),
Northern: () => applyWorldPreset(33, 25),
Tropical: () => applyWorldPreset(33, 50),
Southern: () => applyWorldPreset(33, 75)
},
buttons: {"Update world": updateWorld},
open: function () {
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
buttons[0].addEventListener("mousemove", () => tip("Click to set map size to cover the whole World"));
buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes"));
buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes"));
buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes"));
const checkbox = /* html */ `<div class="dontAsk" data-tip="Automatically update world on input changes and button clicks">
<input id="wcAutoChange" class="checkbox" type="checkbox" checked />
<label for="wcAutoChange" class="checkbox-label"><i>auto-apply changes</i></label>
</div>`;
const pane = this.parentElement.querySelector(".ui-dialog-buttonpane");
pane.insertAdjacentHTML("afterbegin", checkbox);
const button = this.parentElement.querySelector(".ui-dialog-buttonset > button");
button.on("mousemove", () => tip("Apply curreny settings to the map"));
},
close: function () {
$(this).dialog("destroy");
@ -34,12 +33,17 @@ function editWorld() {
if (modules.editWorld) return;
modules.editWorld = true;
byId("worldControls").addEventListener("input", e => updateWorld(e.target));
globe.select("#globeWindArrows").on("click", changeWind);
globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule
const graticule = d3.geoGraticule();
globe.select("#globeWindArrows").on("click", handleWindChange);
globe.select("#globeGraticule").attr("d", round(path(graticule()))); // globe graticule
updateWindDirections();
byId("restoreWinds").addEventListener("click", restoreDefaultWinds);
byId("worldControls").on("input", handleControlsChange);
byId("restoreWinds").on("click", restoreDefaultWinds);
byId("wcWholeWorld").on("click", () => applyWorldPreset(100, 50));
byId("wcNorthern").on("click", () => applyWorldPreset(33, 25));
byId("wcTropical").on("click", () => applyWorldPreset(33, 50));
byId("wcSouthern").on("click", () => applyWorldPreset(33, 75));
function updateInputValues() {
byId("temperatureEquatorInput").value = options.temperatureEquator;
@ -55,27 +59,27 @@ function editWorld() {
byId("temperatureSouthPoleF").innerText = convertTemperature(options.temperatureSouthPole, "°F");
}
function updateWorld(el) {
if (el?.dataset.stored) {
const stored = el.dataset.stored;
byId(stored + "Input").value = el.value;
byId(stored + "Output").value = el.value;
lock(el.dataset.stored);
function handleControlsChange({target}) {
const stored = target.dataset.stored;
byId(stored + "Input").value = target.value;
byId(stored + "Output").value = target.value;
lock(stored);
if (stored === "temperatureEquator") {
options.temperatureEquator = Number(el.value);
byId("temperatureEquatorF").innerText = convertTemperature(options.temperatureEquator, "°F");
}
if (stored === "temperatureNorthPole") {
options.temperatureNorthPole = Number(el.value);
byId("temperatureNorthPoleF").innerText = convertTemperature(options.temperatureNorthPole, "°F");
}
if (stored === "temperatureSouthPole") {
options.temperatureSouthPole = Number(el.value);
byId("temperatureSouthPoleF").innerText = convertTemperature(options.temperatureSouthPole, "°F");
}
if (stored === "temperatureEquator") {
options.temperatureEquator = Number(target.value);
byId("temperatureEquatorF").innerText = convertTemperature(options.temperatureEquator, "°F");
} else if (stored === "temperatureNorthPole") {
options.temperatureNorthPole = Number(target.value);
byId("temperatureNorthPoleF").innerText = convertTemperature(options.temperatureNorthPole, "°F");
} else if (stored === "temperatureSouthPole") {
options.temperatureSouthPole = Number(target.value);
byId("temperatureSouthPoleF").innerText = convertTemperature(options.temperatureSouthPole, "°F");
}
if (byId("wcAutoChange").checked) updateWorld();
}
function updateWorld() {
updateGlobeTemperature();
updateGlobePosition();
calculateTemperatures();
@ -130,6 +134,7 @@ function editWorld() {
[mc.lonW, mc.latN],
[mc.lonE, mc.latS]
]);
globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area
}
@ -163,21 +168,22 @@ function editWorld() {
});
}
function changeWind() {
function handleWindChange() {
const arrow = d3.event.target.nextElementSibling;
const tier = +arrow.dataset.tier;
options.winds[tier] = (options.winds[tier] + 45) % 360;
const tr = parseTransform(arrow.getAttribute("transform"));
arrow.setAttribute("transform", `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
localStorage.setItem("winds", options.winds);
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => ((90 - c) / 30) | 0);
if (mapTiers.includes(tier)) updateWorld();
if (byId("wcAutoChange").checked && mapTiers.includes(tier)) updateWorld();
}
function restoreDefaultWinds() {
const defaultWinds = [225, 45, 225, 315, 135, 315];
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => ((90 - c) / 30) | 0);
const update = mapTiers.some(t => options.winds[t] != defaultWinds[t]);
const update = byId("wcAutoChange").checked && mapTiers.some(t => options.winds[t] != defaultWinds[t]);
options.winds = defaultWinds;
updateWindDirections();
if (update) updateWorld();
@ -188,6 +194,6 @@ function editWorld() {
byId("latitudeInput").value = byId("latitudeOutput").value = lat;
lock("mapSize");
lock("latitude");
updateWorld();
if (byId("wcAutoChange").checked) updateWorld();
}
}