mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-23 12:31:24 +01:00
Merge branch 'master' into dev-submaps
This commit is contained in:
commit
0a7218db99
24 changed files with 904 additions and 515 deletions
16
README.md
16
README.md
|
|
@ -1,10 +1,10 @@
|
||||||
# Fantasy Map Generator
|
# Fantasy Map Generator
|
||||||
|
|
||||||
Azgaar's _Fantasy Map Generator_ is a free client-side web application generating interactive and highly customizable svg maps based on voronoi diagram.
|
Azgaar's _Fantasy Map Generator_ is a free web application generating interactive and highly customizable svg maps based on voronoi diagram.
|
||||||
|
|
||||||
Project is under development, the current version is available on [Github Pages](https://azgaar.github.io/Fantasy-Map-Generator).
|
Project is under development, the current version is available on [Github Pages](https://azgaar.github.io/Fantasy-Map-Generator).
|
||||||
|
|
||||||
Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for a guidance. Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com), you may also keep an eye on my [Trello devboard](https://trello.com/b/7x832DG4/fantasy-map-generator).
|
Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for guidance. The current progress is tracked in [Trello](https://trello.com/b/7x832DG4/fantasy-map-generator). Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com).
|
||||||
|
|
||||||
[](https://i.redd.it/8bf81ir2cy631.png)
|
[](https://i.redd.it/8bf81ir2cy631.png)
|
||||||
|
|
||||||
|
|
@ -12,18 +12,20 @@ Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki
|
||||||
|
|
||||||
[](https://cdn.discordapp.com/attachments/515359096925454350/593891237984206848/The_Wichin_Island_-_diplomacy.png)
|
[](https://cdn.discordapp.com/attachments/515359096925454350/593891237984206848/The_Wichin_Island_-_diplomacy.png)
|
||||||
|
|
||||||
Join our [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) and [Discord server](https://discordapp.com/invite/X7E84HU) to share the created maps, discuss the Generator, suggest ideas and get a most recent updates. You may also contact me directly via [email](mailto:azgaar.fmg@yandex.by). For bug reports please use the project [issues page](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or Discord "Bugs" channel. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips).
|
Join our [Discord server](https://discordapp.com/invite/X7E84HU) and [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) to share your creations, discuss the Generator, suggest ideas and get the most recent updates.
|
||||||
|
|
||||||
|
Contact me via [email](mailto:azgaar.fmg@yandex.by) if you have non-public suggestions. For bug reports please use [GitHub issues](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or _#bugs_ channel on Discord. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips).
|
||||||
|
|
||||||
Electron desktop application is available in [releases](https://github.com/Azgaar/Fantasy-Map-Generator/releases). Download archive for your architecture, unzip and run.
|
Electron desktop application is available in [releases](https://github.com/Azgaar/Fantasy-Map-Generator/releases). Download archive for your architecture, unzip and run.
|
||||||
|
|
||||||
Pull requests are welcomed. The Tool codebase is messy and requires re-design, but I will appreciate if you start with minor changes.
|
Pull requests are highly welcomed. The codebase is messy and requires re-design, but I will appreciate if you start with minor changes. Check out the [data model](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Data-model) before contributing.
|
||||||
|
|
||||||
You can support the project on [Patreon](https://www.patreon.com/azgaar).
|
You can support the project on [Patreon](https://www.patreon.com/azgaar).
|
||||||
|
|
||||||
_Inspiration:_
|
_Inspiration:_
|
||||||
|
|
||||||
* Martin O'Leary's [_Generating fantasy maps_](https://mewo2.com/notes/terrain)
|
- Martin O'Leary's [_Generating fantasy maps_](https://mewo2.com/notes/terrain)
|
||||||
|
|
||||||
* Amit Patel's [_Polygonal Map Generation for Games_](http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation)
|
- Amit Patel's [_Polygonal Map Generation for Games_](http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation)
|
||||||
|
|
||||||
* Scott Turner's [_Here Dragons Abound_](https://heredragonsabound.blogspot.com)
|
- Scott Turner's [_Here Dragons Abound_](https://heredragonsabound.blogspot.com)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
Azgaar's Fantasy Map Generator
|
Azgaar's Fantasy Map Generator
|
||||||
This is an open-source software available under MIT license
|
|
||||||
|
Developed by Azgaar (azgaar.fmg@yandex.com) and contributors
|
||||||
|
|
||||||
|
Minsk, 2017-2021. MIT License
|
||||||
|
|
||||||
https://github.com/Azgaar/Fantasy-Map-Generator
|
https://github.com/Azgaar/Fantasy-Map-Generator
|
||||||
|
|
||||||
To run the tool unzip ALL files and open index.html in browser
|
To run the tool unzip ALL files and open index.html in browser
|
||||||
46
dropbox.html
Normal file
46
dropbox.html
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script type="text/javascript" src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
|
||||||
|
<title>FMG Dropbox Auth</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
/*
|
||||||
|
open this page in a new window without query parameter to start auth
|
||||||
|
window.opener.setDropBoxToken(token) will be called on the opener
|
||||||
|
window.
|
||||||
|
*/
|
||||||
|
const REDIRECT_URI = window.location.origin + window.location.pathname;
|
||||||
|
const dbxAuth = new Dropbox.DropboxAuth({ clientId: 'sp7tzwm27u2w5ns', });
|
||||||
|
|
||||||
|
const spObj = new URLSearchParams(window.location.search);
|
||||||
|
const searchParams = Object.fromEntries(spObj.entries())
|
||||||
|
|
||||||
|
if (searchParams.code) getToken()
|
||||||
|
else doAuth(); // start authentication
|
||||||
|
|
||||||
|
function doAuth() {
|
||||||
|
dbxAuth.getAuthenticationUrl(REDIRECT_URI, undefined, 'code', 'offline', undefined, undefined, true)
|
||||||
|
.then(authUrl => {
|
||||||
|
window.sessionStorage.clear();
|
||||||
|
window.sessionStorage.setItem("codeVerifier", dbxAuth.codeVerifier);
|
||||||
|
window.location.href = authUrl;
|
||||||
|
})
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
function getToken() {
|
||||||
|
dbxAuth.setCodeVerifier(window.sessionStorage.getItem('codeVerifier'));
|
||||||
|
dbxAuth.getAccessTokenFromCode(REDIRECT_URI, searchParams.code)
|
||||||
|
.then((resp) => {
|
||||||
|
const token = resp.result.access_token;
|
||||||
|
window.opener.Cloud.providers.dropbox.setDropBoxToken(token)
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
42
index.css
42
index.css
|
|
@ -358,14 +358,11 @@ div.tab > button#optionsHide {
|
||||||
}
|
}
|
||||||
|
|
||||||
#options {
|
#options {
|
||||||
margin: 10px;
|
|
||||||
font-family: Consolas, monospace;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
font-family: Consolas, monospace;
|
||||||
border: solid 1px #5e4fa2;
|
border: solid 1px #5e4fa2;
|
||||||
width: 300px;
|
margin: 10px;
|
||||||
background-position: center;
|
padding-bottom: 0.3em;
|
||||||
background-size: cover;
|
|
||||||
background-blend-mode: color-dodge;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#options input,
|
#options input,
|
||||||
|
|
@ -576,14 +573,16 @@ input[type="color"]::-webkit-color-swatch-wrapper {
|
||||||
padding-left: 2.5px;
|
padding-left: 2.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sticked {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#sticked button {
|
#sticked button {
|
||||||
background-color: #997c8900;
|
background-color: transparent;
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
width: 22%;
|
|
||||||
font-size: 1em;
|
|
||||||
border: 0;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sticked button:hover {
|
#sticked button:hover {
|
||||||
|
|
@ -985,16 +984,16 @@ body button.noicon {
|
||||||
|
|
||||||
#controlPoints > path {
|
#controlPoints > path {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: #000000;
|
stroke: #0a0909;
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controlCells > .current {
|
#controlCells {
|
||||||
fill: #82c8ff40;
|
pointer-events: none;
|
||||||
stroke: #82c8ff;
|
fill: #82c8ff80;
|
||||||
stroke-width: 0.4;
|
stroke: "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
#vertices > circle {
|
#vertices > circle {
|
||||||
|
|
@ -1801,16 +1800,13 @@ div.editorLine {
|
||||||
width: 7em;
|
width: 7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#unitsBody > div > select,
|
||||||
#unitsBody > div > input[type="text"] {
|
#unitsBody > div > input[type="text"] {
|
||||||
width: 11.2em;
|
width: 12em;
|
||||||
}
|
|
||||||
|
|
||||||
#unitsBody > div > select {
|
|
||||||
width: 11.32em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#unitsBody > div > input[type="number"] {
|
#unitsBody > div > input[type="number"] {
|
||||||
width: 3.4em;
|
width: 4.35em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#unitsBody > div > input,
|
#unitsBody > div > input,
|
||||||
|
|
|
||||||
233
index.html
233
index.html
|
|
@ -173,7 +173,7 @@
|
||||||
<circle cx="2.5" cy="2.5" r="1" style="fill: black"/>
|
<circle cx="2.5" cy="2.5" r="1" style="fill: black"/>
|
||||||
</pattern>
|
</pattern>
|
||||||
<pattern id="hatch7" patternUnits="userSpaceOnUse" width="4" height="4">
|
<pattern id="hatch7" patternUnits="userSpaceOnUse" width="4" height="4">
|
||||||
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5"></line>
|
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
|
||||||
</pattern>
|
</pattern>
|
||||||
<pattern id="hatch8" patternUnits="userSpaceOnUse" width="4" height="4">
|
<pattern id="hatch8" patternUnits="userSpaceOnUse" width="4" height="4">
|
||||||
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
|
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
|
||||||
|
|
@ -188,22 +188,22 @@
|
||||||
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
|
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
|
||||||
</pattern>
|
</pattern>
|
||||||
<pattern id="hatch12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
|
<pattern id="hatch12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
|
||||||
<line x1="0" y1="1" x2="0" y2="3" style="stroke:black; stroke-width:1.5"></line>
|
<line x1="0" y1="1" x2="0" y2="3" style="stroke:black; stroke-width:1.5" />
|
||||||
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"></line>
|
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5" />
|
||||||
</pattern>
|
</pattern>
|
||||||
<pattern id="hatch13" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
|
<pattern id="hatch13" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
|
||||||
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"></line>
|
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5" />
|
||||||
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"></line>
|
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5" />
|
||||||
</pattern>
|
</pattern>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g id="deftemp">
|
<g id="deftemp">
|
||||||
<mask id="land"></mask>
|
<mask id="land"></mask>
|
||||||
<mask id="water">
|
<mask id="water">
|
||||||
<rect x="0" y="0" width="100%" height="100%" fill="white"></rect>
|
<rect x="0" y="0" width="100%" height="100%" fill="white" />
|
||||||
</mask>
|
</mask>
|
||||||
<mask id="fog" style="stroke-width: 10; stroke: black; stroke-linejoin: round; stroke-opacity: .1;">
|
<mask id="fog" style="stroke-width: 10; stroke: black; stroke-linejoin: round; stroke-opacity: .1;">
|
||||||
<rect x="0" y="0" width="100%" height="100%" fill="white" stroke="none"></rect>
|
<rect x="0" y="0" width="100%" height="100%" fill="white" stroke="none" />
|
||||||
</mask>
|
</mask>
|
||||||
<g id="textPaths"></g>
|
<g id="textPaths"></g>
|
||||||
<g id="statePaths"></g>
|
<g id="statePaths"></g>
|
||||||
|
|
@ -212,8 +212,8 @@
|
||||||
|
|
||||||
<g id="defs-markers">
|
<g id="defs-markers">
|
||||||
<symbol id="marker0" viewBox="0 0 30 30">
|
<symbol id="marker0" viewBox="0 0 30 30">
|
||||||
<path d="M6,19 l9,10 L24,19" fill="#000000" stroke="none"></path>
|
<path d="M6,19 l9,10 L24,19" fill="#000000" stroke="none" />
|
||||||
<circle cx="15" cy="15" r="10" stroke-width="1" stroke="#000000" fill="#ffffff"></circle>
|
<circle cx="15" cy="15" r="10" stroke-width="1" stroke="#000000" fill="#ffffff" />
|
||||||
<text x="50%" y="50%" fill="#000000" stroke-width="0" stroke="#3200ff" font-size="22px" dominant-baseline="central">?</text>
|
<text x="50%" y="50%" fill="#000000" stroke-width="0" stroke="#3200ff" font-size="22px" dominant-baseline="central">?</text>
|
||||||
</symbol>
|
</symbol>
|
||||||
</g>
|
</g>
|
||||||
|
|
@ -234,7 +234,7 @@
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
<div id="titleName"><t data-t="titleName">Azgaar's</t></div>
|
<div id="titleName"><t data-t="titleName">Azgaar's</t></div>
|
||||||
<div id="title"><t data-t="title">Fantasy Map Generator</t></div>
|
<div id="title"><t data-t="title">Fantasy Map Generator</t></div>
|
||||||
<div id="version"><t data-t="version">v. </t>1.65</div>
|
<div id="version"><t data-t="version">v. </t>1.66</div>
|
||||||
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
|
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1432,8 +1432,8 @@
|
||||||
<div>
|
<div>
|
||||||
<div style="width: .8em; display: inline-block; padding: 0 .2em; fill: white">
|
<div style="width: .8em; display: inline-block; padding: 0 .2em; fill: white">
|
||||||
<svg viewBox="0 0 569 546">
|
<svg viewBox="0 0 569 546">
|
||||||
<circle cx="362.589996" cy="204.589996" data-fill="1" id="Oval" r="204.589996"></circle>
|
<circle cx="362.589996" cy="204.589996" data-fill="1" id="Oval" r="204.589996" />
|
||||||
<rect data-fill="2" height="545.799988" id="Rectangle" width="100" x="0" y="0"></rect>
|
<rect data-fill="2" height="545.799988" id="Rectangle" width="100" x="0" y="0" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>SUPPORT ON PATREON
|
</div>SUPPORT ON PATREON
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1452,11 +1452,11 @@
|
||||||
|
|
||||||
<div id="sticked">
|
<div id="sticked">
|
||||||
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F2">New Map</button>
|
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F2">New Map</button>
|
||||||
<button id="saveButton" data-tip="Select format to save map">Save</button>
|
<button id="exportButton" data-tip="Select format to download image or export map data">Export</button>
|
||||||
<button id="loadButton" data-tip="Load fully functional map in a .map format">Load</button>
|
<button id="saveButton" data-tip="Save fully-functional map in .map format">Save</button>
|
||||||
|
<button id="loadButton" data-tip="Load fully-functional map in .map format">Load</button>
|
||||||
<button id="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
|
<button id="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1541,27 +1541,27 @@
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<g id="globeNoteLines">
|
<g id="globeNoteLines">
|
||||||
<line x1="5" x2="220" y1="0" y2="0"></line>
|
<line x1="5" x2="220" y1="0" y2="0" />
|
||||||
<line x1="5" x2="220" y1="13" y2="13"></line>
|
<line x1="5" x2="220" y1="13" y2="13" />
|
||||||
<line x1="5" x2="220" y1="49.5" y2="49.5"></line>
|
<line x1="5" x2="220" y1="49.5" y2="49.5" />
|
||||||
<line x1="-5" x2="220" y1="100" y2="100"></line>
|
<line x1="-5" x2="220" y1="100" y2="100" />
|
||||||
<line x1="5" x2="220" y1="150.5" y2="150.5"></line>
|
<line x1="5" x2="220" y1="150.5" y2="150.5" />
|
||||||
<line x1="5" x2="220" y1="187" y2="187"></line>
|
<line x1="5" x2="220" y1="187" y2="187" />
|
||||||
<line x1="5" x2="220" y1="200" y2="200"></line>
|
<line x1="5" x2="220" y1="200" y2="200" />
|
||||||
</g>
|
</g>
|
||||||
<g id="globeWindArrows" data-tip="Click to change wind direction">
|
<g id="globeWindArrows" data-tip="Click to change wind direction" stroke-linejoin="round">
|
||||||
<circle cx=210 cy=6 r=12></circle>
|
<circle cx=210 cy=6 r=12 />
|
||||||
<path data-tier=0 d="M210,11 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(225 210 6)"></path>
|
<path data-tier=0 d="M210,11 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(225 210 6)" />
|
||||||
<circle cx=210 cy=30 r=12></circle>
|
<circle cx=210 cy=30 r=12 />
|
||||||
<path data-tier=1 d="M210,35 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(45 210 30)"></path>
|
<path data-tier=1 d="M210,35 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(45 210 30)" />
|
||||||
<circle cx=210 cy=75 r=12></circle>
|
<circle cx=210 cy=75 r=12 />
|
||||||
<path data-tier=2 d="M210,80 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(225 210 75)"></path>
|
<path data-tier=2 d="M210,80 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(225 210 75)"/>
|
||||||
<circle cx=210 cy=130 r=12></circle>
|
<circle cx=210 cy=130 r=12 />
|
||||||
<path data-tier=3 d="M210,135 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(315 210 130)"></path>
|
<path data-tier=3 d="M210,135 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(315 210 130)"/>
|
||||||
<circle cx=210 cy=173 r=12></circle>
|
<circle cx=210 cy=173 r=12 />
|
||||||
<path data-tier=4 d="M210,178 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(135 210 173)"></path>
|
<path data-tier=4 d="M210,178 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(135 210 173)"/>
|
||||||
<circle cx=210 cy=194 r=12></circle>
|
<circle cx=210 cy=194 r=12 />
|
||||||
<path data-tier=5 d="M210,199 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(315 210 194)"></path>
|
<path data-tier=5 d="M210,199 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(315 210 194)"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="globaAxisLabels">
|
<g id="globaAxisLabels">
|
||||||
<text x="82%" y="-4%">wind</text>
|
<text x="82%" y="-4%">wind</text>
|
||||||
|
|
@ -1576,10 +1576,10 @@
|
||||||
<text x="-15" y="190">60°</text>
|
<text x="-15" y="190">60°</text>
|
||||||
<text x="-15" y="204">90°</text>
|
<text x="-15" y="204">90°</text>
|
||||||
</g>
|
</g>
|
||||||
<circle id="globeOutline" cx="100" cy="100" r="100"></circle>
|
<circle id="globeOutline" cx="100" cy="100" r="100" />
|
||||||
<line id="globeEquator" x1="1" x2="199" y1="100" y2="100"></line>
|
<line id="globeEquator" x1="1" x2="199" y1="100" y2="100" />
|
||||||
<path id="globeGraticule"></path>
|
<path id="globeGraticule" />
|
||||||
<path id="globeArea"></path>
|
<path id="globeArea" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1657,9 +1657,9 @@
|
||||||
<input id="riverWidth" disabled/>
|
<input id="riverWidth" disabled/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tip="River additional width. Default value is 0">
|
<div data-tip="River source additional width. Default value is 0">
|
||||||
<div class="label">Source width:</div>
|
<div class="label">Source width:</div>
|
||||||
<input id="riverSourceWidth" type="number" min=0 max=3 step=.1 />
|
<input id="riverSourceWidth" type="number" min=0 max=3 step=.01 />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tip="River width multiplier. Default value is 1">
|
<div data-tip="River width multiplier. Default value is 1">
|
||||||
|
|
@ -2249,43 +2249,43 @@
|
||||||
<div id="brushesButtons" style="display: inline-block">
|
<div id="brushesButtons" style="display: inline-block">
|
||||||
<button id="brushRaise" data-tip="Raise brush: increase height of cells in radius by Power value">
|
<button id="brushRaise" data-tip="Raise brush: increase height of cells in radius by Power value">
|
||||||
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
||||||
<path d="m20,39 h60 M50,85 v-35 l-12,8 m12,-8 l12,8" fill="none" stroke="#000" stroke-width="5"></path>
|
<path d="m20,39 h60 M50,85 v-35 l-12,8 m12,-8 l12,8" fill="none" stroke="#000" stroke-width="5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="brushElevate" data-tip="Elevate brush: drag to gradually increase height of cells in radius by Power value">
|
<button id="brushElevate" data-tip="Elevate brush: drag to gradually increase height of cells in radius by Power value">
|
||||||
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
||||||
<path d="m20,50 q30,-35 60,0 M50,85 v-35 l-12,8 m12,-8 l12,8" fill="none" stroke="#000" stroke-width="5"></path>
|
<path d="m20,50 q30,-35 60,0 M50,85 v-35 l-12,8 m12,-8 l12,8" fill="none" stroke="#000" stroke-width="5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="brushLower" data-tip="Lower brush: drag to decrease height of cells in radius by Power value">
|
<button id="brushLower" data-tip="Lower brush: drag to decrease height of cells in radius by Power value">
|
||||||
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
||||||
<path d="M50,30 v35 l-12,-8 m12,8 l12,-8 M20,78 h60" fill="none" stroke="#000" stroke-width="5"></path>
|
<path d="M50,30 v35 l-12,-8 m12,8 l12,-8 M20,78 h60" fill="none" stroke="#000" stroke-width="5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="brushDepress" data-tip="Depress brush: drag to gradually decrease height of cells in radius by Power value">
|
<button id="brushDepress" data-tip="Depress brush: drag to gradually decrease height of cells in radius by Power value">
|
||||||
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
||||||
<path d="M50,30 v35 l-12,-8 m12,8 l12,-8 M20,63 q30,35 60,0" fill="none" stroke="#000" stroke-width="5"></path>
|
<path d="M50,30 v35 l-12,-8 m12,8 l12,-8 M20,63 q30,35 60,0" fill="none" stroke="#000" stroke-width="5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="brushAlign" data-tip="Align brush: drag to set height of cells in radius to height of the cell at mousepoint">
|
<button id="brushAlign" data-tip="Align brush: drag to set height of cells in radius to height of the cell at mousepoint">
|
||||||
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
||||||
<path d="m20,50 h56 m0,20 h-56" fill="none" stroke="#000" stroke-width="5"></path>
|
<path d="m20,50 h56 m0,20 h-56" fill="none" stroke="#000" stroke-width="5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="brushSmooth" data-tip="Smooth brush: drag to level height of cells in radius to height of adjacent cells">
|
<button id="brushSmooth" data-tip="Smooth brush: drag to level height of cells in radius to height of adjacent cells">
|
||||||
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
||||||
<path d="m15,60 q15,-15 30,0 q15,15 35,0" fill="none" stroke="#000" stroke-width="5"></path>
|
<path d="m15,60 q15,-15 30,0 q15,15 35,0" fill="none" stroke="#000" stroke-width="5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="brushDisrupt" data-tip="Disrupt brush: drag to randomize height of cells in radius based on Power value">
|
<button id="brushDisrupt" data-tip="Disrupt brush: drag to randomize height of cells in radius based on Power value">
|
||||||
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
<svg viewBox="15 15 70 70" height="1em" width="1.6em">
|
||||||
<path d="m15,63 l15,-13 15,20 15,-20 15,19 15,-14" fill="none" stroke="#000" stroke-width="5"></path>
|
<path d="m15,63 l15,-13 15,20 15,-20 15,19 15,-14" fill="none" stroke="#000" stroke-width="5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -2550,6 +2550,7 @@
|
||||||
<optgroup label="Monarchy">
|
<optgroup label="Monarchy">
|
||||||
<option value="Beylik">Beylik</option>
|
<option value="Beylik">Beylik</option>
|
||||||
<option value="Despotate">Despotate</option>
|
<option value="Despotate">Despotate</option>
|
||||||
|
<option value="Dominion">Dominion</option>
|
||||||
<option value="Duchy">Duchy</option>
|
<option value="Duchy">Duchy</option>
|
||||||
<option value="Emirate">Emirate</option>
|
<option value="Emirate">Emirate</option>
|
||||||
<option value="Empire">Empire</option>
|
<option value="Empire">Empire</option>
|
||||||
|
|
@ -2723,6 +2724,7 @@
|
||||||
<select id="provinceNameEditorSelectForm" style="display: inline-block; width: 11em; height: 1.645em">
|
<select id="provinceNameEditorSelectForm" style="display: inline-block; width: 11em; height: 1.645em">
|
||||||
<option value="">blank</option>
|
<option value="">blank</option>
|
||||||
<option value="Area">Area</option>
|
<option value="Area">Area</option>
|
||||||
|
<option value="Autonomy">Autonomy</option>
|
||||||
<option value="Barony">Barony</option>
|
<option value="Barony">Barony</option>
|
||||||
<option value="Canton">Canton</option>
|
<option value="Canton">Canton</option>
|
||||||
<option value="Clan">Clan</option>
|
<option value="Clan">Clan</option>
|
||||||
|
|
@ -2739,13 +2741,15 @@
|
||||||
<option value="Islands">Islands</option>
|
<option value="Islands">Islands</option>
|
||||||
<option value="Land">Land</option>
|
<option value="Land">Land</option>
|
||||||
<option value="Landgrave">Landgrave</option>
|
<option value="Landgrave">Landgrave</option>
|
||||||
|
<option value="Mandate">Mandate</option>
|
||||||
<option value="Margrave">Margrave</option>
|
<option value="Margrave">Margrave</option>
|
||||||
|
<option value="Occupation zone">Occupation zone</option>
|
||||||
<option value="Parish">Parish</option>
|
<option value="Parish">Parish</option>
|
||||||
<option value="Prefecture">Prefecture</option>
|
<option value="Prefecture">Prefecture</option>
|
||||||
<option value="Province">Province</option>
|
<option value="Province">Province</option>
|
||||||
<option value="Region">Region</option>
|
<option value="Region">Region</option>
|
||||||
<option value="Republic">Republic</option>
|
<option value="Republic">Republic</option>
|
||||||
<option value="reservation">Reservation</option>
|
<option value="Reservation">Reservation</option>
|
||||||
<option value="Shire">Shire</option>
|
<option value="Shire">Shire</option>
|
||||||
<option value="State">State</option>
|
<option value="State">State</option>
|
||||||
<option value="Territory">Territory</option>
|
<option value="Territory">Territory</option>
|
||||||
|
|
@ -3107,8 +3111,8 @@
|
||||||
|
|
||||||
<div data-tip="Set height exponent, i.e. a value for altitude change sharpness. Altitude affects temperature and hence biomes">
|
<div data-tip="Set height exponent, i.e. a value for altitude change sharpness. Altitude affects temperature and hence biomes">
|
||||||
<div>Exponent:</div>
|
<div>Exponent:</div>
|
||||||
<input id="heightExponentOutput" type="range" min=1.5 max=2.1 value=1.8 step=.01>
|
<input id="heightExponentOutput" type="range" min=1.5 max=2.2 value=2 step=.01>
|
||||||
<input id="heightExponentInput" data-stored="heightExponent" type="number" min=1.5 max=2.1 value=1.8 step=.01>
|
<input id="heightExponentInput" data-stored="heightExponent" type="number" min=1.5 max=2.2 value=2 step=.01>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="unitsHeader" data-tip="Select Temperature scale">
|
<div class="unitsHeader" data-tip="Select Temperature scale">
|
||||||
|
|
@ -3165,8 +3169,8 @@
|
||||||
|
|
||||||
<div data-tip="Set how many people are in one population point">
|
<div data-tip="Set how many people are in one population point">
|
||||||
<div>1 population point =</div>
|
<div>1 population point =</div>
|
||||||
<input id="populationRateOutput" data-stored="populationRate" type="range" min=10 max=9990 step=10 value=1000 style="width:6em">
|
<input id="populationRateOutput" data-stored="populationRate" type="range" min=10 max=9990 step=10 value=1000 />
|
||||||
<input id="populationRateInput" data-stored="populationRate" type="number" min=10 max=9990 step=10 value=1000 style="width:4.5em">
|
<input id="populationRateInput" data-stored="populationRate" type="number" min=10 max=9990 step=10 value=1000 />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-tip="Set urbanization rate: burgs population relative to all population">
|
<div data-tip="Set urbanization rate: burgs population relative to all population">
|
||||||
|
|
@ -3450,37 +3454,70 @@
|
||||||
|
|
||||||
<div id="preview3d" class="dialog stable" style="display: none; padding: 0px"></div>
|
<div id="preview3d" class="dialog stable" style="display: none; padding: 0px"></div>
|
||||||
|
|
||||||
<div id="saveMapData" style="display: none" class="dialog">
|
<div id="exportMapData" style="display: none" class="dialog">
|
||||||
<div style="margin-bottom: .3em; font-weight: bold">Please select saving method:</div>
|
<div style="margin-bottom: .3em; font-weight: bold">Download image</div>
|
||||||
<div>
|
<div>
|
||||||
<button onclick="saveMap()" data-tip="Download the project in internal .map format (reliable). Then open via 'Load' in menu. Shortcut: Ctrl + S">.map</button>
|
<button onclick="saveSVG()" data-tip="Download the map as vector image (open directly in browser or Inkscape)">.svg</button>
|
||||||
<button onclick="saveSVG()" data-tip="Download the map as vector image (open in browser or Inkscape)">.svg</button>
|
|
||||||
<button onclick="savePNG()" data-tip="Download visible part of the map as .png (lossless compressed)">.png</button>
|
<button onclick="savePNG()" data-tip="Download visible part of the map as .png (lossless compressed)">.png</button>
|
||||||
<button onclick="saveJPEG()" data-tip="Download visible part of the map as .jpeg (lossy compressed) image">.jpeg</button>
|
<button onclick="saveJPEG()" data-tip="Download visible part of the map as .jpeg (lossy compressed) image">.jpeg</button>
|
||||||
<button onclick="openSaveTiles()" data-tip="Split map into smaller png tiles and download as zip archive">tiles</button>
|
<button onclick="openSaveTiles()" data-tip="Split map into smaller png tiles and download as zip archive">tiles</button>
|
||||||
<button onclick="saveGeoJSON()" data-tip="Download map data in GeoJSON format">.json</button>
|
<span data-tip="Check to not allow system to automatically hide labels">
|
||||||
<button onclick="quickSave()" data-tip="Save the project to browser storage (unreliable). Shortcut: F6">storage</button>
|
|
||||||
</div>
|
|
||||||
<p style="font-style: italic">Keep noted that the only reliable project saving method is having .map file stored on your machine. There is no way to restore map if .map file is lost. We don't keep any data on our side.</p>
|
|
||||||
<p style="font-style: italic">Generator uses pop-up window to download files. Please ensure your browser does not block popups.</p>
|
|
||||||
<div data-tip="Define scale of a saved png/jpeg image (e.g. 5x). Saving big images is slow and may cause a browser crash!" style="margin-bottom: .3em">
|
|
||||||
PNG / JPEG scale:
|
|
||||||
<input id="pngResolutionInput" data-stored="pngResolution" type="range" min=1 max=8 value=1 style="width: 14em">
|
|
||||||
<input id="pngResolutionOutput" data-stored="pngResolution" type="number" min=1 max=8 value=1>
|
|
||||||
</div>
|
|
||||||
<div data-tip="Check to not allow system to automatically hide labels">
|
|
||||||
<input id="showLabels" class="checkbox" type="checkbox" onchange="hideLabels.checked = !this.checked; invokeActiveZooming()" checked="">
|
<input id="showLabels" class="checkbox" type="checkbox" onchange="hideLabels.checked = !this.checked; invokeActiveZooming()" checked="">
|
||||||
<label for="showLabels" class="checkbox-label">Show all labels</label>
|
<label for="showLabels" class="checkbox-label">Show all labels</label>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-tip="Define scale of a saved png/jpeg image (e.g. 5x). Saving big images is slow and may cause a browser crash!" style="margin-bottom: .3em">
|
||||||
|
PNG / JPEG scale:
|
||||||
|
<input id="pngResolutionInput" data-stored="pngResolution" type="range" min=1 max=8 value=1 style="width: 10em">
|
||||||
|
<input id="pngResolutionOutput" data-stored="pngResolution" type="number" min=1 max=8 value=1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin: 1em 0 .3em; font-weight: bold">Export to GeoJSON</div>
|
||||||
|
<div>
|
||||||
|
<button onclick="saveGeoJSON_Cells()" data-tip="Download cells data in GeoJSON format">cells</button>
|
||||||
|
<button onclick="saveGeoJSON_Routes()" data-tip="Download routes data in GeoJSON format">routes</button>
|
||||||
|
<button onclick="saveGeoJSON_Rivers()" data-tip="Download rivers data in GeoJSON format">rivers</button>
|
||||||
|
<button onclick="saveGeoJSON_Markers()" data-tip="Download markers data in GeoJSON format">markers</button>
|
||||||
|
</div>
|
||||||
|
<p style="font-style: italic">GeoJSON format is used in GIS tools such as QGIS. Check out <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export" target="_blank">wiki-page</a> for guidance</p>
|
||||||
|
|
||||||
|
<p style="font-style: italic">Generator uses pop-up window to download files. Please ensure your browser does not block popups.</p>
|
||||||
|
|
||||||
|
<p style="font-style: italic">It's also possible to export map to Foundry VTT, see <a href="https://github.com/Ethck/azgaar-foundry" target="_blank">the module.</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="saveMapData" style="display: none" class="dialog">
|
||||||
|
<div style="margin-top: .3em">
|
||||||
|
<strong>Save map to</strong>
|
||||||
|
<button onclick="dowloadMap()" data-tip="Download .map file to your local disk. Shortcut: Ctrl + S">machine</button>
|
||||||
|
<button onclick="saveToDropbox()" data-tip="Save .map file to your Dropbox">dropbox</button>
|
||||||
|
<button onclick="quickSave()" data-tip="Save the project to browser storage (quick save). It can be unreliable. Shortcut: F6">browser</button>
|
||||||
|
</div>
|
||||||
|
<p style="font-style: italic">Maps are saved in <i>.map</i> format, that can be loaded back via 'Load' in menu. Please keep noted that we do not keep any data on our side. There is no way to restore the progress if .map file is lost. Please keep old .map files on your machine or cloud storage as backups.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="loadMapData" style="display: none" class="dialog">
|
<div id="loadMapData" style="display: none" class="dialog">
|
||||||
<div style="margin-bottom: .3em">Load map from:</div>
|
<div style="margin-bottom: .3em">Load map from</div>
|
||||||
<div>
|
<div>
|
||||||
<button onclick="mapToLoad.click()" data-tip="Load .map file from local disk">local disk</button>
|
<button onclick="mapToLoad.click()" data-tip="Load .map file from local disk">local disk</button>
|
||||||
<button onclick="loadURL()" data-tip="Load .map file from URL (server should allow CORS)">URL</button>
|
<button onclick="loadURL()" data-tip="Load .map file from URL (server should allow CORS)">URL</button>
|
||||||
<button onclick="quickLoad()" data-tip="Load map from browser storage (if saved before)">storage</button>
|
<button onclick="quickLoad()" data-tip="Load map from browser storage (if saved before)">storage</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="loadFromDropbox">
|
||||||
|
<p>From your Dropbox account:</p>
|
||||||
|
<select style="margin-bottom:.3em">
|
||||||
|
</select>
|
||||||
|
<button onclick="loadFromDropbox()" data-tip="Load .map file from your Dropbox">Open</button>
|
||||||
|
<button onclick="createSharableDropboxLink()" data-tip="Select .map file on dropbox and share a sharable link">Create link</button>
|
||||||
|
|
||||||
|
<div style="margin-top: .3em">
|
||||||
|
<div id="sharableLinkContainer" style="display: none">
|
||||||
|
<a id="sharableLink" target="_blank"></a>
|
||||||
|
<i data-tip="Copy link to the clipboard" onclick="copyLinkToClickboard()" class="icon-clone pointer"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="saveTilesScreen" style="display: none" class="dialog">
|
<div id="saveTilesScreen" style="display: none" class="dialog">
|
||||||
|
|
@ -3585,64 +3622,64 @@
|
||||||
<svg id="defElements" width="0" height="0" style="position: absolute">
|
<svg id="defElements" width="0" height="0" style="position: absolute">
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="end-arrow" viewBox="0 -5 10 10" refX="6" markerWidth="7" markerHeight="7" orient="auto">
|
<marker id="end-arrow" viewBox="0 -5 10 10" refX="6" markerWidth="7" markerHeight="7" orient="auto">
|
||||||
<path d="M0,-5L10,0L0,5" fill="#000"></path>
|
<path d="M0,-5L10,0L0,5" fill="#000" />
|
||||||
</marker>
|
</marker>
|
||||||
<marker id="end-arrow-small" viewBox="0 -5 10 10" refX="6" markerWidth="2" markerHeight="2" orient="auto">
|
<marker id="end-arrow-small" viewBox="0 -5 10 10" refX="6" markerWidth="2" markerHeight="2" orient="auto">
|
||||||
<path d="M0,-5L10,0L0,5" fill="#555"></path>
|
<path d="M0,-5L10,0L0,5" fill="#555" />
|
||||||
</marker>
|
</marker>
|
||||||
|
|
||||||
<symbol id="icon-store" viewBox="0 0 616 512">
|
<symbol id="icon-store" viewBox="0 0 616 512">
|
||||||
<path d="M602 118.6L537.1 15C531.3 5.7 521 0 510 0H106C95 0 84.7 5.7 78.9 15L14 118.6c-33.5 53.5-3.8 127.9 58.8 136.4 4.5.6 9.1.9 13.7.9 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18.1 20.1 44.3 33.1 73.8 33.1 4.7 0 9.2-.3 13.7-.9 62.8-8.4 92.6-82.8 59-136.4zM529.5 288c-10 0-19.9-1.5-29.5-3.8V384H116v-99.8c-9.6 2.2-19.5 3.8-29.5 3.8-6 0-12.1-.4-18-1.2-5.6-.8-11.1-2.1-16.4-3.6V480c0 17.7 14.3 32 32 32h448c17.7 0 32-14.3 32-32V283.2c-5.4 1.6-10.8 2.9-16.4 3.6-6.1.8-12.1 1.2-18.2 1.2z"></path>
|
<path d="M602 118.6L537.1 15C531.3 5.7 521 0 510 0H106C95 0 84.7 5.7 78.9 15L14 118.6c-33.5 53.5-3.8 127.9 58.8 136.4 4.5.6 9.1.9 13.7.9 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18 20.1 44.3 33.1 73.8 33.1 29.6 0 55.8-13 73.8-33.1 18.1 20.1 44.3 33.1 73.8 33.1 4.7 0 9.2-.3 13.7-.9 62.8-8.4 92.6-82.8 59-136.4zM529.5 288c-10 0-19.9-1.5-29.5-3.8V384H116v-99.8c-9.6 2.2-19.5 3.8-29.5 3.8-6 0-12.1-.4-18-1.2-5.6-.8-11.1-2.1-16.4-3.6V480c0 17.7 14.3 32 32 32h448c17.7 0 32-14.3 32-32V283.2c-5.4 1.6-10.8 2.9-16.4 3.6-6.1.8-12.1 1.2-18.2 1.2z" />
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="icon-anchor" viewBox="0 0 30 28">
|
<symbol id="icon-anchor" viewBox="0 0 30 28">
|
||||||
<title>Port</title>
|
<title>Port</title>
|
||||||
<path d="M15 4c0-0.547-0.453-1-1-1s-1 0.453-1 1 0.453 1 1 1 1-0.453 1-1zM28 18.5v5.5c0 0.203-0.125 0.391-0.313 0.469-0.063 0.016-0.125 0.031-0.187 0.031-0.125 0-0.25-0.047-0.359-0.141l-1.453-1.453c-2.453 2.953-6.859 4.844-11.688 4.844s-9.234-1.891-11.688-4.844l-1.453 1.453c-0.094 0.094-0.234 0.141-0.359 0.141-0.063 0-0.125-0.016-0.187-0.031-0.187-0.078-0.313-0.266-0.313-0.469v-5.5c0-0.281 0.219-0.5 0.5-0.5h5.5c0.203 0 0.391 0.125 0.469 0.313s0.031 0.391-0.109 0.547l-1.563 1.563c1.406 1.891 4.109 3.266 7.203 3.687v-10.109h-3c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h3v-2.547c-1.188-0.688-2-1.969-2-3.453 0-2.203 1.797-4 4-4s4 1.797 4 4c0 1.484-0.812 2.766-2 3.453v2.547h3c0.547 0 1 0.453 1 1v2c0 0.547-0.453 1-1 1h-3v10.109c3.094-0.422 5.797-1.797 7.203-3.687l-1.563-1.563c-0.141-0.156-0.187-0.359-0.109-0.547s0.266-0.313 0.469-0.313h5.5c0.281 0 0.5 0.219 0.5 0.5z"></path>
|
<path d="M15 4c0-0.547-0.453-1-1-1s-1 0.453-1 1 0.453 1 1 1 1-0.453 1-1zM28 18.5v5.5c0 0.203-0.125 0.391-0.313 0.469-0.063 0.016-0.125 0.031-0.187 0.031-0.125 0-0.25-0.047-0.359-0.141l-1.453-1.453c-2.453 2.953-6.859 4.844-11.688 4.844s-9.234-1.891-11.688-4.844l-1.453 1.453c-0.094 0.094-0.234 0.141-0.359 0.141-0.063 0-0.125-0.016-0.187-0.031-0.187-0.078-0.313-0.266-0.313-0.469v-5.5c0-0.281 0.219-0.5 0.5-0.5h5.5c0.203 0 0.391 0.125 0.469 0.313s0.031 0.391-0.109 0.547l-1.563 1.563c1.406 1.891 4.109 3.266 7.203 3.687v-10.109h-3c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h3v-2.547c-1.188-0.688-2-1.969-2-3.453 0-2.203 1.797-4 4-4s4 1.797 4 4c0 1.484-0.812 2.766-2 3.453v2.547h3c0.547 0 1 0.453 1 1v2c0 0.547-0.453 1-1 1h-3v10.109c3.094-0.422 5.797-1.797 7.203-3.687l-1.563-1.563c-0.141-0.156-0.187-0.359-0.109-0.547s0.266-0.313 0.469-0.313h5.5c0.281 0 0.5 0.219 0.5 0.5z" />
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="icon-route" viewBox="0 0 512 512">
|
<symbol id="icon-route" viewBox="0 0 512 512">
|
||||||
<path d="M416 320h-96c-17.6 0-32-14.4-32-32s14.4-32 32-32h96s96-107 96-160-43-96-96-96-96 43-96 96c0 25.5 22.2 63.4 45.3 96H320c-52.9 0-96 43.1-96 96s43.1 96 96 96h96c17.6 0 32 14.4 32 32s-14.4 32-32 32H185.5c-16 24.8-33.8 47.7-47.3 64H416c52.9 0 96-43.1 96-96s-43.1-96-96-96zm0-256c17.7 0 32 14.3 32 32s-14.3 32-32 32-32-14.3-32-32 14.3-32 32-32zM96 256c-53 0-96 43-96 96s96 160 96 160 96-107 96-160-43-96-96-96zm0 128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"></path>
|
<path d="M416 320h-96c-17.6 0-32-14.4-32-32s14.4-32 32-32h96s96-107 96-160-43-96-96-96-96 43-96 96c0 25.5 22.2 63.4 45.3 96H320c-52.9 0-96 43.1-96 96s43.1 96 96 96h96c17.6 0 32 14.4 32 32s-14.4 32-32 32H185.5c-16 24.8-33.8 47.7-47.3 64H416c52.9 0 96-43.1 96-96s-43.1-96-96-96zm0-256c17.7 0 32 14.3 32 32s-14.3 32-32 32-32-14.3-32-32 14.3-32 32-32zM96 256c-53 0-96 43-96 96s96 160 96 160 96-107 96-160-43-96-96-96zm0 128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z" />
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<g id="defs-relief">
|
<g id="defs-relief">
|
||||||
<symbol id="relief-mount-1" viewBox="0 0 100 100">
|
<symbol id="relief-mount-1" viewBox="0 0 100 100">
|
||||||
<path d="m3,69 16,-12 31,-32 15,20 30,24" fill="#fff" stroke="#5c5c70" stroke-width="1"></path>
|
<path d="m3,69 16,-12 31,-32 15,20 30,24" fill="#fff" stroke="#5c5c70" stroke-width="1" />
|
||||||
<path d="m3,69 16,-12 31,-32 -14,44" fill="#999999"></path>
|
<path d="m3,69 16,-12 31,-32 -14,44" fill="#999999" />
|
||||||
<path d="m3,71 h92 m-83,3 h83" stroke="#5c5c70" stroke-dasharray="7, 11" stroke-width="1"></path>
|
<path d="m3,71 h92 m-83,3 h83" stroke="#5c5c70" stroke-dasharray="7, 11" stroke-width="1" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="relief-hill-1" viewBox="0 0 100 100">
|
<symbol id="relief-hill-1" viewBox="0 0 100 100">
|
||||||
<path d="m20,55 q30,-28 60,0" fill="#999999" stroke="#5c5c70"></path>
|
<path d="m20,55 q30,-28 60,0" fill="#999999" stroke="#5c5c70" />
|
||||||
<path d="m38,55 q13,-24 40,0" fill="#fff"></path>
|
<path d="m38,55 q13,-24 40,0" fill="#fff" />
|
||||||
<path d="m20,58 h70 m-62,3 h50" stroke="#5c5c70" stroke-dasharray="7, 11" stroke-width="1"></path>
|
<path d="m20,58 h70 m-62,3 h50" stroke="#5c5c70" stroke-dasharray="7, 11" stroke-width="1" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="relief-deciduous-1" viewBox="0 0 100 100">
|
<symbol id="relief-deciduous-1" viewBox="0 0 100 100">
|
||||||
<path d="m49.5,52 v7 h1 v-7 h-0.5 q13,-7 0,-16 q-13,9 0,16" fill="#fff" stroke="#5c5c70"></path>
|
<path d="m49.5,52 v7 h1 v-7 h-0.5 q13,-7 0,-16 q-13,9 0,16" fill="#fff" stroke="#5c5c70" />
|
||||||
<path d="M 50,51.5 C 44,49 40,43 50,36.5" fill="#999999"></path>
|
<path d="M 50,51.5 C 44,49 40,43 50,36.5" fill="#999999" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="relief-conifer-1" viewBox="0 0 100 100">
|
<symbol id="relief-conifer-1" viewBox="0 0 100 100">
|
||||||
<path d="m49.5,55 v4 h1 v-4 l4.5,0 -4,-8 l3.5,0 -4.5,-9 -4,9 3,0 -3.5,8 7,0" fill="#fff" stroke="#5c5c70"></path>
|
<path d="m49.5,55 v4 h1 v-4 l4.5,0 -4,-8 l3.5,0 -4.5,-9 -4,9 3,0 -3.5,8 7,0" fill="#fff" stroke="#5c5c70" />
|
||||||
<path d="m 46,54.5 3.5,-8 H 46.6 L 50,39 v 15.5 z" fill="#999999"></path>
|
<path d="m 46,54.5 3.5,-8 H 46.6 L 50,39 v 15.5 z" fill="#999999" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="relief-acacia-1" viewBox="0 0 100 100">
|
<symbol id="relief-acacia-1" viewBox="0 0 100 100">
|
||||||
<path d="m34.5 44.5 c 1.8, -3 8.1, -5.7 12.6, -5.4 6, -2.2 9.3, -0.9 11.9, 1.3 1.7, 0.2 3.2,-0.3 5.2, 2.2 2.7, 1.2 3.7, 2.4 2.7, 3.7 -1.6, 0.3 -2.2, 0 -4.7, -1.6 -5.2, 0.1 -7, 0.7 -8.7, -0.9 -2.8, 1 -3.6, 0 -9.7, 0.2 -4.6, 0 -8, 1.6 -9.3, 0.4 z" fill="#fff"></path>
|
<path d="m34.5 44.5 c 1.8, -3 8.1, -5.7 12.6, -5.4 6, -2.2 9.3, -0.9 11.9, 1.3 1.7, 0.2 3.2,-0.3 5.2, 2.2 2.7, 1.2 3.7, 2.4 2.7, 3.7 -1.6, 0.3 -2.2, 0 -4.7, -1.6 -5.2, 0.1 -7, 0.7 -8.7, -0.9 -2.8, 1 -3.6, 0 -9.7, 0.2 -4.6, 0 -8, 1.6 -9.3, 0.4 z" fill="#fff" />
|
||||||
<path d="m52 38 c-2.3 -0.1 -4.3 1.1 -4.9 1.1 -2.2 -0.2 -5 0.2 -6.4 1 -1.3 0.7 -2.8 1.6 -3.7 2.1 -1 0.6 -3.4 1.8 -2.2 2.7 1.1 0.9 3.1 -0.2 4.2 0.3 1.4 0.8 2.9 1 4.5 0.9 1.1 -0.1 2.2 -0.4 2.4 1 0.3 1.9 1.1 3.5 2.1 5.1 0.8 2.4 1 2.8 1 6.8 l2 0 c 0 -1.1 -0.1 -4 1.2 -5.7 1.1 -1.4 1.4 -3.4 3 -4.4 0.9 -1.4 2 -2.6 3.8 -2.7 1.7 -0.3 3.8 0.8 5.1 0.3 0.9 -0.1 3.2 1 3.5 -1 0.1 -2 -2.2 -2.1 -3.2 -3.3 -1.1 -1.5 -3.3 -1.9 -4.9 -1.8 -1 -0.5 -2 -2.5 -7.3 -2.5 z m -0.5 0.4 c2.7338 -0.2 5.6 0.2 7.5 2.4 1.7 0 3.7 0 4.8 1.5 1 1.2 3.4 1.8 3.4 3 0 2.1 -3.2 0.5 -3.6 0.1 -1.3 -1.4 -2.9 -0.6 -4.5 -0.7 -1.6 -0.1 -3.2 0.4 -4.6 -0.6 -1.1 -0.7 -2.5 0.1 -3.8 -0.1 -1.8 -0.2 -4 -0.4 -5.9 -0.1 -1.4 0 -2.8 0.1 -4.2 0 -1.7 0.5 -5.5 1.1 -5.4 0.4 0.2 -1.1 4.5 -3.2 5.9 -3.9 1.9 -0.9 3.7 -1.1 6.2 -0.8 0.7 -0.2 1.7 -1.1 4.3 -1.3 z m2 6 c1.6 0.3 2 2.2 1.2 3.3 -1 1.3 -1 -1.3 -1.3 -2 -0.2 -0.5 -0.8 -1.3 0.1 -1.3 z m -12.9 0.2 c1 -0.1 3.5 -0.3 3.1 0.9 -1.4 0 -3.4 0.1 -4.4 -0.6 0.4 -0.2 0.9 -0.2 1.3 -0.3 z m5.6 -0.1 c0.8 0.1 3.1 -0.3 3 0.5 -1.3 0.6 -1.6 2.2 -2.1 3.1 -0.4 -1.2 -0.7 -2.7 -2.1 -3.2 -0.9 -0.6 1 -0.5 1.3 -0.4 z m5.3 0.3 c1.1 0.1 1.6 2.4 0.1 1.3 -1.6 -1.2 -0.6 -1.3 -0.1 -1.3 z m7.5 0.4 c1.2 0 3.3 -0.2 2.9 0.2 -1.4 1.2 -3 -0.3 -4.8 0.8 -0.9 0.5 -2 0.8 -1.1 -0.4 0.5 -0.6 1.3 -0.5 3 -0.6 z m -8.9 0.1 c0.7 1.2 2.1 1.5 2.9 2.1 0.9 1.6 -0.5 3.1 -1.3 4.5 -0.9 1.5 -1.9 2.2 -2.4 0.3 -0.1 -0.5 -1.8 -2.2 -1.2 -3.7 0.3 -1.3 0.6 -2.6 2 -3.2 z m12.5 0.1 c0.6 0.2 1.3 1.1 0.2 0.9 -1.4 -0.1 -1.4 -0.3 -0.2 -0.9 z" fill="#5c5c70"></path>
|
<path d="m52 38 c-2.3 -0.1 -4.3 1.1 -4.9 1.1 -2.2 -0.2 -5 0.2 -6.4 1 -1.3 0.7 -2.8 1.6 -3.7 2.1 -1 0.6 -3.4 1.8 -2.2 2.7 1.1 0.9 3.1 -0.2 4.2 0.3 1.4 0.8 2.9 1 4.5 0.9 1.1 -0.1 2.2 -0.4 2.4 1 0.3 1.9 1.1 3.5 2.1 5.1 0.8 2.4 1 2.8 1 6.8 l2 0 c 0 -1.1 -0.1 -4 1.2 -5.7 1.1 -1.4 1.4 -3.4 3 -4.4 0.9 -1.4 2 -2.6 3.8 -2.7 1.7 -0.3 3.8 0.8 5.1 0.3 0.9 -0.1 3.2 1 3.5 -1 0.1 -2 -2.2 -2.1 -3.2 -3.3 -1.1 -1.5 -3.3 -1.9 -4.9 -1.8 -1 -0.5 -2 -2.5 -7.3 -2.5 z m -0.5 0.4 c2.7338 -0.2 5.6 0.2 7.5 2.4 1.7 0 3.7 0 4.8 1.5 1 1.2 3.4 1.8 3.4 3 0 2.1 -3.2 0.5 -3.6 0.1 -1.3 -1.4 -2.9 -0.6 -4.5 -0.7 -1.6 -0.1 -3.2 0.4 -4.6 -0.6 -1.1 -0.7 -2.5 0.1 -3.8 -0.1 -1.8 -0.2 -4 -0.4 -5.9 -0.1 -1.4 0 -2.8 0.1 -4.2 0 -1.7 0.5 -5.5 1.1 -5.4 0.4 0.2 -1.1 4.5 -3.2 5.9 -3.9 1.9 -0.9 3.7 -1.1 6.2 -0.8 0.7 -0.2 1.7 -1.1 4.3 -1.3 z m2 6 c1.6 0.3 2 2.2 1.2 3.3 -1 1.3 -1 -1.3 -1.3 -2 -0.2 -0.5 -0.8 -1.3 0.1 -1.3 z m -12.9 0.2 c1 -0.1 3.5 -0.3 3.1 0.9 -1.4 0 -3.4 0.1 -4.4 -0.6 0.4 -0.2 0.9 -0.2 1.3 -0.3 z m5.6 -0.1 c0.8 0.1 3.1 -0.3 3 0.5 -1.3 0.6 -1.6 2.2 -2.1 3.1 -0.4 -1.2 -0.7 -2.7 -2.1 -3.2 -0.9 -0.6 1 -0.5 1.3 -0.4 z m5.3 0.3 c1.1 0.1 1.6 2.4 0.1 1.3 -1.6 -1.2 -0.6 -1.3 -0.1 -1.3 z m7.5 0.4 c1.2 0 3.3 -0.2 2.9 0.2 -1.4 1.2 -3 -0.3 -4.8 0.8 -0.9 0.5 -2 0.8 -1.1 -0.4 0.5 -0.6 1.3 -0.5 3 -0.6 z m -8.9 0.1 c0.7 1.2 2.1 1.5 2.9 2.1 0.9 1.6 -0.5 3.1 -1.3 4.5 -0.9 1.5 -1.9 2.2 -2.4 0.3 -0.1 -0.5 -1.8 -2.2 -1.2 -3.7 0.3 -1.3 0.6 -2.6 2 -3.2 z m12.5 0.1 c0.6 0.2 1.3 1.1 0.2 0.9 -1.4 -0.1 -1.4 -0.3 -0.2 -0.9 z" fill="#5c5c70" />
|
||||||
<path d="m47 42.33 c2 0.1 4.1 0.5 6.1 -0.3 1.4 -0.3 2.6 0.8 3.6 1.6 0.7 0.4 2.5 0.7 2.7 1.2 -2.2 -0.1 -3.6 0.4 -4.8 -0.4 -1 -0.7 -2.2 -0.3 -3 -0.2 -0.9 0.1 -3 -0.4 -5.5 -0.2 -2.6 0.2 -5.1 -0.1 -7.2 0.5 -3.6 0.6 -3.7 0 -3.7 0 2.2 -2 9.1 -1.7 11.9 -2.2 z" fill="#999999"/>
|
<path d="m47 42.33 c2 0.1 4.1 0.5 6.1 -0.3 1.4 -0.3 2.6 0.8 3.6 1.6 0.7 0.4 2.5 0.7 2.7 1.2 -2.2 -0.1 -3.6 0.4 -4.8 -0.4 -1 -0.7 -2.2 -0.3 -3 -0.2 -0.9 0.1 -3 -0.4 -5.5 -0.2 -2.6 0.2 -5.1 -0.1 -7.2 0.5 -3.6 0.6 -3.7 0 -3.7 0 2.2 -2 9.1 -1.7 11.9 -2.2 z" fill="#999999"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="relief-palm-1" viewBox="0 0 100 100">
|
<symbol id="relief-palm-1" viewBox="0 0 100 100">
|
||||||
<path d="m 48.1,55.5 2.1,0 c 0,0 1.3,-5.5 1.2,-8.6 0,-3.2 -1.1,-5.5 -1.1,-5.5 l -0.5,-0.4 -0.2,0.1 c 0,0 0.9,2.7 0.5,6.2 -0.5,3.8 -2.1,8.2 -2.1,8.2 z" fill="#5c5c70"></path>
|
<path d="m 48.1,55.5 2.1,0 c 0,0 1.3,-5.5 1.2,-8.6 0,-3.2 -1.1,-5.5 -1.1,-5.5 l -0.5,-0.4 -0.2,0.1 c 0,0 0.9,2.7 0.5,6.2 -0.5,3.8 -2.1,8.2 -2.1,8.2 z" fill="#5c5c70" />
|
||||||
<path d="m 54.9,48.8 c 0,0 1.9,-2.5 0.3,-5.4 -1.4,-2.6 -4.3,-3.2 -4.3,-3.2 0,0 1.6,-0.6 3.3,-0.3 1.7,0.3 4.1,2.5 4.1,2.5 0,0 -0.6,-3.6 -3.6,-4.4 -2.2,-0.6 -4.2,1.3 -4.2,1.3 0,0 0.3,-1.5 -0.2,-2.9 -0.6,-1.4 -2.6,-1.9 -2.6,-1.9 0,0 0.8,1.1 1.2,2.2 0.3,0.9 0.3,2 0.3,2 0,0 -1.3,-1.8 -3.7,-1.5 -2.5,0.2 -3.7,2.5 -3.7,2.5 0,0 2.3,-0.6 3.4,-0.6 1.1,0.1 2.6,0.8 2.6,0.8 l -0.4,0.2 c 0,0 -1.2,-0.4 -2.7,0.4 -1.9,1.1 -2.9,3.7 -2.9,3.7 0,0 1.4,-1.4 2.3,-1.9 0.5,-0.3 1.8,-0.7 1.8,-0.7 0,0 -0.7,1.3 -0.9,3.1 -0.1,2.5 1.1,4.6 1.1,4.6 0,0 0.1,-3.4 1.2,-5.6 1,-1.9 2.3,-2.6 2.3,-2.6 l 0.4,-0.2 c 0,0 1.5,0.7 2.8,2.8 1,1.7 2.3,5 2.3,5 z" fill="#fff" stroke="#5c5c70" stroke-width=".6"></path>
|
<path d="m 54.9,48.8 c 0,0 1.9,-2.5 0.3,-5.4 -1.4,-2.6 -4.3,-3.2 -4.3,-3.2 0,0 1.6,-0.6 3.3,-0.3 1.7,0.3 4.1,2.5 4.1,2.5 0,0 -0.6,-3.6 -3.6,-4.4 -2.2,-0.6 -4.2,1.3 -4.2,1.3 0,0 0.3,-1.5 -0.2,-2.9 -0.6,-1.4 -2.6,-1.9 -2.6,-1.9 0,0 0.8,1.1 1.2,2.2 0.3,0.9 0.3,2 0.3,2 0,0 -1.3,-1.8 -3.7,-1.5 -2.5,0.2 -3.7,2.5 -3.7,2.5 0,0 2.3,-0.6 3.4,-0.6 1.1,0.1 2.6,0.8 2.6,0.8 l -0.4,0.2 c 0,0 -1.2,-0.4 -2.7,0.4 -1.9,1.1 -2.9,3.7 -2.9,3.7 0,0 1.4,-1.4 2.3,-1.9 0.5,-0.3 1.8,-0.7 1.8,-0.7 0,0 -0.7,1.3 -0.9,3.1 -0.1,2.5 1.1,4.6 1.1,4.6 0,0 0.1,-3.4 1.2,-5.6 1,-1.9 2.3,-2.6 2.3,-2.6 l 0.4,-0.2 c 0,0 1.5,0.7 2.8,2.8 1,1.7 2.3,5 2.3,5 z" fill="#fff" stroke="#5c5c70" stroke-width=".6" />
|
||||||
<path d="m 47.75,34.61 c 0,0 0.97,1.22 1.22,2.31 0.2,0.89 0.35,2.81 0.35,2.81 0,0 -1.59,-1.5 -3.2,-1.61 -1.82,-0.13 -3.97,1.31 -3.97,1.31 0,0 2.11,-0.49 3.34,-0.47 1.51,0.03 3.33,1.21 3.33,1.21 0,0 -1.7,0.83 -2.57,2.8 -0.88,1.97 -0.34,6.01 -0.34,6.01 0,0 0.04,-2.95 0.94,-4.96 0.8,-1.78 2.11,-2.67 2.44,-2.85 0.66,-0.34 0.49,-1.09 0.49,-1.09 0,0 -0.1,-2.18 -0.52,-3.37 -0.42,-1.21 -1.51,-2.11 -1.51,-2.11 z" fill="#999"></path>
|
<path d="m 47.75,34.61 c 0,0 0.97,1.22 1.22,2.31 0.2,0.89 0.35,2.81 0.35,2.81 0,0 -1.59,-1.5 -3.2,-1.61 -1.82,-0.13 -3.97,1.31 -3.97,1.31 0,0 2.11,-0.49 3.34,-0.47 1.51,0.03 3.33,1.21 3.33,1.21 0,0 -1.7,0.83 -2.57,2.8 -0.88,1.97 -0.34,6.01 -0.34,6.01 0,0 0.04,-2.95 0.94,-4.96 0.8,-1.78 2.11,-2.67 2.44,-2.85 0.66,-0.34 0.49,-1.09 0.49,-1.09 0,0 -0.1,-2.18 -0.52,-3.37 -0.42,-1.21 -1.51,-2.11 -1.51,-2.11 z" fill="#999" />
|
||||||
<path d="m 42,43.7 c 0,0 1.2,-1.1 1.8,-1.5 0.7,-0.4 2,-0.8 2,-0.8 L 46.5,40.5 c 0,0 -0.8,0 -2.3,0.8 -1.3,0.8 -2.2,2.3 -2.2,2.3 z" fill="#999"></path>
|
<path d="m 42,43.7 c 0,0 1.2,-1.1 1.8,-1.5 0.7,-0.4 2,-0.8 2,-0.8 L 46.5,40.5 c 0,0 -0.8,0 -2.3,0.8 -1.3,0.8 -2.2,2.3 -2.2,2.3 z" fill="#999" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="relief-grass-1" viewBox="0 0 100 100">
|
<symbol id="relief-grass-1" viewBox="0 0 100 100">
|
||||||
<path d="m 49.5,53.1 c 0,-3.4 -2.4,-4.8 -3,-5.4 1,1.8 2.4,3.7 1.8,5.4 z M 51,53.2 C 51.4,49.6 49.6,47.9 48,46.8 c 1.1,1.8 2.8,4.6 1.8,6.5 z M 51.4,51.4 c 0.6,-1.9 1.8,-3.4 3,-4.3 -0.8,0.3 -2.9,1.5 -3.4,2.8 0.2,0.4 0.3,0.8 0.4,1.5 z M 52.9,53.2 c -0.7,-1.9 0.5,-3.3 1.5,-4.4 -1.7,1 -3,2.2 -2.7,4.4 z" fill="#5c5c70" stroke="none"></path>
|
<path d="m 49.5,53.1 c 0,-3.4 -2.4,-4.8 -3,-5.4 1,1.8 2.4,3.7 1.8,5.4 z M 51,53.2 C 51.4,49.6 49.6,47.9 48,46.8 c 1.1,1.8 2.8,4.6 1.8,6.5 z M 51.4,51.4 c 0.6,-1.9 1.8,-3.4 3,-4.3 -0.8,0.3 -2.9,1.5 -3.4,2.8 0.2,0.4 0.3,0.8 0.4,1.5 z M 52.9,53.2 c -0.7,-1.9 0.5,-3.3 1.5,-4.4 -1.7,1 -3,2.2 -2.7,4.4 z" fill="#5c5c70" stroke="none" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="relief-swamp-1" viewBox="0 0 100 100">
|
<symbol id="relief-swamp-1" viewBox="0 0 100 100">
|
||||||
<path d="m 50,46 v 6 m 0,0 3,-4 m -3,4 -3,-4 m -6,4.5 h 3 m 4,0 h 4 m 4,0 3,0" fill="none" stroke="#5c5c70" stroke-linecap="round"></path>
|
<path d="m 50,46 v 6 m 0,0 3,-4 m -3,4 -3,-4 m -6,4.5 h 3 m 4,0 h 4 m 4,0 3,0" fill="none" stroke="#5c5c70" stroke-linecap="round" />
|
||||||
</symbol>
|
</symbol>
|
||||||
<symbol id="relief-dune-1" viewBox="0 0 100 100">
|
<symbol id="relief-dune-1" viewBox="0 0 100 100">
|
||||||
<path d="m 28.7,52.8 c 5,-3.9 10,-8.2 15.8,-8.3 4.5,0 10.8,3.8 15.2,6.5 3.5,2.2 6.8,2 6.8,2" fill="none" stroke="#5c5c70" stroke-width="1.8"></path>
|
<path d="m 28.7,52.8 c 5,-3.9 10,-8.2 15.8,-8.3 4.5,0 10.8,3.8 15.2,6.5 3.5,2.2 6.8,2 6.8,2" fill="none" stroke="#5c5c70" stroke-width="1.8" />
|
||||||
<path d="m 44.2,47.6 c -3.2,3.2 3.5,5.7 5.9,7.8" fill="none" stroke="#5c5c70"></path>
|
<path d="m 44.2,47.6 c -3.2,3.2 3.5,5.7 5.9,7.8" fill="none" stroke="#5c5c70" />
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="relief-mount-2-bw" viewBox="-5 -5 50 50">
|
<symbol id="relief-mount-2-bw" viewBox="-5 -5 50 50">
|
||||||
|
|
@ -4189,9 +4226,9 @@
|
||||||
<use href="#sL" transform="rotate(-56.25)"/>
|
<use href="#sL" transform="rotate(-56.25)"/>
|
||||||
<g stroke-width="8" stroke-opacity="1" shape-rendering="geometricprecision">
|
<g stroke-width="8" stroke-opacity="1" shape-rendering="geometricprecision">
|
||||||
<circle r="9" stroke="#000000" fill="#1b1b1b"/>
|
<circle r="9" stroke="#000000" fill="#1b1b1b"/>
|
||||||
<circle r="75" stroke="#008000" fill="#ffffff" fill-opacity=".1"></circle>
|
<circle r="75" stroke="#008000" fill="#ffffff" fill-opacity=".1" />
|
||||||
<circle r="212" stroke="#1b1b1b"></circle>
|
<circle r="212" stroke="#1b1b1b" />
|
||||||
<circle r="211" stroke="#008000" fill="#ffffff" fill-opacity=".1"></circle>
|
<circle r="211" stroke="#008000" fill="#ffffff" fill-opacity=".1" />
|
||||||
</g>
|
</g>
|
||||||
<g stroke="#1b1b1b" stroke-opacity="1" shape-rendering="geometricprecision">
|
<g stroke="#1b1b1b" stroke-opacity="1" shape-rendering="geometricprecision">
|
||||||
<circle r="71"/>
|
<circle r="71"/>
|
||||||
|
|
@ -4260,11 +4297,13 @@
|
||||||
<script src="modules/ui/layers.js"></script>
|
<script src="modules/ui/layers.js"></script>
|
||||||
<script src="modules/ui/measurers.js"></script>
|
<script src="modules/ui/measurers.js"></script>
|
||||||
|
|
||||||
|
<script defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
|
||||||
<script defer src="modules/ui/general.js"></script>
|
<script defer src="modules/ui/general.js"></script>
|
||||||
<script defer src="modules/ui/options.js"></script>
|
<script defer src="modules/ui/options.js"></script>
|
||||||
<script defer src="modules/ui/style.js"></script>
|
<script defer src="modules/ui/style.js"></script>
|
||||||
<script defer src="modules/save.js"></script>
|
<script defer src="modules/save.js"></script>
|
||||||
<script defer src="modules/load.js"></script>
|
<script defer src="modules/load.js"></script>
|
||||||
|
<script defer src="modules/cloud.js"></script>
|
||||||
<script defer src="main.js"></script>
|
<script defer src="main.js"></script>
|
||||||
<script defer src="modules/relief-icons.js"></script>
|
<script defer src="modules/relief-icons.js"></script>
|
||||||
<script defer src="modules/ui/tools.js"></script>
|
<script defer src="modules/ui/tools.js"></script>
|
||||||
|
|
|
||||||
191
main.js
191
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.652"; // generator version1
|
const version = "1.66"; // generator version1
|
||||||
document.title += " v" + version;
|
document.title += " v" + version;
|
||||||
|
|
||||||
// Switches to disable/enable logging features
|
// Switches to disable/enable logging features
|
||||||
|
|
@ -220,37 +220,6 @@ void (function checkLoadParameters() {
|
||||||
generateMapOnLoad();
|
generateMapOnLoad();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function loadMapFromURL(maplink, random) {
|
|
||||||
const URL = decodeURIComponent(maplink);
|
|
||||||
|
|
||||||
fetch(URL, {method: "GET", mode: "cors"})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) return response.blob();
|
|
||||||
throw new Error("Cannot load map from URL");
|
|
||||||
})
|
|
||||||
.then(blob => uploadMap(blob))
|
|
||||||
.catch(error => {
|
|
||||||
showUploadErrorMessage(error.message, URL, random);
|
|
||||||
if (random) generateMapOnLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUploadErrorMessage(error, URL, random) {
|
|
||||||
ERROR && console.error(error);
|
|
||||||
alertMessage.innerHTML = `Cannot load map from the ${link(URL, "link provided")}.
|
|
||||||
${random ? `A new random map is generated. ` : ""}
|
|
||||||
Please ensure the linked file is reachable and CORS is allowed on server side`;
|
|
||||||
$("#alert").dialog({
|
|
||||||
title: "Loading error",
|
|
||||||
width: "32em",
|
|
||||||
buttons: {
|
|
||||||
OK: function () {
|
|
||||||
$(this).dialog("close");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateMapOnLoad() {
|
function generateMapOnLoad() {
|
||||||
applyStyleOnLoad(); // apply default of previously selected style
|
applyStyleOnLoad(); // apply default of previously selected style
|
||||||
generate(); // generate map
|
generate(); // generate map
|
||||||
|
|
@ -263,10 +232,12 @@ function focusOn() {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
const params = url.searchParams;
|
const params = url.searchParams;
|
||||||
|
|
||||||
if (params.get("from") === "MFCG" && document.referrer) {
|
const fromMGCG = params.get("from") === "MFCG" && document.referrer;
|
||||||
|
if (fromMGCG) {
|
||||||
if (params.get("seed").length === 13) {
|
if (params.get("seed").length === 13) {
|
||||||
// show back burg from MFCG
|
// show back burg from MFCG
|
||||||
params.set("burg", params.get("seed").slice(-4));
|
const burgSeed = params.get("seed").slice(-4);
|
||||||
|
params.set("burg", burgSeed);
|
||||||
} else {
|
} else {
|
||||||
// select burg for MFCG
|
// select burg for MFCG
|
||||||
findBurgForMFCG(params);
|
findBurgForMFCG(params);
|
||||||
|
|
@ -274,23 +245,33 @@ function focusOn() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const s = +params.get("scale") || 8;
|
const scaleParam = params.get("scale");
|
||||||
let x = +params.get("x");
|
const cellParam = params.get("cell");
|
||||||
let y = +params.get("y");
|
const burgParam = params.get("burg");
|
||||||
|
|
||||||
const c = +params.get("cell");
|
if (scaleParam || cellParam || burgParam) {
|
||||||
if (c) {
|
const scale = +scaleParam || 8;
|
||||||
x = pack.cells.p[c][0];
|
|
||||||
y = pack.cells.p[c][1];
|
if (cellParam) {
|
||||||
|
const cell = +params.get("cell");
|
||||||
|
const [x, y] = pack.cells.p[cell];
|
||||||
|
zoomTo(x, y, scale, 1600);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const b = +params.get("burg");
|
if (burgParam) {
|
||||||
if (b && pack.burgs[b]) {
|
const burg = isNaN(+burgParam) ? pack.burgs.find(burg => burg.name === burgParam) : pack.burgs[+burgParam];
|
||||||
x = pack.burgs[b].x;
|
if (!burg) return;
|
||||||
y = pack.burgs[b].y;
|
|
||||||
|
const {x, y} = burg;
|
||||||
|
zoomTo(x, y, scale, 1600);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x && y) zoomTo(x, y, s, 1600);
|
const x = +params.get("x") || graphWidth / 2;
|
||||||
|
const y = +params.get("y") || graphHeight / 2;
|
||||||
|
zoomTo(x, y, scale, 1600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find burg for MFCG and focus on it
|
// find burg for MFCG and focus on it
|
||||||
|
|
@ -390,7 +371,7 @@ function applyDefaultBiomesSystem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWelcomeMessage() {
|
function showWelcomeMessage() {
|
||||||
const changelog = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "previous version");
|
const changelog = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "previous versions");
|
||||||
const reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit community");
|
const reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit community");
|
||||||
const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server");
|
const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server");
|
||||||
const patreon = link("https://www.patreon.com/azgaar", "Patreon");
|
const patreon = link("https://www.patreon.com/azgaar", "Patreon");
|
||||||
|
|
@ -398,9 +379,10 @@ function showWelcomeMessage() {
|
||||||
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
|
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
|
||||||
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>Main changes:
|
<ul>Main changes:
|
||||||
<li>Ability to add river selecting its cells</li>
|
<li>Save and load <i>.map</i> files to Dropbox</li>
|
||||||
<li>Keep river course on edit</li>
|
<li>Ability to add control points on river edit</li>
|
||||||
<li>Refactor river rendering code</li>
|
<li>New heightmap template: Taklamakan</li>
|
||||||
|
<li>Option to not scale labels on zoom</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||||
|
|
@ -882,8 +864,8 @@ function openNearSeaLakes() {
|
||||||
function defineMapSize() {
|
function defineMapSize() {
|
||||||
const [size, latitude] = getSizeAndLatitude();
|
const [size, latitude] = getSizeAndLatitude();
|
||||||
const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options
|
const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options
|
||||||
if (randomize || !locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = size;
|
if (randomize || !locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = rn(size);
|
||||||
if (randomize || !locked("latitude")) latitudeOutput.value = latitudeInput.value = latitude;
|
if (randomize || !locked("latitude")) latitudeOutput.value = latitudeInput.value = rn(latitude);
|
||||||
|
|
||||||
function getSizeAndLatitude() {
|
function getSizeAndLatitude() {
|
||||||
const template = document.getElementById("templateInput").value; // heightmap template
|
const template = document.getElementById("templateInput").value; // heightmap template
|
||||||
|
|
@ -916,11 +898,11 @@ function calculateMapCoordinates() {
|
||||||
const size = +document.getElementById("mapSizeOutput").value;
|
const size = +document.getElementById("mapSizeOutput").value;
|
||||||
const latShift = +document.getElementById("latitudeOutput").value;
|
const latShift = +document.getElementById("latitudeOutput").value;
|
||||||
|
|
||||||
const latT = (size / 100) * 180;
|
const latT = rn((size / 100) * 180, 1);
|
||||||
const latN = 90 - ((180 - latT) * latShift) / 100;
|
const latN = rn(90 - ((180 - latT) * latShift) / 100, 1);
|
||||||
const latS = latN - latT;
|
const latS = rn(latN - latT, 1);
|
||||||
|
|
||||||
const lon = Math.min(((graphWidth / graphHeight) * latT) / 2, 180);
|
const lon = rn(Math.min(((graphWidth / graphHeight) * latT) / 2, 180));
|
||||||
mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon};
|
mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -959,43 +941,45 @@ function calculateTemperatures() {
|
||||||
function generatePrecipitation() {
|
function generatePrecipitation() {
|
||||||
TIME && console.time("generatePrecipitation");
|
TIME && console.time("generatePrecipitation");
|
||||||
prec.selectAll("*").remove();
|
prec.selectAll("*").remove();
|
||||||
const cells = grid.cells;
|
const {cells, cellsX, cellsY} = grid;
|
||||||
cells.prec = new Uint8Array(cells.i.length); // precipitation array
|
cells.prec = new Uint8Array(cells.i.length); // precipitation array
|
||||||
const modifier = precInput.value / 100; // user's input
|
const modifier = precInput.value / 100; // user's input
|
||||||
const cellsX = grid.cellsX,
|
|
||||||
cellsY = grid.cellsY;
|
|
||||||
let westerly = [],
|
|
||||||
easterly = [],
|
|
||||||
southerly = 0,
|
|
||||||
northerly = 0;
|
|
||||||
|
|
||||||
{
|
const westerly = [];
|
||||||
// latitude bands
|
const easterly = [];
|
||||||
|
let southerly = 0;
|
||||||
|
let northerly = 0;
|
||||||
|
|
||||||
|
// precipitation modifier per latitude band
|
||||||
// x4 = 0-5 latitude: wet through the year (rising zone)
|
// x4 = 0-5 latitude: wet through the year (rising zone)
|
||||||
// x2 = 5-20 latitude: wet summer (rising zone), dry winter (sinking zone)
|
// x2 = 5-20 latitude: wet summer (rising zone), dry winter (sinking zone)
|
||||||
// x1 = 20-30 latitude: dry all year (sinking zone)
|
// x1 = 20-30 latitude: dry all year (sinking zone)
|
||||||
// x2 = 30-50 latitude: wet winter (rising zone), dry summer (sinking zone)
|
// x2 = 30-50 latitude: wet winter (rising zone), dry summer (sinking zone)
|
||||||
// x3 = 50-60 latitude: wet all year (rising zone)
|
// x3 = 50-60 latitude: wet all year (rising zone)
|
||||||
// x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone)
|
// x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone)
|
||||||
// x1 = 70-90 latitude: dry all year (sinking zone)
|
// x1 = 70-85 latitude: dry all year (sinking zone)
|
||||||
}
|
// x0.5 = 85-90 latitude: dry all year (sinking zone)
|
||||||
const lalitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5]; // by 5d step
|
const lalitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5];
|
||||||
|
const MAX_PASSABLE_ELEVATION = 85;
|
||||||
|
|
||||||
// difine wind directions based on cells latitude and prevailing winds there
|
// define wind directions based on cells latitude and prevailing winds there
|
||||||
d3.range(0, cells.i.length, cellsX).forEach(function (c, i) {
|
d3.range(0, cells.i.length, cellsX).forEach(function (c, i) {
|
||||||
const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT;
|
const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT;
|
||||||
const band = ((Math.abs(lat) - 1) / 5) | 0;
|
const latBand = ((Math.abs(lat) - 1) / 5) | 0;
|
||||||
const latMod = lalitudeModifier[band];
|
const latMod = lalitudeModifier[latBand];
|
||||||
const tier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S
|
const windTier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S
|
||||||
if (options.winds[tier] > 40 && options.winds[tier] < 140) westerly.push([c, latMod, tier]);
|
const {isWest, isEast, isNorth, isSouth} = getWindDirections(windTier);
|
||||||
else if (options.winds[tier] > 220 && options.winds[tier] < 320) easterly.push([c + cellsX - 1, latMod, tier]);
|
|
||||||
if (options.winds[tier] > 100 && options.winds[tier] < 260) northerly++;
|
if (isWest) westerly.push([c, latMod, windTier]);
|
||||||
else if (options.winds[tier] > 280 || options.winds[tier] < 80) southerly++;
|
if (isEast) easterly.push([c + cellsX - 1, latMod, windTier]);
|
||||||
|
if (isNorth) northerly++;
|
||||||
|
if (isSouth) southerly++;
|
||||||
});
|
});
|
||||||
|
|
||||||
// distribute winds by direction
|
// distribute winds by direction
|
||||||
if (westerly.length) passWind(westerly, 120 * modifier, 1, cellsX);
|
if (westerly.length) passWind(westerly, 120 * modifier, 1, cellsX);
|
||||||
if (easterly.length) passWind(easterly, 120 * modifier, -1, cellsX);
|
if (easterly.length) passWind(easterly, 120 * modifier, -1, cellsX);
|
||||||
|
|
||||||
const vertT = southerly + northerly;
|
const vertT = southerly + northerly;
|
||||||
if (northerly) {
|
if (northerly) {
|
||||||
const bandN = ((Math.abs(mapCoordinates.latN) - 1) / 5) | 0;
|
const bandN = ((Math.abs(mapCoordinates.latN) - 1) / 5) | 0;
|
||||||
|
|
@ -1003,6 +987,7 @@ function generatePrecipitation() {
|
||||||
const maxPrecN = (northerly / vertT) * 60 * modifier * latModN;
|
const maxPrecN = (northerly / vertT) * 60 * modifier * latModN;
|
||||||
passWind(d3.range(0, cellsX, 1), maxPrecN, cellsX, cellsY);
|
passWind(d3.range(0, cellsX, 1), maxPrecN, cellsX, cellsY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (southerly) {
|
if (southerly) {
|
||||||
const bandS = ((Math.abs(mapCoordinates.latS) - 1) / 5) | 0;
|
const bandS = ((Math.abs(mapCoordinates.latS) - 1) / 5) | 0;
|
||||||
const latModS = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandS];
|
const latModS = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandS];
|
||||||
|
|
@ -1010,20 +995,34 @@ function generatePrecipitation() {
|
||||||
passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY);
|
passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWindDirections(tier) {
|
||||||
|
const angle = options.winds[tier];
|
||||||
|
|
||||||
|
const isWest = angle > 40 && angle < 140;
|
||||||
|
const isEast = angle > 220 && angle < 320;
|
||||||
|
const isNorth = angle > 100 && angle < 260;
|
||||||
|
const isSouth = angle > 280 || angle < 80;
|
||||||
|
|
||||||
|
return {isWest, isEast, isNorth, isSouth};
|
||||||
|
}
|
||||||
|
|
||||||
function passWind(source, maxPrec, next, steps) {
|
function passWind(source, maxPrec, next, steps) {
|
||||||
const maxPrecInit = maxPrec;
|
const maxPrecInit = maxPrec;
|
||||||
|
|
||||||
for (let first of source) {
|
for (let first of source) {
|
||||||
if (first[0]) {
|
if (first[0]) {
|
||||||
maxPrec = Math.min(maxPrecInit * first[1], 255);
|
maxPrec = Math.min(maxPrecInit * first[1], 255);
|
||||||
first = first[0];
|
first = first[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
let humidity = maxPrec - cells.h[first]; // initial water amount
|
let humidity = maxPrec - cells.h[first]; // initial water amount
|
||||||
if (humidity <= 0) continue; // if first cell in row is too elevated cosdired wind dry
|
if (humidity <= 0) continue; // if first cell in row is too elevated cosdired wind dry
|
||||||
|
|
||||||
for (let s = 0, current = first; s < steps; s++, current += next) {
|
for (let s = 0, current = first; s < steps; s++, current += next) {
|
||||||
// no flux on permafrost
|
if (cells.temp[current] < -5) continue; // no flux in permafrost
|
||||||
if (cells.temp[current] < -5) continue;
|
|
||||||
// water cell
|
|
||||||
if (cells.h[current] < 20) {
|
if (cells.h[current] < 20) {
|
||||||
|
// water cell
|
||||||
if (cells.h[current + next] >= 20) {
|
if (cells.h[current + next] >= 20) {
|
||||||
cells.prec[current + next] += Math.max(humidity / rand(10, 20), 1); // coastal precipitation
|
cells.prec[current + next] += Math.max(humidity / rand(10, 20), 1); // coastal precipitation
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1034,16 +1033,16 @@ function generatePrecipitation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// land cell
|
// land cell
|
||||||
const precipitation = getPrecipitation(humidity, current, next);
|
const isPassable = cells.h[current + next] <= MAX_PASSABLE_ELEVATION;
|
||||||
|
const precipitation = isPassable ? getPrecipitation(humidity, current, next) : humidity;
|
||||||
cells.prec[current] += precipitation;
|
cells.prec[current] += precipitation;
|
||||||
const evaporation = precipitation > 1.5 ? 1 : 0; // some humidity evaporates back to the atmosphere
|
const evaporation = precipitation > 1.5 ? 1 : 0; // some humidity evaporates back to the atmosphere
|
||||||
humidity = Math.min(Math.max(humidity - precipitation + evaporation, 0), maxPrec);
|
humidity = isPassable ? Math.min(Math.max(humidity - precipitation + evaporation, 0), maxPrec) : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrecipitation(humidity, i, n) {
|
function getPrecipitation(humidity, i, n) {
|
||||||
if (cells.h[i + n] > 85) return humidity; // 85 is max passable height
|
|
||||||
const normalLoss = Math.max(humidity / (10 * modifier), 1); // precipitation in normal conditions
|
const normalLoss = Math.max(humidity / (10 * modifier), 1); // precipitation in normal conditions
|
||||||
const diff = Math.max(cells.h[i + n] - cells.h[i], 0); // difference in height
|
const diff = Math.max(cells.h[i + n] - cells.h[i], 0); // difference in height
|
||||||
const mod = (cells.h[i + n] / 70) ** 2; // 50 stands for hills, 70 for mountains
|
const mod = (cells.h[i + n] / 70) ** 2; // 50 stands for hills, 70 for mountains
|
||||||
|
|
@ -1361,22 +1360,21 @@ function reMarkFeatures() {
|
||||||
// assign biome id for each cell
|
// assign biome id for each cell
|
||||||
function defineBiomes() {
|
function defineBiomes() {
|
||||||
TIME && console.time("defineBiomes");
|
TIME && console.time("defineBiomes");
|
||||||
const cells = pack.cells,
|
const {cells} = pack;
|
||||||
f = pack.features,
|
const {temp, prec} = grid.cells;
|
||||||
temp = grid.cells.temp,
|
|
||||||
prec = grid.cells.prec;
|
|
||||||
cells.biome = new Uint8Array(cells.i.length); // biomes array
|
cells.biome = new Uint8Array(cells.i.length); // biomes array
|
||||||
|
|
||||||
for (const i of cells.i) {
|
for (const i of cells.i) {
|
||||||
const t = temp[cells.g[i]]; // cell temperature
|
const temperature = temp[cells.g[i]];
|
||||||
const h = cells.h[i]; // cell height
|
const height = cells.h[i];
|
||||||
const m = h < 20 ? 0 : calculateMoisture(i); // cell moisture
|
const moisture = height < 20 ? 0 : calculateMoisture(i);
|
||||||
cells.biome[i] = getBiomeId(m, t, h);
|
cells.biome[i] = getBiomeId(moisture, temperature, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateMoisture(i) {
|
function calculateMoisture(i) {
|
||||||
let moist = prec[cells.g[i]];
|
let moist = prec[cells.g[i]];
|
||||||
if (cells.r[i]) moist += Math.max(cells.fl[i] / 20, 2);
|
if (cells.r[i]) moist += Math.max(cells.fl[i] / 20, 2);
|
||||||
|
|
||||||
const n = cells.c[i]
|
const n = cells.c[i]
|
||||||
.filter(isLand)
|
.filter(isLand)
|
||||||
.map(c => prec[cells.g[c]])
|
.map(c => prec[cells.g[c]])
|
||||||
|
|
@ -1389,12 +1387,13 @@ function defineBiomes() {
|
||||||
|
|
||||||
// assign biome id to a cell
|
// assign biome id to a cell
|
||||||
function getBiomeId(moisture, temperature, height) {
|
function getBiomeId(moisture, temperature, height) {
|
||||||
if (temperature < -5) return 11; // permafrost biome, including sea ice
|
if (height < 20) return 0; // marine biome: all water cells
|
||||||
if (height < 20) return 0; // marine biome: liquid water cells
|
if (temperature < -5) return 11; // permafrost biome
|
||||||
if (moisture > 40 && temperature > -2 && (height < 25 || (moisture > 24 && height > 24))) return 12; // wetland biome
|
if (moisture > 40 && temperature > -2 && (height < 25 || (moisture > 24 && height > 24 && height < 60))) return 12; // wetland biome
|
||||||
const m = Math.min((moisture / 5) | 0, 4); // moisture band from 0 to 4
|
|
||||||
const t = Math.min(Math.max(20 - temperature, 0), 25); // temparature band from 0 to 25
|
const moistureBand = Math.min((moisture / 5) | 0, 4); // [0-4]
|
||||||
return biomesData.biomesMartix[m][t];
|
const temperatureBand = Math.min(Math.max(20 - temperature, 0), 25); // [0-25]
|
||||||
|
return biomesData.biomesMartix[moistureBand][temperatureBand];
|
||||||
}
|
}
|
||||||
|
|
||||||
// assess cells suitability to calculate population and rand cells for culture center and burgs placement
|
// assess cells suitability to calculate population and rand cells for culture center and burgs placement
|
||||||
|
|
|
||||||
|
|
@ -975,6 +975,7 @@ window.BurgsAndStates = (function () {
|
||||||
// Default name depends on exponent tier, some culture bases have special names for tiers
|
// Default name depends on exponent tier, some culture bases have special names for tiers
|
||||||
if (s.diplomacy) {
|
if (s.diplomacy) {
|
||||||
if (form === "Duchy" && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes("Vassal")) return "Marches"; // some vassal dutchies on borderland
|
if (form === "Duchy" && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes("Vassal")) return "Marches"; // some vassal dutchies on borderland
|
||||||
|
if (base === 1 && P(0.3) && s.diplomacy.includes("Vassal")) return "Dominion"; // English vassals
|
||||||
if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals
|
if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
141
modules/cloud.js
Normal file
141
modules/cloud.js
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cloud provider implementations (Dropbox only as now)
|
||||||
|
|
||||||
|
provider Interface:
|
||||||
|
|
||||||
|
name: name of the provider
|
||||||
|
async auth(): authenticate and get access tokens from provider
|
||||||
|
async save(filename): save map file to provider as filename
|
||||||
|
async load(filename): load filename from provider
|
||||||
|
async list(): list available filenames at provider
|
||||||
|
async getLink(filePath): get shareable link for file
|
||||||
|
restore(): restore access tokens from storage if possible
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
window.Cloud = (function () {
|
||||||
|
|
||||||
|
// helpers to use in providers for token handling
|
||||||
|
const lSKey = x => `auth-${x}`
|
||||||
|
const setToken = (prov, key) => localStorage.setItem(lSKey(prov), key)
|
||||||
|
const getToken = prov => localStorage.getItem(lSKey(prov))
|
||||||
|
|
||||||
|
/**********************************************************/
|
||||||
|
/* Dropbox provider */
|
||||||
|
/**********************************************************/
|
||||||
|
|
||||||
|
const DBP = {
|
||||||
|
name: 'dropbox',
|
||||||
|
clientId: 'sp7tzwm27u2w5ns',
|
||||||
|
authWindow: undefined,
|
||||||
|
token: null, // Access token
|
||||||
|
api: null,
|
||||||
|
|
||||||
|
restore() {
|
||||||
|
this.token = getToken(this.name)
|
||||||
|
if (this.token) this.connect(this.token)
|
||||||
|
},
|
||||||
|
|
||||||
|
async call(name, param) {
|
||||||
|
try {
|
||||||
|
return await this.api[name](param)
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name !== "DropboxResponseError") throw(e)
|
||||||
|
// retry with auth
|
||||||
|
await this.auth()
|
||||||
|
return await this.api[name](param)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
connect(token) {
|
||||||
|
const clientId = this.clientId
|
||||||
|
const auth = new Dropbox.DropboxAuth({ clientId })
|
||||||
|
auth.setAccessToken(token)
|
||||||
|
this.api = new Dropbox.Dropbox({ auth })
|
||||||
|
},
|
||||||
|
|
||||||
|
async save(fileName, contents) {
|
||||||
|
if (!this.api) await this.auth()
|
||||||
|
const resp = this.call('filesUpload', { path: '/' + fileName, contents })
|
||||||
|
console.log("Dropbox response:", resp)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
async load(path) {
|
||||||
|
if (!this.api) await this.auth()
|
||||||
|
const resp = await this.call('filesDownload', { path })
|
||||||
|
const blob = resp.result.fileBlob
|
||||||
|
if (!blob) throw(new Error('Invalid response from dropbox.'))
|
||||||
|
return blob
|
||||||
|
},
|
||||||
|
|
||||||
|
async list() {
|
||||||
|
if (!this.api) return null
|
||||||
|
const resp = await this.call('filesListFolder', { path: '' })
|
||||||
|
return resp.result.entries.map(e => ({ name: e.name, path: e.path_lower }))
|
||||||
|
},
|
||||||
|
|
||||||
|
auth() {
|
||||||
|
const url = window.location.origin + window.location.pathname + 'dropbox.html'
|
||||||
|
this.authWindow = window.open(url, 'auth', 'width=640,height=480')
|
||||||
|
// child window expected to call
|
||||||
|
// window.opener.Cloud.providers.dropbox.setDropBoxToken (see below)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const watchDog = () => {
|
||||||
|
this.authWindow.close()
|
||||||
|
reject(new Error("Timeout. No auth for dropbox."))
|
||||||
|
}
|
||||||
|
setTimeout(watchDog, 120*1000)
|
||||||
|
window.addEventListener('dropboxauth', e => {
|
||||||
|
clearTimeout(watchDog)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// Callback function for auth window.
|
||||||
|
setDropBoxToken(token) {
|
||||||
|
console.log('Access token got:', token)
|
||||||
|
setToken(this.name, token)
|
||||||
|
this.connect(token)
|
||||||
|
this.authWindow.close()
|
||||||
|
window.dispatchEvent(new Event('dropboxauth'))
|
||||||
|
},
|
||||||
|
|
||||||
|
async getLink(path) {
|
||||||
|
if (!this.api) await this.auth()
|
||||||
|
let resp
|
||||||
|
|
||||||
|
// already exists?
|
||||||
|
resp = await this.call('sharingListSharedLinks', { path })
|
||||||
|
if (resp.result.links.length)
|
||||||
|
return resp.result.links[0].url
|
||||||
|
|
||||||
|
// create new
|
||||||
|
resp = await this.call('sharingCreateSharedLinkWithSettings', {
|
||||||
|
path,
|
||||||
|
settings: {
|
||||||
|
require_password: false,
|
||||||
|
audience: 'public',
|
||||||
|
access: 'viewer',
|
||||||
|
requested_visibility: 'public',
|
||||||
|
allow_download: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log("dropbox link object:", resp.result)
|
||||||
|
return resp.result.url
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// register providers here:
|
||||||
|
const providers = {
|
||||||
|
dropbox: DBP,
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore all providers at startup
|
||||||
|
for (const p of Object.values(providers)) p.restore()
|
||||||
|
|
||||||
|
return { providers }
|
||||||
|
})()
|
||||||
|
|
@ -379,14 +379,16 @@ window.HeightmapGenerator = (function () {
|
||||||
const modify = function (range, add, mult, power) {
|
const modify = function (range, add, mult, power) {
|
||||||
const min = range === "land" ? 20 : range === "all" ? 0 : +range.split("-")[0];
|
const min = range === "land" ? 20 : range === "all" ? 0 : +range.split("-")[0];
|
||||||
const max = range === "land" || range === "all" ? 100 : +range.split("-")[1];
|
const max = range === "land" || range === "all" ? 100 : +range.split("-")[1];
|
||||||
grid.cells.h = grid.cells.h.map(h => (h >= min && h <= max ? mod(h) : h));
|
const isLand = min === 20;
|
||||||
|
|
||||||
function mod(v) {
|
grid.cells.h = grid.cells.h.map(h => {
|
||||||
if (add) v = min === 20 ? Math.max(v + add, 20) : v + add;
|
if (h < min || h > max) return h;
|
||||||
if (mult !== 1) v = min === 20 ? (v - 20) * mult + 20 : v * mult;
|
|
||||||
if (power) v = min === 20 ? (v - 20) ** power + 20 : v ** power;
|
if (add) h = isLand ? Math.max(h + add, 20) : h + add;
|
||||||
return lim(v);
|
if (mult !== 1) h = isLand ? (h - 20) * mult + 20 : h * mult;
|
||||||
}
|
if (power) h = isLand ? (h - 20) ** power + 20 : h ** power;
|
||||||
|
return lim(h);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const smooth = function (fr = 2, add = 0) {
|
const smooth = function (fr = 2, add = 0) {
|
||||||
|
|
|
||||||
192
modules/load.js
192
modules/load.js
|
|
@ -12,6 +12,36 @@ function quickLoad() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadFromDropbox(fileName) {
|
||||||
|
const map = document.querySelector("#loadFromDropbox select").value;
|
||||||
|
console.log('loading dropbox map', map);
|
||||||
|
const blob = await Cloud.providers.dropbox.load(map);
|
||||||
|
uploadMap(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSharableDropboxLink() {
|
||||||
|
const mapFile = document.querySelector("#loadFromDropbox select").value;
|
||||||
|
const sharableLink = document.getElementById("sharableLink");
|
||||||
|
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||||
|
let url
|
||||||
|
try {
|
||||||
|
url = await Cloud.providers.dropbox.getLink(mapFile);
|
||||||
|
} catch {
|
||||||
|
tip("Dropbox API error. Can not create link.", true, "error", 2000);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fmg = window.location.href.split("?")[0];
|
||||||
|
const reallink= `${fmg}?maplink=${url}`;
|
||||||
|
// voodoo magic required by the yellow god of CORS
|
||||||
|
const link = reallink.replace('www.dropbox.com/s/', 'dl.dropboxusercontent.com/1/view/')
|
||||||
|
const shortLink = link.slice(0, 50) + "...";
|
||||||
|
|
||||||
|
sharableLinkContainer.style.display = "block";
|
||||||
|
sharableLink.innerText = shortLink;
|
||||||
|
sharableLink.setAttribute("href", link);
|
||||||
|
}
|
||||||
|
|
||||||
function loadMapPrompt(blob) {
|
function loadMapPrompt(blob) {
|
||||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||||
if (workingTime < 5) {
|
if (workingTime < 5) {
|
||||||
|
|
@ -46,54 +76,111 @@ function loadMapPrompt(blob) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadMapFromURL(maplink, random) {
|
||||||
|
const URL = decodeURIComponent(maplink);
|
||||||
|
|
||||||
|
fetch(URL, {method: "GET", mode: "cors"})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) return response.blob();
|
||||||
|
throw new Error("Cannot load map from URL");
|
||||||
|
})
|
||||||
|
.then(blob => uploadMap(blob))
|
||||||
|
.catch(error => {
|
||||||
|
showUploadErrorMessage(error.message, URL, random);
|
||||||
|
if (random) generateMapOnLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUploadErrorMessage(error, URL, random) {
|
||||||
|
ERROR && console.error(error);
|
||||||
|
alertMessage.innerHTML = `Cannot load map from the ${link(URL, "link provided")}.
|
||||||
|
${random ? `A new random map is generated. ` : ""}
|
||||||
|
Please ensure the linked file is reachable and CORS is allowed on server side`;
|
||||||
|
$("#alert").dialog({
|
||||||
|
title: "Loading error",
|
||||||
|
width: "32em",
|
||||||
|
buttons: {
|
||||||
|
OK: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function uploadMap(file, callback) {
|
function uploadMap(file, callback) {
|
||||||
uploadMap.timeStart = performance.now();
|
uploadMap.timeStart = performance.now();
|
||||||
|
const OLDEST_SUPPORTED_VERSION = 0.7;
|
||||||
|
const currentVersion = parseFloat(version);
|
||||||
|
|
||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
fileReader.onload = function (fileLoadedEvent) {
|
fileReader.onload = function (fileLoadedEvent) {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
|
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
|
||||||
|
const result = fileLoadedEvent.target.result;
|
||||||
|
const [mapData, mapVersion] = parseLoadedResult(result);
|
||||||
|
|
||||||
const dataLoaded = fileLoadedEvent.target.result;
|
const isInvalid = !mapData || isNaN(mapVersion) || mapData.length < 26 || !mapData[5];
|
||||||
const data = dataLoaded.split("\r\n");
|
const isUpdated = mapVersion === currentVersion;
|
||||||
|
const isAncient = mapVersion < OLDEST_SUPPORTED_VERSION;
|
||||||
|
const isNewer = mapVersion > currentVersion;
|
||||||
|
const isOutdated = mapVersion < currentVersion;
|
||||||
|
|
||||||
const mapVersion = data[0].split("|")[0] || data[0];
|
if (isInvalid) return showUploadMessage("invalid", mapData, mapVersion);
|
||||||
if (mapVersion === version) {
|
if (isUpdated) return parseLoadedData(mapData);
|
||||||
parseLoadedData(data);
|
if (isAncient) return showUploadMessage("ancient", mapData, mapVersion);
|
||||||
return;
|
if (isNewer) return showUploadMessage("newer", mapData, mapVersion);
|
||||||
}
|
if (isOutdated) return showUploadMessage("outdated", mapData, mapVersion);
|
||||||
|
|
||||||
const archive = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "archived version");
|
|
||||||
const parsed = parseFloat(mapVersion);
|
|
||||||
let message = "",
|
|
||||||
load = false;
|
|
||||||
if (isNaN(parsed) || data.length < 26 || !data[5]) {
|
|
||||||
message = `The file you are trying to load is outdated or not a valid .map file.
|
|
||||||
<br>Please try to open it using an ${archive}`;
|
|
||||||
} else if (parsed < 0.7) {
|
|
||||||
message = `The map version you are trying to load (${mapVersion}) is too old and cannot be updated to the current version.
|
|
||||||
<br>Please keep using an ${archive}`;
|
|
||||||
} else {
|
|
||||||
load = true;
|
|
||||||
message = `The map version (${mapVersion}) does not match the Generator version (${version}).
|
|
||||||
<br>Click OK to get map <b>auto-updated</b>. In case of issues please keep using an ${archive} of the Generator`;
|
|
||||||
}
|
|
||||||
alertMessage.innerHTML = message;
|
|
||||||
$("#alert").dialog({
|
|
||||||
title: "Version conflict",
|
|
||||||
width: "38em",
|
|
||||||
buttons: {
|
|
||||||
OK: function () {
|
|
||||||
$(this).dialog("close");
|
|
||||||
if (load) parseLoadedData(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fileReader.readAsText(file, "UTF-8");
|
fileReader.readAsText(file, "UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseLoadedResult(result) {
|
||||||
|
try {
|
||||||
|
// data can be in FMG internal format or base64 encoded
|
||||||
|
const isDelimited = result.substr(0, 10).includes("|");
|
||||||
|
const decoded = isDelimited ? result : decodeURIComponent(atob(result));
|
||||||
|
const mapData = decoded.split("\r\n");
|
||||||
|
const mapVersion = parseFloat(mapData[0].split("|")[0] || mapData[0]);
|
||||||
|
return [mapData, mapVersion];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUploadMessage(type, mapData, mapVersion) {
|
||||||
|
const archive = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "archived version");
|
||||||
|
let message, title, canBeLoaded;
|
||||||
|
|
||||||
|
if (type === "invalid") {
|
||||||
|
message = `The file does not look like a valid <i>.map</i> file.<br>Please check the data format`;
|
||||||
|
title = "Invalid file";
|
||||||
|
canBeLoaded = false;
|
||||||
|
} else if (type === "ancient") {
|
||||||
|
message = `The map version you are trying to load (${mapVersion}) is too old and cannot be updated to the current version.<br>Please keep using an ${archive}`;
|
||||||
|
title = "Ancient file";
|
||||||
|
canBeLoaded = false;
|
||||||
|
} else if (type === "newer") {
|
||||||
|
message = `The map version you are trying to load (${mapVersion}) is newer than the current version.<br>Please load the file in the appropriate version`;
|
||||||
|
title = "Newer file";
|
||||||
|
canBeLoaded = false;
|
||||||
|
} else if (type === "outdated") {
|
||||||
|
message = `The map version (${mapVersion}) does not match the Generator version (${version}).<br>Click OK to get map <b>auto-updated</b>.<br>In case of issues please keep using an ${archive} of the Generator`;
|
||||||
|
title = "Outdated file";
|
||||||
|
canBeLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
alertMessage.innerHTML = message;
|
||||||
|
const buttons = {
|
||||||
|
OK: function () {
|
||||||
|
$(this).dialog("close");
|
||||||
|
if (canBeLoaded) parseLoadedData(mapData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$("#alert").dialog({title, buttons});
|
||||||
|
}
|
||||||
|
|
||||||
function parseLoadedData(data) {
|
function parseLoadedData(data) {
|
||||||
try {
|
try {
|
||||||
// exit customization
|
// exit customization
|
||||||
|
|
@ -710,29 +797,40 @@ function parseLoadedData(data) {
|
||||||
|
|
||||||
if (version < 1.65) {
|
if (version < 1.65) {
|
||||||
// v 1.65 changed rivers data
|
// v 1.65 changed rivers data
|
||||||
rivers.attr("style", null); // remove style to unhide layer
|
d3.select("#rivers").attr("style", null); // remove style to unhide layer
|
||||||
|
const {cells, rivers} = pack;
|
||||||
|
|
||||||
for (const river of pack.rivers) {
|
for (const river of rivers) {
|
||||||
const node = document.getElementById("river" + river.i);
|
const node = document.getElementById("river" + river.i);
|
||||||
if (node && !river.cells) {
|
if (node && !river.cells) {
|
||||||
const riverCells = new Set();
|
const riverCells = [];
|
||||||
|
const riverPoints = [];
|
||||||
|
|
||||||
const length = node.getTotalLength() / 2;
|
const length = node.getTotalLength() / 2;
|
||||||
const segments = Math.ceil(length / 6);
|
const segments = Math.ceil(length / 6);
|
||||||
const increment = length / segments;
|
const increment = length / segments;
|
||||||
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
|
|
||||||
const p1 = node.getPointAtLength(i);
|
for (let i = 0; i <= segments; i++) {
|
||||||
const p2 = node.getPointAtLength(c);
|
const shift = increment * i;
|
||||||
const x = (p1.x + p2.x) / 2;
|
const {x: x1, y: y1} = node.getPointAtLength(length + shift);
|
||||||
const y = (p1.y + p2.y) / 2;
|
const {x: x2, y: y2} = node.getPointAtLength(length - shift);
|
||||||
const cell = findCell(x, y, 6);
|
const x = rn((x1 + x2) / 2, 1);
|
||||||
if (cell) riverCells.add(cell);
|
const y = rn((y1 + y2) / 2, 1);
|
||||||
|
|
||||||
|
const cell = findCell(x, y);
|
||||||
|
riverPoints.push([x, y]);
|
||||||
|
riverCells.push(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
river.cells = Array.from(riverCells);
|
river.cells = riverCells;
|
||||||
|
river.points = riverPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
pack.cells.i.forEach(i => {
|
river.widthFactor = 1;
|
||||||
if (pack.cells.r[i] && pack.cells.h[i] < 20) pack.cells.r[i] = 0;
|
|
||||||
|
cells.i.forEach(i => {
|
||||||
|
const riverInWater = cells.r[i] && cells.h[i] < 20;
|
||||||
|
if (riverInWater) cells.r[i] = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,6 @@ window.Names = (function () {
|
||||||
// parse word to get a final name
|
// parse word to get a final name
|
||||||
const l = last(w); // last letter
|
const l = last(w); // last letter
|
||||||
if (l === "'" || l === " " || l === "-") w = w.slice(0, -1); // not allow some characters at the end
|
if (l === "'" || l === " " || l === "-") w = w.slice(0, -1); // not allow some characters at the end
|
||||||
const basic = !/[^\u0000-\u007f]/.test(w); // true if word has only basic characters
|
|
||||||
|
|
||||||
let name = [...w].reduce(function (r, c, i, d) {
|
let name = [...w].reduce(function (r, c, i, d) {
|
||||||
if (c === d[i + 1] && !dupl.includes(c)) return r; // duplication is not allowed
|
if (c === d[i + 1] && !dupl.includes(c)) return r; // duplication is not allowed
|
||||||
|
|
@ -108,7 +107,6 @@ window.Names = (function () {
|
||||||
if (r.slice(-1) === " ") return r + c.toUpperCase(); // capitalize letter after space
|
if (r.slice(-1) === " ") return r + c.toUpperCase(); // capitalize letter after space
|
||||||
if (r.slice(-1) === "-") return r + c.toUpperCase(); // capitalize letter after hyphen
|
if (r.slice(-1) === "-") return r + c.toUpperCase(); // capitalize letter after hyphen
|
||||||
if (c === "a" && d[i + 1] === "e") return r; // "ae" => "e"
|
if (c === "a" && d[i + 1] === "e") return r; // "ae" => "e"
|
||||||
if (basic && i + 1 < d.length && !vowel(c) && !vowel(d[i - 1]) && !vowel(d[i + 1])) return r; // remove consonant between 2 consonants
|
|
||||||
if (i + 2 < d.length && c === d[i + 1] && c === d[i + 2]) return r; // remove three same letters in a row
|
if (i + 2 < d.length && c === d[i + 1] && c === d[i + 2]) return r; // remove three same letters in a row
|
||||||
return r + c;
|
return r + c;
|
||||||
}, "");
|
}, "");
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ window.Rivers = (function () {
|
||||||
const lakeOutCells = Lakes.setClimateData(h);
|
const lakeOutCells = Lakes.setClimateData(h);
|
||||||
|
|
||||||
land.forEach(function (i) {
|
land.forEach(function (i) {
|
||||||
cells.fl[i] += prec[cells.g[i]] * area(i) / 1000; // add flux from precipitation
|
cells.fl[i] += (prec[cells.g[i]] * area[i]) / 100; // add flux from precipitation
|
||||||
|
|
||||||
// create lake outlet if lake is not in deep depression and flux > evaporation
|
// create lake outlet if lake is not in deep depression and flux > evaporation
|
||||||
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
|
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
|
||||||
|
|
@ -170,7 +170,7 @@ window.Rivers = (function () {
|
||||||
const widthFactor = (!parent || parent === riverId ? 3.6 : 3) / distanceScale;
|
const widthFactor = (!parent || parent === riverId ? 3.6 : 3) / distanceScale;
|
||||||
const meanderedPoints = addMeandering(riverCells);
|
const meanderedPoints = addMeandering(riverCells);
|
||||||
const discharge = cells.fl[mouth]; // m3 in second
|
const discharge = cells.fl[mouth]; // m3 in second
|
||||||
const length = rn(getApproximateLength(meanderedPoints), 2);
|
const length = getApproximateLength(meanderedPoints);
|
||||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
|
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
|
||||||
|
|
||||||
pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells});
|
pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells});
|
||||||
|
|
@ -320,9 +320,10 @@ window.Rivers = (function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRiverPoints = (riverCells, riverPoints) => {
|
const getRiverPoints = (riverCells, riverPoints) => {
|
||||||
|
if (riverPoints) return riverPoints;
|
||||||
|
|
||||||
const {p} = pack.cells;
|
const {p} = pack.cells;
|
||||||
return riverCells.map((cell, i) => {
|
return riverCells.map((cell, i) => {
|
||||||
if (riverPoints && riverPoints[i]) return riverPoints[i];
|
|
||||||
if (cell === -1) return getBorderPoint(riverCells[i - 1]);
|
if (cell === -1) return getBorderPoint(riverCells[i - 1]);
|
||||||
return p[cell];
|
return p[cell];
|
||||||
});
|
});
|
||||||
|
|
@ -415,7 +416,10 @@ window.Rivers = (function () {
|
||||||
return rw(riverTypes[isFork ? "fork" : "main"][isSmall ? "small" : "big"]);
|
return rw(riverTypes[isFork ? "fork" : "main"][isSmall ? "small" : "big"]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getApproximateLength = points => points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0);
|
const getApproximateLength = points => {
|
||||||
|
const length = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0);
|
||||||
|
return rn(length, 2);
|
||||||
|
};
|
||||||
|
|
||||||
// Real mouth width examples: Amazon 6000m, Volga 6000m, Dniepr 3000m, Mississippi 1300m, Themes 900m,
|
// Real mouth width examples: Amazon 6000m, Volga 6000m, Dniepr 3000m, Mississippi 1300m, Themes 900m,
|
||||||
// Danube 800m, Daugava 600m, Neva 500m, Nile 450m, Don 400m, Wisla 300m, Pripyat 150m, Bug 140m, Muchavets 40m
|
// Danube 800m, Daugava 600m, Neva 500m, Nile 450m, Don 400m, Wisla 300m, Pripyat 150m, Bug 140m, Muchavets 40m
|
||||||
|
|
|
||||||
|
|
@ -144,18 +144,18 @@ async function getMapURL(type, options = {}) {
|
||||||
cloneEl.id = "fantasyMap";
|
cloneEl.id = "fantasyMap";
|
||||||
document.body.appendChild(cloneEl);
|
document.body.appendChild(cloneEl);
|
||||||
const clone = d3.select(cloneEl);
|
const clone = d3.select(cloneEl);
|
||||||
if (!debug) clone.select("#debug").remove();
|
if (!debug) clone.select("#debug")?.remove();
|
||||||
|
|
||||||
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
||||||
const svgDefs = document.getElementById("defElements");
|
const svgDefs = document.getElementById("defElements");
|
||||||
|
|
||||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
||||||
if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove();
|
if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove();
|
||||||
if (globe) clone.select("#scaleBar").remove();
|
if (globe) clone.select("#scaleBar")?.remove();
|
||||||
if (noLabels) {
|
if (noLabels) {
|
||||||
clone.select("#labels #states").remove();
|
clone.select("#labels #states")?.remove();
|
||||||
clone.select("#labels #burgLabels").remove();
|
clone.select("#labels #burgLabels")?.remove();
|
||||||
clone.select("#icons #burgIcons").remove();
|
clone.select("#icons #burgIcons")?.remove();
|
||||||
}
|
}
|
||||||
if (noWater) {
|
if (noWater) {
|
||||||
clone.select("#oceanBase").attr("opacity", 0);
|
clone.select("#oceanBase").attr("opacity", 0);
|
||||||
|
|
@ -258,10 +258,10 @@ async function getMapURL(type, options = {}) {
|
||||||
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); // remove unused hatching group
|
if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching")?.remove(); // remove unused hatching group
|
||||||
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog").remove(); // remove unused fog
|
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
|
||||||
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths").remove(); // removed unused statePaths
|
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
|
||||||
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths").remove(); // removed unused textPaths
|
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
|
||||||
|
|
||||||
// add armies style
|
// add armies style
|
||||||
if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", "<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>");
|
if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", "<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>");
|
||||||
|
|
@ -296,8 +296,8 @@ async function getMapURL(type, options = {}) {
|
||||||
|
|
||||||
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
||||||
function removeUnusedElements(clone) {
|
function removeUnusedElements(clone) {
|
||||||
if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove();
|
if (!terrain.selectAll("use").size()) clone.select("#defs-relief")?.remove();
|
||||||
if (markers.style("display") === "none") clone.select("#defs-markers").remove();
|
if (markers.style("display") === "none") clone.select("#defs-markers")?.remove();
|
||||||
|
|
||||||
for (let empty = 1; empty; ) {
|
for (let empty = 1; empty; ) {
|
||||||
empty = 0;
|
empty = 0;
|
||||||
|
|
@ -367,9 +367,8 @@ function inlineStyle(clone) {
|
||||||
|
|
||||||
// prepare map data for saving
|
// prepare map data for saving
|
||||||
function getMapData() {
|
function getMapData() {
|
||||||
TIME && console.time("createMapDataBlob");
|
TIME && console.time("createMapData");
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
||||||
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
||||||
|
|
@ -380,21 +379,21 @@ function getMapData() {
|
||||||
const notesData = JSON.stringify(notes);
|
const notesData = JSON.stringify(notes);
|
||||||
const rulersString = rulers.toString();
|
const rulersString = rulers.toString();
|
||||||
|
|
||||||
// clone svg
|
// save svg
|
||||||
const cloneEl = document.getElementById("map").cloneNode(true);
|
const cloneEl = document.getElementById("map").cloneNode(true);
|
||||||
|
|
||||||
// set transform values to default
|
// reset transform values to default
|
||||||
cloneEl.setAttribute("width", graphWidth);
|
cloneEl.setAttribute("width", graphWidth);
|
||||||
cloneEl.setAttribute("height", graphHeight);
|
cloneEl.setAttribute("height", graphHeight);
|
||||||
cloneEl.querySelector("#viewbox").removeAttribute("transform");
|
cloneEl.querySelector("#viewbox").removeAttribute("transform");
|
||||||
|
|
||||||
// always remove rulers
|
cloneEl.querySelector("#ruler").innerHTML = ""; // always remove rulers
|
||||||
cloneEl.querySelector("#ruler").innerHTML = "";
|
|
||||||
|
|
||||||
const svg_xml = new XMLSerializer().serializeToString(cloneEl);
|
const serializedSVG = new XMLSerializer().serializeToString(cloneEl);
|
||||||
|
|
||||||
const gridGeneral = JSON.stringify({spacing: grid.spacing, cellsX: grid.cellsX, cellsY: grid.cellsY, boundary: grid.boundary, points: grid.points, features: grid.features});
|
const {spacing, cellsX, cellsY, boundary, points, features} = grid;
|
||||||
const features = JSON.stringify(pack.features);
|
const gridGeneral = JSON.stringify({spacing, cellsX, cellsY, boundary, points, features});
|
||||||
|
const packFeatures = JSON.stringify(pack.features);
|
||||||
const cultures = JSON.stringify(pack.cultures);
|
const cultures = JSON.stringify(pack.cultures);
|
||||||
const states = JSON.stringify(pack.states);
|
const states = JSON.stringify(pack.states);
|
||||||
const burgs = JSON.stringify(pack.burgs);
|
const burgs = JSON.stringify(pack.burgs);
|
||||||
|
|
@ -402,7 +401,7 @@ function getMapData() {
|
||||||
const provinces = JSON.stringify(pack.provinces);
|
const provinces = JSON.stringify(pack.provinces);
|
||||||
const rivers = JSON.stringify(pack.rivers);
|
const rivers = JSON.stringify(pack.rivers);
|
||||||
|
|
||||||
// store name array only if it is not the same as default
|
// store name array only if not the same as default
|
||||||
const defaultNB = Names.getNameBases();
|
const defaultNB = Names.getNameBases();
|
||||||
const namesData = nameBases
|
const namesData = nameBases
|
||||||
.map((b, i) => {
|
.map((b, i) => {
|
||||||
|
|
@ -411,24 +410,22 @@ function getMapData() {
|
||||||
})
|
})
|
||||||
.join("/");
|
.join("/");
|
||||||
|
|
||||||
// round population to save resources
|
// round population to save space
|
||||||
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
|
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
|
||||||
|
|
||||||
// data format as below
|
// data format as below
|
||||||
const data = [params, settings, coords, biomes, notesData, svg_xml, gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, features, cultures, states, burgs, pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, namesData, rivers, rulersString].join("\r\n");
|
const mapData = [params, settings, coords, biomes, notesData, serializedSVG, gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, packFeatures, cultures, states, burgs, pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, namesData, rivers, rulersString].join("\r\n");
|
||||||
const blob = new Blob([data], {type: "text/plain"});
|
TIME && console.timeEnd("createMapData");
|
||||||
|
return mapData;
|
||||||
TIME && console.timeEnd("createMapDataBlob");
|
|
||||||
resolve(blob);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download .map file
|
// Download .map file
|
||||||
async function saveMap() {
|
function dowloadMap() {
|
||||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||||
closeDialogs("#alert");
|
closeDialogs("#alert");
|
||||||
|
|
||||||
const blob = await getMapData();
|
const mapData = getMapData();
|
||||||
|
const blob = new Blob([mapData], {type: "text/plain"});
|
||||||
const URL = window.URL.createObjectURL(blob);
|
const URL = window.URL.createObjectURL(blob);
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.download = getFileName() + ".map";
|
link.download = getFileName() + ".map";
|
||||||
|
|
@ -438,6 +435,21 @@ async function saveMap() {
|
||||||
window.URL.revokeObjectURL(URL);
|
window.URL.revokeObjectURL(URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveToDropbox() {
|
||||||
|
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||||
|
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||||
|
closeDialogs("#alert");
|
||||||
|
const mapData = getMapData();
|
||||||
|
const filename = getFileName() + ".map";
|
||||||
|
try {
|
||||||
|
await Cloud.providers.dropbox.save(filename, mapData);
|
||||||
|
tip("Map is saved to your Dropbox", true, "success", 8000);
|
||||||
|
} catch (msg) {
|
||||||
|
console.error(msg);
|
||||||
|
tip("Cannot save .map to your Dropbox", true, "error", 8000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function saveGeoJSON_Cells() {
|
function saveGeoJSON_Cells() {
|
||||||
const json = {type: "FeatureCollection", features: []};
|
const json = {type: "FeatureCollection", features: []};
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
|
|
@ -556,9 +568,11 @@ function getRiverPoints(node) {
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function quickSave() {
|
function quickSave() {
|
||||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||||
const blob = await getMapData();
|
|
||||||
|
const mapData = getMapData();
|
||||||
|
const blob = new Blob([mapData], {type: "text/plain"});
|
||||||
if (blob) ldb.set("lastMap", blob); // auto-save map
|
if (blob) ldb.set("lastMap", blob); // auto-save map
|
||||||
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
|
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -447,15 +447,17 @@ function overviewBurgs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadBurgsData() {
|
function downloadBurgsData() {
|
||||||
let data = "Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation (" + heightUnit.value + "),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
let data = "Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Longitude,Latitude,Elevation (" + heightUnit.value + "),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
||||||
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||||
|
|
||||||
valid.forEach(b => {
|
valid.forEach(b => {
|
||||||
data += b.i + ",";
|
data += b.i + ",";
|
||||||
data += b.name + ",";
|
data += b.name + ",";
|
||||||
const province = pack.cells.province[b.cell];
|
const province = pack.cells.province[b.cell];
|
||||||
|
data += province ? pack.provinces[province].name + "," : ",";
|
||||||
data += province ? pack.provinces[province].fullName + "," : ",";
|
data += province ? pack.provinces[province].fullName + "," : ",";
|
||||||
data += b.state ? pack.states[b.state].fullName + "," : pack.states[b.state].name + ",";
|
data += pack.states[b.state].name + ",";
|
||||||
|
data += pack.states[b.state].fullName + ",";
|
||||||
data += pack.cultures[b.culture].name + ",";
|
data += pack.cultures[b.culture].name + ",";
|
||||||
data += pack.religions[pack.cells.religion[b.cell]].name + ",";
|
data += pack.religions[pack.cells.religion[b.cell]].name + ",";
|
||||||
data += rn(b.population * populationRate * urbanization) + ",";
|
data += rn(b.population * populationRate * urbanization) + ",";
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ function editHeightmap() {
|
||||||
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
|
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
|
||||||
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
|
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
|
||||||
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
|
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
|
||||||
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
|
<p>Please <span class="pseudoLink" onclick=dowloadMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
|
||||||
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
|
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
|
||||||
|
|
||||||
$("#alert").dialog({
|
$("#alert").dialog({
|
||||||
|
|
@ -328,10 +328,10 @@ function editHeightmap() {
|
||||||
|
|
||||||
for (const i of pack.cells.i) {
|
for (const i of pack.cells.i) {
|
||||||
const g = pack.cells.g[i];
|
const g = pack.cells.g[i];
|
||||||
const land = pack.cells.h[i] >= 20;
|
const isLand = pack.cells.h[i] >= 20;
|
||||||
|
|
||||||
// check biome
|
// check biome
|
||||||
pack.cells.biome[i] = land && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], pack.cells.h[i]);
|
pack.cells.biome[i] = isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]);
|
||||||
|
|
||||||
// rivers data
|
// rivers data
|
||||||
if (!erosionAllowed) {
|
if (!erosionAllowed) {
|
||||||
|
|
@ -340,7 +340,7 @@ function editHeightmap() {
|
||||||
pack.cells.fl[i] = fl[g];
|
pack.cells.fl[i] = fl[g];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!land) continue;
|
if (!isLand) continue;
|
||||||
pack.cells.culture[i] = culture[g];
|
pack.cells.culture[i] = culture[g];
|
||||||
pack.cells.pop[i] = pop[g];
|
pack.cells.pop[i] = pop[g];
|
||||||
pack.cells.road[i] = road[g];
|
pack.cells.road[i] = road[g];
|
||||||
|
|
@ -837,31 +837,27 @@ function editHeightmap() {
|
||||||
const steps = body.querySelectorAll("#templateBody > div");
|
const steps = body.querySelectorAll("#templateBody > div");
|
||||||
if (!steps.length) return;
|
if (!steps.length) return;
|
||||||
|
|
||||||
|
const {addHill, addPit, addRange, addTrough, addStrait, modify, smooth} = HeightmapGenerator;
|
||||||
grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights
|
grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights
|
||||||
|
|
||||||
for (const s of steps) {
|
for (const step of steps) {
|
||||||
if (s.style.opacity == 0.5) continue;
|
if (step.style.opacity === "0.5") continue;
|
||||||
const type = s.dataset.type;
|
const type = step.dataset.type;
|
||||||
|
|
||||||
const elCount = s.querySelector(".templateCount") || "";
|
const count = step.querySelector(".templateCount")?.value || "";
|
||||||
const elHeight = s.querySelector(".templateHeight") || "";
|
const height = step.querySelector(".templateHeight")?.value || "";
|
||||||
|
const dist = step.querySelector(".templateDist")?.value || null;
|
||||||
|
const x = step.querySelector(".templateX")?.value || null;
|
||||||
|
const y = step.querySelector(".templateY")?.value || null;
|
||||||
|
|
||||||
const elDist = s.querySelector(".templateDist");
|
if (type === "Hill") addHill(count, height, x, y);
|
||||||
const dist = elDist ? elDist.value : null;
|
else if (type === "Pit") addPit(count, height, x, y);
|
||||||
|
else if (type === "Range") addRange(count, height, x, y);
|
||||||
const templateX = s.querySelector(".templateX");
|
else if (type === "Trough") addTrough(count, height, x, y);
|
||||||
const x = templateX ? templateX.value : null;
|
else if (type === "Strait") addStrait(count, dist);
|
||||||
const templateY = s.querySelector(".templateY");
|
else if (type === "Add") modify(dist, +count, 1);
|
||||||
const y = templateY ? templateY.value : null;
|
else if (type === "Multiply") modify(dist, 0, +count);
|
||||||
|
else if (type === "Smooth") smooth(+count);
|
||||||
if (type === "Hill") HeightmapGenerator.addHill(elCount.value, elHeight.value, x, y);
|
|
||||||
else if (type === "Pit") HeightmapGenerator.addPit(elCount.value, elHeight.value, x, y);
|
|
||||||
else if (type === "Range") HeightmapGenerator.addRange(elCount.value, elHeight.value, x, y);
|
|
||||||
else if (type === "Trough") HeightmapGenerator.addTrough(elCount.value, elHeight.value, x, y);
|
|
||||||
else if (type === "Strait") HeightmapGenerator.addStrait(elCount.value, dist);
|
|
||||||
else if (type === "Add") HeightmapGenerator.modify(dist, +elCount.value, 1);
|
|
||||||
else if (type === "Multiply") HeightmapGenerator.modify(dist, 0, +elCount.value);
|
|
||||||
else if (type === "Smooth") HeightmapGenerator.smooth(+elCount.value);
|
|
||||||
|
|
||||||
updateHistory("noStat"); // update history every step
|
updateHistory("noStat"); // update history every step
|
||||||
}
|
}
|
||||||
|
|
@ -880,17 +876,13 @@ function editHeightmap() {
|
||||||
|
|
||||||
let data = "";
|
let data = "";
|
||||||
for (const s of steps) {
|
for (const s of steps) {
|
||||||
if (s.style.opacity == 0.5) continue;
|
if (s.style.opacity === "0.5") continue;
|
||||||
|
|
||||||
const type = s.getAttribute("data-type");
|
const type = s.getAttribute("data-type");
|
||||||
const elCount = s.querySelector(".templateCount");
|
const count = s.querySelector(".templateCount")?.value || "0";
|
||||||
const count = elCount ? elCount.value : "0";
|
const arg3 = s.querySelector(".templateHeight")?.value || s.querySelector(".templateDist")?.value || "0";
|
||||||
const elHeight = s.querySelector(".templateHeight");
|
const x = s.querySelector(".templateX")?.value || "0";
|
||||||
const elDist = s.querySelector(".templateDist");
|
const y = s.querySelector(".templateY")?.value || "0";
|
||||||
const arg3 = elHeight ? elHeight.value : elDist ? elDist.value : "0";
|
|
||||||
const templateX = s.querySelector(".templateX");
|
|
||||||
const x = templateX ? templateX.value : "0";
|
|
||||||
const templateY = s.querySelector(".templateY");
|
|
||||||
const y = templateY ? templateY.value : "0";
|
|
||||||
data += `${type} ${count} ${arg3} ${x} ${y}\r\n`;
|
data += `${type} ${count} ${arg3} ${x} ${y}\r\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1456,16 +1456,19 @@ function toggleRivers(event) {
|
||||||
|
|
||||||
function drawRivers() {
|
function drawRivers() {
|
||||||
TIME && console.time("drawRivers");
|
TIME && console.time("drawRivers");
|
||||||
|
rivers.selectAll("*").remove();
|
||||||
|
|
||||||
const {addMeandering, getRiverPath} = Rivers;
|
const {addMeandering, getRiverPath} = Rivers;
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||||
const riverPaths = pack.rivers.map(river => {
|
|
||||||
const meanderedPoints = addMeandering(river.cells, river.points);
|
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
|
||||||
const widthFactor = river.widthFactor || 1;
|
if (!cells || cells.length < 2) return;
|
||||||
const startingWidth = river.sourceWidth || 0;
|
const meanderedPoints = addMeandering(cells, points);
|
||||||
const path = getRiverPath(meanderedPoints, widthFactor, startingWidth);
|
const path = getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||||
return `<path id="river${river.i}" d="${path}"/>`;
|
return `<path id="river${i}" d="${path}"/>`;
|
||||||
});
|
});
|
||||||
rivers.html(riverPaths.join(""));
|
rivers.html(riverPaths.join(""));
|
||||||
|
|
||||||
TIME && console.timeEnd("drawRivers");
|
TIME && console.timeEnd("drawRivers");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,9 @@ function showSupporters() {
|
||||||
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,
|
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,
|
||||||
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,
|
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,
|
||||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,
|
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,
|
||||||
"Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas"`;
|
Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,
|
||||||
|
George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,Mike Conley,Xavier privé,Hope You're Well,
|
||||||
|
Mark Sprietsma,Robert Landry,Nick Mowry"`;
|
||||||
|
|
||||||
const array = supporters
|
const array = supporters
|
||||||
.replace(/(?:\r\n|\r|\n)/g, "")
|
.replace(/(?:\r\n|\r|\n)/g, "")
|
||||||
|
|
@ -621,6 +623,7 @@ document.getElementById("sticked").addEventListener("click", function (event) {
|
||||||
const id = event.target.id;
|
const id = event.target.id;
|
||||||
if (id === "newMapButton") regeneratePrompt();
|
if (id === "newMapButton") regeneratePrompt();
|
||||||
else if (id === "saveButton") showSavePane();
|
else if (id === "saveButton") showSavePane();
|
||||||
|
else if (id === "exportButton") showExportPane();
|
||||||
else if (id === "loadButton") showLoadPane();
|
else if (id === "loadButton") showLoadPane();
|
||||||
else if (id === "zoomReset") resetZoom(1000);
|
else if (id === "zoomReset") resetZoom(1000);
|
||||||
});
|
});
|
||||||
|
|
@ -654,12 +657,13 @@ function regeneratePrompt() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSavePane() {
|
function showSavePane() {
|
||||||
document.getElementById("showLabels").checked = !hideLabels.checked;
|
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||||
|
sharableLinkContainer.style.display = "none";
|
||||||
|
|
||||||
$("#saveMapData").dialog({
|
$("#saveMapData").dialog({
|
||||||
title: "Save map",
|
title: "Save map",
|
||||||
resizable: false,
|
resizable: false,
|
||||||
width: "30em",
|
width: "27em",
|
||||||
position: {my: "center", at: "center", of: "svg"},
|
position: {my: "center", at: "center", of: "svg"},
|
||||||
buttons: {
|
buttons: {
|
||||||
Close: function () {
|
Close: function () {
|
||||||
|
|
@ -669,21 +673,21 @@ function showSavePane() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// download map data as GeoJSON
|
function copyLinkToClickboard() {
|
||||||
function saveGeoJSON() {
|
const shrableLink = document.getElementById("sharableLink");
|
||||||
alertMessage.innerHTML = `You can export map data in GeoJSON format used in GIS tools such as QGIS.
|
const link = shrableLink.getAttribute("href");
|
||||||
Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export", "wiki-page")} for guidance`;
|
navigator.clipboard.writeText(link).then(() => tip("Link is copied to the clipboard", true, "success", 8000));
|
||||||
|
}
|
||||||
|
|
||||||
$("#alert").dialog({
|
function showExportPane() {
|
||||||
title: "GIS data export",
|
document.getElementById("showLabels").checked = !hideLabels.checked;
|
||||||
|
|
||||||
|
$("#exportMapData").dialog({
|
||||||
|
title: "Export map data",
|
||||||
resizable: false,
|
resizable: false,
|
||||||
width: "35em",
|
width: "26em",
|
||||||
position: {my: "center", at: "center", of: "svg"},
|
position: {my: "center", at: "center", of: "svg"},
|
||||||
buttons: {
|
buttons: {
|
||||||
Cells: saveGeoJSON_Cells,
|
|
||||||
Routes: saveGeoJSON_Routes,
|
|
||||||
Rivers: saveGeoJSON_Rivers,
|
|
||||||
Markers: saveGeoJSON_Markers,
|
|
||||||
Close: function () {
|
Close: function () {
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
}
|
}
|
||||||
|
|
@ -691,11 +695,11 @@ function saveGeoJSON() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLoadPane() {
|
async function showLoadPane() {
|
||||||
$("#loadMapData").dialog({
|
$("#loadMapData").dialog({
|
||||||
title: "Load map",
|
title: "Load map",
|
||||||
resizable: false,
|
resizable: false,
|
||||||
width: "17em",
|
width: "22em",
|
||||||
position: {my: "center", at: "center", of: "svg"},
|
position: {my: "center", at: "center", of: "svg"},
|
||||||
buttons: {
|
buttons: {
|
||||||
Close: function () {
|
Close: function () {
|
||||||
|
|
@ -703,6 +707,19 @@ function showLoadPane() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dpx = document.getElementById("loadFromDropbox");
|
||||||
|
const dpf = dpx.querySelector("select");
|
||||||
|
const files = await Cloud.providers.dropbox.list();
|
||||||
|
dpx.style.display = files? "block" : "none";
|
||||||
|
if (!files) return;
|
||||||
|
while(dpf.firstChild) dpf.removeChild(dpf.firstChild);
|
||||||
|
files.forEach(f => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.innerText = f.name;
|
||||||
|
opt.value = f.path;
|
||||||
|
dpf.appendChild(opt);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadURL() {
|
function loadURL() {
|
||||||
|
|
|
||||||
|
|
@ -934,20 +934,22 @@ function editProvinces() {
|
||||||
|
|
||||||
function downloadProvincesData() {
|
function downloadProvincesData() {
|
||||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||||
let data = "Id,Province,Form,State,Color,Capital,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers
|
let data = "Id,Province,Full Name,Form,State,Color,Capital,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers
|
||||||
|
|
||||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||||
let key = parseInt(el.dataset.id);
|
const key = parseInt(el.dataset.id);
|
||||||
|
const provincePack = pack.provinces[key];
|
||||||
data += el.dataset.id + ",";
|
data += el.dataset.id + ",";
|
||||||
data += el.dataset.name + ",";
|
data += el.dataset.name + ",";
|
||||||
|
data += provincePack.fullName + ",";
|
||||||
data += el.dataset.form + ",";
|
data += el.dataset.form + ",";
|
||||||
data += el.dataset.state + ",";
|
data += el.dataset.state + ",";
|
||||||
data += el.dataset.color + ",";
|
data += el.dataset.color + ",";
|
||||||
data += el.dataset.capital + ",";
|
data += el.dataset.capital + ",";
|
||||||
data += el.dataset.area + ",";
|
data += el.dataset.area + ",";
|
||||||
data += el.dataset.population + ",";
|
data += el.dataset.population + ",";
|
||||||
data += `${Math.round(pack.provinces[key].rural * populationRate)},`;
|
data += `${Math.round(provincePack.rural * populationRate)},`;
|
||||||
data += `${Math.round(pack.provinces[key].urban * populationRate * urbanization)}\n`;
|
data += `${Math.round(provincePack.urban * populationRate * urbanization)}\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const name = getFileName("Provinces") + ".csv";
|
const name = getFileName("Provinces") + ".csv";
|
||||||
|
|
|
||||||
|
|
@ -100,16 +100,13 @@ function createRiver() {
|
||||||
const basin = getBasin(parent);
|
const basin = getBasin(parent);
|
||||||
|
|
||||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"});
|
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"});
|
||||||
|
const id = "river" + riverId;
|
||||||
|
|
||||||
// render river
|
// render river
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||||
viewbox
|
viewbox.select("#rivers").append("path").attr("id", id).attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||||
.select("#rivers")
|
|
||||||
.append("path")
|
|
||||||
.attr("id", "river" + riverId)
|
|
||||||
.attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
|
||||||
|
|
||||||
editRiver(riverId);
|
editRiver(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeRiverCreator() {
|
function closeRiverCreator() {
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ function editRiver(id) {
|
||||||
document.getElementById("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
|
document.getElementById("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
|
||||||
if (!layerIsOn("toggleCells")) toggleCells();
|
if (!layerIsOn("toggleCells")) toggleCells();
|
||||||
|
|
||||||
elSelected = d3.select("#" + id);
|
elSelected = d3.select("#" + id).on("click", addControlPoint);
|
||||||
|
|
||||||
tip("Drag control points to change the river course. For major changes please create a new river instead", true);
|
tip("Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead", true);
|
||||||
debug.append("g").attr("id", "controlCells");
|
debug.append("g").attr("id", "controlCells");
|
||||||
debug.append("g").attr("id", "controlPoints");
|
debug.append("g").attr("id", "controlPoints");
|
||||||
|
|
||||||
|
|
@ -19,8 +19,8 @@ function editRiver(id) {
|
||||||
const river = getRiver();
|
const river = getRiver();
|
||||||
const {cells, points} = river;
|
const {cells, points} = river;
|
||||||
const riverPoints = Rivers.getRiverPoints(cells, points);
|
const riverPoints = Rivers.getRiverPoints(cells, points);
|
||||||
drawControlPoints(riverPoints, cells);
|
drawControlPoints(riverPoints);
|
||||||
drawCells(cells, "current");
|
drawCells(cells);
|
||||||
|
|
||||||
$("#riverEditor").dialog({
|
$("#riverEditor").dialog({
|
||||||
title: "Edit River",
|
title: "Edit River",
|
||||||
|
|
@ -92,37 +92,35 @@ function editRiver(id) {
|
||||||
document.getElementById("riverWidth").value = width;
|
document.getElementById("riverWidth").value = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawControlPoints(points, cells) {
|
function drawControlPoints(points) {
|
||||||
debug
|
debug
|
||||||
.select("#controlPoints")
|
.select("#controlPoints")
|
||||||
.selectAll("circle")
|
.selectAll("circle")
|
||||||
.data(points)
|
.data(points)
|
||||||
.enter()
|
.join("circle")
|
||||||
.append("circle")
|
|
||||||
.attr("cx", d => d[0])
|
.attr("cx", d => d[0])
|
||||||
.attr("cy", d => d[1])
|
.attr("cy", d => d[1])
|
||||||
.attr("r", 0.6)
|
.attr("r", 0.6)
|
||||||
.attr("data-cell", (d, i) => cells[i])
|
.call(d3.drag().on("start", dragControlPoint))
|
||||||
.attr("data-i", (d, i) => i)
|
.on("click", removeControlPoint);
|
||||||
.call(d3.drag().on("start", dragControlPoint));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawCells(cells, type) {
|
function drawCells(cells) {
|
||||||
|
const validCells = [...new Set(cells)].filter(i => pack.cells.i[i]);
|
||||||
debug
|
debug
|
||||||
.select("#controlCells")
|
.select("#controlCells")
|
||||||
.selectAll(`polygon.${type}`)
|
.selectAll(`polygon`)
|
||||||
.data(cells.filter(i => pack.cells.i[i]))
|
.data(validCells)
|
||||||
.join("polygon")
|
.join("polygon")
|
||||||
.attr("points", d => getPackPolygon(d))
|
.attr("points", d => getPackPolygon(d));
|
||||||
.attr("class", type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function dragControlPoint() {
|
function dragControlPoint() {
|
||||||
const {i, r, fl} = pack.cells;
|
const {r, fl} = pack.cells;
|
||||||
const river = getRiver();
|
const river = getRiver();
|
||||||
|
|
||||||
const initCell = +this.dataset.cell;
|
const {x: x0, y: y0} = d3.event;
|
||||||
const index = +this.dataset.i;
|
const initCell = findCell(x0, y0);
|
||||||
|
|
||||||
let movedToCell = null;
|
let movedToCell = null;
|
||||||
|
|
||||||
|
|
@ -136,22 +134,18 @@ function editRiver(id) {
|
||||||
this.setAttribute("cy", y);
|
this.setAttribute("cy", y);
|
||||||
this.__data__ = [rn(x, 1), rn(y, 1)];
|
this.__data__ = [rn(x, 1), rn(y, 1)];
|
||||||
redrawRiver();
|
redrawRiver();
|
||||||
|
drawCells(river.cells);
|
||||||
});
|
});
|
||||||
|
|
||||||
d3.event.on("end", () => {
|
d3.event.on("end", () => {
|
||||||
if (movedToCell) {
|
if (movedToCell && !r[movedToCell]) {
|
||||||
this.dataset.cell = movedToCell;
|
|
||||||
river.cells[index] = movedToCell;
|
|
||||||
drawCells(river.cells, "current");
|
|
||||||
|
|
||||||
if (!r[movedToCell]) {
|
|
||||||
// swap river data
|
// swap river data
|
||||||
r[initCell] = 0;
|
r[initCell] = 0;
|
||||||
r[movedToCell] = river.i;
|
r[movedToCell] = river.i;
|
||||||
const sourceFlux = fl[initCell];
|
const sourceFlux = fl[initCell];
|
||||||
fl[initCell] = fl[movedToCell];
|
fl[initCell] = fl[movedToCell];
|
||||||
fl[movedToCell] = sourceFlux;
|
fl[movedToCell] = sourceFlux;
|
||||||
}
|
redrawRiver();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -159,8 +153,10 @@ function editRiver(id) {
|
||||||
function redrawRiver() {
|
function redrawRiver() {
|
||||||
const river = getRiver();
|
const river = getRiver();
|
||||||
river.points = debug.selectAll("#controlPoints > *").data();
|
river.points = debug.selectAll("#controlPoints > *").data();
|
||||||
const {cells, widthFactor, sourceWidth} = river;
|
river.cells = river.points.map(([x, y]) => findCell(x, y));
|
||||||
const meanderedPoints = Rivers.addMeandering(cells, river.points);
|
|
||||||
|
const {widthFactor, sourceWidth} = river;
|
||||||
|
const meanderedPoints = Rivers.addMeandering(river.cells, river.points);
|
||||||
|
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||||
|
|
@ -170,6 +166,27 @@ function editRiver(id) {
|
||||||
if (modules.elevation) showEPForRiver(elSelected.node());
|
if (modules.elevation) showEPForRiver(elSelected.node());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addControlPoint() {
|
||||||
|
const [x, y] = d3.mouse(this);
|
||||||
|
const point = [rn(x, 1), rn(y, 1)];
|
||||||
|
|
||||||
|
const river = getRiver();
|
||||||
|
if (!river.points) river.points = debug.selectAll("#controlPoints > *").data();
|
||||||
|
|
||||||
|
const index = getSegmentId(river.points, point, 2);
|
||||||
|
river.points.splice(index, 0, point);
|
||||||
|
drawControlPoints(river.points);
|
||||||
|
redrawRiver();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeControlPoint() {
|
||||||
|
this.remove();
|
||||||
|
redrawRiver();
|
||||||
|
|
||||||
|
const {cells} = getRiver();
|
||||||
|
drawCells(cells);
|
||||||
|
}
|
||||||
|
|
||||||
function changeName() {
|
function changeName() {
|
||||||
getRiver().name = this.value;
|
getRiver().name = this.value;
|
||||||
}
|
}
|
||||||
|
|
@ -244,6 +261,8 @@ function editRiver(id) {
|
||||||
function closeRiverEditor() {
|
function closeRiverEditor() {
|
||||||
debug.select("#controlPoints").remove();
|
debug.select("#controlPoints").remove();
|
||||||
debug.select("#controlCells").remove();
|
debug.select("#controlCells").remove();
|
||||||
|
|
||||||
|
elSelected.on("click", null);
|
||||||
unselect();
|
unselect();
|
||||||
clearMainTip();
|
clearMainTip();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1028,12 +1028,13 @@ function editStates() {
|
||||||
|
|
||||||
function downloadStatesData() {
|
function downloadStatesData() {
|
||||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||||
let data = "Id,State,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers
|
let data = "Id,State,Full Name,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area " + unit + ",Total Population,Rural Population,Urban Population\n"; // headers
|
||||||
|
|
||||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||||
const key = parseInt(el.dataset.id);
|
const key = parseInt(el.dataset.id);
|
||||||
|
const statePack = pack.states[key];
|
||||||
data += el.dataset.id + ",";
|
data += el.dataset.id + ",";
|
||||||
data += el.dataset.name + ",";
|
data += el.dataset.name + ",";
|
||||||
|
data += (statePack.fullName ? statePack.fullName : "") + ",";
|
||||||
data += el.dataset.form + ",";
|
data += el.dataset.form + ",";
|
||||||
data += el.dataset.color + ",";
|
data += el.dataset.color + ",";
|
||||||
data += el.dataset.capital + ",";
|
data += el.dataset.capital + ",";
|
||||||
|
|
@ -1044,8 +1045,8 @@ function editStates() {
|
||||||
data += el.dataset.burgs + ",";
|
data += el.dataset.burgs + ",";
|
||||||
data += el.dataset.area + ",";
|
data += el.dataset.area + ",";
|
||||||
data += el.dataset.population + ",";
|
data += el.dataset.population + ",";
|
||||||
data += `${Math.round(pack.states[key].rural * populationRate)},`;
|
data += `${Math.round(statePack.rural * populationRate)},`;
|
||||||
data += `${Math.round(pack.states[key].urban * populationRate * urbanization)}\n`;
|
data += `${Math.round(statePack.urban * populationRate * urbanization)}\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const name = getFileName("States") + ".csv";
|
const name = getFileName("States") + ".csv";
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -136,21 +136,11 @@ function recalculatePopulation() {
|
||||||
function regenerateStates() {
|
function regenerateStates() {
|
||||||
const localSeed = Math.floor(Math.random() * 1e9); // new random seed
|
const localSeed = Math.floor(Math.random() * 1e9); // new random seed
|
||||||
Math.random = aleaPRNG(localSeed);
|
Math.random = aleaPRNG(localSeed);
|
||||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
|
||||||
if (!burgs.length) {
|
|
||||||
tip("No burgs to generate states. Please create burgs first", false, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (burgs.length < +regionsInput.value) {
|
|
||||||
tip(`Not enough burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, "warn");
|
|
||||||
}
|
|
||||||
|
|
||||||
// burg local ids sorted by a bit randomized population:
|
const statesCount = +regionsInput.value;
|
||||||
const sorted = burgs
|
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||||
.map((b, i) => [i, b.population * Math.random()])
|
if (!burgs.length) return tip("There are no any burgs to generate states. Please create burgs first", false, "error");
|
||||||
.sort((a, b) => b[1] - a[1])
|
if (burgs.length < statesCount) tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, "warn");
|
||||||
.map(b => b[0]);
|
|
||||||
const capitalsTree = d3.quadtree();
|
|
||||||
|
|
||||||
// turn all old capitals into towns
|
// turn all old capitals into towns
|
||||||
burgs
|
burgs
|
||||||
|
|
@ -167,8 +157,7 @@ function regenerateStates() {
|
||||||
|
|
||||||
unfog();
|
unfog();
|
||||||
|
|
||||||
// if desired states number is 0
|
if (!statesCount) {
|
||||||
if (regionsInput.value == 0) {
|
|
||||||
tip(`Cannot generate zero states. Please check the <i>States Number</i> option`, false, "warn");
|
tip(`Cannot generate zero states. Please check the <i>States Number</i> option`, false, "warn");
|
||||||
pack.states = pack.states.slice(0, 1); // remove all except of neutrals
|
pack.states = pack.states.slice(0, 1); // remove all except of neutrals
|
||||||
pack.states[0].diplomacy = []; // clear diplomacy
|
pack.states[0].diplomacy = []; // clear diplomacy
|
||||||
|
|
@ -184,25 +173,33 @@ function regenerateStates() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const neutral = pack.states[0].name;
|
// burg local ids sorted by a bit randomized population:
|
||||||
const count = Math.min(+regionsInput.value, burgs.length);
|
const sortedBurgs = burgs
|
||||||
|
.map((b, i) => [b, b.population * Math.random()])
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.map(b => b[0]);
|
||||||
|
const capitalsTree = d3.quadtree();
|
||||||
|
|
||||||
|
const neutral = pack.states[0].name; // neutrals name
|
||||||
|
const count = Math.min(statesCount, burgs.length) + 1; // +1 for neutral
|
||||||
let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals
|
let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals
|
||||||
|
|
||||||
pack.states = d3.range(count).map(i => {
|
pack.states = d3.range(count).map(i => {
|
||||||
if (!i) return {i, name: neutral};
|
if (!i) return {i, name: neutral};
|
||||||
|
|
||||||
let capital = null,
|
let capital = null;
|
||||||
x = 0,
|
for (const burg of sortedBurgs) {
|
||||||
y = 0;
|
const {x, y} = burg;
|
||||||
for (const i of sorted) {
|
if (capitalsTree.find(x, y, spacing) === undefined) {
|
||||||
capital = burgs[i];
|
burg.capital = 1;
|
||||||
(x = capital.x), (y = capital.y);
|
capital = burg;
|
||||||
if (capitalsTree.find(x, y, spacing) === undefined) break;
|
capitalsTree.add([x, y]);
|
||||||
spacing = Math.max(spacing - 1, 1);
|
moveBurgToGroup(burg.i, "cities");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
capitalsTree.add([x, y]);
|
spacing = Math.max(spacing - 1, 1);
|
||||||
capital.capital = 1;
|
}
|
||||||
moveBurgToGroup(capital.i, "cities");
|
|
||||||
|
|
||||||
const culture = capital.culture;
|
const culture = capital.culture;
|
||||||
const basename = capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0);
|
const basename = capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
function editWorld() {
|
function editWorld() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
$("#worldConfigurator").dialog({title: "Configure World", resizable: false, width: "42em",
|
$("#worldConfigurator").dialog({
|
||||||
|
title: "Configure World",
|
||||||
|
resizable: false,
|
||||||
|
width: "42em",
|
||||||
buttons: {
|
buttons: {
|
||||||
"Whole World": () => applyWorldPreset(100, 50),
|
"Whole World": () => applyWorldPreset(100, 50),
|
||||||
"Northern": () => applyWorldPreset(33, 25),
|
Northern: () => applyWorldPreset(33, 25),
|
||||||
"Tropical": () => applyWorldPreset(33, 50),
|
Tropical: () => applyWorldPreset(33, 50),
|
||||||
"Southern": () => applyWorldPreset(33, 75),
|
Southern: () => applyWorldPreset(33, 75),
|
||||||
"Restore Winds": restoreDefaultWinds
|
"Restore Winds": restoreDefaultWinds
|
||||||
}, open: function() {
|
},
|
||||||
|
open: function () {
|
||||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
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[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[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes"));
|
||||||
|
|
@ -19,7 +23,8 @@ function editWorld() {
|
||||||
|
|
||||||
const globe = d3.select("#globe");
|
const globe = d3.select("#globe");
|
||||||
const clr = d3.scaleSequential(d3.interpolateSpectral);
|
const clr = d3.scaleSequential(d3.interpolateSpectral);
|
||||||
const tMax = 30, tMin = -25; // temperature extremes
|
const tMax = 30,
|
||||||
|
tMin = -25; // temperature extremes
|
||||||
const projection = d3.geoOrthographic().translate([100, 100]).scale(100);
|
const projection = d3.geoOrthographic().translate([100, 100]).scale(100);
|
||||||
const path = d3.geoPath(projection);
|
const path = d3.geoPath(projection);
|
||||||
|
|
||||||
|
|
@ -29,7 +34,7 @@ function editWorld() {
|
||||||
if (modules.editWorld) return;
|
if (modules.editWorld) return;
|
||||||
modules.editWorld = true;
|
modules.editWorld = true;
|
||||||
|
|
||||||
document.getElementById("worldControls").addEventListener("input", (e) => updateWorld(e.target));
|
document.getElementById("worldControls").addEventListener("input", e => updateWorld(e.target));
|
||||||
globe.select("#globeWindArrows").on("click", changeWind);
|
globe.select("#globeWindArrows").on("click", changeWind);
|
||||||
globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule
|
globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule
|
||||||
updateWindDirections();
|
updateWindDirections();
|
||||||
|
|
@ -56,16 +61,18 @@ function editWorld() {
|
||||||
if (layerIsOn("togglePrec")) drawPrec();
|
if (layerIsOn("togglePrec")) drawPrec();
|
||||||
if (layerIsOn("toggleBiomes")) drawBiomes();
|
if (layerIsOn("toggleBiomes")) drawBiomes();
|
||||||
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
||||||
|
if (layerIsOn("toggleRivers")) drawRivers();
|
||||||
if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500);
|
if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGlobePosition() {
|
function updateGlobePosition() {
|
||||||
const size = +document.getElementById("mapSizeOutput").value;
|
const size = +document.getElementById("mapSizeOutput").value;
|
||||||
const eqD = graphHeight / 2 * 100 / size;
|
const eqD = ((graphHeight / 2) * 100) / size;
|
||||||
|
|
||||||
calculateMapCoordinates();
|
calculateMapCoordinates();
|
||||||
const mc = mapCoordinates; // shortcut
|
const mc = mapCoordinates; // shortcut
|
||||||
const scale = +distanceScaleInput.value, unit = distanceUnitInput.value;
|
const scale = +distanceScaleInput.value,
|
||||||
|
unit = distanceUnitInput.value;
|
||||||
const meridian = toKilometer(eqD * 2 * scale);
|
const meridian = toKilometer(eqD * 2 * scale);
|
||||||
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
|
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
|
||||||
document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
|
document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
|
||||||
|
|
@ -82,24 +89,32 @@ function editWorld() {
|
||||||
return 0; // 0 if distanceUnitInput is a custom unit
|
return 0; // 0 if distanceUnitInput is a custom unit
|
||||||
}
|
}
|
||||||
|
|
||||||
function lat(lat) {return lat > 0 ? Math.abs(rn(lat)) + "°N" : Math.abs(rn(lat)) + "°S";} // parse latitude value
|
function lat(lat) {
|
||||||
const area = d3.geoGraticule().extent([[mc.lonW, mc.latN], [mc.lonE, mc.latS]]);
|
return lat > 0 ? Math.abs(rn(lat)) + "°N" : Math.abs(rn(lat)) + "°S";
|
||||||
|
} // parse latitude value
|
||||||
|
const area = d3.geoGraticule().extent([
|
||||||
|
[mc.lonW, mc.latN],
|
||||||
|
[mc.lonE, mc.latS]
|
||||||
|
]);
|
||||||
globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area
|
globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGlobeTemperature() {
|
function updateGlobeTemperature() {
|
||||||
const tEq = +document.getElementById("temperatureEquatorOutput").value;
|
const tEq = +document.getElementById("temperatureEquatorOutput").value;
|
||||||
document.getElementById("temperatureEquatorF").innerHTML = rn(tEq * 9/5 + 32);
|
document.getElementById("temperatureEquatorF").innerHTML = rn((tEq * 9) / 5 + 32);
|
||||||
const tPole = +document.getElementById("temperaturePoleOutput").value;
|
const tPole = +document.getElementById("temperaturePoleOutput").value;
|
||||||
document.getElementById("temperaturePoleF").innerHTML = rn(tPole * 9/5 + 32);
|
document.getElementById("temperaturePoleF").innerHTML = rn((tPole * 9) / 5 + 32);
|
||||||
globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin)));
|
globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin)));
|
||||||
globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 2/3 - tMin) / (tMax - tMin)));
|
globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 2) / 3 - tMin) / (tMax - tMin)));
|
||||||
globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 1/3 - tMin) / (tMax - tMin)));
|
globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin)));
|
||||||
globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin)));
|
globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWindDirections() {
|
function updateWindDirections() {
|
||||||
globe.select("#globeWindArrows").selectAll("path").each(function(d, i) {
|
globe
|
||||||
|
.select("#globeWindArrows")
|
||||||
|
.selectAll("path")
|
||||||
|
.each(function (d, i) {
|
||||||
const tr = parseTransform(this.getAttribute("transform"));
|
const tr = parseTransform(this.getAttribute("transform"));
|
||||||
this.setAttribute("transform", `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`);
|
this.setAttribute("transform", `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`);
|
||||||
});
|
});
|
||||||
|
|
@ -112,13 +127,13 @@ function editWorld() {
|
||||||
const tr = parseTransform(arrow.getAttribute("transform"));
|
const tr = parseTransform(arrow.getAttribute("transform"));
|
||||||
arrow.setAttribute("transform", `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
|
arrow.setAttribute("transform", `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
|
||||||
localStorage.setItem("winds", options.winds);
|
localStorage.setItem("winds", options.winds);
|
||||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
|
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => ((90 - c) / 30) | 0);
|
||||||
if (mapTiers.includes(tier)) updateWorld();
|
if (mapTiers.includes(tier)) updateWorld();
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreDefaultWinds() {
|
function restoreDefaultWinds() {
|
||||||
const defaultWinds = [225, 45, 225, 315, 135, 315];
|
const defaultWinds = [225, 45, 225, 315, 135, 315];
|
||||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
|
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 = mapTiers.some(t => options.winds[t] != defaultWinds[t]);
|
||||||
options.winds = defaultWinds;
|
options.winds = defaultWinds;
|
||||||
updateWindDirections();
|
updateWindDirections();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue