Merge pull request #744 from Azgaar/predefined-heightmaps
Predefined heightmaps
BIN
heightmaps/africa-centric.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
heightmaps/arabia.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
heightmaps/atlantics.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
heightmaps/britain.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
heightmaps/caribbean.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
heightmaps/east-asia.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
heightmaps/eurasia.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
heightmaps/europe-accented.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
heightmaps/europe-and-central-asia.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
heightmaps/europe-central.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
heightmaps/europe-north.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
heightmaps/europe.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
heightmaps/greenland.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
heightmaps/hellenica.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
heightmaps/iceland.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
8
heightmaps/import-rules.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
To get heightmap with correct height scale:
|
||||||
|
1. Open tangrams.github.io
|
||||||
|
2. Toggle off auto-exposure
|
||||||
|
3. Set max elevation to 2000
|
||||||
|
4. Set min elevation to -500
|
||||||
|
5. Find region you like
|
||||||
|
6. Render image
|
||||||
|
7. Optionally rescale image to a smaller size (e.g. 500x300px) as high resolution is not used
|
||||||
BIN
heightmaps/indian-ocean.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
heightmaps/mediterranean-sea.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
heightmaps/middle-east.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
heightmaps/north-america.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
heightmaps/us-centric.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
heightmaps/us-mainland.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
heightmaps/world-from-pacific.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
heightmaps/world.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
59
index.html
|
|
@ -162,14 +162,14 @@
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<pattern id="oceanic" width="100" height="100" patternUnits="userSpaceOnUse">
|
<pattern id="oceanic" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||||
<image id="oceanicPattern" href="./images/pattern1.png"></image>
|
<image id="oceanicPattern" href="./images/pattern1.png" opacity="0.2"></image>
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<g id="viewbox"></g>
|
<g id="viewbox"></g>
|
||||||
<g id="scaleBar"></g>
|
<g id="scaleBar"></g>
|
||||||
<g id="initial" opacity=1>
|
<g id="initial" opacity=1>
|
||||||
<rect x="-1%" y="-1%" width="102%" height="102%" fill="#466eab"/>
|
<rect x="-1%" y="-1%" width="102%" height="102%" fill="#466eab"/>
|
||||||
<rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)" opacity=".2"/>
|
<rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)"/>
|
||||||
<use href="#rose" id="init-rose" x="50%" y="50%"/>
|
<use href="#rose" id="init-rose" x="50%" y="50%"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -939,25 +939,52 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr data-tip="Select template to be used for a Heightmap generation">
|
<tr data-tip="Select heightmap generation template or precreated heightmap">
|
||||||
<td>
|
<td>
|
||||||
<i data-locked=0 id="lock_template" class="icon-lock-open"></i>
|
<i data-locked=0 id="lock_template" class="icon-lock-open"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>Map template</td>
|
<td>Heightmap</td>
|
||||||
<td>
|
<td>
|
||||||
<select id="templateInput" data-stored="template">
|
<select id="templateInput" data-stored="template">
|
||||||
<option value="volcano">Volcano</option>
|
<optgroup label="Template">
|
||||||
<option value="highIsland">High Island</option>
|
<option value="volcano">Volcano</option>
|
||||||
<option value="lowIsland">Low Island</option>
|
<option value="highIsland">High Island</option>
|
||||||
<option value="continents">Two Continents</option>
|
<option value="lowIsland">Low Island</option>
|
||||||
<option value="archipelago">Archipelago</option>
|
<option value="continents">Two Continents</option>
|
||||||
<option value="atoll">Atoll</option>
|
<option value="archipelago">Archipelago</option>
|
||||||
<option value="mediterranean">Mediterranean</option>
|
<option value="atoll">Atoll</option>
|
||||||
<option value="peninsula">Peninsula</option>
|
<option value="mediterranean">Mediterranean</option>
|
||||||
<option value="pangea">Pangea</option>
|
<option value="peninsula">Peninsula</option>
|
||||||
<option value="isthmus">Isthmus</option>
|
<option value="pangea">Pangea</option>
|
||||||
<option value="shattered">Shattered</option>
|
<option value="isthmus">Isthmus</option>
|
||||||
<option value="taklamakan">Taklamakan</option>
|
<option value="shattered">Shattered</option>
|
||||||
|
<option value="taklamakan">Taklamakan</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Specific">
|
||||||
|
<option value="africa-centric">Africa Centric</option>
|
||||||
|
<option value="arabia">Arabia</option>
|
||||||
|
<option value="atlantics">Atlantics</option>
|
||||||
|
<option value="britain">Britain</option>
|
||||||
|
<option value="caribbean">Caribbean</option>
|
||||||
|
<option value="east-asia">East Asia</option>
|
||||||
|
<option value="eurasia">Eurasia</option>
|
||||||
|
<option value="europe">Europe</option>
|
||||||
|
<option value="europe-accented">Europe Accented</option>
|
||||||
|
<option value="europe-and-central-asia">Europe and Central Asia</option>
|
||||||
|
<option value="europe-central">Europe Central</option>
|
||||||
|
<option value="europe-north">Europe North</option>
|
||||||
|
<option value="greenland">Greenland</option>
|
||||||
|
<option value="hellenica">Hellenica</option>
|
||||||
|
<option value="iceland">Iceland</option>
|
||||||
|
<option value="indian-ocean">Indian Ocean</option>
|
||||||
|
<option value="mediterranean-sea">Mediterranean Sea</option>
|
||||||
|
<option value="middle-east">Middle East</option>
|
||||||
|
<option value="north-america">North America</option>
|
||||||
|
<option value="us-centric">US-centric</option>
|
||||||
|
<option value="us-mainland">US Mainland</option>
|
||||||
|
<option value="world">World</option>
|
||||||
|
<option value="world-from-pacific">World from Pacific</option>
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|
|
||||||
57
main.js
|
|
@ -2,7 +2,7 @@
|
||||||
// https://github.com/Azgaar/Fantasy-Map-Generator
|
// https://github.com/Azgaar/Fantasy-Map-Generator
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
const version = "1.731"; // generator version
|
const version = "1.732"; // generator version
|
||||||
document.title += " v" + version;
|
document.title += " v" + version;
|
||||||
|
|
||||||
// switches to disable/enable logging features
|
// switches to disable/enable logging features
|
||||||
|
|
@ -192,15 +192,24 @@ if (!location.hostname) {
|
||||||
d3.select("#loading-text").transition().duration(1000).style("opacity", 0);
|
d3.select("#loading-text").transition().duration(1000).style("opacity", 0);
|
||||||
d3.select("#init-rose").transition().duration(4000).style("opacity", 0);
|
d3.select("#init-rose").transition().duration(4000).style("opacity", 0);
|
||||||
} else {
|
} else {
|
||||||
|
hideLoading();
|
||||||
checkLoadParameters();
|
checkLoadParameters();
|
||||||
|
}
|
||||||
|
|
||||||
// remove loading screen
|
function hideLoading() {
|
||||||
d3.select("#loading").transition().duration(4000).style("opacity", 0).remove();
|
d3.select("#loading").transition().duration(4000).style("opacity", 0);
|
||||||
d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove();
|
d3.select("#initial").transition().duration(4000).attr("opacity", 0);
|
||||||
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
|
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
|
||||||
d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
|
d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
d3.select("#loading").transition().duration(200).style("opacity", 1);
|
||||||
|
d3.select("#initial").transition().duration(200).attr("opacity", 1);
|
||||||
|
d3.select("#optionsContainer").transition().duration(100).style("opacity", 0);
|
||||||
|
d3.select("#tooltip").transition().duration(200).style("opacity", 0);
|
||||||
|
}
|
||||||
|
|
||||||
// decide which map should be loaded or generated on page load
|
// decide which map should be loaded or generated on page load
|
||||||
function checkLoadParameters() {
|
function checkLoadParameters() {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
|
|
@ -253,7 +262,7 @@ function checkLoadParameters() {
|
||||||
|
|
||||||
async function generateMapOnLoad() {
|
async function generateMapOnLoad() {
|
||||||
await applyStyleOnLoad(); // apply previously selected default or custom style
|
await applyStyleOnLoad(); // apply previously selected default or custom style
|
||||||
generate(); // generate map
|
await generate(); // generate map
|
||||||
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
||||||
applyPreset(); // apply saved layers preset
|
applyPreset(); // apply saved layers preset
|
||||||
}
|
}
|
||||||
|
|
@ -437,6 +446,7 @@ function showWelcomeMessage() {
|
||||||
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <strong>${version}</strong>.
|
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <strong>${version}</strong>.
|
||||||
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
|
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
|
||||||
<ul><strong>Latest changes:</strong>
|
<ul><strong>Latest changes:</strong>
|
||||||
|
<li>Pre-defined heightmaps</li>
|
||||||
<li>Advanced notes editor</li>
|
<li>Advanced notes editor</li>
|
||||||
<li>Zones editor: filter by type</li>
|
<li>Zones editor: filter by type</li>
|
||||||
<li>Color picker: new hatchings</li>
|
<li>Color picker: new hatchings</li>
|
||||||
|
|
@ -632,7 +642,7 @@ void (function addDragToUpload() {
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function generate() {
|
async function generate() {
|
||||||
try {
|
try {
|
||||||
const timeStart = performance.now();
|
const timeStart = performance.now();
|
||||||
invokeActiveZooming();
|
invokeActiveZooming();
|
||||||
|
|
@ -643,7 +653,7 @@ function generate() {
|
||||||
placePoints();
|
placePoints();
|
||||||
calculateVoronoi(grid, grid.points);
|
calculateVoronoi(grid, grid.points);
|
||||||
drawScaleBar();
|
drawScaleBar();
|
||||||
HeightmapGenerator.generate();
|
await HeightmapGenerator.generate();
|
||||||
markFeatures();
|
markFeatures();
|
||||||
markupGridOcean();
|
markupGridOcean();
|
||||||
addLakesInDeepDepressions();
|
addLakesInDeepDepressions();
|
||||||
|
|
@ -929,6 +939,31 @@ function defineMapSize() {
|
||||||
|
|
||||||
function getSizeAndLatitude() {
|
function getSizeAndLatitude() {
|
||||||
const template = document.getElementById("templateInput").value; // heightmap template
|
const template = document.getElementById("templateInput").value; // heightmap template
|
||||||
|
|
||||||
|
if (template === "africa-centric") return [45, 53];
|
||||||
|
if (template === "arabia") return [20, 35];
|
||||||
|
if (template === "atlantics") return [42, 23];
|
||||||
|
if (template === "britain") return [7, 20];
|
||||||
|
if (template === "caribbean") return [15, 40];
|
||||||
|
if (template === "east-asia") return [11, 28];
|
||||||
|
if (template === "eurasia") return [38, 19];
|
||||||
|
if (template === "europe") return [20, 16];
|
||||||
|
if (template === "europe-accented") return [14, 22];
|
||||||
|
if (template === "europe-and-central-asia") return [25, 10];
|
||||||
|
if (template === "europe-central") return [11, 22];
|
||||||
|
if (template === "europe-north") return [7, 18];
|
||||||
|
if (template === "greenland") return [22, 7];
|
||||||
|
if (template === "hellenica") return [8, 27];
|
||||||
|
if (template === "iceland") return [2, 15];
|
||||||
|
if (template === "indian-ocean") return [45, 55];
|
||||||
|
if (template === "mediterranean-sea") return [10, 29];
|
||||||
|
if (template === "middle-east") return [8, 31];
|
||||||
|
if (template === "north-america") return [37, 17];
|
||||||
|
if (template === "us-centric") return [66, 27];
|
||||||
|
if (template === "us-mainland") return [16, 30];
|
||||||
|
if (template === "world") return [78, 27];
|
||||||
|
if (template === "world-from-pacific") return [75, 32];
|
||||||
|
|
||||||
const part = grid.features.some(f => f.land && f.border); // if land goes over map borders
|
const part = grid.features.some(f => f.land && f.border); // if land goes over map borders
|
||||||
const max = part ? 80 : 100; // max size
|
const max = part ? 80 : 100; // max size
|
||||||
const lat = () => gauss(P(0.5) ? 40 : 60, 15, 25, 75); // latitude shift
|
const lat = () => gauss(P(0.5) ? 40 : 60, 15, 25, 75); // latitude shift
|
||||||
|
|
@ -1896,16 +1931,18 @@ function showStatistics() {
|
||||||
INFO && console.log(stats);
|
INFO && console.log(stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
const regenerateMap = debounce(function () {
|
const regenerateMap = debounce(async function () {
|
||||||
WARN && console.warn("Generate new random map");
|
WARN && console.warn("Generate new random map");
|
||||||
|
showLoading();
|
||||||
closeDialogs("#worldConfigurator, #options3d");
|
closeDialogs("#worldConfigurator, #options3d");
|
||||||
customization = 0;
|
customization = 0;
|
||||||
undraw();
|
|
||||||
resetZoom(1000);
|
resetZoom(1000);
|
||||||
generate();
|
undraw();
|
||||||
|
await generate();
|
||||||
restoreLayers();
|
restoreLayers();
|
||||||
if (ThreeD.options.isOn) ThreeD.redraw();
|
if (ThreeD.options.isOn) ThreeD.redraw();
|
||||||
if ($("#worldConfigurator").is(":visible")) editWorld();
|
if ($("#worldConfigurator").is(":visible")) editWorld();
|
||||||
|
hideLoading();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// clear the map
|
// clear the map
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,43 @@
|
||||||
window.HeightmapGenerator = (function () {
|
window.HeightmapGenerator = (function () {
|
||||||
let cells, p;
|
let cells, p;
|
||||||
|
|
||||||
const generate = function () {
|
const generate = async function () {
|
||||||
TIME && console.time("generateHeightmap");
|
|
||||||
cells = grid.cells;
|
cells = grid.cells;
|
||||||
p = grid.points;
|
p = grid.points;
|
||||||
cells.h = new Uint8Array(grid.points.length);
|
cells.h = new Uint8Array(grid.points.length);
|
||||||
|
|
||||||
const template = document.getElementById("templateInput").value;
|
const input = document.getElementById("templateInput");
|
||||||
|
const type = input.querySelector(`[value=${input.value}]`).parentElement.label;
|
||||||
|
|
||||||
|
if (type === "Specific") {
|
||||||
|
// pre-defined heightmap
|
||||||
|
TIME && console.time("defineHeightmap");
|
||||||
|
return new Promise(resolve => {
|
||||||
|
// create canvas where 1px correcponds to a cell
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const {cellsX, cellsY} = grid;
|
||||||
|
canvas.width = cellsX;
|
||||||
|
canvas.height = cellsY;
|
||||||
|
|
||||||
|
// load heightmap into image and render to canvas
|
||||||
|
const img = new Image();
|
||||||
|
img.src = `./heightmaps/${input.value}.png`;
|
||||||
|
img.onload = function () {
|
||||||
|
ctx.drawImage(img, 0, 0, cellsX, cellsY);
|
||||||
|
const imageData = ctx.getImageData(0, 0, cellsX, cellsY);
|
||||||
|
assignColorsToHeight(imageData.data);
|
||||||
|
canvas.remove();
|
||||||
|
img.remove();
|
||||||
|
TIME && console.timeEnd("defineHeightmap");
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// heightmap template
|
||||||
|
TIME && console.time("generateHeightmap");
|
||||||
|
const template = input.value;
|
||||||
const templateString = HeightmapTemplates[template];
|
const templateString = HeightmapTemplates[template];
|
||||||
const steps = templateString.split("\n");
|
const steps = templateString.split("\n");
|
||||||
|
|
||||||
|
|
@ -79,8 +109,8 @@ window.HeightmapGenerator = (function () {
|
||||||
|
|
||||||
function addOneHill() {
|
function addOneHill() {
|
||||||
const change = new Uint8Array(cells.h.length);
|
const change = new Uint8Array(cells.h.length);
|
||||||
let limit = 0,
|
let limit = 0;
|
||||||
start;
|
let start;
|
||||||
let h = lim(getNumberInRange(height));
|
let h = lim(getNumberInRange(height));
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
@ -410,5 +440,13 @@ window.HeightmapGenerator = (function () {
|
||||||
return rand(min * length, max * length);
|
return rand(min * length, max * length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assignColorsToHeight(imageData) {
|
||||||
|
for (let i = 0; i < cells.i.length; i++) {
|
||||||
|
const lightness = imageData[i * 4] / 255;
|
||||||
|
const powered = lightness < 0.2 ? lightness : 0.2 + (lightness - 0.2) ** 0.8;
|
||||||
|
cells.h[i] = minmax(Math.floor(powered * 100), 0, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify};
|
return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify};
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -913,11 +913,9 @@ function editHeightmap() {
|
||||||
|
|
||||||
function uploadTemplate(dataLoaded) {
|
function uploadTemplate(dataLoaded) {
|
||||||
const steps = dataLoaded.split("\r\n");
|
const steps = dataLoaded.split("\r\n");
|
||||||
if (!steps.length) {
|
if (!steps.length) return tip("Cannot parse the template, please check the file", false, "error");
|
||||||
tip("Cannot parse the template, please check the file", false, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
templateBody.innerHTML = "";
|
templateBody.innerHTML = "";
|
||||||
|
|
||||||
for (const s of steps) {
|
for (const s of steps) {
|
||||||
const step = s.split(" ");
|
const step = s.split(" ");
|
||||||
if (step.length !== 5) {
|
if (step.length !== 5) {
|
||||||
|
|
|
||||||
|
|
@ -306,10 +306,8 @@ function showSeedHistoryDialog() {
|
||||||
|
|
||||||
// generate map with historical seed
|
// generate map with historical seed
|
||||||
function restoreSeed(id) {
|
function restoreSeed(id) {
|
||||||
if (mapHistory[id].seed == seed) {
|
if (mapHistory[id].seed == seed) return tip("The current map is already generated with this seed", null, "error");
|
||||||
tip("The current map is already generated with this seed", null, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
optionsSeed.value = mapHistory[id].seed;
|
optionsSeed.value = mapHistory[id].seed;
|
||||||
mapWidthInput.value = mapHistory[id].width;
|
mapWidthInput.value = mapHistory[id].width;
|
||||||
mapHeightInput.value = mapHistory[id].height;
|
mapHeightInput.value = mapHistory[id].height;
|
||||||
|
|
@ -547,7 +545,7 @@ function randomizeOptions() {
|
||||||
|
|
||||||
// 'Options' settings
|
// 'Options' settings
|
||||||
if (randomize || !locked("template")) randomizeHeightmapTemplate();
|
if (randomize || !locked("template")) randomizeHeightmapTemplate();
|
||||||
if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(15, 3, 2, 30);
|
if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(18, 5, 2, 30);
|
||||||
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100);
|
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100);
|
||||||
if (randomize || !locked("manors")) {
|
if (randomize || !locked("manors")) {
|
||||||
manorsInput.value = 1000;
|
manorsInput.value = 1000;
|
||||||
|
|
|
||||||