mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 03:51:23 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into urquhart-routes
This commit is contained in:
commit
c4370774e4
27 changed files with 423 additions and 294 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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("|");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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>`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue