mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
v 1.21
This commit is contained in:
parent
a3fa5443d6
commit
5ffd30dee8
19 changed files with 686 additions and 412 deletions
13
index.css
13
index.css
|
|
@ -593,7 +593,7 @@ button.active {
|
|||
|
||||
#viewMode > button {
|
||||
padding: .35em;
|
||||
margin: .2em .3em;
|
||||
margin: .2em .3em .6em .3em;
|
||||
float: left;
|
||||
width: 30.7%;
|
||||
}
|
||||
|
|
@ -1287,6 +1287,10 @@ div.states span.inactive:hover {
|
|||
color: #abaaaa;
|
||||
}
|
||||
|
||||
div.states>input.riverType {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
#diplomacyBodySection > div {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -1976,6 +1980,13 @@ svg.button {
|
|||
border: 1px solid #916e7f;
|
||||
}
|
||||
|
||||
.announcement {
|
||||
background-color: #a18888;
|
||||
color: white;
|
||||
padding: .4em .5em;
|
||||
border: dashed 1px #5d4651;
|
||||
}
|
||||
|
||||
#debug {
|
||||
font-size: 1px;
|
||||
opacity: .8;
|
||||
|
|
|
|||
188
index.html
188
index.html
|
|
@ -1798,17 +1798,17 @@
|
|||
<button id="editNamesBaseButton" data-tip="Click to open Namesbase Editor. Shortcut: Shift + N">Namesbase</button>
|
||||
<button id="editZonesButton" data-tip="Click to open Zones Editor. Shortcut: Shift + Z">Zones</button>
|
||||
<button id="editReligions" data-tip="Click to open Religions Editor. Shortcut: Shift + R">Religions</button>
|
||||
<button id="editUnitsButton" data-tip="Click to open Units Editor. Shortcut: Shift + U">Units</button>
|
||||
<button id="editUnitsButton" data-tip="Click to open Units Editor. Shortcut: Shift + Q">Units</button>
|
||||
<button id="editNotesButton" data-tip="Click to open Notes Editor. Shortcut: Shift + O">Notes</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Click to overview:</p>
|
||||
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview. Shortcut: Shift + T">Burgs</button>
|
||||
<button id="overviewRiversButton" data-tip="Click to open Rivers Overview. Shortcut: Shift + V">Rivers</button>
|
||||
<button id="overviewCellsButton" data-tip="Click to open Cell details view. Shortcut: Shift + E">Cells</button>
|
||||
<!-- <button id="overviewLandmassedButton" data-tip="Click to open Landmasses Overview. Shortcut: Shift + L">Landmasses</button> -->
|
||||
<!-- <button id="overviewWaterbodiesButton" data-tip="Click to open Waterbodies Overview. Shortcut: Shift + W">Waterbodies</button> -->
|
||||
<!-- <button id="overviewRiversButton" data-tip="Click to open Rivers Overview. Shortcut: Shift + V">Rivers</button> -->
|
||||
<!-- <button id="overviewRoutesButton" data-tip="Click to open Routes Overview. Shortcut: Shift + U">Routes</button> -->
|
||||
</div>
|
||||
|
||||
|
|
@ -1817,7 +1817,7 @@
|
|||
<button id="regenerateStateLabels" data-tip="Click to update state labels placement based on current borders">State labels</button>
|
||||
<button id="regenerateReliefIcons" data-tip="Click to regenerate all relief icons based on current cell biome and elevation">Relief icons</button>
|
||||
<button id="regenerateRoutes" data-tip="Click to regenerate all routes">Routes</button>
|
||||
<button id="regenerateRivers" data-tip="Click to regenerate all rivers">Rivers</button>
|
||||
<button id="regenerateRivers" data-tip="Click to regenerate all rivers (restore default state)">Rivers</button>
|
||||
<button id="regeneratePopulation" data-tip="Click to recalculate rural and urban population">Population</button>
|
||||
<button id="regenerateBurgs" data-tip="Click to regenerate all burgs and routes. States will remain as they are">Burgs</button>
|
||||
<button id="regenerateStates" data-tip="Click to select new capitals and regenerate states. Burgs will remain as they are">States</button>
|
||||
|
|
@ -1829,11 +1829,11 @@
|
|||
|
||||
<div id="addFeature">
|
||||
<p>Click to add:</p>
|
||||
<button id="addBurgTool" data-tip="Click on map to place a burg. Hold Shift to add multiple. Shortcut: Shift + G">Burg</button>
|
||||
<button id="addLabel" data-tip="Click on map to place label. Hold Shift to add multiple. Shortcut: Shift + A">Label</button>
|
||||
<button id="addRiver" data-tip="Click on map to place a river. Hold Shift to add multiple. Shortcut: Shift + I">River</button>
|
||||
<button id="addRoute" data-tip="Click on map to place a route. Shortcut: Shift + E">Route</button>
|
||||
<button id="addMarker" data-tip="Click on map to place a marker. Hold Shift to add multiple. Shortcut: Shift + K">Marker</button>
|
||||
<button id="addBurgTool" data-tip="Click on map to place a burg. Hold Shift to add multiple. Shortcut: Shift + 1">Burg</button>
|
||||
<button id="addLabel" data-tip="Click on map to place label. Hold Shift to add multiple. Shortcut: Shift + 2">Label</button>
|
||||
<button id="addRiver" data-tip="Click on map to place a river. Hold Shift to add multiple. Shortcut: Shift + 3">River</button>
|
||||
<button id="addRoute" data-tip="Click on map to place a route. Shortcut: Shift + 4">Route</button>
|
||||
<button id="addMarker" data-tip="Click on map to place a marker. Hold Shift to add multiple. Shortcut: Shift + 5">Marker</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1901,23 +1901,8 @@
|
|||
|
||||
<div id="sticked">
|
||||
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F2">New Map</button>
|
||||
<button id="saveButton" data-tip="Select file format to save map">Save</button>
|
||||
<div id="saveDropdown">
|
||||
<div id="saveMap" data-tip="Download the map as fully functional .map file to your machine. Shortcut: Ctrl + M">.map</div>
|
||||
<div id="saveSVG" data-tip="Download the map as vector image (open in browser or Inkscape). Shortcut: Ctrl + S">.svg</div>
|
||||
<div id="savePNG" data-tip="Download visible part of the map as .png (lossless compressed) image. Shortcut: Ctrl + P">.png</div>
|
||||
<div id="saveJPEG" data-tip="Download visible part of the map as .jpeg (lossy compressed) image. Shortcut: Ctrl + J">.jpeg</div>
|
||||
<div id="saveGeo" data-tip="Download map data in GeoJSON format. Shortcut: Ctrl + G">.json</div>
|
||||
<div id="quickSave" data-tip="Save map to browser storage. Shortcut: F6">storage</div>
|
||||
<!-- <div id="saveDropbox" data-tip="Save fully functional .map file to Dropbox. Shortcut: Ctrl + B">Dropbox</div> -->
|
||||
</div>
|
||||
<button id="saveButton" data-tip="Select format to save map">Save</button>
|
||||
<button id="loadButton" data-tip="Load fully functional map in a .map format">Load</button>
|
||||
<div id="loadDropdown">
|
||||
<div id="loadMap" data-tip="Load .map file from local disk. Shortcut: Ctrl + L">from local disk</div>
|
||||
<div id="loadURL" data-tip="Load .map file from URL (server should allow CORS). Shortcut: Ctrl + U">from URL</div>
|
||||
<div id="quickLoad" data-tip="Load map from browser storage (if saved before). Shortcut: F9">from storage</div>
|
||||
<!-- <div id="loadDropbox" data-tip="Load .map file from Dropbox. Shortcut: Ctrl + D">from Dropbox</div> -->
|
||||
</div>
|
||||
<button id="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -1931,16 +1916,16 @@
|
|||
<div id="worldControls">
|
||||
<div>
|
||||
<i data-locked=0 id="lock_temperatureEquator" class="icon-lock-open"></i>
|
||||
<label data-tip="Set temerature at equator">
|
||||
<label data-tip="Set temperature at equator">
|
||||
<i>Equator:</i>
|
||||
<input id="temperatureEquatorInput" data-stored="temperatureEquator" type="number" min="-30" max="30">°C =
|
||||
<input id="temperatureEquatorInput" data-stored="temperatureEquator" type="number" min="-30" max="30">°C =
|
||||
<span id="temperatureEquatorF"></span>°F
|
||||
<input id="temperatureEquatorOutput" data-stored="temperatureEquator" type="range" min="-30" max="30"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<i data-locked=0 id="lock_temperaturePole" class="icon-lock-open"></i>
|
||||
<label data-tip="Set temerature at poles">
|
||||
<label data-tip="Set temperature at poles">
|
||||
<i>Poles:</i>
|
||||
<input id="temperaturePoleInput" data-stored="temperaturePole" type="number" min="-30" max="30">°C =
|
||||
<span id="temperaturePoleF"></span>°F
|
||||
|
|
@ -2073,29 +2058,26 @@
|
|||
</div>
|
||||
|
||||
<div id="riverEditor" class="dialog" style="display: none">
|
||||
<button id="riverWidthShow" data-tip="Show the river width and widening change section" class="icon-resize-full"></button>
|
||||
<button id="riverNameShow" data-tip="Show river name section" class="icon-font"></button>
|
||||
<div id="riverNameSection" style="display: none">
|
||||
<button id="riverNameHide" data-tip="Hide the river name section" class="icon-font"></button>
|
||||
<input id="riverName" data-tip="Change river proper name" style="width: 8em">
|
||||
<input id="riverType" data-tip="Change river type name" style="width: 6em">
|
||||
<span id="riverNameCulture" data-tip="Generate culture-specific name for the river" class="icon-book pointer"></span>
|
||||
<span id="riverNameRandom" data-tip="Generate random name for the river" class="icon-globe pointer"></span>
|
||||
</div>
|
||||
|
||||
<button id="riverWidthShow" data-tip="Show river width and widening change section" class="icon-resize-full"></button>
|
||||
<div id="riverWidthSection" style="display: none">
|
||||
<button id="riverWidthHide" data-tip="Hide the river width and widening change section" class="icon-resize-full"></button>
|
||||
<i id="riverWidthIcon" class="icon-w"></i>
|
||||
<input id="riverWidthInput" data-tip="Change river width" value="1" type="range" min=.2 max=5 step=.1>
|
||||
<input id="riverWidthInput" data-tip="Change river width" value="1" type="range" min=.2 max=10 step=.1>
|
||||
<i id="riverIncrementIcon" class="icon-i"></i>
|
||||
<input id="riverIncrement" data-tip="Change river bed increment (widening speed)" type="range" min=.02 max=2 step=.02>
|
||||
</div>
|
||||
|
||||
<button id="riverResizeShow" data-tip="Show river transformation section" class="icon-ccw"></button>
|
||||
<div id="riverResizeSection" style="display: none">
|
||||
<button id="riverResizeHide" data-tip="Hide river transformation section" class="icon-ccw"></button>
|
||||
<i id="riverAngleIcon" class="icon-a"></i>
|
||||
<input id="riverAngle" data-tip="Rotate river by angle" type="range" min="-90" max=90 step=.2>
|
||||
<span id="riverAngleValue">0℉</span>
|
||||
<i id="riverScaleIcon" class="icon-s"></i>
|
||||
<input id="riverScale" data-tip="Change river scale" value="1" type="number" style="width:3.5em" min=.1 max=3 step=.01>
|
||||
<span id="riverReset" data-tip="Reset transformation to default" class="icon-cancel pointer"></span>
|
||||
<input id="riverIncrement" data-tip="Change river bed increment (widening speed)" type="range" min=.01 max=3 step=.01>
|
||||
</div>
|
||||
|
||||
<button id="riverEditStyle" data-tip="Edit style for all rivers in Style Editor" class="icon-brush"></button>
|
||||
<button id="riverLength" data-tip="River length in selected units">0</button>
|
||||
<button id="riverCopy" data-tip="Copy river" class="icon-clone"></button>
|
||||
<button id="riverNew" data-tip="Create new river clicking on map" class="icon-map-pin"></button>
|
||||
<button id="riverLegend" data-tip="Edit free text notes (legend) for the river" class="icon-edit"></button>
|
||||
<button id="riverRemove" data-tip="Remove river. Shortcut: Delete" class="icon-trash"></button>
|
||||
|
|
@ -2808,41 +2790,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="burgsEditor" class="dialog stable" style="display: none">
|
||||
<div id="burgsHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by burg name" class="sortable alphabetically icon-sort-name-up" data-sortby="name">Burg </div>
|
||||
<div style="left:7.6em" data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="province">Province </div>
|
||||
<div style="left:14em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="state">State </div>
|
||||
<div style="left:20.1em" data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="culture">Culture </div>
|
||||
<div style="left:24.7em" data-tip="Click to sort by burg population" class="sortable" data-sortby="population">Population </div>
|
||||
<div style="left:31.2em" data-tip="Click to sort by burg type" class="sortable alphabetically" data-sortby="type">Type </div>
|
||||
</div>
|
||||
|
||||
<div id="burgsBody" class="burgs-table"></div>
|
||||
|
||||
<div id="burgsFilters" data-tip="Apply a filter">
|
||||
<span>State: </span>
|
||||
<select id="burgsFilterState" style="width:28%"></select>
|
||||
<span>Culture:</span>
|
||||
<select id="burgsFilterCulture" style="width:28%"></select>
|
||||
</div>
|
||||
|
||||
<div id="burgsFooter" class="totalLine">
|
||||
<div data-tip="Burgs displayed" style="margin-left: 4px">Burgs: <span id="burgsFooterBurgs">0</span></div>
|
||||
<div data-tip="Average population" style="margin-left: 14px">Average population: <span id="burgsFooterPopulation">0</span></div>
|
||||
</div>
|
||||
|
||||
<div id="burgsBottom">
|
||||
<button id="burgsEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
|
||||
<button id="burgsChart" data-tip="Show burgs bubble chart" class="icon-chart-area"></button>
|
||||
<button id="regenerateBurgNames" data-tip="Regenerate burg names based on assigned culture" class="icon-retweet"></button>
|
||||
<button id="addNewBurg" data-tip="Add a new burg. Hold Shift to add multiple" class="icon-plus"></button>
|
||||
<button id="burgsExport" data-tip="Save burgs-related data as a text file (.csv)" class="icon-download"></button>
|
||||
<button id="burgNamesImport" data-tip="Rename burgs in bulk" class="icon-upload"></button>
|
||||
<button id="burgsRemoveAll" data-tip="Remove all burgs except for capitals. To remove a capital remove its state first" class="icon-trash"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="provinceNameEditor" class="dialog" data-province="0" style="display: none">
|
||||
<div style="padding: .1em">
|
||||
<div data-tip="Province short name" class="label">Short name:</div>
|
||||
|
|
@ -3014,7 +2961,7 @@
|
|||
<span>Select object: </span>
|
||||
<select id="notesSelect" data-tip="Select object" style="width: 12em"></select>
|
||||
<span>Object name: </span>
|
||||
<input id="notesName" data-tip="Type to change object name" autocorrect="off" spellcheck="false" style="width: 12em">
|
||||
<input id="notesName" data-tip="Type to change object name" autocorrect="off" spellcheck="false" style="width: 17em">
|
||||
</div>
|
||||
<div>
|
||||
<span>Legend:</span><br>
|
||||
|
|
@ -3195,6 +3142,64 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="burgsOverview" class="dialog stable" style="display: none">
|
||||
<div id="burgsHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by burg name" class="sortable alphabetically icon-sort-name-up" data-sortby="name">Burg </div>
|
||||
<div style="left:7.6em" data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="province">Province </div>
|
||||
<div style="left:14em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="state">State </div>
|
||||
<div style="left:20.1em" data-tip="Click to sort by culture name" class="sortable alphabetically" data-sortby="culture">Culture </div>
|
||||
<div style="left:24.7em" data-tip="Click to sort by burg population" class="sortable" data-sortby="population">Population </div>
|
||||
<div style="left:31.2em" data-tip="Click to sort by burg type" class="sortable alphabetically" data-sortby="type">Type </div>
|
||||
</div>
|
||||
|
||||
<div id="burgsBody" class="burgs-table"></div>
|
||||
|
||||
<div id="burgsFilters" data-tip="Apply a filter">
|
||||
<span>State: </span>
|
||||
<select id="burgsFilterState" style="width:28%"></select>
|
||||
<span>Culture:</span>
|
||||
<select id="burgsFilterCulture" style="width:28%"></select>
|
||||
</div>
|
||||
|
||||
<div id="burgsFooter" class="totalLine">
|
||||
<div data-tip="Burgs displayed" style="margin-left: 4px">Burgs: <span id="burgsFooterBurgs">0</span></div>
|
||||
<div data-tip="Average population" style="margin-left: 14px">Average population: <span id="burgsFooterPopulation">0</span></div>
|
||||
</div>
|
||||
|
||||
<div id="burgsBottom">
|
||||
<button id="burgsOverviewRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
|
||||
<button id="burgsChart" data-tip="Show burgs bubble chart" class="icon-chart-area"></button>
|
||||
<button id="regenerateBurgNames" data-tip="Regenerate burg names based on assigned culture" class="icon-retweet"></button>
|
||||
<button id="addNewBurg" data-tip="Add a new burg. Hold Shift to add multiple" class="icon-plus"></button>
|
||||
<button id="burgsExport" data-tip="Save burgs-related data as a text file (.csv)" class="icon-download"></button>
|
||||
<button id="burgNamesImport" data-tip="Rename burgs in bulk" class="icon-upload"></button>
|
||||
<button id="burgsRemoveAll" data-tip="Remove all burgs except for capitals. To remove a capital remove its state first" class="icon-trash"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="riversOverview" class="dialog stable" style="display: none">
|
||||
<div id="riversHeader" class="header">
|
||||
<div style="left:1.4em" data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name">River </div>
|
||||
<div style="left:7.7em" data-tip="Click to sort by river type name" class="sortable alphabetically" data-sortby="type">Type </div>
|
||||
<div style="left:12.9em" data-tip="Click to sort by river length" class="sortable icon-sort-number-down" data-sortby="length">Length </div>
|
||||
<div style="left:18.2em" data-tip="Click to sort by river basin" class="sortable alphabetically" data-sortby="basin">Basin </div>
|
||||
</div>
|
||||
|
||||
<div id="riversBody" class="burgs-table"></div>
|
||||
|
||||
<div id="riversFooter" class="totalLine">
|
||||
<div data-tip="Rivers number" style="margin-left: 4px">Rivers: <span id="riversFooterNumber">0</span></div>
|
||||
<div data-tip="Average length" style="margin-left: 14px">Average length: <span id="riversFooterLength">0</span></div>
|
||||
</div>
|
||||
|
||||
<div id="riversBottom">
|
||||
<button id="riversOverviewRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
|
||||
<button id="addNewRiver" data-tip="Add a new river. Hold Shift to add multiple" class="icon-plus"></button>
|
||||
<button id="riversExport" data-tip="Save rivers-related data as a text file (.csv)" class="icon-download"></button>
|
||||
<button id="riversRemoveAll" data-tip="Remove all rivers" class="icon-trash"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="styleSaver" class="dialog stable textual" style="display: none">
|
||||
<div id="styleSaverHeader" style="padding:2px 0">
|
||||
<span>Preset name:</span>
|
||||
|
|
@ -3221,6 +3226,7 @@
|
|||
<p><b>Area:</b> <span id="infoArea">0</span></p>
|
||||
<p><b>Type:</b> <span id="infoFeature">n/a</span></p>
|
||||
<p><b>Precipitation:</b> <span id="infoPrec">0</span></p>
|
||||
<p><b>River:</b> <span id="infoRiver">no</span></p>
|
||||
<p><b>Population:</b> <span id="infoPopulation">0</span></p>
|
||||
<p><b>Height:</b> <span id="infoHeight">0</span></p>
|
||||
<p><b>Temperature:</b> <span id="infoTemp">0</span></p>
|
||||
|
|
@ -3232,6 +3238,28 @@
|
|||
<p><b>Burg:</b> <span id="infoBurg">n/a</span></p>
|
||||
</div>
|
||||
|
||||
<div id="saveMapData" style="display: none" class="dialog">
|
||||
<div style="margin-bottom: .3em">Please select a saving variant:</div>
|
||||
<div>
|
||||
<button onclick="saveMap()" data-tip="Download the map as fully-functional .map file to your machine. Shortcut: Ctrl + M">.map</button>
|
||||
<button onclick="saveSVG()" data-tip="Download the map as vector image (open in browser or Inkscape). Shortcut: Ctrl + S">.svg</button>
|
||||
<button onclick="savePNG()" data-tip="Download visible part of the map as .png (lossless compressed) image. Shortcut: Ctrl + P">.png</button>
|
||||
<button onclick="saveJPEG()" data-tip="Download visible part of the map as .jpeg (lossy compressed) image. Shortcut: Ctrl + J">.jpeg</button>
|
||||
<button onclick="saveGeoJSON()" data-tip="Download map data in GeoJSON format. Shortcut: Ctrl + G">.json</button>
|
||||
<button onclick="quickSave()" data-tip="Save fully-functional map to browser storage. Shortcut: F6">storage</button>
|
||||
</div>
|
||||
<p style="font-style: italic">Generator uses pop-up window to download files. Please ensure your browser does not block popups</p>
|
||||
</div>
|
||||
|
||||
<div id="loadMapData" style="display: none" class="dialog">
|
||||
<div style="margin-bottom: .3em">Load map from:</div>
|
||||
<div>
|
||||
<button onclick="mapToLoad.click()" data-tip="Load .map file from local disk. Shortcut: Ctrl + L">local disk</button>
|
||||
<button onclick="loadURL()" data-tip="Load .map file from URL (server should allow CORS). Shortcut: Ctrl + U">URL</button>
|
||||
<button onclick="quickLoad()" data-tip="Load map from browser storage (if saved before). Shortcut: F9">storage</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="preview3d" class="dialog stable" style="display: none; padding: 0px"></div>
|
||||
|
||||
<div id="alert" style="display: none" class="dialog">
|
||||
|
|
@ -3292,7 +3320,6 @@
|
|||
<script defer src="modules/ui/heightmap-editor.js"></script>
|
||||
<script defer src="modules/ui/states-editor.js"></script>
|
||||
<script defer src="modules/ui/provinces-editor.js"></script>
|
||||
<script defer src="modules/ui/burgs-editor.js"></script>
|
||||
<script defer src="modules/ui/biomes-editor.js"></script>
|
||||
<script defer src="modules/ui/cultures-editor.js"></script>
|
||||
<script defer src="modules/ui/namesbase-editor.js"></script>
|
||||
|
|
@ -3305,11 +3332,12 @@
|
|||
<script defer src="modules/ui/religions-editor.js"></script>
|
||||
<script defer src="modules/ui/markers-editor.js"></script>
|
||||
<script defer src="modules/ui/burg-editor.js"></script>
|
||||
<script defer src="modules/ui/burgs-editor.js"></script>
|
||||
<script defer src="modules/ui/units-editor.js"></script>
|
||||
<script defer src="modules/ui/notes-editor.js"></script>
|
||||
<script defer src="modules/ui/diplomacy-editor.js"></script>
|
||||
<script defer src="modules/ui/zones-editor.js"></script>
|
||||
<script defer src="modules/ui/burgs-overview.js"></script>
|
||||
<script defer src="modules/ui/rivers-overview.js"></script>
|
||||
<script defer src="modules/ui/editors.js"></script>
|
||||
<script defer src="modules/ui/3d.js"></script>
|
||||
<script defer src="libs/quantize.min.js"></script>
|
||||
|
|
|
|||
2
libs/jquery-ui.min.js
vendored
2
libs/jquery-ui.min.js
vendored
File diff suppressed because one or more lines are too long
24
main.js
24
main.js
|
|
@ -7,11 +7,11 @@
|
|||
// See also https://github.com/Azgaar/Fantasy-Map-Generator/issues/153
|
||||
|
||||
"use strict";
|
||||
const version = "1.2"; // generator version
|
||||
const version = "1.21"; // generator version
|
||||
document.title += " v" + version;
|
||||
|
||||
// if map version is not stored, clear localStorage and show a message
|
||||
if (rn(localStorage.getItem("version"),1) !== rn(version,1)) {
|
||||
if (rn(localStorage.getItem("version"), 2) !== rn(version, 2)) {
|
||||
localStorage.clear();
|
||||
setTimeout(showWelcomeMessage, 8000);
|
||||
}
|
||||
|
|
@ -325,10 +325,18 @@ function showWelcomeMessage() {
|
|||
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
|
||||
|
||||
<ul>${post}
|
||||
<li>3d scene</li>
|
||||
<li>Globe view</li>
|
||||
<li>3d scene and Globe view</li>
|
||||
<li>Ability to save map as JPEG image</li>
|
||||
<li>Diplomacy Editor enhancements</li>
|
||||
<li>Rivers Overview screen [v 1.21] <b>*</b></li>
|
||||
</ul>
|
||||
|
||||
<p style="color:#990000; font-style: italic"><b>*</b> It's recommended to regenerate rivers to get clean data for Rivers Overview.<p>
|
||||
|
||||
<p class="announcement">We are happy to invite you to participate in our first map making contest!
|
||||
Valuable prizes for winners and our respect for all participants.
|
||||
See ${link("https://www.reddit.com/r/FantasyMapGenerator/comments/dn2sqv/azgaars_fantasy_map_generator_mapmaking_contest/", "Reddit post")} for the details.</p>
|
||||
|
||||
<p>Join our ${reddit} and ${discord} to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
|
||||
<p>Thanks for all supporters on ${patreon}!</i></p>`;
|
||||
|
||||
|
|
@ -513,6 +521,8 @@ function generate() {
|
|||
drawStates();
|
||||
drawBorders();
|
||||
BurgsAndStates.drawStateLabels();
|
||||
|
||||
Rivers.specify();
|
||||
addMarkers();
|
||||
addZones();
|
||||
Names.getMapName();
|
||||
|
|
@ -1222,9 +1232,9 @@ function addMarkers(number = 1) {
|
|||
.attr("data-size", 1).attr("width", 30).attr("height", 30);
|
||||
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const river = Names.getCulture(cells.culture[cell]); // river name
|
||||
const name = Math.random() < .2 ? river : burg.name;
|
||||
notes.push({id, name:`${name} Bridge`, legend:`A stone bridge over the ${river} River near ${burg.name}`});
|
||||
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
|
||||
const name = Math.random() < .2 ? river.name : burg.name;
|
||||
notes.push({id, name:`${name} Bridge`, legend:`A stone bridge over the ${river.name} ${river.type} near ${burg.name}`});
|
||||
count--;
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -73,12 +73,16 @@
|
|||
riversData.push({river: riverNext, cell: i, x, y});
|
||||
riverNext++;
|
||||
}
|
||||
|
||||
|
||||
if (cells.r[min]) { // downhill cell already has river assigned
|
||||
if (cells.fl[min] < cells.fl[i]) {
|
||||
cells.conf[min] = cells.fl[min]; // confluence
|
||||
cells.conf[min] = cells.fl[min]; // mark confluence
|
||||
if (h[min] >= 20) riversData.find(r => r.river === cells.r[min]).parent = cells.r[i]; // min river is a tributary of current river
|
||||
cells.r[min] = cells.r[i]; // re-assign river if downhill part has less flux
|
||||
} else cells.conf[min] += cells.fl[i]; // confluence
|
||||
} else {
|
||||
cells.conf[min] += cells.fl[i]; // mark confluence
|
||||
if (h[min] >= 20) riversData.find(r => r.river === cells.r[i]).parent = cells.r[min]; // current river is a tributary of min river
|
||||
}
|
||||
} else cells.r[min] = cells.r[i]; // assign the river to the downhill cell
|
||||
|
||||
const nx = p[min][0], ny = p[min][1];
|
||||
|
|
@ -99,25 +103,29 @@
|
|||
|
||||
});
|
||||
}()
|
||||
|
||||
void function drawRivers() {
|
||||
const riverPaths = []; // to store data for all rivers
|
||||
|
||||
void function defineRivers() {
|
||||
pack.rivers = []; // rivers data
|
||||
const riverPaths = []; // temporary data for all rivers
|
||||
|
||||
for (let r = 1; r <= riverNext; r++) {
|
||||
const riverSegments = riversData.filter(d => d.river === r);
|
||||
|
||||
|
||||
if (riverSegments.length > 2) {
|
||||
const riverEnhanced = addMeandring(riverSegments);
|
||||
const width = rn(0.8 + Math.random() * 0.4, 1); // river width modifier
|
||||
const increment = rn(0.8 + Math.random() * 0.6, 1); // river bed widening modifier
|
||||
const path = getPath(riverEnhanced, width, increment);
|
||||
const width = rn(.8 + Math.random() * .4, 1); // river width modifier
|
||||
const increment = rn(.8 + Math.random() * .6, 1); // river bed widening modifier
|
||||
const [path, length] = getPath(riverEnhanced, width, increment);
|
||||
riverPaths.push([r, path, width, increment]);
|
||||
const parent = riverSegments[0].parent || 0;
|
||||
pack.rivers.push({i:r, parent, length, source:riverSegments[0].cell, mouth:last(riverSegments).cell});
|
||||
} else {
|
||||
// remove too short rivers
|
||||
riverSegments.filter(s => cells.r[s.cell] === r).forEach(s => cells.r[s.cell] = 0);
|
||||
}
|
||||
}
|
||||
|
||||
// drawRivers
|
||||
rivers.selectAll("path").remove();
|
||||
rivers.selectAll("path").data(riverPaths).enter()
|
||||
.append("path").attr("d", d => d[1]).attr("id", d => "river"+d[0])
|
||||
|
|
@ -129,12 +137,10 @@
|
|||
|
||||
// depression filling algorithm (for a correct water flux modeling)
|
||||
const resolveDepressions = function(h) {
|
||||
console.time('resolveDepressions');
|
||||
const cells = pack.cells;
|
||||
const land = cells.i.filter(i => h[i] >= 20 && h[i] < 100 && !cells.b[i]); // exclude near-border cells
|
||||
land.sort((a,b) => h[b] - h[a]); // highest cells go first
|
||||
let depressed = false;
|
||||
const depressions = [];
|
||||
|
||||
for (let l = 0, depression = Infinity; depression && l < 100; l++) {
|
||||
depression = 0;
|
||||
|
|
@ -147,12 +153,8 @@
|
|||
depressed = true;
|
||||
}
|
||||
}
|
||||
depressions.push(depression);
|
||||
}
|
||||
|
||||
console.log(depressions);
|
||||
|
||||
console.timeEnd('resolveDepressions');
|
||||
return depressed;
|
||||
}
|
||||
|
||||
|
|
@ -242,9 +244,56 @@
|
|||
const right = lineGen(riverPointsRight);
|
||||
let left = lineGen(riverPointsLeft);
|
||||
left = left.substring(left.indexOf("C"));
|
||||
return round(right + left, 2);
|
||||
return [round(right + left, 2), rn(riverLength, 2)];
|
||||
}
|
||||
|
||||
return {generate, resolveDepressions, addMeandring, getPath};
|
||||
const specify = function() {
|
||||
if (!pack.rivers.length) return;
|
||||
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
|
||||
const smallType = {"Creek":9, "River":3, "Brook":3, "Stream":1}; // weighted small river types
|
||||
|
||||
for (const r of pack.rivers) {
|
||||
r.basin = getBasin(r.i, r.parent);
|
||||
r.name = getName(r.mouth);
|
||||
const small = r.length < smallLength;
|
||||
r.type = r.parent && !(r.i%6) ? small ? "Branch" : "Fork" : small ? rw(smallType) : "River";
|
||||
}
|
||||
|
||||
return;
|
||||
const basins = [...(new Set(pack.rivers.map(r=>r.basin)))];
|
||||
const colors = getColors(basins.length);
|
||||
basins.forEach((b,i) => {
|
||||
pack.rivers.filter(r => r.basin === b).forEach(r => {
|
||||
rivers.select("#river"+r.i).attr("fill", colors[i]);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const getName = function(cell) {
|
||||
return Names.getCulture(pack.cells.culture[cell]);
|
||||
}
|
||||
|
||||
// remove river and all its tributaries
|
||||
const remove = function(id) {
|
||||
const riversToRemove = pack.rivers.filter(r => r.i === id || getBasin(r.i, r.parent, id) === id).map(r => r.i);
|
||||
riversToRemove.forEach(r => rivers.select("#river"+r).remove());
|
||||
pack.cells.r.forEach((r, i) => {
|
||||
if (r && riversToRemove.includes(r)) pack.cells.r[i] = 0;
|
||||
});
|
||||
pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
|
||||
}
|
||||
|
||||
const getBasin = function(r, p, e) {
|
||||
while (p) {
|
||||
const parent = pack.rivers.find(r => r.i === p);
|
||||
if (parent) r = parent.i;
|
||||
p = parent ? parent.parent : 0;
|
||||
if (r === e) return r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
return {generate, resolveDepressions, addMeandring, getPath, specify, getName, getBasin, remove};
|
||||
|
||||
})));
|
||||
|
|
@ -250,6 +250,7 @@ function getMapData() {
|
|||
const burgs = JSON.stringify(pack.burgs);
|
||||
const religions = JSON.stringify(pack.religions);
|
||||
const provinces = JSON.stringify(pack.provinces);
|
||||
const rivers = JSON.stringify(pack.rivers);
|
||||
|
||||
// store name array only if it is not the same as default
|
||||
const defaultNB = Names.getNameBases();
|
||||
|
|
@ -268,7 +269,7 @@ function getMapData() {
|
|||
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].join("\r\n");
|
||||
namesData, rivers].join("\r\n");
|
||||
const blob = new Blob([data], {type: "text/plain"});
|
||||
|
||||
console.timeEnd("createMapDataBlob");
|
||||
|
|
@ -293,23 +294,6 @@ async function saveMap() {
|
|||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
}
|
||||
|
||||
// download map data as GeoJSON
|
||||
function saveGeoJSON() {
|
||||
alertMessage.innerHTML = `You can export map data in GeoJSON format used in GIS tools such as QGIS.
|
||||
Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export", "wiki-page")} for guidance`;
|
||||
|
||||
$("#alert").dialog({title: "GIS data export", resizable: false, width: "32em", position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Cells: saveGeoJSON_Cells,
|
||||
Routes: saveGeoJSON_Roads,
|
||||
Rivers: saveGeoJSON_Rivers,
|
||||
Markers: saveGeoJSON_Markers,
|
||||
Close: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function saveGeoJSON_Cells() {
|
||||
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
|
||||
const cells = pack.cells, v = pack.vertices;
|
||||
|
|
@ -686,6 +670,7 @@ function parseLoadedData(data) {
|
|||
pack.burgs = JSON.parse(data[15]);
|
||||
pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: "No religion"}];
|
||||
pack.provinces = data[30] ? JSON.parse(data[30]) : [0];
|
||||
pack.rivers = data[32] ? JSON.stringify(data[32]) : [];
|
||||
|
||||
const cells = pack.cells;
|
||||
cells.biome = Uint8Array.from(data[16].split(","));
|
||||
|
|
@ -899,14 +884,6 @@ function parseLoadedData(data) {
|
|||
});
|
||||
}
|
||||
|
||||
// v 1.11 replaced "display" attribute by "display" style
|
||||
viewbox.selectAll("g").each(function() {
|
||||
if (this.hasAttribute("display")) {
|
||||
this.removeAttribute("display");
|
||||
this.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
// v 1.11 had an issue with fogging being displayed on load
|
||||
unfog();
|
||||
|
||||
|
|
@ -916,6 +893,30 @@ function parseLoadedData(data) {
|
|||
if (!terrain.attr("density")) terrain.attr("density", .4);
|
||||
}
|
||||
|
||||
if (version < 1.21) {
|
||||
// v 1.11 replaced "display" attribute by "display" style
|
||||
viewbox.selectAll("g").each(function() {
|
||||
if (this.hasAttribute("display")) {
|
||||
this.removeAttribute("display");
|
||||
this.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
// v 1.21 added rivers data to pack
|
||||
|
||||
pack.rivers = []; // rivers data
|
||||
rivers.selectAll("path").each(function() {
|
||||
const i = +this.id.slice(5);
|
||||
const length = this.getTotalLength() / 2;
|
||||
const s = this.getPointAtLength(length), e = this.getPointAtLength(0);
|
||||
const source = findCell(s.x, s.y), mouth = findCell(e.x, e.y);
|
||||
const name = Rivers.getName(mouth);
|
||||
const type = length < 25 ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
|
||||
pack.rivers.push({i, parent:0, length, source, mouth, basin:i, name, type});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
changeMapSize();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ function editBurg(id) {
|
|||
document.getElementById("burgAddGroup").addEventListener("click", toggleNewGroupInput);
|
||||
document.getElementById("burgRemoveGroup").addEventListener("click", removeBurgsGroup);
|
||||
|
||||
document.getElementById("burgName").addEventListener("input", changeName);
|
||||
document.getElementById("burgName").addEventListener("input", changeName);
|
||||
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
|
||||
document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom);
|
||||
document.getElementById("burgPopulation").addEventListener("change", changePopulation);
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
"use strict";
|
||||
function editBurgs() {
|
||||
function overviewBurgs() {
|
||||
if (customization) return;
|
||||
closeDialogs("#burgsEditor, .stable");
|
||||
closeDialogs("#burgsOverview, .stable");
|
||||
if (!layerIsOn("toggleIcons")) toggleIcons();
|
||||
if (!layerIsOn("toggleLabels")) toggleLabels();
|
||||
|
||||
const body = document.getElementById("burgsBody");
|
||||
updateFilter();
|
||||
burgsEditorAddLines();
|
||||
$("#burgsEditor").dialog();
|
||||
burgsOverviewAddLines();
|
||||
$("#burgsOverview").dialog();
|
||||
|
||||
if (modules.editBurgs) return;
|
||||
modules.editBurgs = true;
|
||||
if (modules.overviewBurgs) return;
|
||||
modules.overviewBurgs = true;
|
||||
|
||||
$("#burgsEditor").dialog({
|
||||
$("#burgsOverview").dialog({
|
||||
title: "Burgs Overview", resizable: false, width: fitContent(), close: exitAddBurgMode,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("burgsEditorRefresh").addEventListener("click", refreshBurgsEditor);
|
||||
document.getElementById("burgsOverviewRefresh").addEventListener("click", refreshBurgsEditor);
|
||||
document.getElementById("burgsChart").addEventListener("click", showBurgsChart);
|
||||
document.getElementById("burgsFilterState").addEventListener("change", burgsEditorAddLines);
|
||||
document.getElementById("burgsFilterCulture").addEventListener("change", burgsEditorAddLines);
|
||||
document.getElementById("burgsFilterState").addEventListener("change", burgsOverviewAddLines);
|
||||
document.getElementById("burgsFilterCulture").addEventListener("change", burgsOverviewAddLines);
|
||||
document.getElementById("regenerateBurgNames").addEventListener("click", regenerateNames);
|
||||
document.getElementById("addNewBurg").addEventListener("click", enterAddBurgMode);
|
||||
document.getElementById("burgsExport").addEventListener("click", downloadBurgsData);
|
||||
|
|
@ -32,7 +32,7 @@ function editBurgs() {
|
|||
|
||||
function refreshBurgsEditor() {
|
||||
updateFilter();
|
||||
burgsEditorAddLines();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function updateFilter() {
|
||||
|
|
@ -53,8 +53,8 @@ function editBurgs() {
|
|||
culturesSorted.forEach(c => cultureFilter.options.add(new Option(c.name, c.i, false, c.i == selectedCulture)));
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
function burgsEditorAddLines() {
|
||||
// add line for each burg
|
||||
function burgsOverviewAddLines() {
|
||||
const selectedState = +document.getElementById("burgsFilterState").value;
|
||||
const selectedCulture = +document.getElementById("burgsFilterCulture").value;
|
||||
let filtered = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
|
|
@ -168,7 +168,7 @@ function editBurgs() {
|
|||
function toggleCapitalStatus() {
|
||||
const burg = +this.parentNode.parentNode.dataset.id;
|
||||
toggleCapital(burg);
|
||||
burgsEditorAddLines();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function togglePortStatus() {
|
||||
|
|
@ -193,7 +193,7 @@ function editBurgs() {
|
|||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
removeBurg(burg);
|
||||
burgsEditorAddLines();
|
||||
burgsOverviewAddLines();
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
|
|
@ -228,7 +228,7 @@ function editBurgs() {
|
|||
|
||||
if (d3.event.shiftKey === false) {
|
||||
exitAddBurgMode();
|
||||
burgsEditorAddLines();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -448,7 +448,7 @@ function editBurgs() {
|
|||
burgLabels.select("[data-id='" + id + "']").text(change[i].name);
|
||||
}
|
||||
$(this).dialog("close");
|
||||
burgsEditorAddLines();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -470,7 +470,7 @@ function editBurgs() {
|
|||
|
||||
function removeAllBurgs() {
|
||||
pack.burgs.filter(b => b.i && !b.capital).forEach(b => removeBurg(b.i));
|
||||
burgsEditorAddLines();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ function editCultures() {
|
|||
title: "Cultures Editor", resizable: false, width: fitContent(), close: closeCulturesEditor,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
||||
});
|
||||
body.focus();
|
||||
|
||||
// add listeners
|
||||
document.getElementById("culturesEditorRefresh").addEventListener("click", refreshCulturesEditor);
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ function createPicker() {
|
|||
|
||||
function updateSelectedRect(fill) {
|
||||
document.getElementById("picker").querySelector("rect.selected").classList.remove("selected");
|
||||
document.getElementById("picker").querySelector("rect[fill='"+fill+"']").classList.add("selected");
|
||||
document.getElementById("picker").querySelector("rect[fill='"+fill.toLowerCase()+"']").classList.add("selected");
|
||||
}
|
||||
|
||||
function updateSpaces() {
|
||||
|
|
@ -558,3 +558,25 @@ function uploadFile(el, callback) {
|
|||
el.value = "";
|
||||
fileReader.onload = loaded => callback(loaded.target.result);
|
||||
}
|
||||
|
||||
function highlightElement(element) {
|
||||
if (debug.select(".highlighted").size()) return; // allow only 1 highlight element simultaniosly
|
||||
const box = element.getBBox();
|
||||
const transform = element.getAttribute("transform") || null;
|
||||
const enter = d3.transition().duration(1000).ease(d3.easeBounceOut);
|
||||
const exit = d3.transition().duration(500).ease(d3.easeLinear);
|
||||
|
||||
const highlight = debug.append("rect").attr("x", box.x).attr("y", box.y)
|
||||
.attr("width", box.width).attr("height", box.height).attr("transform", transform);
|
||||
|
||||
highlight.classed("highlighted", 1)
|
||||
.transition(enter).style("outline-offset", "0px")
|
||||
.transition(exit).style("outline-color", "transparent").delay(1000).remove();
|
||||
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
if (scale >= 2) zoomTo(x, y, scale, 1600);
|
||||
}
|
||||
|
|
@ -80,10 +80,9 @@ function showMapTooltip(point, e, i, g) {
|
|||
const group = path[path.length - 7].id;
|
||||
const subgroup = path[path.length - 8].id;
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
//const type = pack.features[cells.f[i]].type;
|
||||
|
||||
// specific elements
|
||||
if (group === "rivers") {tip("Click to edit the River"); return;}
|
||||
if (group === "rivers") {tip(getRiverName(e.target.id) + "Click to edit"); return;}
|
||||
if (group === "routes") {tip("Click to edit the Route"); return;}
|
||||
if (group === "terrain") {tip("Click to edit the Relief Icon"); return;}
|
||||
if (subgroup === "burgLabels" || subgroup === "burgIcons") {tip("Click to open Burg Editor"); return;}
|
||||
|
|
@ -121,6 +120,11 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point));
|
||||
}
|
||||
|
||||
function getRiverName(id) {
|
||||
const r = pack.rivers.find(r => r.i == id.slice(5));
|
||||
return r ? r.name + " " + r.type + ". " : "";
|
||||
}
|
||||
|
||||
// get cell info on mouse move
|
||||
function updateCellInfo(point, i, g) {
|
||||
const cells = pack.cells;
|
||||
|
|
@ -133,6 +137,7 @@ function updateCellInfo(point, i, g) {
|
|||
infoHeight.innerHTML = getFriendlyHeight(point) + " (" + h + ")";
|
||||
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
||||
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a";
|
||||
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no";
|
||||
infoState.innerHTML = cells.h[i] >= 20 ? cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : "neutral lands (0)" : "no";
|
||||
infoProvince.innerHTML = cells.province[i] ? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})` : "no";
|
||||
infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : "no";
|
||||
|
|
@ -171,6 +176,11 @@ function getFriendlyPrecipitation(i) {
|
|||
return prec * 100 + " mm";
|
||||
}
|
||||
|
||||
function getRiverInfo(id) {
|
||||
const r = pack.rivers.find(r => r.i == id);
|
||||
return r ? `${r.name} ${r.type} (${id})` : "n/a";
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) population value from map data
|
||||
function getFriendlyPopulation(i) {
|
||||
const rural = pack.cells.pop[i] * populationRate.value;
|
||||
|
|
@ -274,6 +284,7 @@ function showInfo() {
|
|||
|
||||
// prevent default browser behavior for FMG-used hotkeys
|
||||
document.addEventListener("keydown", event => {
|
||||
if (event.altKey && event.keyCode !== 18) event.preventDefault(); // disallowalt key combinations
|
||||
if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab
|
||||
});
|
||||
|
||||
|
|
@ -286,7 +297,7 @@ document.addEventListener("keyup", event => {
|
|||
if (active === "DIV" && document.activeElement.contentEditable === "true") return; // don't trigger if user inputs a text
|
||||
event.stopPropagation();
|
||||
|
||||
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey, meta = event.metaKey;
|
||||
const key = event.keyCode, ctrl = event.ctrlKey || event.metaKey, shift = event.shiftKey, alt = event.altKey;
|
||||
|
||||
if (key === 112) showInfo(); // "F1" to show info
|
||||
else if (key === 113) regeneratePrompt(); // "F2" for new map
|
||||
|
|
@ -321,22 +332,23 @@ document.addEventListener("keyup", event => {
|
|||
else if (shift && key === 78) editNamesbase(); // Shift + "N" to edit Namesbase
|
||||
else if (shift && key === 90) editZones(); // Shift + "Z" to edit Zones
|
||||
else if (shift && key === 82) editReligions(); // Shift + "R" to edit Religions
|
||||
else if (shift && key === 84) editBurgs(); // Shift + "T" to edit Burgs
|
||||
else if (shift && key === 85) editUnits(); // Shift + "U" to edit Units
|
||||
else if (shift && key === 81) editUnits(); // Shift + "Q" to edit Units
|
||||
else if (shift && key === 79) editNotes(); // Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs(); // Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers(); // Shift + "V" to open Rivers overview
|
||||
else if (shift && key === 69) viewCellDetails(); // Shift + "E" to open Cell Details
|
||||
|
||||
else if (shift && key === 71) toggleAddBurg(); // Shift + "G" to click to add Burg
|
||||
else if (shift && key === 65) toggleAddLabel(); // Shift + "A" to click to add Label
|
||||
else if (shift && key === 73) toggleAddRiver(); // Shift + "I" to click to add River
|
||||
else if (shift && key === 69) toggleAddRoute(); // Shift + "E" to click to add Route
|
||||
else if (shift && key === 75) toggleAddMarker(); // Shift + "K" to click to add Marker
|
||||
else if (shift && key === 49) toggleAddBurg(); // Shift + "1" to click to add Burg
|
||||
else if (shift && key === 50) toggleAddLabel(); // Shift + "2" to click to add Label
|
||||
else if (shift && key === 51) toggleAddRiver(); // Shift + "3" to click to add River
|
||||
else if (shift && key === 52) toggleAddRoute(); // Shift + "4" to click to add Route
|
||||
else if (shift && key === 53) toggleAddMarker(); // Shift + "5" to click to add Marker
|
||||
|
||||
else if (meta && key === 192) console.log(pack.cells); // Metakey + "`" to log cells data
|
||||
else if (meta && key === 66) console.table(pack.burgs); // Metakey + "B" to log burgs data
|
||||
else if (meta && key === 83) console.table(pack.states); // Metakey + "S" to log states data
|
||||
else if (meta && key === 67) console.table(pack.cultures); // Metakey + "C" to log cultures data
|
||||
else if (meta && key === 82) console.table(pack.religions); // Metakey + "R" to log religions data
|
||||
else if (meta && key === 70) console.table(pack.features); // Metakey + "F" to log features data
|
||||
else if (alt && key === 66) console.table(pack.burgs); // Alt + "B" to log burgs data
|
||||
else if (alt && key === 83) console.table(pack.states); // Alt + "S" to log states data
|
||||
else if (alt && key === 67) console.table(pack.cultures); // Alt + "C" to log cultures data
|
||||
else if (alt && key === 82) console.table(pack.religions); // Alt + "R" to log religions data
|
||||
else if (alt && key === 70) console.table(pack.features); // Alt + "F" to log features data
|
||||
|
||||
else if (key === 88) toggleTexture(); // "X" to toggle Texture layer
|
||||
else if (key === 72) toggleHeight(); // "H" to toggle Heightmap layer
|
||||
|
|
|
|||
|
|
@ -4,16 +4,12 @@
|
|||
function editHeightmap() {
|
||||
void function selectEditMode() {
|
||||
alertMessage.innerHTML = `<p>Heightmap is a core element on which all other data (rivers, burgs, states etc) is based.
|
||||
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.</p>
|
||||
|
||||
<p>You can also <i>keep</i> all the data, but you won't be able to change the coastline.</p>
|
||||
|
||||
<p>If you need to change the coastline and keep the data, you may try the <i>risk</i> edit option.
|
||||
The data will be restored as much as possible, but the coastline change can cause unexpected fluctuations and errors.</p>
|
||||
|
||||
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>
|
||||
|
||||
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before edditing the heightmap!</p>`;
|
||||
So the best edit approach is to <i>erase</i> the secondary data and let the system automatically regenerate it on edit completion.</p>
|
||||
<p>You can also <i>keep</i> all the data, but you won't be able to change the coastline.</p>
|
||||
<p>If you need to change the coastline and keep the data, you may try the <i>risk</i> edit option.
|
||||
The data will be restored as much as possible, but the coastline change can cause unexpected fluctuations and errors.</p>
|
||||
<p>Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>
|
||||
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before edditing the heightmap!</p>`;
|
||||
|
||||
$("#alert").dialog({resizable: false, title: "Edit Heightmap", width: "28em",
|
||||
buttons: {
|
||||
|
|
@ -179,6 +175,8 @@ function editHeightmap() {
|
|||
drawStates();
|
||||
drawBorders();
|
||||
BurgsAndStates.drawStateLabels();
|
||||
|
||||
Rivers.specify();
|
||||
addMarkers();
|
||||
addZones();
|
||||
console.timeEnd("regenerateErasedData");
|
||||
|
|
@ -338,6 +336,8 @@ function editHeightmap() {
|
|||
drawStates();
|
||||
drawBorders();
|
||||
|
||||
if (changeHeights.checked) Rivers.specify();
|
||||
|
||||
// restore zones from grid
|
||||
zones.selectAll("g").each(function() {
|
||||
const zone = d3.select(this);
|
||||
|
|
@ -378,8 +378,9 @@ function editHeightmap() {
|
|||
function mockHeightmap() {
|
||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
|
||||
const scheme = getColorScheme();
|
||||
viewbox.select("#heights").selectAll("polygon").data(data).join("polygon").attr("points", d => getGridPolygon(d))
|
||||
.attr("id", d => "cell"+d).attr("fill", d => getColor(grid.cells.h[d], scheme));
|
||||
viewbox.select("#heights").selectAll("polygon").data(data).join("polygon")
|
||||
.attr("points", d => getGridPolygon(d)).attr("id", d => "cell"+d)
|
||||
.attr("fill", d => getColor(grid.cells.h[d], scheme));
|
||||
}
|
||||
|
||||
// draw or update heightmap for a selection of cells
|
||||
|
|
@ -456,11 +457,11 @@ function editHeightmap() {
|
|||
document.getElementById("redo").addEventListener("click", () => restoreHistory(edits.n+1));
|
||||
document.getElementById("rescaleShow").addEventListener("click", () => {
|
||||
document.getElementById("modifyButtons").style.display = "none";
|
||||
document.getElementById("rescaleSection").style.display = "block";
|
||||
document.getElementById("rescaleSection").style.display = "block";
|
||||
});
|
||||
document.getElementById("rescaleHide").addEventListener("click", () => {
|
||||
document.getElementById("modifyButtons").style.display = "block";
|
||||
document.getElementById("rescaleSection").style.display = "none";
|
||||
document.getElementById("rescaleSection").style.display = "none";
|
||||
});
|
||||
document.getElementById("rescaler").addEventListener("change", (e) => rescale(e.target.valueAsNumber));
|
||||
document.getElementById("rescaleCondShow").addEventListener("click", () => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ function editNotes(id, name) {
|
|||
}
|
||||
|
||||
// select an object
|
||||
if (id) {
|
||||
if (notes.length) {
|
||||
if (!id) id = notes[0].id;
|
||||
let note = notes.find(note => note.id === id);
|
||||
if (note === undefined) {
|
||||
if (!name) name = id;
|
||||
|
|
@ -81,28 +82,6 @@ function editNotes(id, name) {
|
|||
highlightElement(element); // if element is found
|
||||
}
|
||||
|
||||
function highlightElement(element) {
|
||||
if (debug.select(".highlighted").size()) return; // allow only 1 highlight element simultaniosly
|
||||
const box = element.getBBox();
|
||||
const transform = element.getAttribute("transform") || null;
|
||||
const t = d3.transition().duration(1000).ease(d3.easeBounceOut);
|
||||
const r = d3.transition().duration(500).ease(d3.easeLinear);
|
||||
|
||||
const highlight = debug.append("rect").attr("x", box.x).attr("y", box.y)
|
||||
.attr("width", box.width).attr("height", box.height).attr("transform", transform);
|
||||
|
||||
highlight.classed("highlighted", 1)
|
||||
.transition(t).style("outline-offset", "0px")
|
||||
.transition(r).style("outline-color", "transparent").remove();
|
||||
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
if (scale >= 2) zoomTo(x, y, scale, 1600);
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
const data = JSON.stringify(notes);
|
||||
const name = getFileName("Notes") + ".txt";
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ function changeMapSize() {
|
|||
landmass.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
|
||||
oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
|
||||
oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight);
|
||||
defs.select("#mapClip > rect").attr("width", maxWidth).attr("height", maxHeight);
|
||||
//defs.select("#mapClip > rect").attr("width", maxWidth).attr("height", maxHeight);
|
||||
|
||||
fitScaleBar();
|
||||
if (window.fitLegendBox) fitLegendBox();
|
||||
|
|
@ -157,8 +157,8 @@ function applyMapSize() {
|
|||
svgHeight = Math.min(graphHeight, window.innerHeight)
|
||||
svg.attr("width", svgWidth).attr("height", svgHeight);
|
||||
zoom.translateExtent([[0, 0],[graphWidth, graphHeight]]).scaleExtent([1, 20]).scaleTo(svg, 1);
|
||||
viewbox.attr("transform", null).attr("clip-path", "url(#mapClip)");
|
||||
defs.append("clipPath").attr("id", "mapClip").append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||
//viewbox.attr("transform", null).attr("clip-path", "url(#mapClip)");
|
||||
//defs.append("clipPath").attr("id", "mapClip").append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||
//zoom.translateExtent([[-svgWidth*.2, -graphHeight*.2], [svgWidth*1.2, graphHeight*1.2]]);
|
||||
}
|
||||
|
||||
|
|
@ -348,22 +348,9 @@ function restoreDefaultOptions() {
|
|||
document.getElementById("sticked").addEventListener("click", function(event) {
|
||||
const id = event.target.id;
|
||||
if (id === "newMapButton") regeneratePrompt();
|
||||
else if (id === "saveButton") toggleSavePane();
|
||||
else if (id === "loadButton") toggleLoadPane();
|
||||
else if (id === "saveButton") showSavePane();
|
||||
else if (id === "loadButton") showLoadPane();
|
||||
else if (id === "zoomReset") resetZoom(1000);
|
||||
else if (id === "quickSave") quickSave();
|
||||
else if (id === "saveMap") saveMap();
|
||||
else if (id === "saveSVG") saveSVG();
|
||||
else if (id === "savePNG") savePNG();
|
||||
else if (id === "saveJPEG") saveJPEG();
|
||||
else if (id === "saveGeo") saveGeoJSON();
|
||||
else if (id === "saveDropbox") saveDropbox();
|
||||
if (id === "quickSave" || id === "saveMap" || id === "saveSVG" || id === "savePNG" || id === "saveGeo" || id === "saveDropbox") toggleSavePane();
|
||||
if (id === "loadMap") mapToLoad.click();
|
||||
else if (id === "quickLoad") quickLoad();
|
||||
else if (id === "loadURL") loadURL();
|
||||
else if (id === "loadDropbox") loadDropbox();
|
||||
if (id === "quickLoad" || id === "loadURL" || id === "loadMap" || id === "loadDropbox") toggleLoadPane();
|
||||
});
|
||||
|
||||
function regeneratePrompt() {
|
||||
|
|
@ -381,28 +368,34 @@ function regeneratePrompt() {
|
|||
});
|
||||
}
|
||||
|
||||
function toggleSavePane() {
|
||||
if (saveDropdown.style.display === "block") {saveDropdown.style.display = "none"; return;}
|
||||
saveDropdown.style.display = "block";
|
||||
|
||||
// ask users to allow popups
|
||||
if (!localStorage.getItem("dns_allow_popup_message")) {
|
||||
alertMessage.innerHTML = `Generator uses pop-up window to download files.
|
||||
<br>Please ensure your browser does not block popups.
|
||||
<br>Please check browser settings and turn off adBlocker if it is enabled`;
|
||||
|
||||
$("#alert").dialog({title: "File saver", resizable: false, position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {OK: function() {
|
||||
localStorage.setItem("dns_allow_popup_message", true);
|
||||
$(this).dialog("close");
|
||||
}}
|
||||
});
|
||||
}
|
||||
function showSavePane() {
|
||||
$("#saveMapData").dialog({title: "Save map", resizable: false, width: "27em",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {Close: function() {$(this).dialog("close");}}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLoadPane() {
|
||||
if (loadDropdown.style.display === "block") {loadDropdown.style.display = "none"; return;}
|
||||
loadDropdown.style.display = "block";
|
||||
// download map data as GeoJSON
|
||||
function saveGeoJSON() {
|
||||
alertMessage.innerHTML = `You can export map data in GeoJSON format used in GIS tools such as QGIS.
|
||||
Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export", "wiki-page")} for guidance`;
|
||||
|
||||
$("#alert").dialog({title: "GIS data export", resizable: false, width: "35em", position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Cells: saveGeoJSON_Cells,
|
||||
Routes: saveGeoJSON_Roads,
|
||||
Rivers: saveGeoJSON_Rivers,
|
||||
Markers: saveGeoJSON_Markers,
|
||||
Close: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showLoadPane() {
|
||||
$("#loadMapData").dialog({title: "Load map", resizable: false, width: "17em",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {Close: function() {$(this).dialog("close");}}
|
||||
});
|
||||
}
|
||||
|
||||
function loadURL() {
|
||||
|
|
@ -411,7 +404,7 @@ function loadURL() {
|
|||
<input id="mapURL" type="url" style="width: 24em" placeholder="https://e-cloud.com/test.map">
|
||||
<br><i>Please note server should allow CORS for file to be loaded. If CORS is not allowed, save file to Dropbox and provide a direct link</i>`;
|
||||
alertMessage.innerHTML = inner;
|
||||
$("#alert").dialog({resizable: false, title: "Load map from URL", width: "26em",
|
||||
$("#alert").dialog({resizable: false, title: "Load map from URL", width: "27em",
|
||||
buttons: {
|
||||
Load: function() {
|
||||
const value = mapURL.value;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
"use strict";
|
||||
function editRiver() {
|
||||
function editRiver(id) {
|
||||
if (customization) return;
|
||||
if (elSelected && d3.event.target.id === elSelected.attr("id")) return;
|
||||
if (elSelected && d3.event && d3.event.target.id === elSelected.attr("id")) return;
|
||||
closeDialogs(".stable");
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
|
||||
const node = d3.event.target;
|
||||
elSelected = d3.select(node).on("click", addInterimControlPoint)
|
||||
.call(d3.drag().on("start", dragRiver)).classed("draggable", true);
|
||||
const node = id ? document.getElementById(id) : d3.event.target;
|
||||
elSelected = d3.select(node).on("click", addInterimControlPoint);
|
||||
viewbox.on("touchmove mousemove", showEditorTips);
|
||||
debug.append("g").attr("id", "controlPoints").attr("transform", elSelected.attr("transform"));
|
||||
drawControlPoints(node);
|
||||
updateValues(node);
|
||||
|
||||
$("#riverEditor").dialog({
|
||||
title: "Edit River", resizable: false,
|
||||
position: {my: "center top+20", at: "top", of: d3.event, collision: "fit"},
|
||||
position: {my: "center top+20", at: "top", of: node, collision: "fit"},
|
||||
close: closeRiverEditor
|
||||
});
|
||||
|
||||
|
|
@ -23,19 +21,19 @@ function editRiver() {
|
|||
modules.editRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("riverNameShow").addEventListener("click", showRiverName);
|
||||
document.getElementById("riverNameHide").addEventListener("click", hideRiverName);
|
||||
document.getElementById("riverName").addEventListener("input", changeName);
|
||||
document.getElementById("riverType").addEventListener("input", changeType);
|
||||
document.getElementById("riverNameCulture").addEventListener("click", generateNameCulture);
|
||||
document.getElementById("riverNameRandom").addEventListener("click", generateNameRandom);
|
||||
|
||||
document.getElementById("riverWidthShow").addEventListener("click", showRiverWidth);
|
||||
document.getElementById("riverWidthHide").addEventListener("click", hideRiverWidth);
|
||||
document.getElementById("riverWidthInput").addEventListener("input", changeWidth);
|
||||
document.getElementById("riverIncrement").addEventListener("input", changeIncrement);
|
||||
|
||||
document.getElementById("riverResizeShow").addEventListener("click", showRiverSize);
|
||||
document.getElementById("riverResizeHide").addEventListener("click", hideRiverSize);
|
||||
document.getElementById("riverAngle").addEventListener("input", changeAngle);
|
||||
document.getElementById("riverScale").addEventListener("input", changeScale);
|
||||
document.getElementById("riverReset").addEventListener("click", resetTransformation);
|
||||
|
||||
document.getElementById("riverEditStyle").addEventListener("click", () => editStyle("rivers"));
|
||||
document.getElementById("riverCopy").addEventListener("click", copyRiver);
|
||||
document.getElementById("riverNew").addEventListener("click", toggleRiverCreationMode);
|
||||
document.getElementById("riverLegend").addEventListener("click", editRiverLegend);
|
||||
document.getElementById("riverRemove").addEventListener("click", removeRiver);
|
||||
|
|
@ -46,27 +44,6 @@ function editRiver() {
|
|||
if (d3.event.target.parentNode.id === "controlPoints") tip("Drag to move, click to delete the control point");
|
||||
}
|
||||
|
||||
function updateValues(node) {
|
||||
const tr = parseTransform(node.getAttribute("transform"));
|
||||
document.getElementById("riverAngle").value = tr[2];
|
||||
document.getElementById("riverAngleValue").innerHTML = Math.abs(+tr[2]) + "°";
|
||||
document.getElementById("riverScale").value = tr[5];
|
||||
document.getElementById("riverWidthInput").value = node.dataset.width;
|
||||
document.getElementById("riverIncrement").value = node.dataset.increment;
|
||||
}
|
||||
|
||||
function dragRiver() {
|
||||
const x = d3.event.x, y = d3.event.y;
|
||||
const tr = parseTransform(elSelected.attr("transform"));
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
let xc = d3.event.x, yc = d3.event.y;
|
||||
let transform = `translate(${(+tr[0]+xc-x)},${(+tr[1]+yc-y)}) rotate(${tr[2]} ${tr[3]} ${tr[4]}) scale(${tr[5]})`;
|
||||
elSelected.attr("transform", transform);
|
||||
debug.select("#controlPoints").attr("transform", transform);
|
||||
});
|
||||
}
|
||||
|
||||
function drawControlPoints(node) {
|
||||
const l = node.getTotalLength() / 2;
|
||||
const segments = Math.ceil(l / 8);
|
||||
|
|
@ -99,19 +76,29 @@ function editRiver() {
|
|||
});
|
||||
|
||||
if (points.length === 1) return;
|
||||
if (points.length === 2) {elSelected.attr("d", `M${points[0][0]},${points[0][1]} L${points[1][0]},${points[1][1]}`); return;}
|
||||
const d = Rivers.getPath(points, +riverWidthInput.value, +riverIncrement.value);
|
||||
if (points.length === 2) {
|
||||
const p0 = points[0], p1 = points[1];
|
||||
const angle = Math.atan2(p1[1] - p0[1], p1[0] - p0[0]);
|
||||
const sin = Math.sin(angle), cos = Math.cos(angle);
|
||||
elSelected.attr("d", `M${p0[0]},${p0[1]} L${p1[0]},${p1[1]} l${-sin/2},${cos/2} Z`);
|
||||
return;
|
||||
}
|
||||
const [d, length] = Rivers.getPath(points, +riverWidthInput.value, +riverIncrement.value);
|
||||
elSelected.attr("d", d);
|
||||
updateRiverLength();
|
||||
updateRiverLength(length);
|
||||
}
|
||||
|
||||
function updateRiverLength(l = elSelected.node().getTotalLength() / 2) {
|
||||
const tr = parseTransform(elSelected.attr("transform"));
|
||||
riverLength.innerHTML = rn(l * tr[5] * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const length = l * tr[5];
|
||||
riverLength.innerHTML = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const river = +elSelected.attr("id").slice(5);
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
if (r) r.length = length;
|
||||
}
|
||||
|
||||
function clickControlPoint() {
|
||||
this.remove();
|
||||
this.remove();
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
|
|
@ -142,9 +129,50 @@ function editRiver() {
|
|||
redrawRiver();
|
||||
}
|
||||
|
||||
function showRiverName() {
|
||||
document.querySelectorAll("#riverEditor > button").forEach(el => el.style.display = "none");
|
||||
document.getElementById("riverNameSection").style.display = "inline-block";
|
||||
const river = +elSelected.attr("id").slice(5);
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
if (!r) return;
|
||||
document.getElementById("riverName").value = r.name;
|
||||
document.getElementById("riverType").value = r.type;
|
||||
}
|
||||
|
||||
function hideRiverName() {
|
||||
document.querySelectorAll("#riverEditor > button").forEach(el => el.style.display = "inline-block");
|
||||
document.getElementById("riverNameSection").style.display = "none";
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
const river = +elSelected.attr("id").slice(5);
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
if (r) r.name = this.value;
|
||||
}
|
||||
|
||||
function changeType() {
|
||||
const river = +elSelected.attr("id").slice(5);
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
if (r) r.type = this.value;
|
||||
}
|
||||
|
||||
function generateNameCulture() {
|
||||
const river = +elSelected.attr("id").slice(5);
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
if (r) r.name = riverName.value = Rivers.getName(r.mouth);
|
||||
}
|
||||
|
||||
function generateNameRandom() {
|
||||
const river = +elSelected.attr("id").slice(5);
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
if (r) r.name = riverName.value = Names.getBase(rand(nameBases.length-1));
|
||||
}
|
||||
|
||||
function showRiverWidth() {
|
||||
document.querySelectorAll("#riverEditor > button").forEach(el => el.style.display = "none");
|
||||
document.getElementById("riverWidthSection").style.display = "inline-block";
|
||||
document.getElementById("riverWidthInput").value = elSelected.attr("data-width");
|
||||
document.getElementById("riverIncrement").value = elSelected.attr("data-increment");
|
||||
}
|
||||
|
||||
function hideRiverWidth() {
|
||||
|
|
@ -161,76 +189,14 @@ function editRiver() {
|
|||
elSelected.attr("data-increment", this.value);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function showRiverSize() {
|
||||
document.querySelectorAll("#riverEditor > button").forEach(el => el.style.display = "none");
|
||||
document.getElementById("riverResizeSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideRiverSize() {
|
||||
document.querySelectorAll("#riverEditor > button").forEach(el => el.style.display = "inline-block");
|
||||
document.getElementById("riverResizeSection").style.display = "none";
|
||||
}
|
||||
|
||||
function changeAngle() {
|
||||
const tr = parseTransform(elSelected.attr("transform"));
|
||||
riverAngleValue.innerHTML = Math.abs(+this.value) + "°";
|
||||
const c = elSelected.node().getBBox();
|
||||
const angle = +this.value, scale = +tr[5];
|
||||
const transform = `translate(${tr[0]},${tr[1]}) rotate(${angle} ${(c.x+c.width/2)*scale} ${(c.y+c.height/2)*scale}) scale(${scale})`;
|
||||
elSelected.attr("transform", transform);
|
||||
debug.select("#controlPoints").attr("transform", transform);
|
||||
}
|
||||
|
||||
function changeScale() {
|
||||
const tr = parseTransform(elSelected.attr("transform"));
|
||||
const scaleOld = +tr[5],scale = +this.value;
|
||||
const c = elSelected.node().getBBox();
|
||||
const cx = c.x + c.width / 2, cy = c.y + c.height / 2;
|
||||
const trX = +tr[0] + cx * (scaleOld - scale);
|
||||
const trY = +tr[1] + cy * (scaleOld - scale);
|
||||
const scX = +tr[3] * scale / scaleOld;
|
||||
const scY = +tr[4] * scale / scaleOld;
|
||||
const transform = `translate(${trX},${trY}) rotate(${tr[2]} ${scX} ${scY}) scale(${scale})`;
|
||||
elSelected.attr("transform", transform);
|
||||
debug.select("#controlPoints").attr("transform", transform);
|
||||
updateRiverLength();
|
||||
}
|
||||
|
||||
function resetTransformation() {
|
||||
elSelected.attr("transform", null);
|
||||
debug.select("#controlPoints").attr("transform", null);
|
||||
riverAngle.value = 0;
|
||||
riverAngleValue.innerHTML = "0°";
|
||||
riverScale.value = 1;
|
||||
updateRiverLength();
|
||||
}
|
||||
|
||||
function copyRiver() {
|
||||
const tr = parseTransform(elSelected.attr("transform"));
|
||||
const d = elSelected.attr("d");
|
||||
let x = 2, y = 2;
|
||||
let transform = `translate(${tr[0]-x},${tr[1]-y}) rotate(${tr[2]} ${tr[3]} ${tr[4]}) scale(${tr[5]})`;
|
||||
while (rivers.selectAll("[transform='" + transform + "'][d='" + d + "']").size() > 0) {
|
||||
x += 2; y += 2;
|
||||
transform = `translate(${tr[0]-x},${tr[1]-y}) rotate(${tr[2]} ${tr[3]} ${tr[4]}) scale(${tr[5]})`;
|
||||
}
|
||||
|
||||
rivers.append("path").attr("d", d).attr("transform", transform).attr("id", getNextId("river"))
|
||||
.attr("data-width", elSelected.attr("data-width")).attr("data-increment", elSelected.attr("data-increment"));
|
||||
}
|
||||
|
||||
function toggleRiverCreationMode() {
|
||||
document.getElementById("riverNew").classList.toggle("pressed");
|
||||
if (document.getElementById("riverNew").classList.contains("pressed")) {
|
||||
tip("Click on map to add control points", true);
|
||||
if (document.getElementById("riverNew").classList.contains("pressed")) exitRiverCreationMode();
|
||||
else {
|
||||
document.getElementById("riverNew").classList.add("pressed");
|
||||
tip("Click on map to add control points", true, "warn");
|
||||
viewbox.on("click", addPointOnClick).style("cursor", "crosshair");
|
||||
elSelected.on("click", null);
|
||||
} else {
|
||||
clearMainTip();
|
||||
viewbox.on("click", clicked).style("cursor", "default");
|
||||
elSelected.on("click", addInterimControlPoint).attr("data-new", null)
|
||||
.call(d3.drag().on("start", dragRiver)).classed("draggable", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +205,7 @@ function editRiver() {
|
|||
debug.select("#controlPoints").selectAll("circle").remove();
|
||||
const id = getNextId("river");
|
||||
elSelected = d3.select(elSelected.node().parentNode).append("path").attr("id", id)
|
||||
.attr("data-new", 1).attr("data-width", 2).attr("data-increment", 1);
|
||||
.attr("data-new", 1).attr("data-width", 1).attr("data-increment", .5);
|
||||
}
|
||||
|
||||
// add control point
|
||||
|
|
@ -252,15 +218,16 @@ function editRiver() {
|
|||
function editRiverLegend() {
|
||||
const id = elSelected.attr("id");
|
||||
editNotes(id, id);
|
||||
}
|
||||
}
|
||||
|
||||
function removeRiver() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the river?";
|
||||
$("#alert").dialog({resizable: false, title: "Remove river",
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the river? All tributaries will be auto-removed";
|
||||
$("#alert").dialog({resizable: false, width: "22em", title: "Remove river",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
elSelected.remove();
|
||||
const river = +elSelected.attr("id").slice(5);
|
||||
Rivers.remove(river);
|
||||
$("#riverEditor").dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
|
|
@ -268,10 +235,38 @@ function editRiver() {
|
|||
});
|
||||
}
|
||||
|
||||
function closeRiverEditor() {
|
||||
elSelected.attr("data-new", null).on("click", null);
|
||||
function exitRiverCreationMode() {
|
||||
riverNew.classList.remove("pressed");
|
||||
clearMainTip();
|
||||
riverNew.classList.remove("pressed");
|
||||
viewbox.on("click", clicked).style("cursor", "default");
|
||||
elSelected.on("click", addInterimControlPoint);
|
||||
|
||||
if (!elSelected.attr("data-new")) return; // no need to create a new river
|
||||
elSelected.attr("data-new", null);
|
||||
|
||||
// add a river
|
||||
const r = +elSelected.attr("id").slice(5);
|
||||
const node = elSelected.node(), length = node.getTotalLength() / 2;
|
||||
|
||||
const cells = [];
|
||||
const segments = Math.ceil(length / 8), increment = rn(length / segments * 1e5);
|
||||
for (let i=increment*segments, c=i; i >= 0; i -= increment, c += increment) {
|
||||
const p = node.getPointAtLength(i / 1e5);
|
||||
const cell = findCell(p.x, p.y);
|
||||
if (!pack.cells.r[cell]) pack.cells.r[cell] = r;
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
const source = cells[0], mouth = last(cells);
|
||||
const name = Rivers.getName(mouth);
|
||||
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
|
||||
const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
|
||||
pack.rivers.push({i:r, parent:0, length, source, mouth, basin:r, name, type});
|
||||
}
|
||||
|
||||
function closeRiverEditor() {
|
||||
exitRiverCreationMode();
|
||||
elSelected.on("click", null);
|
||||
debug.select("#controlPoints").remove();
|
||||
unselect();
|
||||
}
|
||||
|
|
|
|||
157
modules/ui/rivers-overview.js
Normal file
157
modules/ui/rivers-overview.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
"use strict";
|
||||
function overviewRivers() {
|
||||
if (customization) return;
|
||||
closeDialogs("#riversOverview, .stable");
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
|
||||
const body = document.getElementById("riversBody");
|
||||
riversOverviewAddLines();
|
||||
$("#riversOverview").dialog();
|
||||
|
||||
if (modules.overviewRivers) return;
|
||||
modules.overviewRivers = true;
|
||||
|
||||
$("#riversOverview").dialog({
|
||||
title: "Rivers Overview", resizable: false, width: fitContent(),
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("riversOverviewRefresh").addEventListener("click", riversOverviewAddLines);
|
||||
document.getElementById("addNewRiver").addEventListener("click", toggleAddRiver);
|
||||
document.getElementById("riversExport").addEventListener("click", downloadRiversData);
|
||||
document.getElementById("riversRemoveAll").addEventListener("click", triggerAllRiversRemove);
|
||||
|
||||
// add line for each river
|
||||
function riversOverviewAddLines() {
|
||||
body.innerHTML = "";
|
||||
let lines = "";
|
||||
|
||||
for (const r of pack.rivers) {
|
||||
const length = rn(r.length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const basin = pack.rivers.find(river => river.i === r.basin).name;
|
||||
|
||||
lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-type="${r.type}" data-length="${r.length}" data-basin="${basin}">
|
||||
<span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span>
|
||||
<input data-tip="River proper name. Click to change. Ctrl + click to regenerate" class="riverName" value="${r.name}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="River type name. Click to change" class="riverType" value="${r.type}">
|
||||
<div data-tip="River length" class="biomeArea">${length}</div>
|
||||
<input data-tip="River basin (name of the main stem)" class="stateName" value="${basin}" disabled>
|
||||
<span data-tip="Edit river" class="icon-pencil"></span>
|
||||
<span data-tip="Remove river" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML("beforeend", lines);
|
||||
|
||||
// update footer
|
||||
riversFooterNumber.innerHTML = pack.rivers.length;
|
||||
const averageLength = rn(d3.sum(pack.rivers.map(r => r.length)) / pack.rivers.length);
|
||||
riversFooterLength.innerHTML = (averageLength * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => riverHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev)));
|
||||
body.querySelectorAll("div > input.riverName").forEach(el => el.addEventListener("input", changeRiverName));
|
||||
body.querySelectorAll("div > input.riverName").forEach(el => el.addEventListener("click", regenerateRiverName));
|
||||
body.querySelectorAll("div > input.riverType").forEach(el => el.addEventListener("input", changeRiverType));
|
||||
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver));
|
||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor));
|
||||
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerRiverRemove));
|
||||
|
||||
applySorting(riversHeader);
|
||||
}
|
||||
|
||||
function riverHighlightOn(event) {
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
const r = +event.target.dataset.id;
|
||||
rivers.select("#river"+r).attr("stroke", "red").attr("stroke-width", 1);
|
||||
}
|
||||
|
||||
function riverHighlightOff() {
|
||||
const r = +event.target.dataset.id;
|
||||
rivers.select("#river"+r).attr("stroke", null).attr("stroke-width", null);
|
||||
}
|
||||
|
||||
function changeRiverName() {
|
||||
if (this.value == "") tip("Please provide a proper name", false, "error");
|
||||
const river = +this.parentNode.dataset.id;
|
||||
pack.rivers.find(r => r.i === river).name = this.value;
|
||||
this.parentNode.dataset.name = this.value;
|
||||
}
|
||||
|
||||
function regenerateRiverName(event) {
|
||||
if (!event.ctrlKey) return;
|
||||
const river = +this.parentNode.dataset.id;
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
r.name = this.value = this.parentNode.dataset.name = Rivers.getName(r.mouth);
|
||||
}
|
||||
|
||||
function changeRiverType() {
|
||||
if (this.value == "") tip("Please provide a type name", false, "error");
|
||||
const river = +this.parentNode.dataset.id;
|
||||
pack.rivers.find(r => r.i === river).type = this.value;
|
||||
this.parentNode.dataset.type = this.value;
|
||||
}
|
||||
|
||||
function zoomToRiver() {
|
||||
const r = +this.parentNode.dataset.id;
|
||||
const river = rivers.select("#river"+r).node();
|
||||
highlightElement(river);
|
||||
}
|
||||
|
||||
function downloadRiversData() {
|
||||
let data = "Id,River,Type,Length,Basin\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function(el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += el.dataset.type + ",";
|
||||
data += el.querySelector(".biomeArea").innerHTML + ",";
|
||||
data += el.dataset.basin + "\n";
|
||||
});
|
||||
|
||||
const name = getFileName("Rivers") + ".csv";
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function openRiverEditor() {
|
||||
editRiver("river"+this.parentNode.dataset.id);
|
||||
}
|
||||
|
||||
function triggerRiverRemove() {
|
||||
const river = +this.parentNode.dataset.id;
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the river?
|
||||
All tributaries will be auto-removed`;
|
||||
|
||||
$("#alert").dialog({resizable: false, width: "22em", title: "Remove river",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
Rivers.remove(river);
|
||||
riversOverviewAddLines();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllRiversRemove() {
|
||||
alertMessage.innerHTML = `Are you sure you want to remove all rivers?`;
|
||||
$("#alert").dialog({resizable: false, title: "Remove all rivers",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
$(this).dialog("close");
|
||||
removeAllRivers();
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllRivers() {
|
||||
pack.rivers = [];
|
||||
rivers.selectAll("*").remove();
|
||||
riversOverviewAddLines();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -872,7 +872,7 @@ function editStates() {
|
|||
let data = "Id,State,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area "+unit+",Total Population,Rural Population,Urban Population\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function(el) {
|
||||
let key = parseInt(el.dataset.id)
|
||||
const key = parseInt(el.dataset.id);
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += el.dataset.color + ",";
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ toolsContent.addEventListener("click", function(event) {
|
|||
if (button === "editUnitsButton") editUnits(); else
|
||||
if (button === "editNotesButton") editNotes(); else
|
||||
if (button === "editZonesButton") editZones(); else
|
||||
if (button === "overviewBurgsButton") editBurgs(); else
|
||||
if (button === "overviewBurgsButton") overviewBurgs(); else
|
||||
if (button === "overviewRiversButton") overviewRivers(); else
|
||||
if (button === "overviewCellsButton") viewCellDetails();
|
||||
|
||||
// Click to Regenerate buttons
|
||||
|
|
@ -67,9 +68,13 @@ function processFeatureRegeneration(event, button) {
|
|||
}
|
||||
|
||||
function regenerateRivers() {
|
||||
const heights = new Float32Array(pack.cells.h);
|
||||
elevateLakes();
|
||||
Rivers.generate();
|
||||
pack.cells.h = new Float32Array(heights);
|
||||
for (const i of pack.cells.i) {
|
||||
const f = pack.features[pack.cells.f[i]]; // feature
|
||||
if (f.group === "freshwater") pack.cells.h[i] = 19; // de-elevate lakes
|
||||
}
|
||||
Rivers.specify();
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +139,7 @@ function regenerateBurgs() {
|
|||
BurgsAndStates.drawBurgs();
|
||||
Routes.regenerate();
|
||||
|
||||
if (document.getElementById("burgsEditorRefresh").offsetParent) burgsEditorRefresh.click();
|
||||
if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click();
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +178,7 @@ function regenerateStates() {
|
|||
labels.select("#states").selectAll("text"); // remove state labels
|
||||
defs.select("#textPaths").selectAll("path[id*='stateLabel']").remove(); // remove state labels paths
|
||||
|
||||
if (document.getElementById("burgsEditorRefresh").offsetParent) burgsEditorRefresh.click();
|
||||
if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click();
|
||||
return;
|
||||
}
|
||||
|
|
@ -216,7 +221,7 @@ function regenerateStates() {
|
|||
if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders();
|
||||
BurgsAndStates.drawStateLabels();
|
||||
|
||||
if (document.getElementById("burgsEditorRefresh").offsetParent) burgsEditorRefresh.click();
|
||||
if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click();
|
||||
if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click();
|
||||
}
|
||||
|
||||
|
|
@ -325,19 +330,24 @@ function addLabelOnClick() {
|
|||
function toggleAddBurg() {
|
||||
unpressClickToAddButton();
|
||||
document.getElementById("addBurgTool").classList.add("pressed");
|
||||
editBurgs();
|
||||
overviewBurgs();
|
||||
document.getElementById("addNewBurg").click();
|
||||
}
|
||||
|
||||
function toggleAddRiver() {
|
||||
const pressed = document.getElementById("addRiver").classList.contains("pressed");
|
||||
if (pressed) {unpressClickToAddButton(); return;}
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
document.getElementById("addNewRiver").classList.remove("pressed");
|
||||
return;
|
||||
}
|
||||
|
||||
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
|
||||
addRiver.classList.add('pressed');
|
||||
document.getElementById("addNewRiver").classList.add("pressed");
|
||||
closeDialogs(".stable");
|
||||
viewbox.style("cursor", "crosshair").on("click", addRiverOnClick);
|
||||
tip("Click on map to place new river or extend an existing one. Hold Shift to place multiple rivers", true);
|
||||
tip("Click on map to place new river or extend an existing one. Hold Shift to place multiple rivers", true, "warn");
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
}
|
||||
|
||||
|
|
@ -348,57 +358,55 @@ function addRiverOnClick() {
|
|||
if (cells.r[i] || cells.h[i] < 20 || cells.b[i]) return;
|
||||
|
||||
const dataRiver = []; // to store river points
|
||||
const river = +getNextId("river").slice(5); // river id
|
||||
let river = +getNextId("river").slice(5); // river id
|
||||
cells.fl[i] = grid.cells.prec[cells.g[i]]; // initial flux
|
||||
let depressed = false;
|
||||
const heights = new Uint8Array(pack.cells.h); // initial heights
|
||||
|
||||
// height with added t value to make map less depressed
|
||||
const h = Array.from(cells.h)
|
||||
.map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100)
|
||||
.map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000);
|
||||
Rivers.resolveDepressions(h);
|
||||
|
||||
while (i) {
|
||||
cells.r[i] = river;
|
||||
const x = cells.p[i][0], y = cells.p[i][1];
|
||||
dataRiver.push({x, y, cell:i});
|
||||
|
||||
let min = cells.c[i][d3.scan(cells.c[i], (a, b) => cells.h[a] - cells.h[b])]; // downhill cell
|
||||
|
||||
if (cells.h[i] <= cells.h[min]) {
|
||||
if (depressed) {tip("The heightmap is too depressed, please try again", false, "error"); return;}
|
||||
depressed = Rivers.resolveDepressions();
|
||||
min = cells.c[i][d3.scan(cells.c[i], (a, b) => cells.h[a] - cells.h[b])];
|
||||
}
|
||||
|
||||
const min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell
|
||||
if (h[i] <= h[min]) {tip(`Cell ${i} is depressed, river cannot flow further`, false, "error"); return;}
|
||||
const tx = cells.p[min][0], ty = cells.p[min][1];
|
||||
|
||||
if (cells.h[min] < 20) {
|
||||
const px = (x + tx) / 2;
|
||||
const py = (y + ty) / 2;
|
||||
dataRiver.push({x: px, y: py, cell:i});
|
||||
if (h[min] < 20) {
|
||||
// pour to water body
|
||||
dataRiver.push({x: tx, y: ty, cell:i});
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cells.r[min]) {
|
||||
// continue if next cell has not river
|
||||
cells.fl[min] += cells.fl[i];
|
||||
i = min;
|
||||
continue;
|
||||
}
|
||||
|
||||
// hadnle case when lowest cell already has a river
|
||||
const r = cells.r[min];
|
||||
const riverCellsUpper = cells.i.filter(i => cells.r[i] === r && cells.h[i] > cells.h[min]);
|
||||
const riverCells = cells.i.filter(i => cells.r[i] === r);
|
||||
const riverCellsUpper = riverCells.filter(i => h[i] > h[min]);
|
||||
|
||||
// new river is not perspective
|
||||
// finish new river if old river is longer
|
||||
if (dataRiver.length <= riverCellsUpper.length) {
|
||||
cells.conf[min] += cells.fl[i];
|
||||
dataRiver.push({x: tx, y: ty, cell: min});
|
||||
dataRiver[0].parent = r; // new river is tributary
|
||||
break;
|
||||
}
|
||||
|
||||
// new river is more perspective
|
||||
// extend old river
|
||||
rivers.select("#river"+r).remove();
|
||||
riverCellsUpper.forEach(i => cells.r[i] = 0);
|
||||
if (riverCellsUpper.length > 1) {
|
||||
// redraw upper part of the old river
|
||||
}
|
||||
|
||||
cells.conf[min] = cells.fl[min];
|
||||
cells.i.filter(i => cells.r[i] === river).forEach(i => cells.r[i] = r);
|
||||
riverCells.forEach(i => cells.r[i] = 0);
|
||||
river = r;
|
||||
cells.fl[min] = cells.fl[i] + grid.cells.prec[cells.g[min]];
|
||||
i = min;
|
||||
}
|
||||
|
|
@ -406,27 +414,30 @@ function addRiverOnClick() {
|
|||
const points = Rivers.addMeandring(dataRiver, Math.random() * .5 + .1);
|
||||
const width = Math.random() * .5 + .9;
|
||||
const increment = Math.random() * .4 + .8;
|
||||
const d = Rivers.getPath(points, width, increment);
|
||||
rivers.append("path").attr("d", d).attr("id", "river"+river).attr("data-width", width).attr("data-increment", increment);
|
||||
const [path, length] = Rivers.getPath(points, width, increment);
|
||||
rivers.append("path").attr("d", path).attr("id", "river"+river).attr("data-width", width).attr("data-increment", increment);
|
||||
|
||||
if (depressed) {
|
||||
if (layerIsOn("toggleHeight")) drawHeightmap();
|
||||
alertMessage.innerHTML = `<p>Heightmap is depressed and the system had to change the heightmap to allow water flux.</p>
|
||||
Would you like to <i>keep</i> the changes or <i>restore</i> the initial heightmap?`;
|
||||
|
||||
$("#alert").dialog({resizable: false, title: "Heightmap is changed", width: "30em", modal: true,
|
||||
buttons: {
|
||||
Keep: function() {$(this).dialog("close");},
|
||||
Restore: function() {
|
||||
$(this).dialog("close");
|
||||
pack.cells.h = new Float32Array(heights);
|
||||
if (layerIsOn("toggleHeight")) drawHeightmap();
|
||||
}
|
||||
}
|
||||
});
|
||||
// add new river to data or change extended river attributes
|
||||
const r = pack.rivers.find(r => r.i === river);
|
||||
if (r) {
|
||||
r.source = dataRiver[0].cell;
|
||||
r.length = length;
|
||||
} else {
|
||||
const parent = dataRiver[0].parent || 0;
|
||||
const basin = Rivers.getBasin(river, parent);
|
||||
const source = dataRiver[0].cell;
|
||||
const mouth = last(dataRiver).cell;
|
||||
const name = Rivers.getName(mouth);
|
||||
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
|
||||
const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
|
||||
pack.rivers.push({i:river, parent, length, source, mouth, basin, name, type});
|
||||
}
|
||||
|
||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||
if (d3.event.shiftKey === false) {
|
||||
unpressClickToAddButton();
|
||||
document.getElementById("addNewRiver").classList.remove("pressed");
|
||||
if (addNewRiver.offsetParent) riversOverviewRefresh.click();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAddRoute() {
|
||||
|
|
@ -482,5 +493,8 @@ function addMarkerOnClick() {
|
|||
}
|
||||
|
||||
function viewCellDetails() {
|
||||
$("#cellInfo").dialog({resizable: false, width: "22em", title: "Cell Details"});
|
||||
$("#cellInfo").dialog({
|
||||
resizable: false, width: "22em", title: "Cell Details",
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
}
|
||||
|
|
@ -48,6 +48,7 @@ function editWorld() {
|
|||
elevateLakes();
|
||||
const heights = new Uint8Array(pack.cells.h);
|
||||
Rivers.generate();
|
||||
Rivers.specify();
|
||||
pack.cells.h = new Float32Array(heights);
|
||||
defineBiomes();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue