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

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

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright 2017-2021 Max Haniyeu (Azgaar), azgaar.fmg@yandex.com Copyright 2017-2024 Max Haniyeu (Azgaar), azgaar.fmg@yandex.com
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -14,6 +14,7 @@ copies or substantial portions of the Software.
You can produce, without restrictions, any derivative works from the original You can produce, without restrictions, any derivative works from the original
software and even reap commercial benefits from the sale of the secondary product. software and even reap commercial benefits from the sale of the secondary product.
The derivates include created maps, map images, screenshots, videos, and other materials.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

View file

@ -436,6 +436,14 @@ button.options:hover {
margin: 0.8em 0 0 0; margin: 0.8em 0 0 0;
} }
#options .tip {
color: #444;
font-size: 0.9em;
font-family: sans-serif;
font-style: italic;
margin-left: 0.5em;
}
#aboutContent { #aboutContent {
text-align: justify; text-align: justify;
} }
@ -733,7 +741,7 @@ input[type="color"]::-webkit-color-swatch-wrapper {
background-color: var(--header-active); background-color: var(--header-active);
} }
#toolsContent div { #toolsContent > .grid {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
margin: 0.2em 0; margin: 0.2em 0;
@ -757,7 +765,7 @@ input[type="color"]::-webkit-color-swatch-wrapper {
#viewMode > button { #viewMode > button {
padding: 0.35em; padding: 0.35em;
margin: 0.2em 0.3em 0.6em 0.3em; margin: 0.3em 0.3em 0.6em 0.3em;
float: left; float: left;
width: 30.7%; width: 30.7%;
} }
@ -2188,7 +2196,7 @@ svg.button {
#worldControls input[type="number"] { #worldControls input[type="number"] {
border: 1px solid #e5e5e5; border: 1px solid #e5e5e5;
padding: 0px; padding: 0px;
width: 3.2em; width: 4em;
} }
#worldControls i.icon-lock-open, #worldControls i.icon-lock-open,
@ -2247,10 +2255,6 @@ svg.button {
fill: blue; fill: blue;
} }
#globeOutline {
fill: url(#temperatureGradient);
}
#globeArea { #globeArea {
fill: white; fill: white;
fill-opacity: 0.3; fill-opacity: 0.3;
@ -2261,6 +2265,11 @@ svg.button {
stroke-width: 0.2; stroke-width: 0.2;
} }
#globePrimeMeridian {
stroke: blue;
stroke-width: 1.4;
}
#globeEquator { #globeEquator {
stroke: red; stroke: red;
stroke-width: 1.4; stroke-width: 1.4;
@ -2382,6 +2391,29 @@ svg.button {
background: #ccc; background: #ccc;
} }
.separator {
display: flex;
align-items: center;
text-align: center;
font-style: italic;
font-weight: bold;
color: #222;
margin: 0.8em 0 0 0;
}
.separator::before,
.separator::after {
content: "";
flex: 1;
border-bottom: 1px solid #333;
}
.separator:not(:empty)::before {
margin-right: 0.25em;
}
.separator:not(:empty)::after {
margin-left: 0.25em;
}
@media print { @media print {
div, div,
canvas { canvas {

View file

@ -138,7 +138,7 @@
} }
</style> </style>
<link rel="preload" href="index.css?v=1.96.00" as="style" onload="this.onload=null; this.rel='stylesheet'" /> <link rel="preload" href="index.css?v=1.98.01" as="style" onload="this.onload=null; this.rel='stylesheet'" />
<link rel="preload" href="icons.css" as="style" onload="this.onload=null; this.rel='stylesheet'" /> <link rel="preload" href="icons.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
<link rel="preload" href="libs/jquery-ui.css" as="style" onload="this.onload=null; this.rel='stylesheet'" /> <link rel="preload" href="libs/jquery-ui.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
</head> </head>
@ -380,7 +380,7 @@
<rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)" /> <rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)" />
</svg> </svg>
<svg id="loading-rose" width="100%" height="100%" viewBox="0 0 700 700"> <svg id="loading-rose" width="100%" height="100%" viewBox="0 0 700 700">
<use href="#rose" x="50%" y="50%" /> <use href="#defs-compass-rose" x="50%" y="50%" />
</svg> </svg>
<div id="loading-typography"> <div id="loading-typography">
<div id="titleName">Azgaar's</div> <div id="titleName">Azgaar's</div>
@ -469,9 +469,7 @@
onclick="removePreset()" onclick="removePreset()"
></button> ></button>
<p data-tip="Click to toggle a layer, drag to raise or lower a layer. Ctrl + click to edit layer style"> <p>Displayed layers and layers order:</p>
Displayed layers and layers order:
</p>
<ul <ul
data-tip="Click to toggle a layer, drag to raise or lower a layer. Ctrl + click to edit layer style" data-tip="Click to toggle a layer, drag to raise or lower a layer. Ctrl + click to edit layer style"
id="mapLayers" id="mapLayers"
@ -717,13 +715,15 @@
<li <li
id="toggleVignette" id="toggleVignette"
data-tip="Vignette (border fading): click to toggle. Ctrl + click to edit style" data-tip="Vignette (border fading): click to toggle. Ctrl + click to edit style"
data-shortcut="[ (left bracket)" data-shortcut="[ (left square bracket)"
onclick="toggleVignette(event)" onclick="toggleVignette(event)"
class="solid" class="solid"
> >
Vignette Vignette
</li> </li>
</ul> </ul>
<div class="tip">Click to toggle, drag to raise or lower the layer</div>
<div class="tip">Ctrl + click to edit layer style</div>
<div id="viewMode" data-tip="Set view node"> <div id="viewMode" data-tip="Set view node">
<p>View mode:</p> <p>View mode:</p>
@ -1317,6 +1317,12 @@
<td><select id="styleStatesBodyFilter" /></td> <td><select id="styleStatesBodyFilter" /></td>
</tr> </tr>
<tr style="margin-top: 0.8em">
<td style="font-style: italic">
Halo is only rendered if "Rendering" option is set to "Best quality"!
</td>
</tr>
<tr data-tip="Set states halo effect width"> <tr data-tip="Set states halo effect width">
<td>Halo width</td> <td>Halo width</td>
<td> <td>
@ -2063,23 +2069,16 @@
</div> </div>
<div id="toolsContent" class="tabcontent"> <div id="toolsContent" class="tabcontent">
<p>Click to configure:</p> <div class="separator">Edit</div>
<div> <div class="grid">
<button
id="editHeightmapButton"
data-tip="Click to open Heightmap customization menu"
data-shortcut="Shift + H"
>
Heightmap
</button>
<button id="editBiomesButton" data-tip="Click to open Biomes Editor" data-shortcut="Shift + B"> <button id="editBiomesButton" data-tip="Click to open Biomes Editor" data-shortcut="Shift + B">
Biomes Biomes
</button> </button>
<button id="editStatesButton" data-tip="Click to open States Editor" data-shortcut="Shift + S"> <button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T">
States Burgs
</button> </button>
<button id="editProvincesButton" data-tip="Click to open Provinces Editor" data-shortcut="Shift + P"> <button id="editCulturesButton" data-tip="Click to open Cultures Editor" data-shortcut="Shift + C">
Provinces Cultures
</button> </button>
<button <button
id="editDiplomacyButton" id="editDiplomacyButton"
@ -2088,40 +2087,18 @@
> >
Diplomacy Diplomacy
</button> </button>
<button id="editCulturesButton" data-tip="Click to open Cultures Editor" data-shortcut="Shift + C">
Cultures
</button>
<button id="editNamesBaseButton" data-tip="Click to open Namesbase Editor" data-shortcut="Shift + N">
Namesbase
</button>
<button id="editZonesButton" data-tip="Click to open Zones Editor" data-shortcut="Shift + Z">Zones</button>
<button id="editReligions" data-tip="Click to open Religions Editor" data-shortcut="Shift + R">
Religions
</button>
<button id="editEmblemButton" data-tip="Click to open Emblem Editor" data-shortcut="Shift + Y"> <button id="editEmblemButton" data-tip="Click to open Emblem Editor" data-shortcut="Shift + Y">
Emblems Emblems
</button> </button>
<button id="editUnitsButton" data-tip="Click to open Units Editor" data-shortcut="Shift + Q">Units</button>
<button id="editNotesButton" data-tip="Click to open Notes Editor" data-shortcut="Shift + O">Notes</button>
</div>
<p>Click to overview:</p>
<div>
<button <button
id="overviewChartsButton" id="editHeightmapButton"
data-tip="Click to open Charts to overview cells data" data-tip="Click to open Heightmap customization menu"
data-shortcut="Shift + A" data-shortcut="Shift + H"
> >
Charts Heightmap
</button> </button>
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T"> <button id="overviewMarkersButton" data-tip="Click to open Markers Overview" data-shortcut="Shift + K">
Burgs Markers
</button>
<button id="overviewRoutesButton" data-tip="Click to open Routes Overview" data-shortcut="Shift + U">
Routes
</button>
<button id="overviewRiversButton" data-tip="Click to open Rivers Overview" data-shortcut="Shift + V">
Rivers
</button> </button>
<button <button
id="overviewMilitaryButton" id="overviewMilitaryButton"
@ -2130,41 +2107,58 @@
> >
Military Military
</button> </button>
<button id="overviewMarkersButton" data-tip="Click to open Markers Overview" data-shortcut="Shift + K"> <button id="editNamesBaseButton" data-tip="Click to open Namesbase Editor" data-shortcut="Shift + N">
Markers Namesbase
</button> </button>
<button id="overviewCellsButton" data-tip="Click to open Cell details view" data-shortcut="Shift + E"> <button id="editNotesButton" data-tip="Click to open Notes Editor" data-shortcut="Shift + O">Notes</button>
Cells <button id="editProvincesButton" data-tip="Click to open Provinces Editor" data-shortcut="Shift + P">
Provinces
</button> </button>
<button id="editReligions" data-tip="Click to open Religions Editor" data-shortcut="Shift + R">
Religions
</button>
<button id="overviewRiversButton" data-tip="Click to open Rivers Overview" data-shortcut="Shift + V">
Rivers
</button>
<button id="overviewRoutesButton" data-tip="Click to open Routes Overview" data-shortcut="Shift + U">
Routes
</button>
<button id="editStatesButton" data-tip="Click to open States Editor" data-shortcut="Shift + S">
States
</button>
<button id="editUnitsButton" data-tip="Click to open Units Editor" data-shortcut="Shift + Q">Units</button>
<button id="editZonesButton" data-tip="Click to open Zones Editor" data-shortcut="Shift + Z">Zones</button>
</div> </div>
<p>Click to regenerate:</p> <div class="separator">Regenerate</div>
<div id="regenerateFeature"> <div id="regenerateFeature" class="grid">
<button
id="regenerateBurgs"
data-tip="Click to regenerate all unlocked burgs and routes. States will remain as they are. Note: burgs are only generated in populated areas with culture assigned"
>
Burgs
</button>
<button id="regenerateCultures" data-tip="Click to regenerate non-locked cultures">Cultures</button>
<button id="regenerateEmblems" data-tip="Click to regenerate all emblems">Emblems</button>
<button id="regenerateIce" data-tip="Click to regenerate icebergs and glaciers">Ice</button>
<button <button
id="regenerateStateLabels" id="regenerateStateLabels"
data-tip="Click to update state labels placement based on current borders" data-tip="Click to update state labels placement based on current borders"
> >
Labels Labels
</button> </button>
<button <button id="regenerateMarkers" data-tip="Click to regenerate unlocked markers">
id="regenerateReliefIcons" Markers <i id="configRegenerateMarkers" class="icon-cog" data-tip="Click to set number multiplier"></i>
data-tip="Click to regenerate all relief icons based on current cell biome and elevation"
>
Relief
</button> </button>
<button id="regenerateRoutes" data-tip="Click to regenerate all routes">Routes</button> <button
<button id="regenerateRivers" data-tip="Click to regenerate all rivers (restore default state)"> id="regenerateMilitary"
Rivers data-tip="Click to recalculate military forces based on current military options"
>
Military
</button> </button>
<button id="regeneratePopulation" data-tip="Click to recalculate rural and urban population"> <button id="regeneratePopulation" data-tip="Click to recalculate rural and urban population">
Population Population
</button> </button>
<button
id="regenerateStates"
data-tip="Click to select new capitals and regenerate non-locked states. Emblems and military forces will be regenerated as well, burgs will remain as they are"
>
States
</button>
<button <button
id="regenerateProvinces" id="regenerateProvinces"
data-tip="Click to regenerate non-locked provinces. States will remain as they are" data-tip="Click to regenerate non-locked provinces. States will remain as they are"
@ -2172,23 +2166,21 @@
Provinces Provinces
</button> </button>
<button <button
id="regenerateBurgs" id="regenerateReliefIcons"
data-tip="Click to regenerate all unlocked burgs and routes. States will remain as they are. Note: burgs are only generated in populated areas with culture assigned" data-tip="Click to regenerate all relief icons based on current cell biome and elevation"
> >
Burgs Relief
</button> </button>
<button id="regenerateEmblems" data-tip="Click to regenerate all emblems">Emblems</button>
<button id="regenerateReligions" data-tip="Click to regenerate non-locked religions">Religions</button> <button id="regenerateReligions" data-tip="Click to regenerate non-locked religions">Religions</button>
<button id="regenerateCultures" data-tip="Click to regenerate non-locked cultures">Cultures</button> <button id="regenerateRivers" data-tip="Click to regenerate all rivers (restore default state)">
<button Rivers
id="regenerateMilitary"
data-tip="Click to recalculate military forces based on current military options"
>
Military
</button> </button>
<button id="regenerateIce" data-tip="Click to regenerate icebergs and glaciers">Ice</button> <button id="regenerateRoutes" data-tip="Click to regenerate all routes">Routes</button>
<button id="regenerateMarkers" data-tip="Click to regenerate unlocked markers"> <button
Markers <i id="configRegenerateMarkers" class="icon-cog" data-tip="Click to set number multiplier"></i> id="regenerateStates"
data-tip="Click to select new capitals and regenerate non-locked states. Emblems and military forces will be regenerated as well, burgs will remain as they are"
>
States
</button> </button>
<button <button
id="regenerateZones" id="regenerateZones"
@ -2198,8 +2190,8 @@
</button> </button>
</div> </div>
<p>Click to add:</p> <div class="separator">Add</div>
<div id="addFeature"> <div id="addFeature" class="grid">
<button <button
id="addBurgTool" id="addBurgTool"
data-tip="Click on map to place a burg. Hold Shift to add multiple" data-tip="Click on map to place a burg. Hold Shift to add multiple"
@ -2214,14 +2206,6 @@
> >
Label Label
</button> </button>
<button
id="addRiver"
data-tip="Click on map to place a river. Hold Shift to add multiple"
data-shortcut="Shift + 3"
>
River
</button>
<button id="addRoute" data-tip="Open route creation dialog" data-shortcut="Shift + 4">Route</button>
<button <button
id="addMarker" id="addMarker"
data-tip="Click on map to place a marker. Hold Shift to add multiple" data-tip="Click on map to place a marker. Hold Shift to add multiple"
@ -2229,10 +2213,36 @@
> >
Marker Marker
</button> </button>
<button
id="addRiver"
data-tip="Click on map to place a river. Hold Shift to add multiple"
data-shortcut="Shift + 3"
>
River
</button>
<<<<<<< HEAD
<button id="addRoute" data-tip="Open route creation dialog" data-shortcut="Shift + 4">Route</button>
=======
<button id="addRoute" data-tip="Click on map to place a route" data-shortcut="Shift + 4">Route</button>
</div> </div>
<p>Click to create a new map:</p> <div class="separator">Show</div>
<div> <div class="grid">
<button id="overviewCellsButton" data-tip="Click to open Cell details view" data-shortcut="Shift + E">
Cells
</button>
>>>>>>> 00abd5213b446922a60e2053eaca711a6d4067e2
<button
id="overviewChartsButton"
data-tip="Click to open Charts to overview cells data"
data-shortcut="Shift + A"
>
Charts
</button>
</div>
<div class="separator">Create</div>
<div class="grid">
<button id="openSubmapMenu" data-tip="Click to generate a submap from the current viewport">Submap</button> <button id="openSubmapMenu" data-tip="Click to generate a submap from the current viewport">Submap</button>
<button id="openResampleMenu" data-tip="Click to transform the map">Transform</button> <button id="openResampleMenu" data-tip="Click to transform the map">Transform</button>
</div> </div>
@ -2321,8 +2331,7 @@
<div id="aboutContent" class="tabcontent"> <div id="aboutContent" class="tabcontent">
<p> <p>
<a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a <a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is an
free
<a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank" <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank"
>open source</a >open source</a
> >
@ -2341,7 +2350,7 @@
<p> <p>
Join our <a href="https://discordapp.com/invite/X7E84HU" target="_blank">Discord server</a> and Join our <a href="https://discordapp.com/invite/X7E84HU" target="_blank">Discord server</a> and
<a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask
questions, get help and share maps. questions, get help and share maps. The created maps can be used for free, even for commercial purposes.
</p> </p>
<p> <p>
@ -2539,15 +2548,15 @@
<i data-locked="0" id="lock_mapSize" class="icon-lock-open"></i> <i data-locked="0" id="lock_mapSize" class="icon-lock-open"></i>
<label data-tip="Set map size relative to the world size"> <label data-tip="Set map size relative to the world size">
<i>Map size:</i> <i>Map size:</i>
<input id="mapSizeInput" data-stored="mapSize" type="number" min="1" max="100" />% <input id="mapSizeInput" data-stored="mapSize" type="number" min="1" max="100" step="0.1" />%
<input id="mapSizeOutput" data-stored="mapSize" type="range" min="1" max="100" /> <input id="mapSizeOutput" data-stored="mapSize" type="range" min="1" max="100" step="0.1" />
</label> </label>
</div> </div>
<div> <div>
<i data-locked="0" id="lock_latitude" class="icon-lock-open"></i> <i data-locked="0" id="lock_latitude" class="icon-lock-open"></i>
<label data-tip="Set a North-South map shift"> <label data-tip="Set a North-South map shift, set to 50 to make map center lie on Equator">
<i>Latitudes:</i> <i>Latitudes:</i>
<input id="latitudeInput" data-stored="latitude" type="number" min="0" max="100" step="1" /> <input id="latitudeInput" data-stored="latitude" type="number" min="0" max="100" step="0.1" />
<br /><i>N</i <br /><i>N</i
><input ><input
id="latitudeOutput" id="latitudeOutput"
@ -2555,14 +2564,42 @@
type="range" type="range"
min="0" min="0"
max="100" max="100"
step="1" step="0.1"
style="width: 10.3em" style="width: 10.3em"
/><i>S</i> /><i>S</i>
</label> </label>
</div> </div>
<div> <div>
<label data-tip="Set precipitation - water amount clouds can bring. Defines rivers and biomes generation"> <i data-locked="0" id="lock_longitude" class="icon-lock-open"></i>
<label data-tip="Set a West-East map shift, set to 50 to make map center lie on Prime meridian">
<i>Longitudes:</i>
<input
id="longitudeInput"
data-stored="longitude"
type="number"
min="0"
max="100"
value="50"
step="0.1"
/>
<br /><i>W</i
><input
id="longitudeOutput"
data-stored="longitude"
type="range"
min="0"
max="100"
step="0.1"
style="width: 10.3em"
/><i>E</i>
</label>
</div>
<div>
<label
data-tip="Set precipitation - water amount clouds can bring. Defines rivers and biomes generation. Keep around 100% for default generation"
>
<i data-locked="0" id="lock_prec" class="icon-lock-open"></i> <i data-locked="0" id="lock_prec" class="icon-lock-open"></i>
<i>Precipitation:</i> <i>Precipitation:</i>
<input id="precInput" data-stored="prec" type="number" />% <input id="precInput" data-stored="prec" type="number" />%
@ -2637,8 +2674,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 id="globeGradient" cx="100" cy="100" r="100" fill="url(#temperatureGradient)" stroke="none" />
<line id="globeEquator" x1="1" x2="199" y1="100" y2="100" /> <line id="globePrimeMeridian" x1="100" x2="100" y1="0" y2="200" />
<line id="globeEquator" x1="1" x2="200" y1="100" y2="100" />
<circle id="globeOutline" cx="100" cy="100" r="100" fill="none" />
<path id="globeGraticule" /> <path id="globeGraticule" />
<path id="globeArea" /> <path id="globeArea" />
</svg> </svg>
@ -2648,6 +2687,14 @@
</button> </button>
</div> </div>
</div> </div>
<div style="margin-top: 0.3em">
<i>Presets:</i>
<button id="wcWholeWorld" data-tip="Click to set map size to cover the whole world">Whole world</button>
<button id="wcNorthern" data-tip="Click to set map size to cover the Northern latitudes">Northern</button>
<button id="wcTropical" data-tip="Click to set map size to cover the Tropical latitudes">Tropical</button>
<button id="wcSouthern" data-tip="Click to set map size to cover the Southern latitudes">Southern</button>
</div>
</div> </div>
<div id="labelEditor" class="dialog" style="display: none"> <div id="labelEditor" class="dialog" style="display: none">
@ -7894,18 +7941,19 @@
</symbol> </symbol>
</g> </g>
<g id="rose" stroke-width="1"> <g id="defs-compass-rose" stroke-width="1.1">
<g id="sL" stroke="#3f3f3f"> <g id="rose-coord-line" stroke="#3f3f3f">
<line id="sL1" x1="0" y1="-20000" x2="0" y2="20000" /> <line id="sL1" x1="0" y1="-20000" x2="0" y2="20000" />
<line id="sL2" x1="-20000" y1="0" x2="20000" y2="0" /> <line id="sL2" x1="-20000" y1="0" x2="20000" y2="0" />
</g> </g>
<use href="#sL" transform="rotate(45)" /> <use href="#rose-coord-line" transform="rotate(45)" />
<use href="#sL" transform="rotate(22.5)" /> <use href="#rose-coord-line" transform="rotate(22.5)" />
<use href="#sL" transform="rotate(-22.5)" /> <use href="#rose-coord-line" transform="rotate(-22.5)" />
<use href="#sL" transform="rotate(11.25)" /> <use href="#rose-coord-line" transform="rotate(11.25)" />
<use href="#sL" transform="rotate(-11.25)" /> <use href="#rose-coord-line" transform="rotate(-11.25)" />
<use href="#sL" transform="rotate(56.25)" /> <use href="#rose-coord-line" transform="rotate(56.25)" />
<use href="#sL" transform="rotate(-56.25)" /> <use href="#rose-coord-line" 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 r="75" stroke="#008000" fill="#ffffff" fill-opacity=".1" />
@ -8136,7 +8184,7 @@
<script src="config/heightmap-templates.js"></script> <script src="config/heightmap-templates.js"></script>
<script src="config/precreated-heightmaps.js"></script> <script src="config/precreated-heightmaps.js"></script>
<script src="modules/heightmap-generator.js?v=1.88.00"></script> <script src="modules/heightmap-generator.js?v=1.88.00"></script>
<script src="modules/ocean-layers.js?v=1.96.00"></script> <script src="modules/ocean-layers.js?v=1.98.04"></script>
<script src="modules/river-generator.js?v=1.89.13"></script> <script src="modules/river-generator.js?v=1.89.13"></script>
<script src="modules/lakes.js"></script> <script src="modules/lakes.js"></script>
<script src="modules/biomes.js"></script> <script src="modules/biomes.js"></script>
@ -8158,15 +8206,15 @@
<script src="modules/ui/measurers.js?v=1.96.00"></script> <script src="modules/ui/measurers.js?v=1.96.00"></script>
<script src="modules/ui/stylePresets.js?v=1.96.00"></script> <script src="modules/ui/stylePresets.js?v=1.96.00"></script>
<script src="modules/ui/general.js?v=1.96.00"></script> <script src="modules/ui/general.js?v=1.98.01"></script>
<script src="modules/ui/options.js?v=1.97.14"></script> <script src="modules/ui/options.js?v=1.98.04"></script>
<script src="main.js?v=1.97.11"></script> <script src="main.js?v=1.98.01"></script>
<script defer src="modules/relief-icons.js"></script> <script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/style.js?v=1.96.00"></script> <script defer src="modules/ui/style.js?v=1.96.00"></script>
<script defer src="modules/ui/editors.js?v=1.97.12"></script> <script defer src="modules/ui/editors.js?v=1.97.12"></script>
<script defer src="modules/ui/tools.js?v=1.97.12"></script> <script defer src="modules/ui/tools.js?v=1.97.12"></script>
<script defer src="modules/ui/world-configurator.js?v=1.98.00"></script> <script defer src="modules/ui/world-configurator.js?v=1.98.01"></script>
<script defer src="modules/ui/heightmap-editor.js?v=1.96.00"></script> <script defer src="modules/ui/heightmap-editor.js?v=1.96.00"></script>
<script defer src="modules/ui/provinces-editor.js?v=1.96.00"></script> <script defer src="modules/ui/provinces-editor.js?v=1.96.00"></script>
<script defer src="modules/ui/biomes-editor.js?v=1.91.05"></script> <script defer src="modules/ui/biomes-editor.js?v=1.91.05"></script>
@ -8204,10 +8252,10 @@
<script defer src="modules/coa-renderer.js?v=1.94.00"></script> <script defer src="modules/coa-renderer.js?v=1.94.00"></script>
<script defer src="libs/rgbquant.min.js"></script> <script defer src="libs/rgbquant.min.js"></script>
<script defer src="libs/jquery.ui.touch-punch.min.js"></script> <script defer src="libs/jquery.ui.touch-punch.min.js"></script>
<script defer src="modules/io/save.js?v=1.96.00"></script> <script defer src="modules/io/save.js?v=1.98.01"></script>
<script defer src="modules/io/load.js?v=1.97.04"></script> <script defer src="modules/io/load.js?v=1.98.01"></script>
<script defer src="modules/io/cloud.js?v=1.96.00"></script> <script defer src="modules/io/cloud.js?v=1.96.00"></script>
<script defer src="modules/io/export.js?v=1.97.03"></script> <script defer src="modules/io/export.js?v=1.98.05"></script>
<!-- Web Components --> <!-- Web Components -->
<script defer src="components/fill-box.js"></script> <script defer src="components/fill-box.js"></script>

125
main.js
View file

@ -53,7 +53,7 @@ let biomes = viewbox.append("g").attr("id", "biomes");
let cells = viewbox.append("g").attr("id", "cells"); let cells = viewbox.append("g").attr("id", "cells");
let gridOverlay = viewbox.append("g").attr("id", "gridOverlay"); let gridOverlay = viewbox.append("g").attr("id", "gridOverlay");
let coordinates = viewbox.append("g").attr("id", "coordinates"); let coordinates = viewbox.append("g").attr("id", "coordinates");
let compass = viewbox.append("g").attr("id", "compass"); let compass = viewbox.append("g").attr("id", "compass").style("display", "none");
let rivers = viewbox.append("g").attr("id", "rivers"); let rivers = viewbox.append("g").attr("id", "rivers");
let terrain = viewbox.append("g").attr("id", "terrain"); let terrain = viewbox.append("g").attr("id", "terrain");
let relig = viewbox.append("g").attr("id", "relig"); let relig = viewbox.append("g").attr("id", "relig");
@ -126,6 +126,9 @@ emblems.append("g").attr("id", "burgEmblems");
emblems.append("g").attr("id", "provinceEmblems"); emblems.append("g").attr("id", "provinceEmblems");
emblems.append("g").attr("id", "stateEmblems"); emblems.append("g").attr("id", "stateEmblems");
// compass
compass.append("use").attr("xlink:href", "#defs-compass-rose");
// fogging // fogging
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
fogging fogging
@ -195,10 +198,10 @@ let options = {
}; };
let mapCoordinates = {}; // map coordinates on globe let mapCoordinates = {}; // map coordinates on globe
let populationRate = +document.getElementById("populationRateInput").value; let populationRate = +byId("populationRateInput").value;
let distanceScale = +document.getElementById("distanceScaleInput").value; let distanceScale = +byId("distanceScaleInput").value;
let urbanization = +document.getElementById("urbanizationInput").value; let urbanization = +byId("urbanizationInput").value;
let urbanDensity = +document.getElementById("urbanDensityInput").value; let urbanDensity = +byId("urbanDensityInput").value;
applyStoredOptions(); applyStoredOptions();
@ -439,10 +442,10 @@ function handleZoom(isScaleChanged, isPositionChanged) {
// zoom image converter overlay // zoom image converter overlay
if (customization === 1) { if (customization === 1) {
const canvas = document.getElementById("canvas"); const canvas = byId("canvas");
if (!canvas || canvas.style.opacity === "0") return; if (!canvas || canvas.style.opacity === "0") return;
const img = document.getElementById("imageToConvert"); const img = byId("imageToConvert");
if (!img) return; if (!img) return;
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
@ -524,7 +527,7 @@ function invokeActiveZooming() {
+markers.attr("rescale") && +markers.attr("rescale") &&
pack.markers?.forEach(marker => { pack.markers?.forEach(marker => {
const {i, x, y, size = 30, hidden} = marker; const {i, x, y, size = 30, hidden} = marker;
const el = !hidden && document.getElementById(`marker${i}`); const el = !hidden && byId(`marker${i}`);
if (!el) return; if (!el) return;
const zoomedSize = Math.max(rn(size / 5 + 24 / scale, 2), 1); const zoomedSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
@ -561,18 +564,18 @@ void (function addDragToUpload() {
document.addEventListener("dragover", function (e) { document.addEventListener("dragover", function (e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
document.getElementById("mapOverlay").style.display = null; byId("mapOverlay").style.display = null;
}); });
document.addEventListener("dragleave", function (e) { document.addEventListener("dragleave", function (e) {
document.getElementById("mapOverlay").style.display = "none"; byId("mapOverlay").style.display = "none";
}); });
document.addEventListener("drop", function (e) { document.addEventListener("drop", function (e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
const overlay = document.getElementById("mapOverlay"); const overlay = byId("mapOverlay");
overlay.style.display = "none"; overlay.style.display = "none";
if (e.dataTransfer.items == null || e.dataTransfer.items.length !== 1) return; // no files or more than one if (e.dataTransfer.items == null || e.dataTransfer.items.length !== 1) return; // no files or more than one
const file = e.dataTransfer.items[0].getAsFile(); const file = e.dataTransfer.items[0].getAsFile();
@ -780,7 +783,7 @@ function addLakesInDeepDepressions() {
TIME && console.time("addLakesInDeepDepressions"); TIME && console.time("addLakesInDeepDepressions");
const {cells, features} = grid; const {cells, features} = grid;
const {c, h, b} = cells; const {c, h, b} = cells;
const ELEVATION_LIMIT = +document.getElementById("lakeElevationLimitOutput").value; const ELEVATION_LIMIT = +byId("lakeElevationLimitOutput").value;
if (ELEVATION_LIMIT === 80) return; if (ELEVATION_LIMIT === 80) return;
for (const i of cells.i) { for (const i of cells.i) {
@ -880,73 +883,77 @@ function openNearSeaLakes() {
// define map size and position based on template and random factor // define map size and position based on template and random factor
function defineMapSize() { function defineMapSize() {
const [size, latitude] = getSizeAndLatitude(); const [size, latitude, longitude] = 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 = rn(size); if (randomize || !locked("mapSize")) mapSizeOutput.value = mapSizeInput.value = size;
if (randomize || !locked("latitude")) latitudeOutput.value = latitudeInput.value = rn(latitude); if (randomize || !locked("latitude")) latitudeOutput.value = latitudeInput.value = latitude;
if (randomize || !locked("longitude")) longitudeOutput.value = longitudeInput.value = longitude;
function getSizeAndLatitude() { function getSizeAndLatitude() {
const template = byId("templateInput").value; // heightmap template const template = byId("templateInput").value; // heightmap template
if (template === "africa-centric") return [45, 53]; if (template === "africa-centric") return [45, 53, 38];
if (template === "arabia") return [20, 35]; if (template === "arabia") return [20, 35, 35];
if (template === "atlantics") return [42, 23]; if (template === "atlantics") return [42, 23, 65];
if (template === "britain") return [7, 20]; if (template === "britain") return [7, 20, 51.3];
if (template === "caribbean") return [15, 40]; if (template === "caribbean") return [15, 40, 74.8];
if (template === "east-asia") return [11, 28]; if (template === "east-asia") return [11, 28, 9.4];
if (template === "eurasia") return [38, 19]; if (template === "eurasia") return [38, 19, 27];
if (template === "europe") return [20, 16]; if (template === "europe") return [20, 16, 44.8];
if (template === "europe-accented") return [14, 22]; if (template === "europe-accented") return [14, 22, 44.8];
if (template === "europe-and-central-asia") return [25, 10]; if (template === "europe-and-central-asia") return [25, 10, 39.5];
if (template === "europe-central") return [11, 22]; if (template === "europe-central") return [11, 22, 46.4];
if (template === "europe-north") return [7, 18]; if (template === "europe-north") return [7, 18, 48.9];
if (template === "greenland") return [22, 7]; if (template === "greenland") return [22, 7, 55.8];
if (template === "hellenica") return [8, 27]; if (template === "hellenica") return [8, 27, 43.5];
if (template === "iceland") return [2, 15]; if (template === "iceland") return [2, 15, 55.3];
if (template === "indian-ocean") return [45, 55]; if (template === "indian-ocean") return [45, 55, 14];
if (template === "mediterranean-sea") return [10, 29]; if (template === "mediterranean-sea") return [10, 29, 45.8];
if (template === "middle-east") return [8, 31]; if (template === "middle-east") return [8, 31, 34.4];
if (template === "north-america") return [37, 17]; if (template === "north-america") return [37, 17, 87];
if (template === "us-centric") return [66, 27]; if (template === "us-centric") return [66, 27, 100];
if (template === "us-mainland") return [16, 30]; if (template === "us-mainland") return [16, 30, 77.5];
if (template === "world") return [78, 27]; if (template === "world") return [78, 27, 40];
if (template === "world-from-pacific") return [75, 32]; if (template === "world-from-pacific") return [75, 32, 30]; // longitude doesn't fit
const part = grid.features.some(f => f.land && f.border); // if land goes over map borders const part = grid.features.some(f => f.land && f.border); // if land goes over map borders
const max = part ? 80 : 100; // max size const max = part ? 80 : 100; // max size
const lat = () => gauss(P(0.5) ? 40 : 60, 20, 25, 75); // latitude shift const lat = () => gauss(P(0.5) ? 40 : 60, 20, 25, 75); // latitude shift
if (!part) { if (!part) {
if (template === "Pangea") return [100, 50]; if (template === "pangea") return [100, 50, 50];
if (template === "Shattered" && P(0.7)) return [100, 50]; if (template === "shattered" && P(0.7)) return [100, 50, 50];
if (template === "Continents" && P(0.5)) return [100, 50]; if (template === "continents" && P(0.5)) return [100, 50, 50];
if (template === "Archipelago" && P(0.35)) return [100, 50]; if (template === "archipelago" && P(0.35)) return [100, 50, 50];
if (template === "High Island" && P(0.25)) return [100, 50]; if (template === "highIsland" && P(0.25)) return [100, 50, 50];
if (template === "Low Island" && P(0.1)) return [100, 50]; if (template === "lowIsland" && P(0.1)) return [100, 50, 50];
} }
if (template === "Pangea") return [gauss(70, 20, 30, max), lat()]; if (template === "pangea") return [gauss(70, 20, 30, max), lat(), 50];
if (template === "Volcano") return [gauss(20, 20, 10, max), lat()]; if (template === "volcano") return [gauss(20, 20, 10, max), lat(), 50];
if (template === "Mediterranean") return [gauss(25, 30, 15, 80), lat()]; if (template === "mediterranean") return [gauss(25, 30, 15, 80), lat(), 50];
if (template === "Peninsula") return [gauss(15, 15, 5, 80), lat()]; if (template === "peninsula") return [gauss(15, 15, 5, 80), lat(), 50];
if (template === "Isthmus") return [gauss(15, 20, 3, 80), lat()]; if (template === "isthmus") return [gauss(15, 20, 3, 80), lat(), 50];
if (template === "Atoll") return [gauss(5, 10, 2, max), lat()]; if (template === "atoll") return [gauss(3, 2, 1, 5, 1), lat(), 50];
return [gauss(30, 20, 15, max), lat()]; // Continents, Archipelago, High Island, Low Island return [gauss(30, 20, 15, max), lat(), 50]; // Continents, Archipelago, High Island, Low Island
} }
} }
// calculate map position on globe // calculate map position on globe
function calculateMapCoordinates() { function calculateMapCoordinates() {
const size = +document.getElementById("mapSizeOutput").value; const sizeFraction = +byId("mapSizeOutput").value / 100;
const latShift = +document.getElementById("latitudeOutput").value; const latShift = +byId("latitudeOutput").value / 100;
const lonShift = +byId("longitudeOutput").value / 100;
const latT = rn((size / 100) * 180, 1); const latT = rn(sizeFraction * 180, 1);
const latN = rn(90 - ((180 - latT) * latShift) / 100, 1); const latN = rn(90 - (180 - latT) * latShift, 1);
const latS = rn(latN - latT, 1); const latS = rn(latN - latT, 1);
const lon = rn(Math.min(((graphWidth / graphHeight) * latT) / 2, 180)); const lonT = rn(Math.min((graphWidth / graphHeight) * latT, 360), 1);
mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon}; const lonE = rn(180 - (360 - lonT) * lonShift, 1);
const lonW = rn(lonE - lonT, 1);
mapCoordinates = {latT, latN, latS, lonT, lonW, lonE};
} }
// temperature model, trying to follow real-world data // temperature model, trying to follow real-world data
@ -1759,7 +1766,7 @@ function addZones(number = 1) {
} }
function addEruption() { function addEruption() {
const volcano = document.getElementById("markers").querySelector("use[data-id='#marker_volcano']"); const volcano = byId("markers").querySelector("use[data-id='#marker_volcano']");
if (!volcano) return; if (!volcano) return;
const x = +volcano.dataset.x, const x = +volcano.dataset.x,
@ -1978,7 +1985,7 @@ function undraw() {
.getElementById("deftemp") .getElementById("deftemp")
.querySelectorAll("path, clipPath, svg") .querySelectorAll("path, clipPath, svg")
.forEach(el => el.remove()); .forEach(el => el.remove());
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems byId("coas").innerHTML = ""; // remove auto-generated emblems
notes = []; notes = [];
rulers = new Rulers(); rulers = new Rulers();
unfog(); unfog();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

3
run_php_server.bat Normal file
View file

@ -0,0 +1,3 @@
start chrome.exe http://localhost:3000/
@echo off
php -S localhost:3000

View file

@ -72,8 +72,8 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": "translate(80 80) scale(.25)" "transform": "translate(80 80) scale(0.25)"
}, },
"#relig": { "#relig": {
"opacity": 0.7, "opacity": 0.7,

View file

@ -72,7 +72,7 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": "translate(80 80) scale(.25)" "transform": "translate(80 80) scale(.25)"
}, },
"#relig": { "#relig": {

View file

@ -73,8 +73,8 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": null "transform": "translate(80 80) scale(.25)"
}, },
"#relig": { "#relig": {
"opacity": 0.7, "opacity": 0.7,

View file

@ -72,8 +72,8 @@
"mask": "", "mask": "",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": null "transform": "translate(80 80) scale(.25)"
}, },
"#relig": { "#relig": {
"opacity": 0.5, "opacity": 0.5,

View file

@ -72,8 +72,8 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": null "transform": "translate(80 80) scale(0.25)"
}, },
"#relig": { "#relig": {
"opacity": 0.7, "opacity": 0.7,

View file

@ -73,7 +73,7 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": "translate(100 100) scale(0.3)" "transform": "translate(100 100) scale(0.3)"
}, },
"#relig": { "#relig": {

View file

@ -72,8 +72,8 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": null "transform": "translate(80 80) scale(.25)"
}, },
"#relig": { "#relig": {
"opacity": 0.5, "opacity": 0.5,

View file

@ -73,8 +73,8 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": null "transform": "translate(80 80) scale(.25)"
}, },
"#relig": { "#relig": {
"opacity": 0.7, "opacity": 0.7,

View file

@ -72,7 +72,7 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": "translate(80 80) scale(0.25)" "transform": "translate(80 80) scale(0.25)"
}, },
"#relig": { "#relig": {

View file

@ -72,8 +72,8 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": null "transform": "translate(80 80) scale(.25)"
}, },
"#relig": { "#relig": {
"opacity": 0.5, "opacity": 0.5,

View file

@ -72,7 +72,7 @@
"mask": "url(#water)", "mask": "url(#water)",
"shape-rendering": "optimizespeed" "shape-rendering": "optimizespeed"
}, },
"#rose": { "#compass > use": {
"transform": "translate(80 80) scale(.25)" "transform": "translate(80 80) scale(.25)"
}, },
"#relig": { "#relig": {

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
// version and caching control // version and caching control
const version = "1.98.00"; // generator version, update each time const version = "1.99.00"; // generator version, update each time
{ {
document.title += " v" + version; document.title += " v" + version;
@ -28,7 +28,11 @@ const version = "1.98.00"; // generator version, update each time
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<<<<<<< HEAD
<li>New routes generatation algorithm</li> <li>New routes generatation algorithm</li>
=======
<li>Configurable longitude</li>
>>>>>>> 00abd5213b446922a60e2053eaca711a6d4067e2
<li>Preview villages map</li> <li>Preview villages map</li>
<li>Ability to render ocean heightmap</li> <li>Ability to render ocean heightmap</li>
<li>Scale bar styling features</li> <li>Scale bar styling features</li>
@ -41,6 +45,10 @@ const version = "1.98.00"; // generator version, update each time
<li>North and South Poles temperature can be set independently</li> <li>North and South Poles temperature can be set independently</li>
<li>More than 70 new heraldic charges</li> <li>More than 70 new heraldic charges</li>
<li>Multi-color heraldic charges support</li> <li>Multi-color heraldic charges support</li>
<<<<<<< HEAD
=======
<li>New 3D scene options and improvements</li>
>>>>>>> 00abd5213b446922a60e2053eaca711a6d4067e2
</ul> </ul>
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p> <p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>