mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 02:01:22 +01:00
refactor: submap - start
This commit is contained in:
parent
26b659a59e
commit
58ccd238f6
8 changed files with 486 additions and 522 deletions
270
index.html
270
index.html
|
|
@ -1081,8 +1081,14 @@
|
|||
id="styleGridSizeFriendly"
|
||||
data-tip="Distance between grid cell centers (in map scale)"
|
||||
></output>
|
||||
<a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Scale-and-distance#grids" target="_blank">
|
||||
<span data-tip="Open wiki article scale and distance to know about grid scale" class="icon-info-circled pointer"></span>
|
||||
<a
|
||||
href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Scale-and-distance#grids"
|
||||
target="_blank"
|
||||
>
|
||||
<span
|
||||
data-tip="Open wiki article scale and distance to know about grid scale"
|
||||
class="icon-info-circled pointer"
|
||||
></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -1834,6 +1840,7 @@
|
|||
<select id="azgaarAssistant" data-stored="azgaarAssistant">
|
||||
<option value="show" selected>Show</option>
|
||||
<option value="hide">Hide</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
@ -2179,8 +2186,8 @@
|
|||
|
||||
<div class="separator">Create</div>
|
||||
<div class="grid">
|
||||
<button id="openSubmapMenu" data-tip="Click to generate a submap from the current viewport">Submap</button>
|
||||
<button id="openResampleMenu" data-tip="Click to transform the map">Transform</button>
|
||||
<button id="openSubmapTool" data-tip="Click to generate a submap from the current viewport">Submap</button>
|
||||
<button id="openTransformTool" data-tip="Click to transform the map">Transform</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2418,7 +2425,9 @@
|
|||
|
||||
<div id="exitCustomization">
|
||||
<div data-tip="Drag to move the pane">
|
||||
<button data-tip="Finalize the heightmap and exit the edit mode" id="finalizeHeightmap">Exit Customization</button>
|
||||
<button data-tip="Finalize the heightmap and exit the edit mode" id="finalizeHeightmap">
|
||||
Exit Customization
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -3512,7 +3521,11 @@
|
|||
<button id="burgEditEmblem" data-tip="Edit emblem" class="icon-shield-alt"></button>
|
||||
<button id="burgTogglePreview" data-tip="Toggle preview" class="icon-map"></button>
|
||||
<button id="burgLocate" data-tip="Zoom map and center view in the burg" class="icon-target"></button>
|
||||
<button id="burgRelocate" data-tip="Relocate burg. Click on map to move the burg" class="icon-map-pin"></button>
|
||||
<button
|
||||
id="burgRelocate"
|
||||
data-tip="Relocate burg. Click on map to move the burg"
|
||||
class="icon-map-pin"
|
||||
></button>
|
||||
<button id="burglLegend" data-tip="Edit free text notes (legend) for this burg" class="icon-edit"></button>
|
||||
<button id="burgLock" class="icon-lock-open" onmouseover="showElementLockTip(event)"></button>
|
||||
<button
|
||||
|
|
@ -5297,7 +5310,9 @@
|
|||
>
|
||||
Population
|
||||
</div>
|
||||
<div data-tip="Click to sort by burg features" class="sortable alphabetically" data-sortby="features">Features </div>
|
||||
<div data-tip="Click to sort by burg features" class="sortable alphabetically" data-sortby="features">
|
||||
Features
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="burgsBody" class="table"></div>
|
||||
|
|
@ -5751,6 +5766,104 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="submapTool" style="display: none" class="dialog">
|
||||
<p style="font-weight: bold">
|
||||
This operation is destructive and irreversible. It will create a completely new map based on the current one.
|
||||
Don't forget to save the current project as a .map file first!
|
||||
</p>
|
||||
|
||||
<p>Settings to be changed: population rate, map pixel size</p>
|
||||
<p>
|
||||
Data to be copied: heightmap, biomes, religions, population, precipitation, cultures, states, provinces,
|
||||
military regiments
|
||||
</p>
|
||||
<p>Data to be regenerated: zones, routes, rivers</p>
|
||||
<p>Burgs may be remapped incorrectly, manual change is required</p>
|
||||
|
||||
<p>Keep data for:</p>
|
||||
<div data-tip="Lock all markers copied from the original map">
|
||||
<input id="submapLockMarkers" class="checkbox" type="checkbox" checked />
|
||||
<label for="submapLockMarkers" class="checkbox-label">Markers</label>
|
||||
</div>
|
||||
<div data-tip="Lock all burgs copied from the original map">
|
||||
<input id="submapLockBurgs" class="checkbox" type="checkbox" checked />
|
||||
<label for="submapLockBurgs" class="checkbox-label">Burgs</label>
|
||||
</div>
|
||||
|
||||
<p>Experimental features:</p>
|
||||
<div data-tip="Rivers on the parent map will errode land (helps to get similar river network)">
|
||||
<input id="submapDepressRivers" class="checkbox" type="checkbox" />
|
||||
<label for="submapDepressRivers" class="checkbox-label">Errode riverbeds</label>
|
||||
</div>
|
||||
<div data-tip="Rescale styles (burg labels, emblem size) to match the new scale">
|
||||
<input id="submapRescaleStyles" class="checkbox" type="checkbox" checked />
|
||||
<label for="submapRescaleStyles" class="checkbox-label">Rescale styles</label>
|
||||
</div>
|
||||
<div data-tip="Move all existing towns to the 'largetown' burg group">
|
||||
<input id="submapPromoteTowns" class="checkbox" type="checkbox" />
|
||||
<label for="submapPromoteTowns" class="checkbox-label">Promote towns to largetowns</label>
|
||||
</div>
|
||||
<div data-tip="Add lakes in depressions (can be very slow on big landmasses)">
|
||||
<input id="submapAddLakeInDepression" class="checkbox" type="checkbox" />
|
||||
<label for="submapAddLakeInDepression" class="checkbox-label">Add lakes in depressions (slow)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="transformTool" style="display: none" class="dialog">
|
||||
<div style="padding-top: 0.5em; width: 40em; font-weight: bold">
|
||||
This operation is destructive and irreversible. It will create a completely new map based on the current one.
|
||||
Don't forget to save the .map file to your machine first!
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="transformToolBody"
|
||||
style="
|
||||
padding: 0.5em 0;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: repeat(5, 1fr);
|
||||
align-items: center;
|
||||
"
|
||||
>
|
||||
<div>Points number</div>
|
||||
<div>
|
||||
<input id="transformPointsInput" type="range" min="1" max="13" value="4" />
|
||||
<output id="transformPointsFormatted" style="color: #053305">10K</output>
|
||||
</div>
|
||||
|
||||
<div>Shift</div>
|
||||
<div>
|
||||
<label>X: <input id="transformShiftX" type="number" size="4" value="0" /></label>
|
||||
<label>Y: <input id="transformShiftY" type="number" size="4" value="0" /></label>
|
||||
</div>
|
||||
|
||||
<div>Rotate</div>
|
||||
<div>
|
||||
<input id="transformAngleInput" type="range" min="0" max="359" value="0" />
|
||||
<output id="transformAngleOutput">0</output>°
|
||||
</div>
|
||||
|
||||
<div>Scale</div>
|
||||
<div>
|
||||
<input id="transformScaleInput" type="range" min="-25" max="25" value="0" />
|
||||
<output id="transformScaleResult">1</output>x
|
||||
</div>
|
||||
|
||||
<div>Mirror</div>
|
||||
<div style="display: flex; gap: 0.5em">
|
||||
<input type="checkbox" class="checkbox" id="transformMirrorH" />
|
||||
<label for="transformMirrorH" class="checkbox-label">horizontally</label>
|
||||
<input type="checkbox" class="checkbox" id="transformMirrorV" />
|
||||
<label for="transformMirrorV" class="checkbox-label">vertically</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="transformPreview" style="position: relative; overflow: hidden; outline: 1px solid #666">
|
||||
<canvas id="transformPreviewCanvas" style="position: absolute; transform-origin: center"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="options3d" class="dialog stable" style="display: none">
|
||||
<div id="options3dMesh" style="display: none">
|
||||
<div data-tip="Set map rotation speed. Set to 0 is you want to toggle off the rotation">
|
||||
|
|
@ -6070,105 +6183,6 @@
|
|||
<div id="tileStatus" style="font-style: italic"></div>
|
||||
</div>
|
||||
|
||||
<div id="resampleDialog" style="display: none" class="dialog">
|
||||
<div style="width: 34em; max-width: 80vw; font-weight: bold; padding: 6px">
|
||||
This operation is destructive and irreversible. It will create a completely new map based on the current one.
|
||||
Don't forget to save the current project to your machine first!
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="
|
||||
width: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: repeat(5, 1fr);
|
||||
align-items: center;
|
||||
padding: 0.5em;
|
||||
"
|
||||
>
|
||||
<div>Points number</div>
|
||||
<div>
|
||||
<input id="submapPointsInput" type="range" min="1" max="13" value="4" />
|
||||
<output id="submapPointsOutputFormatted" style="color: #053305">10K</output>
|
||||
</div>
|
||||
|
||||
<div>Shift</div>
|
||||
<div>
|
||||
<label>X: <input id="submapShiftX" type="number" size="4" value="0" /></label>
|
||||
<label>Y: <input id="submapShiftY" type="number" size="4" value="0" /></label>
|
||||
</div>
|
||||
|
||||
<div>Rotate</div>
|
||||
<div>
|
||||
<input id="submapAngleInput" type="range" min="0" max="359" value="0" />
|
||||
<output id="submapAngleOutput">0</output>°
|
||||
</div>
|
||||
|
||||
<div>Scale</div>
|
||||
<div>
|
||||
<input id="submapScaleInput" type="range" min="-25" max="25" value="0" />
|
||||
<output id="submapScaleOutput">1</output>x
|
||||
</div>
|
||||
|
||||
<div>Mirror</div>
|
||||
<div>
|
||||
<input type="checkbox" class="checkbox" id="submapMirrorH" />
|
||||
<label for="submapMirrorH" class="checkbox-label">horizontally</label>
|
||||
|
||||
<input type="checkbox" class="checkbox" id="submapMirrorV" />
|
||||
<label for="submapMirrorV" class="checkbox-label">vertically</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="submapPreview"
|
||||
style="border: 1px solid #666; margin: 1em auto; overflow: hidden; position: relative"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div id="submapOptionsDialog" style="display: none" class="dialog">
|
||||
<p style="font-weight: bold">
|
||||
This operation is destructive and irreversible. It will create a completely new map based on the current one.
|
||||
Don't forget to save the current project as a .map file first!
|
||||
</p>
|
||||
|
||||
<p>Settings to be changed: population rate, map pixel size</p>
|
||||
<p>
|
||||
Data to be copied: heightmap, biomes, religions, population, precipitation, cultures, states, provinces,
|
||||
military regiments
|
||||
</p>
|
||||
<p>Data to be regenerated: zones, routes, rivers</p>
|
||||
<p>Burgs may be remapped incorrectly, manual change is required</p>
|
||||
|
||||
<p>Keep data for:</p>
|
||||
<div data-tip="Lock all markers copied from the original map">
|
||||
<input id="submapLockMarkers" class="checkbox" type="checkbox" checked />
|
||||
<label for="submapLockMarkers" class="checkbox-label">Markers</label>
|
||||
</div>
|
||||
<div data-tip="Lock all burgs copied from the original map">
|
||||
<input id="submapLockBurgs" class="checkbox" type="checkbox" checked />
|
||||
<label for="submapLockBurgs" class="checkbox-label">Burgs</label>
|
||||
</div>
|
||||
|
||||
<p>Experimental features:</p>
|
||||
<div data-tip="Rivers on the parent map will errode land (helps to get similar river network)">
|
||||
<input id="submapDepressRivers" class="checkbox" type="checkbox" />
|
||||
<label for="submapDepressRivers" class="checkbox-label">Errode riverbeds</label>
|
||||
</div>
|
||||
<div data-tip="Rescale styles (burg labels, emblem size) to match the new scale">
|
||||
<input id="submapRescaleStyles" class="checkbox" type="checkbox" checked />
|
||||
<label for="submapRescaleStyles" class="checkbox-label">Rescale styles</label>
|
||||
</div>
|
||||
<div data-tip="Move all existing towns to the 'largetown' burg group">
|
||||
<input id="submapPromoteTowns" class="checkbox" type="checkbox" />
|
||||
<label for="submapPromoteTowns" class="checkbox-label">Promote towns to largetowns</label>
|
||||
</div>
|
||||
<div data-tip="Add lakes in depressions (can be very slow on big landmasses)">
|
||||
<input id="submapAddLakeInDepression" class="checkbox" type="checkbox" />
|
||||
<label for="submapAddLakeInDepression" class="checkbox-label">Add lakes in depressions (slow)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="alert" style="display: none" class="dialog">
|
||||
<p id="alertMessage">Warning!</p>
|
||||
</div>
|
||||
|
|
@ -7837,32 +7851,57 @@
|
|||
<pattern id="pattern_square" width="25" height="25" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 25 0 L 0 0 0 25" />
|
||||
</pattern>
|
||||
|
||||
<pattern id="pattern_pointyHex" width="25" height="43.4" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 0,0 12.5,7.2 25,0 M 12.5,21.7 V 7.2 Z M 0,43.4 V 28.9 L 12.5,21.7 25,28.9 v 14.5" />
|
||||
</pattern>
|
||||
|
||||
<pattern id="pattern_flatHex" width="43.4" height="25" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 43.4,0 36.2,12.5 43.4,25 M 21.7,12.5 H 36.2 Z M 0,0 H 14.5 L 21.7,12.5 14.5,25 H 0" />
|
||||
</pattern>
|
||||
|
||||
<pattern id="pattern_square45deg" width="35.355" height="35.355" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 0 0 L 35.355 35.355 M 0 35.355 L 35.355 0" />
|
||||
</pattern>
|
||||
|
||||
<pattern id="pattern_squareTruncated" width="25" height="25" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 8.33 25 L 0 16.66 V 8.33 L 8.33 0 16.66 0 25 8.33 M 16.66 25 L 25 16.66 L 25 8.33 M 8.33 25 L 16.66 25" />
|
||||
<path
|
||||
d="M 8.33 25 L 0 16.66 V 8.33 L 8.33 0 16.66 0 25 8.33 M 16.66 25 L 25 16.66 L 25 8.33 M 8.33 25 L 16.66 25"
|
||||
/>
|
||||
</pattern>
|
||||
|
||||
<pattern id="pattern_squareTetrakis" width="25" height="25" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 25 0 L 0 0 0 25 M 0 0 L 25 25 M 0 25 L 25 0 M 12.5 0 L 12.5 25 M 0 12.5 L 25 12.5 M 0 25 L 25 25 L 25 0" />
|
||||
<path
|
||||
d="M 25 0 L 0 0 0 25 M 0 0 L 25 25 M 0 25 L 25 0 M 12.5 0 L 12.5 25 M 0 12.5 L 25 12.5 M 0 25 L 25 25 L 25 0"
|
||||
/>
|
||||
</pattern>
|
||||
<pattern id="pattern_triangleHorizontal" width="41.76" height="72.33" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 41.76 36.165 H 0 L 20.88 0 41.76 36.165 20.88 72.33 0 36.165 M 0 0 H 72.33 M 0 72.33 L 41.76 72.33" />
|
||||
|
||||
<pattern
|
||||
id="pattern_triangleHorizontal"
|
||||
width="41.76"
|
||||
height="72.33"
|
||||
patternUnits="userSpaceOnUse"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M 41.76 36.165 H 0 L 20.88 0 41.76 36.165 20.88 72.33 0 36.165 M 0 0 H 72.33 M 0 72.33 L 41.76 72.33"
|
||||
/>
|
||||
</pattern>
|
||||
|
||||
<pattern id="pattern_triangleVertical" width="72.33" height="41.76" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 36.165 0 L 0 20.88 36.165 41.76 72.33 20.88 36.165 0 V 41.76 M 0 0 V 72.33 M 72.33 0 L 72.33 41.76">
|
||||
<path
|
||||
d="M 36.165 0 L 0 20.88 36.165 41.76 72.33 20.88 36.165 0 V 41.76 M 0 0 V 72.33 M 72.33 0 L 72.33 41.76"
|
||||
/>
|
||||
</pattern>
|
||||
|
||||
<pattern id="pattern_trihexagonal" width="25" height="43.4" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 25 10.85 H 0 L 18.85 43.4 25 32.55 H 0 L 18.85 0 25 10.85" />
|
||||
</pattern>
|
||||
|
||||
<pattern id="pattern_rhombille" width="82.5" height="50" patternUnits="userSpaceOnUse" fill="none">
|
||||
<path d="M 13.8 50 L 0 25 13.8 0 H 41.2 L 27.5 25 41.2 50 55 25 41.2 0 68.8 0 82.5 25 68.8 50 M 0 25 H 27.5 M 55 25 H 82.5 M 13.8 50 H 41.2 L 68.8 50" />
|
||||
<path
|
||||
d="M 13.8 50 L 0 25 13.8 0 H 41.2 L 27.5 25 41.2 50 55 25 41.2 0 68.8 0 82.5 25 68.8 50 M 0 25 H 27.5 M 55 25 H 82.5 M 13.8 50 H 41.2 L 68.8 50"
|
||||
/>
|
||||
</pattern>
|
||||
</g>
|
||||
|
||||
|
|
@ -8062,7 +8101,7 @@
|
|||
<script src="modules/markers-generator.js?v=1.104.0"></script>
|
||||
<script src="modules/zones-generator.js?v=1.104.0"></script>
|
||||
<script src="modules/coa-generator.js?v=1.99.00"></script>
|
||||
<script src="modules/submap.js?v=1.104.0"></script>
|
||||
<script src="modules/submap.js?v=1.105.13"></script>
|
||||
<script src="libs/alea.min.js?v1.105.0"></script>
|
||||
<script src="libs/polylabel.min.js?v1.105.0"></script>
|
||||
<script src="libs/lineclip.min.js?v1.105.0"></script>
|
||||
|
|
@ -8078,7 +8117,7 @@
|
|||
<script defer src="modules/relief-icons.js?v=1.99.05"></script>
|
||||
<script defer src="modules/ui/style.js?v=1.104.0"></script>
|
||||
<script defer src="modules/ui/editors.js?v=1.105.11"></script>
|
||||
<script defer src="modules/ui/tools.js?v=1.105.12"></script>
|
||||
<script defer src="modules/ui/tools.js?v=1.105.13"></script>
|
||||
<script defer src="modules/ui/world-configurator.js?v=1.105.4"></script>
|
||||
<script defer src="modules/ui/heightmap-editor.js?v=1.105.2"></script>
|
||||
<script defer src="modules/ui/provinces-editor.js?v=1.104.0"></script>
|
||||
|
|
@ -8113,7 +8152,8 @@
|
|||
<script defer src="modules/ui/emblems-editor.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/markers-editor.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/3d.js?v=1.99.00"></script>
|
||||
<script defer src="modules/ui/submap.js?v=1.99.10"></script>
|
||||
<script defer src="modules/ui/submap-tool.js?v=1.105.13"></script>
|
||||
<script defer src="modules/ui/transform-tool.js?v=1.105.13"></script>
|
||||
<script defer src="modules/ui/hotkeys.js?v=1.104.0"></script>
|
||||
<script defer src="modules/coa-renderer.js?v=1.99.00"></script>
|
||||
<script defer src="libs/rgbquant.min.js"></script>
|
||||
|
|
|
|||
|
|
@ -2,75 +2,51 @@
|
|||
|
||||
window.Submap = (function () {
|
||||
const isWater = (pack, id) => pack.cells.h[id] < 20;
|
||||
const inMap = (x, y) => x > 0 && x < graphWidth && y > 0 && y < graphHeight;
|
||||
const inMap = (x, y) => x => 0 && x <= graphWidth && y >= 0 && y <= graphHeight;
|
||||
|
||||
/*
|
||||
generate new map based on an existing one (resampling parentMap)
|
||||
parentMap: {seed, grid, pack} from original map
|
||||
parentMap: {grid, pack, notes} from original map
|
||||
options = {
|
||||
projection: f(Number,Number)->[Number, Number]
|
||||
projection: f(Number, Number) -> [Number, Number]
|
||||
function to calculate new coordinates
|
||||
inverse: g(Number,Number)->[Number, Number]
|
||||
inverse: g(Number, Number) -> [Number, Number]
|
||||
inverse of f
|
||||
depressRivers: Bool carve out riverbeds?
|
||||
depressRivers: Bool carve out riverbeds
|
||||
smoothHeightMap: Bool run smooth filter on heights
|
||||
addLakesInDepressions: call FMG original funtion on heightmap
|
||||
|
||||
lockMarkers: Bool Auto lock all copied markers
|
||||
lockBurgs: Bool Auto lock all copied burgs
|
||||
}
|
||||
*/
|
||||
function resample(parentMap, options) {
|
||||
const projection = options.projection;
|
||||
const inverse = options.inverse;
|
||||
const stage = s => INFO && console.info("SUBMAP:", s);
|
||||
const timeStart = performance.now();
|
||||
invokeActiveZooming();
|
||||
const {grid: parentGrid, pack: parentPack} = parentMap;
|
||||
const {projection, inverse} = options;
|
||||
|
||||
// copy seed
|
||||
seed = parentMap.seed;
|
||||
Math.random = aleaPRNG(seed);
|
||||
INFO && console.group("SubMap with seed: " + seed);
|
||||
DEBUG && console.info("Using Options:", options);
|
||||
|
||||
applyGraphSize();
|
||||
grid = generateGrid();
|
||||
|
||||
drawScaleBar(scaleBar, scale);
|
||||
fitScaleBar(scaleBar, svgWidth, svgHeight);
|
||||
|
||||
const resampler = (points, qtree, f) => {
|
||||
for (const [i, [x, y]] of points.entries()) {
|
||||
const [tx, ty] = inverse(x, y);
|
||||
const oldid = qtree.find(tx, ty, Infinity)[2];
|
||||
f(i, oldid);
|
||||
}
|
||||
};
|
||||
|
||||
stage("Resampling heightmap, temperature and precipitation");
|
||||
// resample heightmap from old WorldState
|
||||
const n = grid.points.length;
|
||||
grid.cells.h = new Uint8Array(n); // heightmap
|
||||
grid.cells.temp = new Int8Array(n); // temperature
|
||||
grid.cells.prec = new Uint8Array(n); // precipitation
|
||||
const reverseGridMap = new Uint32Array(n); // cellmap from new -> oldcell
|
||||
const forwardGridMap = parentGrid.points.map(_ => []); // old -> [newcelllist]
|
||||
|
||||
const oldGrid = parentMap.grid;
|
||||
// build cache old -> [newcelllist]
|
||||
const forwardGridMap = parentMap.grid.points.map(_ => []);
|
||||
resampler(grid.points, parentMap.pack.cells.q, (id, oldid) => {
|
||||
const cid = parentMap.pack.cells.g[oldid];
|
||||
grid.cells.h[id] = oldGrid.cells.h[cid];
|
||||
grid.cells.temp[id] = oldGrid.cells.temp[cid];
|
||||
grid.cells.prec[id] = oldGrid.cells.prec[cid];
|
||||
if (options.depressRivers) forwardGridMap[cid].push(id);
|
||||
reverseGridMap[id] = cid;
|
||||
});
|
||||
// TODO: add smooth/noise function for h, temp, prec n times
|
||||
for (const [gridId, [x, y]] of grid.points.entries()) {
|
||||
const [parentX, parentY] = inverse(x, y);
|
||||
const parentPackId = parentPack.cells.q.find(parentX, parentY, Infinity)[2];
|
||||
const parentGridId = parentPack.cells.g[parentPackId];
|
||||
|
||||
// smooth heightmap
|
||||
// smoothing should never change cell type (land->water or water->land)
|
||||
grid.cells.h[gridId] = parentGrid.cells.h[parentGridId];
|
||||
grid.cells.temp[gridId] = parentGrid.cells.temp[parentGridId];
|
||||
grid.cells.prec[gridId] = parentGrid.cells.prec[parentGridId];
|
||||
|
||||
if (options.depressRivers) forwardGridMap[parentGridId].push(gridId);
|
||||
reverseGridMap[gridId] = parentGridId;
|
||||
}
|
||||
|
||||
// smoothing should not change cell type (land -> water or vice versa)
|
||||
if (options.smoothHeightMap) {
|
||||
const gcells = grid.cells;
|
||||
gcells.h.forEach((h, i) => {
|
||||
|
|
@ -81,7 +57,6 @@ window.Submap = (function () {
|
|||
}
|
||||
|
||||
if (options.depressRivers) {
|
||||
stage("Generating riverbeds");
|
||||
const rbeds = new Uint16Array(grid.cells.i.length);
|
||||
|
||||
// and erode riverbeds
|
||||
|
|
@ -97,6 +72,7 @@ window.Submap = (function () {
|
|||
});
|
||||
})
|
||||
);
|
||||
|
||||
// raise every land cell a bit except riverbeds
|
||||
grid.cells.h.forEach((h, i) => {
|
||||
if (rbeds[i] || h < 20) return;
|
||||
|
|
@ -104,7 +80,6 @@ window.Submap = (function () {
|
|||
});
|
||||
}
|
||||
|
||||
stage("Detect features, ocean and generating lakes");
|
||||
Features.markupGrid();
|
||||
|
||||
addLakesInDeepDepressions();
|
||||
|
|
@ -115,11 +90,8 @@ window.Submap = (function () {
|
|||
calculateMapCoordinates();
|
||||
calculateTemperatures();
|
||||
generatePrecipitation();
|
||||
stage("Cell cleanup");
|
||||
reGraph();
|
||||
|
||||
// remove misclassified cells
|
||||
stage("Define coastline");
|
||||
Features.markupPack();
|
||||
createDefaultRuler();
|
||||
|
||||
|
|
@ -135,7 +107,6 @@ window.Submap = (function () {
|
|||
cells.religion = new Uint16Array(pn);
|
||||
cells.province = new Uint16Array(pn);
|
||||
|
||||
stage("Resampling culture, state and religion map");
|
||||
for (const [id, gridCellId] of cells.g.entries()) {
|
||||
const oldGridId = reverseGridMap[gridCellId];
|
||||
if (oldGridId === undefined) {
|
||||
|
|
@ -148,8 +119,8 @@ window.Submap = (function () {
|
|||
|
||||
if (!oldChildren.length) {
|
||||
// it *must* be a (deleted) deep ocean cell
|
||||
if (!oldGrid.cells.h[oldGridId] < 20) {
|
||||
console.error(`Warning, ${gridCellId} should be water cell, not ${oldGrid.cells.h[oldGridId]}`);
|
||||
if (!parentGrid.cells.h[oldGridId] < 20) {
|
||||
console.error(`Warning, ${gridCellId} should be water cell, not ${parentGrid.cells.h[oldGridId]}`);
|
||||
continue;
|
||||
}
|
||||
// find replacement: closest water cell
|
||||
|
|
@ -166,9 +137,9 @@ window.Submap = (function () {
|
|||
let d = Infinity;
|
||||
oldChildren.forEach(oid => {
|
||||
// this should be always true, unless some algo modded the height!
|
||||
if (isWater(parentMap.pack, oid) !== isWater(pack, id)) {
|
||||
if (isWater(parentMap.pack, oid) !== isWater(pack, id))
|
||||
console.warn(`cell sank because of addLakesInDepressions: ${oid}`);
|
||||
}
|
||||
|
||||
const [oldpx, oldpy] = oldCells.p[oid];
|
||||
const nd = distance(projection(oldpx, oldpy));
|
||||
if (isNaN(nd)) {
|
||||
|
|
@ -194,18 +165,15 @@ window.Submap = (function () {
|
|||
forwardMap[oldid].push(id);
|
||||
}
|
||||
|
||||
stage("Regenerating river network");
|
||||
Rivers.generate();
|
||||
|
||||
// biome calculation based on (resampled) grid.cells.temp and prec
|
||||
// it's safe to recalculate.
|
||||
stage("Regenerating Biome");
|
||||
Biomes.define();
|
||||
// recalculate suitability and population
|
||||
// TODO: normalize according to the base-map
|
||||
rankCells();
|
||||
|
||||
stage("Porting Cultures");
|
||||
pack.cultures = parentMap.pack.cultures;
|
||||
// fix culture centers
|
||||
const validCultures = new Set(pack.cells.culture);
|
||||
|
|
@ -220,11 +188,9 @@ window.Submap = (function () {
|
|||
c.center = newCenters.length ? newCenters[0] : pack.cells.culture.findIndex(x => x === i);
|
||||
});
|
||||
|
||||
stage("Porting and locking burgs");
|
||||
copyBurgs(parentMap, projection, options);
|
||||
|
||||
// transfer states, mark states without land as removed.
|
||||
stage("Porting states");
|
||||
const validStates = new Set(pack.cells.state);
|
||||
pack.states = parentMap.pack.states;
|
||||
// keep valid states and neighbors only
|
||||
|
|
@ -241,7 +207,6 @@ window.Submap = (function () {
|
|||
BurgsAndStates.getPoles();
|
||||
|
||||
// transfer provinces, mark provinces without land as removed.
|
||||
stage("Porting provinces");
|
||||
const validProvinces = new Set(pack.cells.province);
|
||||
pack.provinces = parentMap.pack.provinces;
|
||||
// mark uneccesary provinces
|
||||
|
|
@ -255,14 +220,11 @@ window.Submap = (function () {
|
|||
p.center = newCenters.length ? newCenters[0] : pack.cells.province.findIndex(x => x === i);
|
||||
});
|
||||
Provinces.getPoles();
|
||||
|
||||
stage("Regenerating routes network");
|
||||
regenerateRoutes();
|
||||
|
||||
Rivers.specify();
|
||||
Features.specify();
|
||||
|
||||
stage("Porting military");
|
||||
for (const s of pack.states) {
|
||||
if (!s.military) continue;
|
||||
for (const m of s.military) {
|
||||
|
|
@ -274,7 +236,6 @@ window.Submap = (function () {
|
|||
s.military = s.military.filter(m => m.cell).map((m, i) => ({...m, i}));
|
||||
}
|
||||
|
||||
stage("Copying markers");
|
||||
for (const m of pack.markers) {
|
||||
const [x, y] = projection(m.x, m.y);
|
||||
if (!inMap(x, y)) {
|
||||
|
|
@ -288,16 +249,11 @@ window.Submap = (function () {
|
|||
}
|
||||
if (layerIsOn("toggleMarkers")) drawMarkers();
|
||||
|
||||
stage("Regenerating Zones");
|
||||
Zones.generate();
|
||||
Names.getMapName();
|
||||
stage("Restoring Notes");
|
||||
notes = parentMap.notes;
|
||||
stage("Submap done");
|
||||
|
||||
WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
|
||||
showStatistics();
|
||||
INFO && console.groupEnd("Generated Map " + seed);
|
||||
}
|
||||
|
||||
/* find the nearest cell accepted by filter f *and* having at
|
||||
|
|
@ -403,6 +359,5 @@ window.Submap = (function () {
|
|||
return [x, y];
|
||||
}
|
||||
|
||||
// export
|
||||
return {resample, findNearest};
|
||||
return {resample};
|
||||
})();
|
||||
|
|
|
|||
92
modules/ui/submap-tool.js
Normal file
92
modules/ui/submap-tool.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"use strict";
|
||||
|
||||
function openSubmapTool() {
|
||||
$("#submapTool").dialog({
|
||||
title: "Create a submap",
|
||||
resizable: false,
|
||||
width: "32em",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Submap: function () {
|
||||
closeDialogs();
|
||||
generateSubmap();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.openSubmapMenu) return;
|
||||
modules.openSubmapMenu = true;
|
||||
|
||||
async function generateSubmap() {
|
||||
INFO && console.group("generateSubmap");
|
||||
const [[x0, y0]] = getViewBoxExtent();
|
||||
|
||||
// converting map position on the planet. TODO: fix, coordinates are wrong
|
||||
const mapSizeOutput = byId("mapSizeOutput");
|
||||
const latitudeOutput = byId("latitudeOutput");
|
||||
const latN = 90 - ((180 - (mapSizeInput.value / 100) * 180) * latitudeOutput.value) / 100;
|
||||
const newLatN = latN - ((y0 / graphHeight) * mapSizeOutput.value * 180) / 100;
|
||||
mapSizeOutput.value /= scale;
|
||||
latitudeOutput.value = ((90 - newLatN) / (180 - (mapSizeOutput.value / 100) * 180)) * 100;
|
||||
byId("mapSizeInput").value = mapSizeOutput.value;
|
||||
byId("latitudeInput").value = latitudeOutput.value;
|
||||
|
||||
distanceScale = distanceScaleInput.value = rn(distanceScaleInput.value / scale, 2);
|
||||
populationRate = populationRateInput.value = rn(populationRateInput.value / scale, 2);
|
||||
|
||||
const parentMap = {grid: deepCopy(grid), pack: deepCopy(pack), notes: deepCopy(notes)};
|
||||
const options = {
|
||||
lockMarkers: byId("submapLockMarkers").checked,
|
||||
lockBurgs: byId("submapLockBurgs").checked,
|
||||
depressRivers: byId("submapDepressRivers").checked,
|
||||
addLakesInDepressions: byId("submapAddLakeInDepression").checked,
|
||||
smoothHeightMap: scale > 2,
|
||||
inverse: (x, y) => [x / scale + x0, y / scale + y0],
|
||||
projection: (x, y) => [(x - x0) * scale, (y - y0) * scale],
|
||||
scale
|
||||
};
|
||||
|
||||
resetZoom(0);
|
||||
undraw();
|
||||
|
||||
const oldScale = scale;
|
||||
await Submap.resample(parentMap, options);
|
||||
|
||||
if (byId("submapPromoteTowns").checked) {
|
||||
const groupName = "largetowns";
|
||||
moveAllBurgsToGroup("towns", groupName);
|
||||
changeRadius(rn(oldScale * 0.8, 2), groupName);
|
||||
changeFontSize(svg.select(`#labels #${groupName}`), rn(oldScale * 2, 2));
|
||||
invokeActiveZooming();
|
||||
}
|
||||
|
||||
if (byId("submapRescaleStyles").checked) changeStyles(oldScale);
|
||||
|
||||
drawLayers();
|
||||
|
||||
INFO && console.groupEnd("generateSubmap");
|
||||
}
|
||||
|
||||
function changeStyles(scale) {
|
||||
// resize burgIcons
|
||||
const burgIcons = [...byId("burgIcons").querySelectorAll("g")];
|
||||
for (const bi of burgIcons) {
|
||||
const newRadius = rn(minmax(bi.getAttribute("size") * scale, 0.2, 10), 2);
|
||||
changeRadius(newRadius, bi.id);
|
||||
const swAttr = bi.attributes["stroke-width"];
|
||||
swAttr.value = +swAttr.value * scale;
|
||||
}
|
||||
|
||||
// burglabels
|
||||
const burgLabels = [...byId("burgLabels").querySelectorAll("g")];
|
||||
for (const bl of burgLabels) {
|
||||
const size = +bl.dataset["size"];
|
||||
bl.dataset["size"] = Math.max(rn((size + size / scale) / 2, 2), 1) * scale;
|
||||
}
|
||||
|
||||
drawEmblems();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,332 +0,0 @@
|
|||
"use strict";
|
||||
// UI elements for submap generation
|
||||
|
||||
window.UISubmap = (function () {
|
||||
byId("submapPointsInput").addEventListener("input", function () {
|
||||
const output = byId("submapPointsOutputFormatted");
|
||||
const cells = cellsDensityMap[+this.value] || 1000;
|
||||
this.dataset.cells = cells;
|
||||
output.value = getCellsDensityValue(cells);
|
||||
output.style.color = getCellsDensityColor(cells);
|
||||
});
|
||||
|
||||
byId("submapScaleInput").addEventListener("input", function (event) {
|
||||
const exp = Math.pow(1.1, +event.target.value);
|
||||
byId("submapScaleOutput").value = rn(exp, 2);
|
||||
});
|
||||
|
||||
byId("submapAngleInput").addEventListener("input", function (event) {
|
||||
byId("submapAngleOutput").value = event.target.value;
|
||||
});
|
||||
|
||||
const $previewBox = byId("submapPreview");
|
||||
const $scaleInput = byId("submapScaleInput");
|
||||
const $shiftX = byId("submapShiftX");
|
||||
const $shiftY = byId("submapShiftY");
|
||||
|
||||
function openSubmapMenu() {
|
||||
$("#submapOptionsDialog").dialog({
|
||||
title: "Create a submap",
|
||||
resizable: false,
|
||||
width: "32em",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Submap: function () {
|
||||
$(this).dialog("close");
|
||||
generateSubmap();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const getTransformInput = _ => ({
|
||||
angle: (+byId("submapAngleInput").value / 180) * Math.PI,
|
||||
shiftX: +byId("submapShiftX").value,
|
||||
shiftY: +byId("submapShiftY").value,
|
||||
ratio: +byId("submapScaleInput").value,
|
||||
mirrorH: byId("submapMirrorH").checked,
|
||||
mirrorV: byId("submapMirrorV").checked
|
||||
});
|
||||
|
||||
async function openResampleMenu() {
|
||||
resetZoom(0);
|
||||
|
||||
byId("submapAngleInput").value = 0;
|
||||
byId("submapAngleOutput").value = "0";
|
||||
byId("submapScaleOutput").value = 1;
|
||||
byId("submapMirrorH").checked = false;
|
||||
byId("submapMirrorV").checked = false;
|
||||
$scaleInput.value = 0;
|
||||
$shiftX.value = 0;
|
||||
$shiftY.value = 0;
|
||||
|
||||
const w = Math.min(400, window.innerWidth * 0.5);
|
||||
const previewScale = w / graphWidth;
|
||||
const h = graphHeight * previewScale;
|
||||
$previewBox.style.width = w + "px";
|
||||
$previewBox.style.height = h + "px";
|
||||
|
||||
// handle mouse input
|
||||
const dispatchInput = e => e.dispatchEvent(new Event("input", {bubbles: true}));
|
||||
|
||||
// mouse wheel
|
||||
$previewBox.onwheel = e => {
|
||||
$scaleInput.value = $scaleInput.valueAsNumber - Math.sign(e.deltaY);
|
||||
dispatchInput($scaleInput);
|
||||
};
|
||||
|
||||
// mouse drag
|
||||
let mouseIsDown = false,
|
||||
mouseX = 0,
|
||||
mouseY = 0;
|
||||
$previewBox.onmousedown = e => {
|
||||
mouseIsDown = true;
|
||||
mouseX = $shiftX.value - e.clientX / previewScale;
|
||||
mouseY = $shiftY.value - e.clientY / previewScale;
|
||||
};
|
||||
$previewBox.onmouseup = _ => (mouseIsDown = false);
|
||||
$previewBox.onmouseleave = _ => (mouseIsDown = false);
|
||||
$previewBox.onmousemove = e => {
|
||||
if (!mouseIsDown) return;
|
||||
e.preventDefault();
|
||||
$shiftX.value = Math.round(mouseX + e.clientX / previewScale);
|
||||
$shiftY.value = Math.round(mouseY + e.clientY / previewScale);
|
||||
dispatchInput($shiftX);
|
||||
// dispatchInput($shiftY); // not needed X bubbles anyway
|
||||
};
|
||||
|
||||
$("#resampleDialog").dialog({
|
||||
title: "Transform map",
|
||||
resizable: false,
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Transform: function () {
|
||||
$(this).dialog("close");
|
||||
resampleCurrentMap();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// use double resolution for PNG to get sharper image
|
||||
const $preview = await loadPreview($previewBox, w * 2, h * 2);
|
||||
// could be done with SVG. Faster to load, slower to use.
|
||||
// const $preview = await loadPreviewSVG($previewBox, w, h);
|
||||
$preview.style.position = "absolute";
|
||||
$preview.style.width = w + "px";
|
||||
$preview.style.height = h + "px";
|
||||
|
||||
byId("resampleDialog").oninput = event => {
|
||||
const {angle, shiftX, shiftY, ratio, mirrorH, mirrorV} = getTransformInput();
|
||||
const scale = Math.pow(1.1, ratio);
|
||||
const transformStyle = `
|
||||
translate(${shiftX * previewScale}px, ${shiftY * previewScale}px)
|
||||
scale(${mirrorH ? -scale : scale}, ${mirrorV ? -scale : scale})
|
||||
rotate(${angle}rad)
|
||||
`;
|
||||
|
||||
$preview.style.transform = transformStyle;
|
||||
$preview.style["transform-origin"] = "center";
|
||||
event.stopPropagation();
|
||||
};
|
||||
}
|
||||
|
||||
async function loadPreview($container, w, h) {
|
||||
const url = await getMapURL("png", {
|
||||
globe: false,
|
||||
noWater: true,
|
||||
fullMap: true,
|
||||
noLabels: true,
|
||||
noScaleBar: true,
|
||||
noVignette: true,
|
||||
noIce: true
|
||||
});
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, 0, 0, w, h);
|
||||
};
|
||||
$container.textContent = "";
|
||||
$container.appendChild(canvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
// Resample the whole map to different cell resolution or shape
|
||||
const resampleCurrentMap = debounce(function () {
|
||||
WARN && console.warn("Resampling current map");
|
||||
const cellNumId = +byId("submapPointsInput").value;
|
||||
if (!cellsDensityMap[cellNumId]) return console.error("Unknown cell number!");
|
||||
|
||||
const {angle, shiftX, shiftY, ratio, mirrorH, mirrorV} = getTransformInput();
|
||||
|
||||
const [cx, cy] = [graphWidth / 2, graphHeight / 2];
|
||||
const rot = alfa => (x, y) =>
|
||||
[
|
||||
(x - cx) * Math.cos(alfa) - (y - cy) * Math.sin(alfa) + cx,
|
||||
(y - cy) * Math.cos(alfa) + (x - cx) * Math.sin(alfa) + cy
|
||||
];
|
||||
const shift = (dx, dy) => (x, y) => [x + dx, y + dy];
|
||||
const scale = r => (x, y) => [(x - cx) * r + cx, (y - cy) * r + cy];
|
||||
const flipH = (x, y) => [-x + 2 * cx, y];
|
||||
const flipV = (x, y) => [x, -y + 2 * cy];
|
||||
const app = (f, g) => (x, y) => f(...g(x, y));
|
||||
const id = (x, y) => [x, y];
|
||||
|
||||
let projection = id;
|
||||
let inverse = id;
|
||||
|
||||
if (angle) [projection, inverse] = [rot(angle), rot(-angle)];
|
||||
if (ratio)
|
||||
[projection, inverse] = [
|
||||
app(scale(Math.pow(1.1, ratio)), projection),
|
||||
app(inverse, scale(Math.pow(1.1, -ratio)))
|
||||
];
|
||||
if (mirrorH) [projection, inverse] = [app(flipH, projection), app(inverse, flipH)];
|
||||
if (mirrorV) [projection, inverse] = [app(flipV, projection), app(inverse, flipV)];
|
||||
if (shiftX || shiftY) {
|
||||
projection = app(shift(shiftX, shiftY), projection);
|
||||
inverse = app(inverse, shift(-shiftX, -shiftY));
|
||||
}
|
||||
|
||||
changeCellsDensity(cellNumId);
|
||||
startResample({
|
||||
lockMarkers: false,
|
||||
lockBurgs: false,
|
||||
depressRivers: false,
|
||||
addLakesInDepressions: false,
|
||||
promoteTowns: false,
|
||||
smoothHeightMap: false,
|
||||
rescaleStyles: false,
|
||||
scale: 1,
|
||||
projection,
|
||||
inverse
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// Create submap from the current map. Submap limits defined by the current window size (canvas viewport)
|
||||
const generateSubmap = debounce(function () {
|
||||
WARN && console.warn("Resampling current map");
|
||||
closeDialogs("#worldConfigurator, #options3d");
|
||||
const checked = id => Boolean(byId(id).checked);
|
||||
|
||||
// Create projection func from current zoom extents
|
||||
const [[x0, y0], [x1, y1]] = getViewBoxExtent();
|
||||
const origScale = scale;
|
||||
|
||||
const options = {
|
||||
lockMarkers: checked("submapLockMarkers"),
|
||||
lockBurgs: checked("submapLockBurgs"),
|
||||
|
||||
depressRivers: checked("submapDepressRivers"),
|
||||
addLakesInDepressions: checked("submapAddLakeInDepression"),
|
||||
promoteTowns: checked("submapPromoteTowns"),
|
||||
rescaleStyles: checked("submapRescaleStyles"),
|
||||
smoothHeightMap: scale > 2,
|
||||
inverse: (x, y) => [x / origScale + x0, y / origScale + y0],
|
||||
projection: (x, y) => [(x - x0) * origScale, (y - y0) * origScale],
|
||||
scale: origScale
|
||||
};
|
||||
|
||||
// converting map position on the planet
|
||||
const mapSizeOutput = byId("mapSizeOutput");
|
||||
const latitudeOutput = byId("latitudeOutput");
|
||||
const latN = 90 - ((180 - (mapSizeInput.value / 100) * 180) * latitudeOutput.value) / 100;
|
||||
const newLatN = latN - ((y0 / graphHeight) * mapSizeOutput.value * 180) / 100;
|
||||
mapSizeOutput.value /= scale;
|
||||
latitudeOutput.value = ((90 - newLatN) / (180 - (mapSizeOutput.value / 100) * 180)) * 100;
|
||||
byId("mapSizeInput").value = mapSizeOutput.value;
|
||||
byId("latitudeInput").value = latitudeOutput.value;
|
||||
|
||||
// fix scale
|
||||
distanceScale = distanceScaleInput.value = rn(distanceScaleInput.value / scale, 2);
|
||||
populationRate = populationRateInput.value = rn(populationRateInput.value / scale, 2);
|
||||
|
||||
customization = 0;
|
||||
startResample(options);
|
||||
}, 1000);
|
||||
|
||||
async function startResample(options) {
|
||||
// Do model changes with Submap.resample then do view changes if needed
|
||||
resetZoom(0);
|
||||
let oldstate = {
|
||||
grid: deepCopy(grid),
|
||||
pack: deepCopy(pack),
|
||||
notes: deepCopy(notes),
|
||||
seed,
|
||||
graphWidth,
|
||||
graphHeight
|
||||
};
|
||||
undraw();
|
||||
try {
|
||||
const oldScale = scale;
|
||||
await Submap.resample(oldstate, options);
|
||||
if (options.promoteTowns) {
|
||||
const groupName = "largetowns";
|
||||
moveAllBurgsToGroup("towns", groupName);
|
||||
changeRadius(rn(oldScale * 0.8, 2), groupName);
|
||||
changeFontSize(svg.select(`#labels #${groupName}`), rn(oldScale * 2, 2));
|
||||
invokeActiveZooming();
|
||||
}
|
||||
if (options.rescaleStyles) changeStyles(oldScale);
|
||||
} catch (error) {
|
||||
showSubmapErrorHandler(error);
|
||||
}
|
||||
|
||||
oldstate = null; // destroy old state to free memory
|
||||
|
||||
drawLayers();
|
||||
if (ThreeD.options.isOn) ThreeD.redraw();
|
||||
if ($("#worldConfigurator").is(":visible")) editWorld();
|
||||
}
|
||||
|
||||
function changeStyles(scale) {
|
||||
// resize burgIcons
|
||||
const burgIcons = [...byId("burgIcons").querySelectorAll("g")];
|
||||
for (const bi of burgIcons) {
|
||||
const newRadius = rn(minmax(bi.getAttribute("size") * scale, 0.2, 10), 2);
|
||||
changeRadius(newRadius, bi.id);
|
||||
const swAttr = bi.attributes["stroke-width"];
|
||||
swAttr.value = +swAttr.value * scale;
|
||||
}
|
||||
|
||||
// burglabels
|
||||
const burgLabels = [...byId("burgLabels").querySelectorAll("g")];
|
||||
for (const bl of burgLabels) {
|
||||
const size = +bl.dataset["size"];
|
||||
bl.dataset["size"] = Math.max(rn((size + size / scale) / 2, 2), 1) * scale;
|
||||
}
|
||||
|
||||
drawEmblems();
|
||||
}
|
||||
|
||||
function showSubmapErrorHandler(error) {
|
||||
ERROR && console.error(error);
|
||||
clearMainTip();
|
||||
|
||||
alertMessage.innerHTML = /* html */ `Map resampling failed: <br />You may retry after clearing stored data or contact us at discord.
|
||||
<p id="errorBox">${parseError(error)}</p>`;
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Resampling error",
|
||||
width: "32em",
|
||||
buttons: {
|
||||
Ok: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
},
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
}
|
||||
|
||||
return {openSubmapMenu, openResampleMenu};
|
||||
})();
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
|
||||
toolsContent.addEventListener("click", function (event) {
|
||||
if (customization) return tip("Please exit the customization mode first", false, "warning");
|
||||
if (customization) return tip("Please exit the customization mode first", false, "error");
|
||||
if (!["BUTTON", "I"].includes(event.target.tagName)) return;
|
||||
const button = event.target.id;
|
||||
|
||||
|
|
@ -70,8 +70,8 @@ toolsContent.addEventListener("click", function (event) {
|
|||
else if (button === "addRoute") createRoute();
|
||||
else if (button === "addMarker") toggleAddMarker();
|
||||
// click to create a new map buttons
|
||||
else if (button === "openSubmapMenu") UISubmap.openSubmapMenu();
|
||||
else if (button === "openResampleMenu") UISubmap.openResampleMenu();
|
||||
else if (button === "openSubmapTool") openSubmapTool();
|
||||
else if (button === "openTransformTool") openTransformTool();
|
||||
});
|
||||
|
||||
function processFeatureRegeneration(event, button) {
|
||||
|
|
|
|||
207
modules/ui/transform-tool.js
Normal file
207
modules/ui/transform-tool.js
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
"use strict";
|
||||
|
||||
async function openTransformTool() {
|
||||
const width = Math.min(400, window.innerWidth * 0.5);
|
||||
const previewScale = width / graphWidth;
|
||||
const height = graphHeight * previewScale;
|
||||
|
||||
let mouseIsDown = false;
|
||||
let mouseX = 0;
|
||||
let mouseY = 0;
|
||||
|
||||
resetInputs();
|
||||
loadPreview();
|
||||
|
||||
$("#transformTool").dialog({
|
||||
title: "Transform map",
|
||||
resizable: false,
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Transform: function () {
|
||||
closeDialogs();
|
||||
transformMap();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.openTransformTool) return;
|
||||
modules.openTransformTool = true;
|
||||
|
||||
// add listeners
|
||||
byId("transformToolBody").on("input", handleInput);
|
||||
byId("transformPointsInput").on("input", handleCellsChange);
|
||||
byId("transformPreview")
|
||||
.on("mousedown", handleMousedown)
|
||||
.on("mouseup", _ => (mouseIsDown = false))
|
||||
.on("mousemove", handleMousemove)
|
||||
.on("wheel", handleWheel);
|
||||
|
||||
async function loadPreview() {
|
||||
byId("transformPreview").style.width = width + "px";
|
||||
byId("transformPreview").style.height = height + "px";
|
||||
|
||||
const options = {noWater: true, fullMap: true, noLabels: true, noScaleBar: true, noVignette: true, noIce: true};
|
||||
const url = await getMapURL("png", options);
|
||||
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
const $canvas = byId("transformPreviewCanvas");
|
||||
$canvas.style.width = width + "px";
|
||||
$canvas.style.height = height + "px";
|
||||
$canvas.width = width * 2;
|
||||
$canvas.height = height * 2;
|
||||
$canvas.getContext("2d").drawImage(img, 0, 0, width * 2, height * 2);
|
||||
};
|
||||
}
|
||||
|
||||
function resetInputs() {
|
||||
byId("transformAngleInput").value = 0;
|
||||
byId("transformAngleOutput").value = "0";
|
||||
byId("transformMirrorH").checked = false;
|
||||
byId("transformMirrorV").checked = false;
|
||||
byId("transformScaleInput").value = 0;
|
||||
byId("transformScaleResult").value = 1;
|
||||
byId("transformShiftX").value = 0;
|
||||
byId("transformShiftY").value = 0;
|
||||
handleInput();
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
const angle = (+byId("transformAngleInput").value / 180) * Math.PI;
|
||||
const shiftX = +byId("transformShiftX").value;
|
||||
const shiftY = +byId("transformShiftY").value;
|
||||
const mirrorH = byId("transformMirrorH").checked;
|
||||
const mirrorV = byId("transformMirrorV").checked;
|
||||
|
||||
const EXP = 1.0965;
|
||||
const scale = rn(EXP ** +byId("transformScaleInput").value, 2); // [0.1, 10]x
|
||||
byId("transformScaleResult").value = scale;
|
||||
|
||||
byId("transformPreviewCanvas").style.transform = `
|
||||
translate(${shiftX * previewScale}px, ${shiftY * previewScale}px)
|
||||
scale(${mirrorH ? -scale : scale}, ${mirrorV ? -scale : scale})
|
||||
rotate(${angle}rad)
|
||||
`;
|
||||
}
|
||||
|
||||
function handleCellsChange() {
|
||||
const cells = cellsDensityMap[+this.value] || 1000;
|
||||
this.dataset.cells = cells;
|
||||
const output = byId("transformPointsFormatted");
|
||||
output.value = getCellsDensityValue(cells);
|
||||
output.style.color = getCellsDensityColor(cells);
|
||||
}
|
||||
|
||||
function handleMousedown(e) {
|
||||
mouseIsDown = true;
|
||||
const shiftX = +byId("transformShiftX").value;
|
||||
const shiftY = +byId("transformShiftY").value;
|
||||
mouseX = shiftX - e.clientX / previewScale;
|
||||
mouseY = shiftY - e.clientY / previewScale;
|
||||
}
|
||||
|
||||
function handleMousemove(e) {
|
||||
if (!mouseIsDown) return;
|
||||
e.preventDefault();
|
||||
|
||||
byId("transformShiftX").value = Math.round(mouseX + e.clientX / previewScale);
|
||||
byId("transformShiftY").value = Math.round(mouseY + e.clientY / previewScale);
|
||||
handleInput();
|
||||
}
|
||||
|
||||
function handleWheel(e) {
|
||||
const $scaleInput = byId("transformScaleInput");
|
||||
$scaleInput.value = $scaleInput.valueAsNumber - Math.sign(e.deltaY);
|
||||
handleInput();
|
||||
}
|
||||
|
||||
async function transformMap() {
|
||||
INFO && console.group("transformMap");
|
||||
|
||||
const parentMap = {grid: deepCopy(grid), pack: deepCopy(pack), notes: deepCopy(notes)};
|
||||
const [projection, inverse] = getProjection();
|
||||
const options = {
|
||||
lockMarkers: false,
|
||||
lockBurgs: false,
|
||||
depressRivers: false,
|
||||
addLakesInDepressions: false,
|
||||
smoothHeightMap: false,
|
||||
scale: 1,
|
||||
inverse,
|
||||
projection
|
||||
};
|
||||
|
||||
const cellsNumber = +byId("transformPointsInput").value;
|
||||
changeCellsDensity(cellsNumber);
|
||||
|
||||
resetZoom(0);
|
||||
undraw();
|
||||
await Submap.resample(parentMap, options);
|
||||
drawLayers();
|
||||
|
||||
INFO && console.groupEnd("transformMap");
|
||||
}
|
||||
|
||||
function getProjection() {
|
||||
const centerX = graphWidth / 2;
|
||||
const centerY = graphHeight / 2;
|
||||
const shiftX = +byId("transformShiftX").value;
|
||||
const shiftY = +byId("transformShiftY").value;
|
||||
const angle = (+byId("transformAngleInput").value / 180) * Math.PI;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
const scale = +byId("transformScaleResult").value;
|
||||
const mirrorH = byId("transformMirrorH").checked;
|
||||
const mirrorV = byId("transformMirrorV").checked;
|
||||
|
||||
function project(x, y) {
|
||||
// center the point
|
||||
x -= centerX;
|
||||
y -= centerY;
|
||||
|
||||
// apply scale
|
||||
if (scale !== 1) {
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
}
|
||||
|
||||
// apply rotation
|
||||
if (angle) [x, y] = [x * cos - y * sin, x * sin + y * cos];
|
||||
|
||||
// apply mirroring
|
||||
if (mirrorH) x = -x;
|
||||
if (mirrorV) y = -y;
|
||||
|
||||
// uncenter the point and apply shift
|
||||
return [x + centerX + shiftX, y + centerY + shiftY];
|
||||
}
|
||||
|
||||
function inverse(x, y) {
|
||||
// undo shift and center the point
|
||||
x -= centerX + shiftX;
|
||||
y -= centerY + shiftY;
|
||||
|
||||
// undo mirroring
|
||||
if (mirrorV) y = -y;
|
||||
if (mirrorH) x = -x;
|
||||
|
||||
// undo rotation
|
||||
if (angle !== 0) [x, y] = [x * cos + y * sin, -x * sin + y * cos];
|
||||
|
||||
// undo scale
|
||||
if (scale !== 1) {
|
||||
x /= scale;
|
||||
y /= scale;
|
||||
}
|
||||
|
||||
// uncenter the point
|
||||
return [x + centerX, y + centerY];
|
||||
}
|
||||
|
||||
return [project, inverse];
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,10 @@ const byId = document.getElementById.bind(document);
|
|||
|
||||
Node.prototype.on = function (name, fn, options) {
|
||||
this.addEventListener(name, fn, options);
|
||||
return this;
|
||||
};
|
||||
|
||||
Node.prototype.off = function (name, fn) {
|
||||
this.removeEventListener(name, fn);
|
||||
return this;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
*
|
||||
* Example: 1.102.2 -> Major version 1, Minor version 102, Patch version 2
|
||||
*/
|
||||
const VERSION = "1.105.12";
|
||||
const VERSION = "1.105.13";
|
||||
if (parseMapVersion(VERSION) !== VERSION) alert("versioning.js: Invalid format or parsing function");
|
||||
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue