mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into dev-economics
This commit is contained in:
commit
1180a3c67b
41 changed files with 5185 additions and 3469 deletions
21
.docker/default.conf
Normal file
21
.docker/default.conf
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src data: 'self'";
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Frame-Options "DENY";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header Referrer-Policy "strict-origin";
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +1,2 @@
|
|||
run_php_server.bat
|
||||
.bat
|
||||
.vscode
|
||||
7
Dockerfile
Normal file
7
Dockerfile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
FROM nginx:stable-alpine
|
||||
|
||||
# Copy the contents of the repo to the container
|
||||
COPY . /usr/share/nginx/html
|
||||
|
||||
# Move the customized nginx config file to the nginx folder
|
||||
RUN mv /usr/share/nginx/html/.docker/default.conf /etc/nginx/conf.d/default.conf
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright 2018-2020 Max Ganiev (Azgaar), azgaar.fmg@yandex.by
|
||||
Copyright 2017-2021 Max Haniyeu (Azgaar), azgaar.fmg@yandex.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
54
index.css
54
index.css
|
|
@ -169,23 +169,28 @@ a {
|
|||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
#statesBody,
|
||||
#provincesBody {
|
||||
stroke-width: 2;
|
||||
fill-rule: evenodd;
|
||||
mask: url(#land);
|
||||
}
|
||||
|
||||
#relig,
|
||||
#biomes,
|
||||
#cults {
|
||||
fill-rule: evenodd;
|
||||
mask: url(#land);
|
||||
#statesBody {
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
#statesHalo {
|
||||
fill: none;
|
||||
filter: url(#blur5);
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
#provincesBody {
|
||||
stroke-width: 0.2;
|
||||
}
|
||||
|
||||
#statesBody,
|
||||
#provincesBody,
|
||||
#relig,
|
||||
#biomes,
|
||||
#cults {
|
||||
stroke-linejoin: round;
|
||||
fill-rule: evenodd;
|
||||
mask: url(#land);
|
||||
}
|
||||
|
||||
#borders {
|
||||
|
|
@ -197,6 +202,7 @@ a {
|
|||
stroke: none;
|
||||
mask: url(#land);
|
||||
cursor: pointer;
|
||||
fill-rule: nonzero;
|
||||
}
|
||||
|
||||
#anchors {
|
||||
|
|
@ -979,6 +985,12 @@ body button.noicon {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#controlCells > .current {
|
||||
fill: #82c8ff40;
|
||||
stroke: #82c8ff;
|
||||
stroke-width: 0.4;
|
||||
}
|
||||
|
||||
#vertices > circle {
|
||||
fill: #ff0000;
|
||||
stroke: #841f1f;
|
||||
|
|
@ -1116,6 +1128,12 @@ div#regimentSelectorBody > div > div {
|
|||
fill: none;
|
||||
}
|
||||
|
||||
#debug > text {
|
||||
font-size: 2px;
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
|
||||
.selectedCell {
|
||||
stroke-width: 1;
|
||||
stroke: #da3126;
|
||||
|
|
@ -1697,6 +1715,12 @@ rect.fillRect {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
div.editorLine {
|
||||
margin: 0.2em 0;
|
||||
padding: 0 0.2em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#emblemDownloadControl > input {
|
||||
width: 4.1em;
|
||||
}
|
||||
|
|
@ -2058,10 +2082,6 @@ svg.button {
|
|||
width: 16em;
|
||||
}
|
||||
|
||||
#reliefEditor input[type='number'] {
|
||||
width: 3em;
|
||||
}
|
||||
|
||||
#reliefIconsDiv {
|
||||
margin-top: 2px;
|
||||
padding: 2px;
|
||||
|
|
|
|||
268
index.html
268
index.html
|
|
@ -45,36 +45,36 @@
|
|||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<g id="filters">
|
||||
<filter id="blurFilter" x="-1" y="-1" width="100" height="100">
|
||||
<filter id="blurFilter" name="Blur 0.2" x="-1" y="-1" width="100" height="100">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="0.2"/>
|
||||
</filter>
|
||||
<filter id="blur1" x="-1" y="-1" width="100" height="100">
|
||||
<filter id="blur1" name="Blur 1" x="-1" y="-1" width="100" height="100">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="1"/>
|
||||
</filter>
|
||||
<filter id="blur3" x="-1" y="-1" width="100" height="100">
|
||||
<filter id="blur3" name="Blur 3" x="-1" y="-1" width="100" height="100">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
|
||||
</filter>
|
||||
<filter id="blur5" x="-1" y="-1" width="100" height="100">
|
||||
<filter id="blur5" name="Blur 5" x="-1" y="-1" width="100" height="100">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="5"/>
|
||||
</filter>
|
||||
<filter id="blur7" x="-1" y="-1" width="100" height="100">
|
||||
<filter id="blur7" name="Blur 7" x="-1" y="-1" width="100" height="100">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="7"/>
|
||||
</filter>
|
||||
<filter id="blur10" x="-1" y="-1" width="100" height="100">
|
||||
<filter id="blur10" name="Blur 10" x="-1" y="-1" width="100" height="100">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="10"/>
|
||||
</filter>
|
||||
<filter id="splotch">
|
||||
<filter id="splotch" name="Splotch">
|
||||
<feTurbulence type="fractalNoise" baseFrequency=".01" numOctaves="4"/>
|
||||
<feColorMatrix values="0 0 0 0 0, 0 0 0 0 0, 0 0 0 0 0, 0 0 0 -0.9 1.2" result="texture"/>
|
||||
<feComposite in="SourceGraphic" in2="texture" operator="in"/>
|
||||
</filter>
|
||||
<filter id="bluredSplotch">
|
||||
<filter id="bluredSplotch" name="Blurred Splotch">
|
||||
<feTurbulence type="fractalNoise" baseFrequency=".01" numOctaves="4"/>
|
||||
<feColorMatrix values="0 0 0 0 0, 0 0 0 0 0, 0 0 0 0 0, 0 0 0 -0.9 1.2" result="texture"/>
|
||||
<feComposite in="SourceGraphic" in2="texture" operator="in"/>
|
||||
<feGaussianBlur stdDeviation="4"/>
|
||||
</filter>
|
||||
<filter id="dropShadow">
|
||||
<filter id="dropShadow" name="Shadow 2">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
|
||||
<feOffset dx="1" dy="2"/>
|
||||
<feMerge>
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<filter id="dropShadow01">
|
||||
<filter id="dropShadow01" name="Shadow 0.1">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation=".1"/>
|
||||
<feOffset dx=".2" dy=".3"/>
|
||||
<feMerge>
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<filter id="dropShadow05">
|
||||
<filter id="dropShadow05" name="Shadow 0.5">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation=".5"/>
|
||||
<feOffset dx=".5" dy=".7"/>
|
||||
<feMerge>
|
||||
|
|
@ -98,23 +98,23 @@
|
|||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<filter id="outline">
|
||||
<filter id="outline" name="Outline">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="1"/>
|
||||
<feMerge>
|
||||
<feMergeNode/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<filter id="pencil">
|
||||
<filter id="pencil" name="Pencil">
|
||||
<feTurbulence baseFrequency="0.03" numOctaves="6" type="fractalNoise"/>
|
||||
<feDisplacementMap scale="3" in="SourceGraphic" xChannelSelector="R" yChannelSelector="G"/>
|
||||
</filter>
|
||||
<filter id="turbulence">
|
||||
<filter id="turbulence" name="Turbulence">
|
||||
<feTurbulence baseFrequency="0.1" numOctaves="3" type="fractalNoise"/>
|
||||
<feDisplacementMap scale="10" in="SourceGraphic" xChannelSelector="R" yChannelSelector="G"/>
|
||||
</filter>
|
||||
|
||||
<filter id="paper" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<filter id="paper" name="Paper" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="1 1" x="0%" y="0%" width="100%" height="100%" in="SourceGraphic" edgeMode="none" result="blur"/>
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.05 0.05" numOctaves="4" seed="1" stitchTiles="stitch" result="turbulence"/>
|
||||
<feDiffuseLighting surfaceScale="2" diffuseConstant="1" lighting-color="#707070" in="turbulence" result="diffuseLighting">
|
||||
|
|
@ -124,7 +124,7 @@
|
|||
<feComposite in="composite" in2="SourceGraphic" operator="in" x="0%" y="0%" width="100%" height="100%" result="composite1"/>
|
||||
</filter>
|
||||
|
||||
<filter id="crumpled" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<filter id="crumpled" name="Crumpled" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="2 2" x="0%" y="0%" width="100%" height="100%" in="SourceGraphic" edgeMode="none" result="blur"/>
|
||||
<feTurbulence type="turbulence" baseFrequency="0.05 0.05" numOctaves="4" seed="1" stitchTiles="stitch" result="turbulence"/>
|
||||
<feDiffuseLighting surfaceScale="2" diffuseConstant="1" lighting-color="#828282" in="turbulence" result="diffuseLighting">
|
||||
|
|
@ -134,16 +134,16 @@
|
|||
<feComposite in="composite" in2="SourceGraphic" operator="in" x="0%" y="0%" width="100%" height="100%" result="composite1"/>
|
||||
</filter>
|
||||
|
||||
<filter id="filter-grayscale">
|
||||
<filter id="filter-grayscale" name="Grayscale">
|
||||
<feColorMatrix values="0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<filter id="filter-sepia">
|
||||
<filter id="filter-sepia" name="Sepia">
|
||||
<feColorMatrix values="0.393 0.769 0.189 0 0 0.349 0.686 0.168 0 0 0.272 0.534 0.131 0 0 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<filter id="filter-dingy">
|
||||
<filter id="filter-dingy" name="Dingy">
|
||||
<feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0.3 0.3 0 0 0 0 0 1 0"></feColorMatrix>
|
||||
</filter>
|
||||
<filter id="filter-tint">
|
||||
<filter id="filter-tint" name="Tint">
|
||||
<feColorMatrix values="1.1 0 0 0 0 0 1.1 0 0 0 0 0 0.9 0 0 0 0 0 1 0"></feColorMatrix>
|
||||
</filter>
|
||||
</g>
|
||||
|
|
@ -235,7 +235,7 @@
|
|||
<div id="loading">
|
||||
<div id="titleName"><t data-t="titleName">Azgaar's</t></div>
|
||||
<div id="title"><t data-t="title">Fantasy Map Generator</t></div>
|
||||
<div id="version"><t data-t="version">v. </t>1.63</div>
|
||||
<div id="version"><t data-t="version">v. </t>1.65</div>
|
||||
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
|
||||
</div>
|
||||
|
||||
|
|
@ -319,13 +319,15 @@
|
|||
</div>
|
||||
|
||||
<div id="styleContent" class="tabcontent">
|
||||
<p data-tip="Select a style preset" style="display: inline-block">Style preset:</p>
|
||||
<p data-tip="Select a style preset. State labels may required regeneration if font is changed" style="display: inline-block">Style preset:</p>
|
||||
<select data-tip="Select a style preset" id="stylePreset" onchange="changeStylePreset(this.value)" style="width:45%">
|
||||
<option value="styleDefault" data-system=1 selected>Default</option>
|
||||
<option value="styleAncient" data-system=1>Ancient</option>
|
||||
<option value="styleGloom" data-system=1>Gloom</option>
|
||||
<option value="styleClean" data-system=1>Clean</option>
|
||||
<option value="styleMonochrome" data-system=1>Monochrome (for heightmap)</option>
|
||||
<option value="styleLight" data-system=1>Light</option>
|
||||
<option value="styleWatercolor" data-system=1>Watercolor</option>
|
||||
<option value="styleMonochrome" data-system=1>Monochrome</option>
|
||||
</select>
|
||||
<button id="addStyleButton" data-tip="Click to save current style as a new preset" class="icon-plus styleButton" style="display: inline-block" onclick="addStylePreset()"></button>
|
||||
<button id="removeStyleButton" data-tip="Click to remove current custom style preset" class="icon-minus styleButton" style="display: none" onclick="removeStylePreset()"></button>
|
||||
|
|
@ -367,16 +369,15 @@
|
|||
<option value="compass">Wind Rose</option>
|
||||
<option value="zones">Zones</option>
|
||||
</select>
|
||||
<!-- <button id="restoreStyle" data-tip="Click to restore default style for all elements" class="icon-ccw styleButton" onclick="askToRestoreDefaultStyle()"></button> -->
|
||||
|
||||
<table id="styleElements">
|
||||
<caption id="styleIsOff" data-tip="The selected layer is not visible. See the buttons above to toggle it on">Please ensure the element is toggled on!</caption>
|
||||
<caption id="styleIsOff" data-tip="The selected layer is not visible. Toogle it on to see style changes effect">Ensure the element visibility is toggled on!</caption>
|
||||
|
||||
<tbody id="styleGroup">
|
||||
<tr data-tip="Select element group">
|
||||
<td><b>Group</b></td>
|
||||
<td>
|
||||
<select id="styleGroupSelect"><option value="regions">regions</option></select>
|
||||
<select id="styleGroupSelect"></select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -391,24 +392,6 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="styleStates" style="display: block">
|
||||
<tr data-tip="Set states halo effect width">
|
||||
<td>Halo width</td>
|
||||
<td>
|
||||
<input id="styleStatesHaloWidth" type="range" min=0 max=30 step=.1 value=10>
|
||||
<output id="styleStatesHaloWidthOutput">10</output>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set states halo effect opacity. 0: invisible, 1: solid">
|
||||
<td>Halo opacity</td>
|
||||
<td>
|
||||
<input id="styleStatesHaloOpacity" type="range" min=0 max=1 step=0.01 value=1>
|
||||
<output id="styleStatesHaloOpacityOutput">1</output>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="styleLegend">
|
||||
<tr data-tip="Set maximum number of items in one column">
|
||||
<td>Column items</td>
|
||||
|
|
@ -600,7 +583,7 @@
|
|||
</tbody>
|
||||
|
||||
<tbody id="styleRelief">
|
||||
<tr data-tip="Select set of relief icons. The change will trigger icons regeneration">
|
||||
<tr data-tip="Select set of relief icons. All relief icons will be regenerated">
|
||||
<td>Style</td>
|
||||
<td>
|
||||
<select id="styleReliefSet">
|
||||
|
|
@ -611,15 +594,15 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Define the size of relief icons">
|
||||
<tr data-tip="Define the size of relief icons. All relief icons will be regenerated">
|
||||
<td>Size</td>
|
||||
<td>
|
||||
<input id="styleReliefSizeInput" data-stored="reliefSize" type="range" min=.2 max=3 step=.01 value=1>
|
||||
<output id="styleReliefSizeOutput">1</output>
|
||||
<input id="styleReliefSizeInput" data-stored="reliefSize" type="range" min=.2 max=4 step=.01>
|
||||
<output id="styleReliefSizeOutput"></output>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Define the density of relief icons. Highly affects performance!">
|
||||
<tr data-tip="Define the density of relief icons. All relief icons will be regenerated. Highly affects performance!">
|
||||
<td>Density</td>
|
||||
<td>
|
||||
<input id="styleReliefDensityInput" data-stored="reliefDensity" type="range" min=.3 max=.8 step=.01 value=.4>
|
||||
|
|
@ -697,8 +680,8 @@
|
|||
<tr data-tip="Set font size">
|
||||
<td>Font size</td>
|
||||
<td>
|
||||
<button id="styleFontPlus" data-tip="Multiply font size by 1.1" class="whiteButton">+</button>
|
||||
<button id="styleFontMinus" data-tip="Multiply font size by 0.9" class="whiteButton" >-</button>
|
||||
<button id="styleFontPlus" data-tip="Increase font" class="whiteButton">+</button>
|
||||
<button id="styleFontMinus" data-tip="Descrease font" class="whiteButton" >-</button>
|
||||
<input id="styleFontSize" type="number" min=.5 max=100 step=.1 value=14>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -761,6 +744,45 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="styleStates" style="display: block">
|
||||
<tr data-tip="Set states fill opacity. 0: invisible, 1: solid">
|
||||
<td>Body opacity</td>
|
||||
<td>
|
||||
<input id="styleStatesBodyOpacity" type="range" min=0 max=1 step=0.01>
|
||||
<output id="styleStatesBodyOpacityOutput"></output>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Select filter for states fill. Please note filters may cause performance issues!">
|
||||
<td>Body filter</td>
|
||||
<td><select id="styleStatesBodyFilter"/></td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set states halo effect width">
|
||||
<td>Halo width</td>
|
||||
<td>
|
||||
<input id="styleStatesHaloWidth" type="range" min=0 max=30 step=.1 value=10>
|
||||
<output id="styleStatesHaloWidthOutput">10</output>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set states halo effect opacity. 0: invisible, 1: solid">
|
||||
<td>Halo opacity</td>
|
||||
<td>
|
||||
<input id="styleStatesHaloOpacity" type="range" min=0 max=1 step=0.01 value=1>
|
||||
<output id="styleStatesHaloOpacityOutput">1</output>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Select halo effect power (blur). Set to 0 to make it solid line">
|
||||
<td>Halo blur</td>
|
||||
<td>
|
||||
<input id="styleStatesHaloBlur" type="range" min=0 max=10 step=0.01 value=4>
|
||||
<output id="styleStatesHaloBlurOutput">4</output>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="styleHeightmap">
|
||||
<tr data-tip="Select color scheme for the element">
|
||||
<td>Color scheme</td>
|
||||
|
|
@ -830,21 +852,21 @@
|
|||
<tr data-tip="Set state emblems size multiplier">
|
||||
<td>State Size</td>
|
||||
<td>
|
||||
<input id="styleEmblemsStateSizeInput" data-stored="styleEmblemsStateSize" type="number" min=0 max=5 step=.02 value=1 />
|
||||
<input id="emblemsStateSizeInput" data-stored="emblemsStateSize" type="number" min=0 max=5 step=.02 value=1 />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set province emblems size multiplier">
|
||||
<td>Province Size</td>
|
||||
<td>
|
||||
<input id="styleEmblemsProvinceSizeInput" data-stored="styleEmblemsProvinceSize" type="number" min=0 max=5 step=.02 value=1 />
|
||||
<input id="emblemsProvinceSizeInput" data-stored="emblemsProvinceSize" type="number" min=0 max=5 step=.02 value=1 />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set burg emblems size multiplier">
|
||||
<td>Burg Size</td>
|
||||
<td>
|
||||
<input id="styleEmblemsBurgSizeInput" data-stored="styleEmblemsBurgSize" type="number" min=0 max=5 step=.02 value=1 />
|
||||
<input id="emblemsBurgSizeInput" data-stored="emblemsBurgSize" type="number" min=0 max=5 step=.02 value=1 />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
@ -859,29 +881,7 @@
|
|||
<tbody id="styleFilter" style="display: block">
|
||||
<tr data-tip="Select filter for element. Please note filters may cause performance issues!">
|
||||
<td>Filter</td>
|
||||
<td>
|
||||
<select id="styleFilterInput">
|
||||
<option value="" selected>None</option>
|
||||
<option value="url(#blurFilter)">Blur 0.2</option>
|
||||
<option value="url(#blur1)">Blur 1</option>
|
||||
<option value="url(#blur3)">Blur 3</option>
|
||||
<option value="url(#blur5)">Blur 5</option>
|
||||
<option value="url(#blur7)">Blur 7</option>
|
||||
<option value="url(#blur10)">Blur 10</option>
|
||||
<option value="url(#splotch)">Splotch</option>
|
||||
<option value="url(#bluredSplotch)">Blurred Splotch</option>
|
||||
<option value="url(#dropShadow01)">Shadow 0.1</option>
|
||||
<option value="url(#dropShadow05)">Shadow 0.5</option>
|
||||
<option value="url(#dropShadow)">Shadow 2</option>
|
||||
<option value="url(#outline)">Outline</option>
|
||||
<option value="url(#pencil)">Pencil</option>
|
||||
<option value="url(#turbulence)">Turbulence</option>
|
||||
<option value="url(#paper)">Paper</option>
|
||||
<option value="url(#crumpled)">Crumpled</option>
|
||||
<option value="url(#filter-grayscale)">Grayscale</option>
|
||||
<option value="url(#filter-sepia)">Sepia</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><select id="styleFilterInput"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
|
|
@ -923,6 +923,13 @@
|
|||
<label for="hideLabels" class="checkbox-label">Toggle visibility automatically</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Allow system to rescale labels on zoom">
|
||||
<td colspan=2>
|
||||
<input id="rescaleLabels" class="checkbox" type="checkbox" onchange="invokeActiveZooming()" checked>
|
||||
<label for="rescaleLabels" class="checkbox-label">Rescale on zoom</label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
@ -1013,17 +1020,18 @@
|
|||
<td>Map template</td>
|
||||
<td>
|
||||
<select id="templateInput" data-stored="template">
|
||||
<option value="Volcano">Volcano</option>
|
||||
<option value="High Island">High Island</option>
|
||||
<option value="Low Island">Low Island</option>
|
||||
<option value="Continents">Two Continents</option>
|
||||
<option value="Archipelago">Archipelago</option>
|
||||
<option value="Atoll">Atoll</option>
|
||||
<option value="Mediterranean">Mediterranean</option>
|
||||
<option value="Peninsula">Peninsula</option>
|
||||
<option value="Pangea">Pangea</option>
|
||||
<option value="Isthmus">Isthmus</option>
|
||||
<option value="Shattered">Shattered</option>
|
||||
<option value="volcano">Volcano</option>
|
||||
<option value="highIsland">High Island</option>
|
||||
<option value="lowIsland">Low Island</option>
|
||||
<option value="continents">Two Continents</option>
|
||||
<option value="archipelago">Archipelago</option>
|
||||
<option value="atoll">Atoll</option>
|
||||
<option value="mediterranean">Mediterranean</option>
|
||||
<option value="peninsula">Peninsula</option>
|
||||
<option value="pangea">Pangea</option>
|
||||
<option value="isthmus">Isthmus</option>
|
||||
<option value="shattered">Shattered</option>
|
||||
<option value="taklamakan">Taklamakan</option>
|
||||
</select>
|
||||
</td>
|
||||
<td></td>
|
||||
|
|
@ -1291,6 +1299,18 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Select shape rendering model">
|
||||
<td></td>
|
||||
<td>Shape rendering</td>
|
||||
<td>
|
||||
<select id="shapeRendering" data-stored="shapeRendering">
|
||||
<option value="geometricPrecision" selected>Best quality</option>
|
||||
<option value="optimizeSpeed">Best performace</option>
|
||||
</select>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<!-- <tr data-tip="Select language (not all languages are fully supported). Reload the page to apply">
|
||||
<td></td>
|
||||
<td>Language</td>
|
||||
|
|
@ -1357,7 +1377,7 @@
|
|||
|
||||
<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 + 1">Burg</button>
|
||||
<button id="addBurgTool" data-tip="Click on map to place a burg. Hold <kbd>Shift</kbd> 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>
|
||||
|
|
@ -1647,19 +1667,19 @@
|
|||
<input id="riverWidth" disabled/>
|
||||
</div>
|
||||
|
||||
<div data-tip="River source width in pixels">
|
||||
<div data-tip="River additional width. Default value is 0">
|
||||
<div class="label">Source width:</div>
|
||||
<input id="riverSourceWidth" type="number" min=0 max=3 step=.1 />
|
||||
</div>
|
||||
|
||||
<div data-tip="River width multiplier">
|
||||
<div data-tip="River width multiplier. Default value is 1">
|
||||
<div class="label">Width modifier:</div>
|
||||
<input id="riverWidthFactor" type="number" min=.1 max=4 step=.1 />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="riverBottom">
|
||||
<button id="riverNew" data-tip="Create new river clicking on map" class="icon-map-pin"></button>
|
||||
<button id="riverCreateSelectingCells" data-tip="Create new river selecting river cells" class="icon-map-pin"></button>
|
||||
<button id="riverEditStyle" data-tip="Edit style for all rivers in Style Editor" class="icon-brush"></button>
|
||||
<button id="riverElevationProfile" data-tip="Show the elevation profile for the river" class="icon-chart-area"></button>
|
||||
<button id="riverLegend" data-tip="Edit free text notes (legend) for the river" class="icon-edit"></button>
|
||||
|
|
@ -1667,6 +1687,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="riverCreator" class="dialog" style="display: none">
|
||||
<div id="riverCreatorBody" class="table"></div>
|
||||
<div id="riverCreatorBottom">
|
||||
<button id="riverCreatorComplete" data-tip="Complete river creation" class="icon-check"></button>
|
||||
<button id="riverCreatorCancel" data-tip="Cancel the creation" class="icon-cancel"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="lakeEditor" class="dialog" style="display: none">
|
||||
<div id="lakeBody" style="padding-bottom: .3em">
|
||||
<div>
|
||||
|
|
@ -1927,7 +1955,7 @@
|
|||
<button id="reliefCopy" data-tip="Copy selected relief icon" class="icon-clone"></button>
|
||||
<button id="reliefMoveFront" data-tip="Move selected relief icon to front" class="icon-level-up"></button>
|
||||
<button id="reliefMoveBack" data-tip="Move selected relief icon back" class="icon-level-down"></button>
|
||||
<button id="reliefRemove" data-tip="Remove selected relief icon. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
<button id="reliefRemove" data-tip="Remove selected relief icon or icon type. Shortcut: Delete" class="icon-trash fastDelete"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2327,18 +2355,19 @@
|
|||
<div id="templateEditor" class="dialog stable" style="display: none">
|
||||
<div id="templateTop">
|
||||
<i>Select template: </i><select id="templateSelect" style="width:16em" data-prev="templateCustom" data-tip="Select base template">
|
||||
<option value="templateCustom" selected>Custom</option>
|
||||
<option value="templateVolcano">Volcano</option>
|
||||
<option value="templateHighIsland">High Island</option>
|
||||
<option value="templateLowIsland">Low Island</option>
|
||||
<option value="templateContinents">Two Continents</option>
|
||||
<option value="templateArchipelago">Archipelago</option>
|
||||
<option value="templateAtoll">Atoll</option>
|
||||
<option value="templateMediterranean">Mediterranean</option>
|
||||
<option value="templatePeninsula">Peninsula</option>
|
||||
<option value="templatePangea">Pangea</option>
|
||||
<option value="templateIsthmus">Isthmus</option>
|
||||
<option value="templateShattered">Shattered</option>
|
||||
<option value="custom" selected>Custom</option>
|
||||
<option value="volcano">Volcano</option>
|
||||
<option value="highIsland">High Island</option>
|
||||
<option value="lowIsland">Low Island</option>
|
||||
<option value="continents">Two Continents</option>
|
||||
<option value="archipelago">Archipelago</option>
|
||||
<option value="atoll">Atoll</option>
|
||||
<option value="mediterranean">Mediterranean</option>
|
||||
<option value="peninsula">Peninsula</option>
|
||||
<option value="pangea">Pangea</option>
|
||||
<option value="isthmus">Isthmus</option>
|
||||
<option value="shattered">Shattered</option>
|
||||
<option value="taklamakan">Taklamakan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="templateTools">
|
||||
|
|
@ -2553,6 +2582,7 @@
|
|||
<option value="Diarchy">Diarchy</option>
|
||||
<option value="Federation">Federation</option>
|
||||
<option value="Free City">Free City</option>
|
||||
<option value="Most Serene Republic">Most Serene Republic</option>
|
||||
<option value="Oligarchy">Oligarchy</option>
|
||||
<option value="Protectorate">Protectorate</option>
|
||||
<option value="Republic">Republic</option>
|
||||
|
|
@ -2575,10 +2605,17 @@
|
|||
<option value="Tribes">United Tribes</option>
|
||||
</optgroup>
|
||||
<optgroup label="Theocracy">
|
||||
<option value="Bishopric">Bishopric</option>
|
||||
<option value="Brotherhood">Brotherhood</option>
|
||||
<option value="Caliphate">Caliphate</option>
|
||||
<option value="Diocese">Diocese</option>
|
||||
<option value="Divine Duchy">Divine Duchy</option>
|
||||
<option value="Divine Grand Duchy">Divine Grand Duchy</option>
|
||||
<option value="Divine Principality">Divine Principality</option>
|
||||
<option value="Divine Kingdom">Divine Kingdom</option>
|
||||
<option value="Divine Empire">Divine Empire</option>
|
||||
<option value="Eparchy">Eparchy</option>
|
||||
<option value="Holy State">Holy State</option>
|
||||
<option value="Imamah">Imamah</option>
|
||||
<option value="Theocracy">Theocracy</option>
|
||||
</optgroup>
|
||||
|
|
@ -3275,7 +3312,8 @@
|
|||
|
||||
<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="addNewRiver" data-tip="Automatically add river starting from clicked cell. Hold Shift to add multiple" class="icon-plus"></button>
|
||||
<button id="riverCreateNew" data-tip="Create new river selecting river cells" class="icon-map-pin"></button>
|
||||
<button id="riversBasinHighlight" data-tip="Toggle basin highlight mode" class="icon-sitemap"></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>
|
||||
|
|
@ -3432,6 +3470,11 @@
|
|||
<input id="options3dSunZ" type="number" min=-1500 max=1500 step=100 style="width:4.7em">
|
||||
</div>
|
||||
|
||||
<div data-tip="Toggle 3d labels" style="margin: .6em 0 .3em -.2em">
|
||||
<input id="options3dMeshLabels3d" class="checkbox" type="checkbox">
|
||||
<label for="options3dMeshLabels3d" class="checkbox-label"><i>Show 3D labels</i></label>
|
||||
</div>
|
||||
|
||||
<div data-tip="Toggle sky mode" style="margin: .6em 0 .3em -.2em">
|
||||
<input id="options3dMeshSkyMode" class="checkbox" type="checkbox">
|
||||
<label for="options3dMeshSkyMode" class="checkbox-label"><i>Show sky and extend water</i></label>
|
||||
|
|
@ -3601,12 +3644,12 @@
|
|||
<path d="m20,58 h70 m-62,3 h50" stroke="#5c5c70" stroke-dasharray="7, 11" stroke-width="1"></path>
|
||||
</symbol>
|
||||
<symbol id="relief-deciduous-1" viewBox="0 0 100 100">
|
||||
<path d="m50,52 v7 h1 v-7 h-0.5 q13,-7 0,-16 q-13,9 0,16" fill="#fff" stroke="#5c5c70"></path>
|
||||
<path d="m50,52 q-12,-7 0,-16 q-3.5,10 0,15.5" fill="#999999"></path>
|
||||
<path d="m49.5,52 v7 h1 v-7 h-0.5 q13,-7 0,-16 q-13,9 0,16" fill="#fff" stroke="#5c5c70"></path>
|
||||
<path d="M 50,51.5 C 44,49 40,43 50,36.5" fill="#999999"></path>
|
||||
</symbol>
|
||||
<symbol id="relief-conifer-1" viewBox="0 0 100 100">
|
||||
<path d="m50,55 v4 h1 v-4 l4.5,0 -4,-8 l3.5,0 -4.5,-9 -4,9 3,0 -3.5,8 7,0" fill="#fff" stroke="#5c5c70"></path>
|
||||
<path d="m46,55 l4,-8 -4,0 5,-9 -2.5,9 l1.5,0 -2,8" fill="#999999"></path>
|
||||
<path d="m49.5,55 v4 h1 v-4 l4.5,0 -4,-8 l3.5,0 -4.5,-9 -4,9 3,0 -3.5,8 7,0" fill="#fff" stroke="#5c5c70"></path>
|
||||
<path d="m 46,54.5 3.5,-8 H 46.6 L 50,39 v 15.5 z" fill="#999999"></path>
|
||||
</symbol>
|
||||
<symbol id="relief-acacia-1" viewBox="0 0 100 100">
|
||||
<path d="m34.5 44.5 c 1.8, -3 8.1, -5.7 12.6, -5.4 6, -2.2 9.3, -0.9 11.9, 1.3 1.7, 0.2 3.2,-0.3 5.2, 2.2 2.7, 1.2 3.7, 2.4 2.7, 3.7 -1.6, 0.3 -2.2, 0 -4.7, -1.6 -5.2, 0.1 -7, 0.7 -8.7, -0.9 -2.8, 1 -3.6, 0 -9.7, 0.2 -4.6, 0 -8, 1.6 -9.3, 0.4 z" fill="#fff"></path>
|
||||
|
|
@ -3623,7 +3666,7 @@
|
|||
<path d="m 49.5,53.1 c 0,-3.4 -2.4,-4.8 -3,-5.4 1,1.8 2.4,3.7 1.8,5.4 z M 51,53.2 C 51.4,49.6 49.6,47.9 48,46.8 c 1.1,1.8 2.8,4.6 1.8,6.5 z M 51.4,51.4 c 0.6,-1.9 1.8,-3.4 3,-4.3 -0.8,0.3 -2.9,1.5 -3.4,2.8 0.2,0.4 0.3,0.8 0.4,1.5 z M 52.9,53.2 c -0.7,-1.9 0.5,-3.3 1.5,-4.4 -1.7,1 -3,2.2 -2.7,4.4 z" fill="#5c5c70" stroke="none"></path>
|
||||
</symbol>
|
||||
<symbol id="relief-swamp-1" viewBox="0 0 100 100">
|
||||
<path d="m50,46 v6 l3,-4 m-3,4 l-3,-4 m-7,4.5 h4 m4,0 h4 m4,0 h4" fill="none" stroke="#5c5c70"></path>
|
||||
<path d="m 50,46 v 6 m 0,0 3,-4 m -3,4 -3,-4 m -6,4.5 h 3 m 4,0 h 4 m 4,0 3,0" fill="none" stroke="#5c5c70" stroke-linecap="round"></path>
|
||||
</symbol>
|
||||
<symbol id="relief-dune-1" viewBox="0 0 100 100">
|
||||
<path d="m 28.7,52.8 c 5,-3.9 10,-8.2 15.8,-8.3 4.5,0 10.8,3.8 15.2,6.5 3.5,2.2 6.8,2 6.8,2" fill="none" stroke="#5c5c70" stroke-width="1.8"></path>
|
||||
|
|
@ -4432,6 +4475,7 @@
|
|||
<script src="libs/delaunator.min.js"></script>
|
||||
<script src="modules/utils.js"></script>
|
||||
<script src="modules/voronoi.js"></script>
|
||||
<script src="modules/heightmap-templates.js"></script>
|
||||
<script src="modules/heightmap-generator.js"></script>
|
||||
<script src="modules/ocean-layers.js"></script>
|
||||
<script src="modules/river-generator.js"></script>
|
||||
|
|
@ -4448,6 +4492,7 @@
|
|||
<script src="libs/lineclip.min.js"></script>
|
||||
<script src="libs/jquery-ui.min.js"></script>
|
||||
<script src="libs/alea.min.js"></script>
|
||||
<script src="modules/fonts.js"></script>
|
||||
<script src="modules/ui/layers.js"></script>
|
||||
<script src="modules/ui/measurers.js"></script>
|
||||
|
||||
|
|
@ -4473,6 +4518,7 @@
|
|||
<script defer src="modules/ui/coastline-editor.js"></script>
|
||||
<script defer src="modules/ui/labels-editor.js"></script>
|
||||
<script defer src="modules/ui/rivers-editor.js"></script>
|
||||
<script defer src="modules/ui/rivers-creator.js"></script>
|
||||
<script defer src="modules/ui/relief-editor.js"></script>
|
||||
<script defer src="modules/ui/religions-editor.js"></script>
|
||||
<script defer src="modules/ui/markers-editor.js"></script>
|
||||
|
|
|
|||
2
libs/delaunator.min.js
vendored
2
libs/delaunator.min.js
vendored
File diff suppressed because one or more lines are too long
212
libs/pell.js
212
libs/pell.js
|
|
@ -1,163 +1,157 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.Pell = factory());
|
||||
}(this, (function () { 'use strict';
|
||||
"use strict";
|
||||
|
||||
const defaultParagraphSeparatorString = 'defaultParagraphSeparator'
|
||||
const formatBlock = 'formatBlock'
|
||||
const addEventListener = (parent, type, listener) => parent.addEventListener(type, listener)
|
||||
const appendChild = (parent, child) => parent.appendChild(child)
|
||||
const createElement = tag => document.createElement(tag)
|
||||
const queryCommandState = command => document.queryCommandState(command)
|
||||
const queryCommandValue = command => document.queryCommandValue(command)
|
||||
const exec = (command, value = null) => document.execCommand(command, false, value)
|
||||
window.Pell = (function () {
|
||||
const defaultParagraphSeparatorString = "defaultParagraphSeparator";
|
||||
const formatBlock = "formatBlock";
|
||||
const addEventListener = (parent, type, listener) => parent.addEventListener(type, listener);
|
||||
const appendChild = (parent, child) => parent.appendChild(child);
|
||||
const createElement = tag => document.createElement(tag);
|
||||
const queryCommandState = command => document.queryCommandState(command);
|
||||
const queryCommandValue = command => document.queryCommandValue(command);
|
||||
const exec = (command, value = null) => document.execCommand(command, false, value);
|
||||
|
||||
const defaultActions = {
|
||||
bold: {
|
||||
icon: '<b>B</b>',
|
||||
title: 'Bold',
|
||||
state: () => queryCommandState('bold'),
|
||||
result: () => exec('bold')
|
||||
icon: "<b>B</b>",
|
||||
title: "Bold",
|
||||
state: () => queryCommandState("bold"),
|
||||
result: () => exec("bold")
|
||||
},
|
||||
italic: {
|
||||
icon: '<i>I</i>',
|
||||
title: 'Italic',
|
||||
state: () => queryCommandState('italic'),
|
||||
result: () => exec('italic')
|
||||
icon: "<i>I</i>",
|
||||
title: "Italic",
|
||||
state: () => queryCommandState("italic"),
|
||||
result: () => exec("italic")
|
||||
},
|
||||
underline: {
|
||||
icon: '<u>U</u>',
|
||||
title: 'Underline',
|
||||
state: () => queryCommandState('underline'),
|
||||
result: () => exec('underline')
|
||||
icon: "<u>U</u>",
|
||||
title: "Underline",
|
||||
state: () => queryCommandState("underline"),
|
||||
result: () => exec("underline")
|
||||
},
|
||||
strikethrough: {
|
||||
icon: '<strike>S</strike>',
|
||||
title: 'Strike-through',
|
||||
state: () => queryCommandState('strikeThrough'),
|
||||
result: () => exec('strikeThrough')
|
||||
icon: "<strike>S</strike>",
|
||||
title: "Strike-through",
|
||||
state: () => queryCommandState("strikeThrough"),
|
||||
result: () => exec("strikeThrough")
|
||||
},
|
||||
heading1: {
|
||||
icon: '<b>H<sub>1</sub></b>',
|
||||
title: 'Heading 1',
|
||||
result: () => exec(formatBlock, '<h1>')
|
||||
icon: "<b>H<sub>1</sub></b>",
|
||||
title: "Heading 1",
|
||||
result: () => exec(formatBlock, "<h1>")
|
||||
},
|
||||
heading2: {
|
||||
icon: '<b>H<sub>2</sub></b>',
|
||||
title: 'Heading 2',
|
||||
result: () => exec(formatBlock, '<h2>')
|
||||
icon: "<b>H<sub>2</sub></b>",
|
||||
title: "Heading 2",
|
||||
result: () => exec(formatBlock, "<h2>")
|
||||
},
|
||||
paragraph: {
|
||||
icon: '¶',
|
||||
title: 'Paragraph',
|
||||
result: () => exec(formatBlock, '<p>')
|
||||
icon: "¶",
|
||||
title: "Paragraph",
|
||||
result: () => exec(formatBlock, "<p>")
|
||||
},
|
||||
quote: {
|
||||
icon: '“ ”',
|
||||
title: 'Quote',
|
||||
result: () => exec(formatBlock, '<blockquote>')
|
||||
icon: "“ ”",
|
||||
title: "Quote",
|
||||
result: () => exec(formatBlock, "<blockquote>")
|
||||
},
|
||||
olist: {
|
||||
icon: '#',
|
||||
title: 'Ordered List',
|
||||
result: () => exec('insertOrderedList')
|
||||
icon: "#",
|
||||
title: "Ordered List",
|
||||
result: () => exec("insertOrderedList")
|
||||
},
|
||||
ulist: {
|
||||
icon: '•',
|
||||
title: 'Unordered List',
|
||||
result: () => exec('insertUnorderedList')
|
||||
icon: "•",
|
||||
title: "Unordered List",
|
||||
result: () => exec("insertUnorderedList")
|
||||
},
|
||||
code: {
|
||||
icon: '</>',
|
||||
title: 'Code',
|
||||
result: () => exec(formatBlock, '<pre>')
|
||||
icon: "</>",
|
||||
title: "Code",
|
||||
result: () => exec(formatBlock, "<pre>")
|
||||
},
|
||||
line: {
|
||||
icon: '―',
|
||||
title: 'Horizontal Line',
|
||||
result: () => exec('insertHorizontalRule')
|
||||
icon: "―",
|
||||
title: "Horizontal Line",
|
||||
result: () => exec("insertHorizontalRule")
|
||||
},
|
||||
link: {
|
||||
icon: '🔗',
|
||||
title: 'Link',
|
||||
result: () => navigator.clipboard.readText().then(url => exec('createLink', url))
|
||||
icon: "🔗",
|
||||
title: "Link",
|
||||
result: () => navigator.clipboard.readText().then(url => exec("createLink", url))
|
||||
},
|
||||
image: {
|
||||
icon: '📷',
|
||||
title: 'Image',
|
||||
icon: "📷",
|
||||
title: "Image",
|
||||
result: () => {
|
||||
navigator.clipboard.readText().then(url => exec('insertImage', url))
|
||||
exec('enableObjectResizing')
|
||||
}
|
||||
navigator.clipboard.readText().then(url => exec("insertImage", url));
|
||||
exec("enableObjectResizing");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const defaultClasses = {
|
||||
actionbar: 'pell-actionbar',
|
||||
button: 'pell-button',
|
||||
content: 'pell-content',
|
||||
selected: 'pell-button-selected'
|
||||
}
|
||||
actionbar: "pell-actionbar",
|
||||
button: "pell-button",
|
||||
content: "pell-content",
|
||||
selected: "pell-button-selected"
|
||||
};
|
||||
|
||||
const init = settings => {
|
||||
const actions = settings.actions
|
||||
? (
|
||||
settings.actions.map(action => {
|
||||
if (typeof action === 'string') return defaultActions[action]
|
||||
else if (defaultActions[action.name]) return { ...defaultActions[action.name], ...action }
|
||||
return action
|
||||
? settings.actions.map(action => {
|
||||
if (typeof action === "string") return defaultActions[action];
|
||||
else if (defaultActions[action.name]) return {...defaultActions[action.name], ...action};
|
||||
return action;
|
||||
})
|
||||
)
|
||||
: Object.keys(defaultActions).map(action => defaultActions[action])
|
||||
: Object.keys(defaultActions).map(action => defaultActions[action]);
|
||||
|
||||
const classes = { ...defaultClasses, ...settings.classes }
|
||||
const classes = {...defaultClasses, ...settings.classes};
|
||||
|
||||
const defaultParagraphSeparator = settings[defaultParagraphSeparatorString] || 'div'
|
||||
const defaultParagraphSeparator = settings[defaultParagraphSeparatorString] || "div";
|
||||
|
||||
const actionbar = createElement('div')
|
||||
actionbar.className = classes.actionbar
|
||||
appendChild(settings.element, actionbar)
|
||||
const actionbar = createElement("div");
|
||||
actionbar.className = classes.actionbar;
|
||||
appendChild(settings.element, actionbar);
|
||||
|
||||
const content = settings.element.content = createElement('div')
|
||||
content.contentEditable = true
|
||||
content.className = classes.content
|
||||
content.oninput = ({ target: { firstChild } }) => {
|
||||
if (firstChild && firstChild.nodeType === 3) exec(formatBlock, `<${defaultParagraphSeparator}>`)
|
||||
else if (content.innerHTML === '<br>') content.innerHTML = ''
|
||||
settings.onChange(content.innerHTML)
|
||||
}
|
||||
const content = (settings.element.content = createElement("div"));
|
||||
content.contentEditable = true;
|
||||
content.className = classes.content;
|
||||
content.oninput = ({target: {firstChild}}) => {
|
||||
if (firstChild && firstChild.nodeType === 3) exec(formatBlock, `<${defaultParagraphSeparator}>`);
|
||||
else if (content.innerHTML === "<br>") content.innerHTML = "";
|
||||
settings.onChange(content.innerHTML);
|
||||
};
|
||||
content.onkeydown = event => {
|
||||
if (event.key === 'Enter' && queryCommandValue(formatBlock) === 'blockquote') {
|
||||
setTimeout(() => exec(formatBlock, `<${defaultParagraphSeparator}>`), 0)
|
||||
if (event.key === "Enter" && queryCommandValue(formatBlock) === "blockquote") {
|
||||
setTimeout(() => exec(formatBlock, `<${defaultParagraphSeparator}>`), 0);
|
||||
}
|
||||
}
|
||||
appendChild(settings.element, content)
|
||||
};
|
||||
appendChild(settings.element, content);
|
||||
|
||||
actions.forEach(action => {
|
||||
const button = createElement('button')
|
||||
button.className = classes.button
|
||||
button.innerHTML = action.icon
|
||||
button.title = action.title
|
||||
button.setAttribute('type', 'button')
|
||||
button.onclick = () => action.result() && content.focus()
|
||||
const button = createElement("button");
|
||||
button.className = classes.button;
|
||||
button.innerHTML = action.icon;
|
||||
button.title = action.title;
|
||||
button.setAttribute("type", "button");
|
||||
button.onclick = () => action.result() && content.focus();
|
||||
|
||||
if (action.state) {
|
||||
const handler = () => button.classList[action.state() ? 'add' : 'remove'](classes.selected)
|
||||
addEventListener(content, 'keyup', handler)
|
||||
addEventListener(content, 'mouseup', handler)
|
||||
addEventListener(button, 'click', handler)
|
||||
const handler = () => button.classList[action.state() ? "add" : "remove"](classes.selected);
|
||||
addEventListener(content, "keyup", handler);
|
||||
addEventListener(content, "mouseup", handler);
|
||||
addEventListener(button, "click", handler);
|
||||
}
|
||||
|
||||
appendChild(actionbar, button)
|
||||
})
|
||||
appendChild(actionbar, button);
|
||||
});
|
||||
|
||||
if (settings.styleWithCSS) exec('styleWithCSS')
|
||||
exec(defaultParagraphSeparatorString, defaultParagraphSeparator)
|
||||
if (settings.styleWithCSS) exec("styleWithCSS");
|
||||
exec(defaultParagraphSeparatorString, defaultParagraphSeparator);
|
||||
|
||||
return settings.element
|
||||
}
|
||||
return settings.element;
|
||||
};
|
||||
|
||||
return {exec, init}
|
||||
|
||||
})));
|
||||
return {exec, init};
|
||||
})();
|
||||
|
|
|
|||
127
main.js
127
main.js
|
|
@ -1,9 +1,9 @@
|
|||
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2021. MIT License
|
||||
// https://github.com/Azgaar/Fantasy-Map-Generator
|
||||
|
||||
'use strict';
|
||||
const version = '1.63'; // generator version
|
||||
document.title += ' v' + version;
|
||||
"use strict";
|
||||
const version = "1.652"; // generator version
|
||||
document.title += " v" + version;
|
||||
|
||||
// Logging constants
|
||||
const PRODUCTION = window.location.host;
|
||||
|
|
@ -123,16 +123,30 @@ let customization = 0; // 0 - no; 1 = heightmap draw; 2 - states draw; 3 - add s
|
|||
|
||||
let biomesData = applyDefaultBiomesSystem();
|
||||
let nameBases = Names.getNameBases(); // cultures-related data
|
||||
const fonts = ['Almendra+SC', 'Georgia', 'Arial', 'Times+New+Roman', 'Comic+Sans+MS', 'Lucida+Sans+Unicode', 'Courier+New']; // default web-safe fonts
|
||||
const fonts = ["Almendra+SC", "Georgia", "Arial", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New", "Verdana", "Arial", "Impact"]; // default fonts
|
||||
|
||||
let color = d3.scaleSequential(d3.interpolateSpectral); // default color scheme
|
||||
const lineGen = d3.line().curve(d3.curveBasis); // d3 line generator with default curve interpolation
|
||||
|
||||
// d3 zoom behavior
|
||||
let scale = 1,
|
||||
viewX = 0,
|
||||
viewY = 0;
|
||||
const zoom = d3.zoom().scaleExtent([1, 20]).on('zoom', zoomed);
|
||||
let scale = 1;
|
||||
let viewX = 0;
|
||||
let viewY = 0;
|
||||
|
||||
const zoomThrottled = throttle(doWorkOnZoom, 100);
|
||||
function zoomed() {
|
||||
const {k, x, y} = d3.event.transform;
|
||||
|
||||
const isScaleChanged = Boolean(scale - k);
|
||||
const isPositionChanged = Boolean(viewX - x || viewY - y);
|
||||
|
||||
scale = k;
|
||||
viewX = x;
|
||||
viewY = y;
|
||||
|
||||
zoomThrottled(isScaleChanged, isPositionChanged);
|
||||
}
|
||||
const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", zoomed);
|
||||
|
||||
// default options
|
||||
let options = {pinNotes: false}; // options object
|
||||
|
|
@ -399,26 +413,17 @@ function applyDefaultBiomesSystem() {
|
|||
}
|
||||
|
||||
function showWelcomeMessage() {
|
||||
const post = link('https://www.patreon.com/posts/48228540', 'Main changes:');
|
||||
const changelog = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog', 'previous version');
|
||||
const reddit = link('https://www.reddit.com/r/FantasyMapGenerator', 'Reddit community');
|
||||
const discord = link('https://discordapp.com/invite/X7E84HU', 'Discord server');
|
||||
const patreon = link('https://www.patreon.com/azgaar', 'Patreon');
|
||||
const changelog = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "previous version");
|
||||
const reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit community");
|
||||
const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server");
|
||||
const patreon = link("https://www.patreon.com/azgaar", "Patreon");
|
||||
|
||||
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
|
||||
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
|
||||
<ul>${post}
|
||||
<li>River overview and River editor rework</li>
|
||||
<li>River generation code refactored and optimized</li>
|
||||
<li>Rivers discharge (flux) and mouth width calculated</li>
|
||||
<li>Lake editor rework</li>
|
||||
<li>Lake type based on evaporation and river system</li>
|
||||
<li>Lake flux, inlets and outlet tracked properly</li>
|
||||
<li>Lake outlet width depends on flux</li>
|
||||
<li>Lakes now have names</li>
|
||||
<li>Rulers rework (v1.61)</li>
|
||||
<li>New ocean pattern by Kiwiroo (v1.61)</li>
|
||||
<li>Water erosion rework (v1.62)</li>
|
||||
<ul>Main changes:
|
||||
<li>Ability to add river selecting its cells</li>
|
||||
<li>Keep river course on edit</li>
|
||||
<li>Refactor river rendering code</li>
|
||||
</ul>
|
||||
|
||||
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||
|
|
@ -438,31 +443,25 @@ function showWelcomeMessage() {
|
|||
});
|
||||
}
|
||||
|
||||
function zoomed() {
|
||||
const transform = d3.event.transform;
|
||||
const scaleDiff = scale - transform.k;
|
||||
const positionDiff = (viewX - transform.x) | (viewY - transform.y);
|
||||
if (!positionDiff && !scaleDiff) return;
|
||||
function doWorkOnZoom(isScaleChanged, isPositionChanged) {
|
||||
viewbox.attr("transform", `translate(${viewX} ${viewY}) scale(${scale})`);
|
||||
|
||||
scale = transform.k;
|
||||
viewX = transform.x;
|
||||
viewY = transform.y;
|
||||
viewbox.attr('transform', transform);
|
||||
if (isPositionChanged) drawCoordinates();
|
||||
|
||||
// update grid only if view position
|
||||
if (positionDiff) drawCoordinates();
|
||||
|
||||
// rescale only if zoom is changed
|
||||
if (scaleDiff) {
|
||||
if (isScaleChanged) {
|
||||
invokeActiveZooming();
|
||||
drawScaleBar();
|
||||
}
|
||||
|
||||
// zoom image converter overlay
|
||||
const canvas = document.getElementById('canvas');
|
||||
if (canvas && +canvas.style.opacity) {
|
||||
const img = document.getElementById('image');
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (customization === 1) {
|
||||
const canvas = document.getElementById("canvas");
|
||||
if (!canvas || canvas.style.opacity === "0") return;
|
||||
|
||||
const img = document.getElementById("imageToConvert");
|
||||
if (!img) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.setTransform(scale, 0, 0, scale, viewX, viewY);
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
|
|
@ -499,15 +498,16 @@ function invokeActiveZooming() {
|
|||
}
|
||||
|
||||
// rescale lables on zoom
|
||||
if (labels.style('display') !== 'none') {
|
||||
labels.selectAll('g').each(function (d) {
|
||||
if (this.id === 'burgLabels') return;
|
||||
if (labels.style("display") !== "none") {
|
||||
labels.selectAll("g").each(function () {
|
||||
if (this.id === "burgLabels") return;
|
||||
const desired = +this.dataset.size;
|
||||
const relative = Math.max(rn((desired + desired / scale) / 2, 2), 1);
|
||||
this.getAttribute('font-size', relative);
|
||||
const hidden = hideLabels.checked && (relative * scale < 6 || relative * scale > 50);
|
||||
if (hidden) this.classList.add('hidden');
|
||||
else this.classList.remove('hidden');
|
||||
if (rescaleLabels.checked) this.setAttribute("font-size", relative);
|
||||
|
||||
const hidden = hideLabels.checked && (relative * scale < 6 || relative * scale > 60);
|
||||
if (hidden) this.classList.add("hidden");
|
||||
else this.classList.remove("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -530,13 +530,14 @@ function invokeActiveZooming() {
|
|||
|
||||
// change states halo width
|
||||
if (!customization) {
|
||||
const haloSize = rn(statesHalo.attr('data-width') / scale, 1);
|
||||
statesHalo.attr('stroke-width', haloSize).style('display', haloSize > 3 ? 'block' : 'none');
|
||||
const desired = +statesHalo.attr("data-width");
|
||||
const haloSize = rn(desired / scale ** 0.8, 2);
|
||||
statesHalo.attr("stroke-width", haloSize).style("display", haloSize > 0.1 ? "block" : "none");
|
||||
}
|
||||
|
||||
// rescale map markers
|
||||
if (+markers.attr('rescale') && markers.style('display') !== 'none') {
|
||||
markers.selectAll('use').each(function (d) {
|
||||
if (+markers.attr("rescale") && markers.style("display") !== "none") {
|
||||
markers.selectAll("use").each(function () {
|
||||
const x = +this.dataset.x,
|
||||
y = +this.dataset.y,
|
||||
desired = +this.dataset.size;
|
||||
|
|
@ -639,6 +640,7 @@ function generate() {
|
|||
drawCoastline();
|
||||
|
||||
Rivers.generate();
|
||||
drawRivers();
|
||||
Lakes.defineGroup();
|
||||
defineBiomes();
|
||||
|
||||
|
|
@ -1316,6 +1318,15 @@ function reMarkFeatures() {
|
|||
cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell);
|
||||
cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells);
|
||||
|
||||
const defineHaven = i => {
|
||||
const water = cells.c[i].filter(c => cells.h[c] < 20);
|
||||
const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2);
|
||||
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))];
|
||||
|
||||
cells.haven[i] = closest;
|
||||
cells.harbor[i] = water.length;
|
||||
};
|
||||
|
||||
for (let i = 1, queue = [0]; queue[0] !== -1; i++) {
|
||||
const start = queue[0]; // first cell
|
||||
cells.f[start] = i; // assign feature number
|
||||
|
|
@ -1331,8 +1342,7 @@ function reMarkFeatures() {
|
|||
if (land && !eLand) {
|
||||
cells.t[q] = 1;
|
||||
cells.t[e] = -1;
|
||||
cells.harbor[q]++;
|
||||
if (!cells.haven[q]) cells.haven[q] = e;
|
||||
if (!cells.haven[q]) defineHaven(q);
|
||||
} else if (land && eLand) {
|
||||
if (!cells.t[e] && cells.t[q] === 1) cells.t[e] = 2;
|
||||
else if (!cells.t[q] && cells.t[e] === 1) cells.t[q] = 2;
|
||||
|
|
@ -2146,8 +2156,13 @@ const regenerateMap = debounce(function () {
|
|||
generate();
|
||||
restoreLayers();
|
||||
if (ThreeD.options.isOn) ThreeD.redraw();
|
||||
<<<<<<< HEAD
|
||||
if ($('#worldConfigurator').is(':visible')) editWorld();
|
||||
}, 500);
|
||||
=======
|
||||
if ($("#worldConfigurator").is(":visible")) editWorld();
|
||||
}, 1000);
|
||||
>>>>>>> 597f9ae038fbcc149315df9b1618e64744fb929d
|
||||
|
||||
// clear the map
|
||||
function undraw() {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? (module.exports = factory()) : typeof define === 'function' && define.amd ? define(factory) : (global.BurgsAndStates = factory());
|
||||
})(this, function () {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
window.BurgsAndStates = (function () {
|
||||
const generate = function () {
|
||||
const cells = pack.cells,
|
||||
cultures = pack.cultures,
|
||||
n = cells.i.length;
|
||||
const {cells, cultures} = pack;
|
||||
const n = cells.i.length;
|
||||
|
||||
cells.burg = new Uint16Array(n); // cell burg
|
||||
cells.road = new Uint16Array(n); // cell road power
|
||||
|
|
@ -80,6 +77,7 @@
|
|||
TIME && console.time('createStates');
|
||||
const states = [{i: 0, name: 'Neutrals'}];
|
||||
const colors = getColors(burgs.length - 1);
|
||||
const each5th = each(5);
|
||||
|
||||
burgs.forEach(function (b, i) {
|
||||
if (!i) return; // skip first element
|
||||
|
|
@ -93,7 +91,7 @@
|
|||
|
||||
// states data
|
||||
const expansionism = rn(Math.random() * powerInput.value + 1, 1);
|
||||
const basename = b.name.length < 9 && b.cell % 5 === 0 ? b.name : Names.getCultureShort(b.culture);
|
||||
const basename = b.name.length < 9 && each5th(b.cell) ? b.name : Names.getCultureShort(b.culture);
|
||||
const name = Names.getState(basename, b.culture);
|
||||
const type = cultures[b.culture].type;
|
||||
|
||||
|
|
@ -110,10 +108,10 @@
|
|||
// place secondary settlements based on geo and economical evaluation
|
||||
function placeTowns() {
|
||||
TIME && console.time('placeTowns');
|
||||
const score = new Int16Array(cells.s.map((s) => s * Math.random())); // a bit randomized cell score for towns placement
|
||||
const sorted = cells.i.filter((i) => score[i] > 0 && cells.culture[i] && !cells.burg[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||
const score = new Int16Array(cells.s.map((s) => s * gauss(1, 3, 0, 20, 3))); // a bit randomized cell score for towns placement
|
||||
const sorted = cells.i.filter((i) => !cells.burg[i] && score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||
|
||||
const desiredNumber = manorsInput.value == 1000 ? rn(sorted.length / 6 / (grid.points.length / 10000) ** 0.8) : +manorsInput.value;
|
||||
const desiredNumber = manorsInput.value == 1000 ? rn(sorted.length / 5 / (grid.points.length / 10000) ** 0.8) : manorsInput.valueAsNumber;
|
||||
const burgsNumber = Math.min(desiredNumber, sorted.length); // towns to generate
|
||||
let burgsAdded = 0;
|
||||
|
||||
|
|
@ -175,9 +173,9 @@
|
|||
|
||||
if (b.port) {
|
||||
b.population = b.population * 1.3; // increase port population
|
||||
const e = cells.v[i].filter((v) => vertices.c[v].some((c) => c === cells.haven[i])); // vertices of common edge
|
||||
b.x = rn((vertices.p[e[0]][0] + vertices.p[e[1]][0]) / 2, 2);
|
||||
b.y = rn((vertices.p[e[0]][1] + vertices.p[e[1]][1]) / 2, 2);
|
||||
const [x, y] = getMiddlePoint(i, haven);
|
||||
b.x = x;
|
||||
b.y = y;
|
||||
}
|
||||
|
||||
// add random factor
|
||||
|
|
@ -477,9 +475,7 @@
|
|||
// calculate and draw curved state labels for a list of states
|
||||
const drawStateLabels = function (list) {
|
||||
TIME && console.time('drawStateLabels');
|
||||
const cells = pack.cells,
|
||||
features = pack.features,
|
||||
states = pack.states;
|
||||
const {cells, features, states} = pack;
|
||||
const paths = []; // text paths
|
||||
lineGen.curve(d3.curveBundle.beta(1));
|
||||
|
||||
|
|
@ -572,8 +568,8 @@
|
|||
}
|
||||
|
||||
void (function drawLabels() {
|
||||
const g = labels.select('#states'),
|
||||
t = defs.select('#textPaths');
|
||||
const g = labels.select('#states');
|
||||
const t = defs.select('#textPaths');
|
||||
const displayed = layerIsOn('toggleLabels');
|
||||
if (!displayed) toggleLabels();
|
||||
|
||||
|
|
@ -602,8 +598,8 @@
|
|||
.attr('id', 'textPath_stateLabel' + id);
|
||||
const pathLength = p[1].length > 1 ? textPath.node().getTotalLength() / letterLength : 0; // path length in letters
|
||||
|
||||
let lines = [],
|
||||
ratio = 100;
|
||||
let lines = [];
|
||||
let ratio = 100;
|
||||
|
||||
if (pathLength < s.name.length) {
|
||||
// only short name will fit
|
||||
|
|
@ -622,10 +618,9 @@
|
|||
// prolongate path if it's too short
|
||||
if (pathLength && pathLength < lines[0].length) {
|
||||
const points = p[1];
|
||||
const f = points[0],
|
||||
l = points[points.length - 1];
|
||||
const dx = l[0] - f[0],
|
||||
dy = l[1] - f[1];
|
||||
const f = points[0];
|
||||
const l = points[points.length - 1];
|
||||
const [dx, dy] = [l[0] - f[0], l[1] - f[1]];
|
||||
const mod = Math.abs((letterLength * lines[0].length) / dx) / 2;
|
||||
points[0] = [rn(f[0] - dx * mod), rn(f[1] - dy * mod)];
|
||||
points[points.length - 1] = [rn(l[0] + dx * mod), rn(l[1] + dy * mod)];
|
||||
|
|
@ -653,8 +648,8 @@
|
|||
if (lines.length < 2) return;
|
||||
|
||||
// check whether multilined label is generally inside the state. If no, replace with short name label
|
||||
const cs = pack.cells.state,
|
||||
b = el.parentNode.getBBox();
|
||||
const cs = pack.cells.state;
|
||||
const b = el.parentNode.getBBox();
|
||||
const c1 = () => +cs[findCell(b.x, b.y)] === id;
|
||||
const c2 = () => +cs[findCell(b.x + b.width / 2, b.y)] === id;
|
||||
const c3 = () => +cs[findCell(b.x + b.width, b.y)] === id;
|
||||
|
|
@ -950,30 +945,31 @@
|
|||
});
|
||||
|
||||
const monarchy = ['Duchy', 'Grand Duchy', 'Principality', 'Kingdom', 'Empire']; // per expansionism tier
|
||||
const republic = {Republic: 75, Federation: 4, Oligarchy: 2, Tetrarchy: 1, Triumvirate: 1, Diarchy: 1, 'Trade Company': 4, Junta: 1}; // weighted random
|
||||
const republic = {Republic: 75, Federation: 4, Oligarchy: 2, 'Most Serene Republic': 2, Tetrarchy: 1, Triumvirate: 1, Diarchy: 1, 'Trade Company': 4, Junta: 1}; // weighted random
|
||||
const union = {Union: 3, League: 4, Confederation: 1, 'United Kingdom': 1, 'United Republic': 1, 'United Provinces': 2, Commonwealth: 1, Heptarchy: 1}; // weighted random
|
||||
const theocracy = {Theocracy: 20, Brotherhood: 1, Thearchy: 2, See: 1};
|
||||
const theocracy = {Theocracy: 20, Brotherhood: 1, Thearchy: 2, See: 1, 'Holy State': 1};
|
||||
const anarchy = {'Free Territory': 2, Council: 3, Commune: 1, Community: 1};
|
||||
|
||||
for (const s of states) {
|
||||
if (list && !list.includes(s.i)) continue;
|
||||
const tier = expTiers[s.i];
|
||||
|
||||
const religion = pack.cells.religion[s.center];
|
||||
const isTheocracy = (religion && pack.religions[religion].expansion === 'state') || (P(0.1) && ['Organized', 'Cult'].includes(pack.religions[religion].type));
|
||||
const isAnarchy = P(0.01 - expTiers[s.i] / 500);
|
||||
const isAnarchy = P(0.01 - tier / 500);
|
||||
|
||||
if (isTheocracy) s.form = 'Theocracy';
|
||||
else if (isAnarchy) s.form = 'Anarchy';
|
||||
else s.form = s.type === 'Naval' ? rw(naval) : rw(generic);
|
||||
s.formName = selectForm(s);
|
||||
s.formName = selectForm(s, tier);
|
||||
s.fullName = getFullName(s);
|
||||
}
|
||||
|
||||
function selectForm(s) {
|
||||
function selectForm(s, tier) {
|
||||
const base = pack.cultures[s.culture].base;
|
||||
|
||||
if (s.form === 'Monarchy') {
|
||||
const form = monarchy[expTiers[s.i]];
|
||||
const form = monarchy[tier];
|
||||
// Default name depends on exponent tier, some culture bases have special names for tiers
|
||||
if (s.diplomacy) {
|
||||
if (form === 'Duchy' && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes('Vassal')) return 'Marches'; // some vassal dutchies on borderland
|
||||
|
|
@ -995,7 +991,7 @@
|
|||
|
||||
if (s.form === 'Republic') {
|
||||
// Default name is from weighted array, special case for small states with only 1 burg
|
||||
if (expTiers[s.i] < 2 && s.burgs === 1) {
|
||||
if (tier < 2 && s.burgs === 1) {
|
||||
if (trimVowels(s.name) === trimVowels(pack.burgs[s.capital].name)) {
|
||||
s.name = pack.burgs[s.capital].name;
|
||||
return 'Free City';
|
||||
|
|
@ -1009,10 +1005,15 @@
|
|||
if (s.form === 'Anarchy') return rw(anarchy);
|
||||
|
||||
if (s.form === 'Theocracy') {
|
||||
if (P(0.5) && [0, 1, 2, 3, 4, 6, 8, 9, 13, 15, 20].includes(base)) return 'Diocese'; // Euporean
|
||||
if (P(0.9) && [7, 5].includes(base)) return 'Eparchy'; // Greek, Ruthenian
|
||||
// European
|
||||
if ([0, 1, 2, 3, 4, 6, 8, 9, 13, 15, 20].includes(base)) {
|
||||
if (P(0.1)) return 'Divine ' + monarchy[tier];
|
||||
if (tier < 2 && P(0.5)) return 'Diocese';
|
||||
if (tier < 2 && P(0.5)) return 'Bishopric';
|
||||
}
|
||||
if (tier < 2 && P(0.9) && [7, 5].includes(base)) return 'Eparchy'; // Greek, Ruthenian
|
||||
if (P(0.9) && [21, 16].includes(base)) return 'Imamah'; // Nigerian, Turkish
|
||||
if (P(0.8) && [18, 17, 28].includes(base)) return 'Caliphate'; // Arabic, Berber, Swahili
|
||||
if (tier > 2 && P(0.8) && [18, 17, 28].includes(base)) return 'Caliphate'; // Arabic, Berber, Swahili
|
||||
return rw(theocracy);
|
||||
}
|
||||
}
|
||||
|
|
@ -1255,4 +1256,4 @@
|
|||
generateProvinces,
|
||||
updateCultures
|
||||
};
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,170 +1,364 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.COA = factory());
|
||||
}(this, (function () {'use strict';
|
||||
"use strict";
|
||||
|
||||
window.COA = (function () {
|
||||
const tinctures = {
|
||||
field: { metals: 3, colours: 4, stains: +P(.03), patterns: 1 },
|
||||
division: { metals: 5, colours: 8, stains: +P(.03), patterns: 1 },
|
||||
charge: { metals: 2, colours: 3, stains: +P(.05), patterns: 0 },
|
||||
metals: { argent: 3, or: 2 },
|
||||
colours: { gules: 5, azure: 4, sable: 3, purpure: 3, vert: 2 },
|
||||
stains: { murrey: 1, sanguine: 1, tenné: 1 },
|
||||
field: {metals: 3, colours: 4, stains: +P(0.03), patterns: 1},
|
||||
division: {metals: 5, colours: 8, stains: +P(0.03), patterns: 1},
|
||||
charge: {metals: 2, colours: 3, stains: +P(0.05), patterns: 0},
|
||||
metals: {argent: 3, or: 2},
|
||||
colours: {gules: 5, azure: 4, sable: 3, purpure: 3, vert: 2},
|
||||
stains: {murrey: 1, sanguine: 1, tenné: 1},
|
||||
patterns: {
|
||||
semy: 8, ermine: 6,
|
||||
vair: 4, counterVair: 1, vairInPale: 1, vairEnPointe: 2, vairAncien: 2,
|
||||
potent: 2, counterPotent: 1, potentInPale: 1, potentEnPointe: 1,
|
||||
chequy: 8, lozengy: 5, fusily: 2, pally: 8, barry: 10, gemelles: 1,
|
||||
bendy: 8, bendySinister: 4, palyBendy: 2, barryBendy: 1,
|
||||
pappellony: 2, pappellony2: 3, scaly: 1, plumetty: 1,
|
||||
masoned: 6, fretty: 3, grillage: 1, chainy: 1, maily: 2, honeycombed: 1 }
|
||||
semy: 8,
|
||||
ermine: 6,
|
||||
vair: 4,
|
||||
counterVair: 1,
|
||||
vairInPale: 1,
|
||||
vairEnPointe: 2,
|
||||
vairAncien: 2,
|
||||
potent: 2,
|
||||
counterPotent: 1,
|
||||
potentInPale: 1,
|
||||
potentEnPointe: 1,
|
||||
chequy: 8,
|
||||
lozengy: 5,
|
||||
fusily: 2,
|
||||
pally: 8,
|
||||
barry: 10,
|
||||
gemelles: 1,
|
||||
bendy: 8,
|
||||
bendySinister: 4,
|
||||
palyBendy: 2,
|
||||
barryBendy: 1,
|
||||
pappellony: 2,
|
||||
pappellony2: 3,
|
||||
scaly: 1,
|
||||
plumetty: 1,
|
||||
masoned: 6,
|
||||
fretty: 3,
|
||||
grillage: 1,
|
||||
chainy: 1,
|
||||
maily: 2,
|
||||
honeycombed: 1
|
||||
}
|
||||
};
|
||||
|
||||
const charges = {
|
||||
// categories selection
|
||||
types: { conventional: 30, crosses: 10, animals: 2, animalHeads: 1, birds: 2, fantastic: 3, plants: 1, agriculture: 1, arms: 3, bodyparts: 1, people: 1, architecture: 1, miscellaneous: 3, inescutcheon: 3 },
|
||||
single: { conventional: 12, crosses: 8, plants: 2, animals: 10, animalHeads: 2, birds: 4, fantastic: 7, agriculture: 1, arms: 6, bodyparts: 1, people: 2, architecture: 1, miscellaneous: 10, inescutcheon: 5 },
|
||||
semy: { conventional: 12, crosses: 3, plants: 1 },
|
||||
types: {conventional: 30, crosses: 10, animals: 2, animalHeads: 1, birds: 2, fantastic: 3, plants: 1, agriculture: 1, arms: 3, bodyparts: 1, people: 1, architecture: 1, miscellaneous: 3, inescutcheon: 3},
|
||||
single: {conventional: 12, crosses: 8, plants: 2, animals: 10, animalHeads: 2, birds: 4, fantastic: 7, agriculture: 1, arms: 6, bodyparts: 1, people: 2, architecture: 1, miscellaneous: 10, inescutcheon: 5},
|
||||
semy: {conventional: 12, crosses: 3, plants: 1},
|
||||
// generic categories
|
||||
conventional: {
|
||||
lozenge: 2, fusil: 4, mascle: 4, rustre: 2, lozengeFaceted: 3, lozengePloye: 1, roundel: 4, roundel2: 3, annulet: 4,
|
||||
mullet: 5, mulletPierced: 1, mulletFaceted: 1, mullet4: 3, mullet6: 4, mullet6Pierced: 1, mullet6Faceted: 1, mullet7: 1, mullet8: 1, mullet10: 1,
|
||||
estoile: 1, compassRose: 1, billet: 5, delf: 0, triangle: 3, trianglePierced: 1, goutte: 4, heart: 4, pique: 2, carreau: 1, trefle: 2,
|
||||
fleurDeLis: 6, sun: 3, sunInSplendour: 1, crescent: 5, fountain: 1
|
||||
lozenge: 2,
|
||||
fusil: 4,
|
||||
mascle: 4,
|
||||
rustre: 2,
|
||||
lozengeFaceted: 3,
|
||||
lozengePloye: 1,
|
||||
roundel: 4,
|
||||
roundel2: 3,
|
||||
annulet: 4,
|
||||
mullet: 5,
|
||||
mulletPierced: 1,
|
||||
mulletFaceted: 1,
|
||||
mullet4: 3,
|
||||
mullet6: 4,
|
||||
mullet6Pierced: 1,
|
||||
mullet6Faceted: 1,
|
||||
mullet7: 1,
|
||||
mullet8: 1,
|
||||
mullet10: 1,
|
||||
estoile: 1,
|
||||
compassRose: 1,
|
||||
billet: 5,
|
||||
delf: 0,
|
||||
triangle: 3,
|
||||
trianglePierced: 1,
|
||||
goutte: 4,
|
||||
heart: 4,
|
||||
pique: 2,
|
||||
carreau: 1,
|
||||
trefle: 2,
|
||||
fleurDeLis: 6,
|
||||
sun: 3,
|
||||
sunInSplendour: 1,
|
||||
crescent: 5,
|
||||
fountain: 1
|
||||
},
|
||||
crosses: {
|
||||
crossHummetty: 15, crossVoided: 1, crossPattee: 2, crossPatteeAlisee: 1, crossFormee: 1, crossFormee2: 2, crossPotent: 2, crossJerusalem:1,
|
||||
crosslet: 1, crossClechy: 3, crossBottony: 1, crossFleury: 3, crossPatonce: 1, crossPommy: 1, crossGamma: 1, crossArrowed: 1, crossFitchy: 1,
|
||||
crossCercelee: 1, crossMoline: 2, crossFourchy: 1, crossAvellane: 1, crossErminee: 1, crossBiparted: 1, crossMaltese: 3, crossTemplar: 2,
|
||||
crossCeltic: 1, crossCeltic2: 1, crossTriquetra: 1, crossCarolingian: 1, crossOccitan: 1, crossSaltire: 3, crossBurgundy: 1,
|
||||
crossLatin: 3, crossPatriarchal: 1, crossOrthodox: 1, crossCalvary: 1, crossDouble: 1, crossTau: 1, crossSantiago: 1, crossAnkh: 1
|
||||
crossHummetty: 15,
|
||||
crossVoided: 1,
|
||||
crossPattee: 2,
|
||||
crossPatteeAlisee: 1,
|
||||
crossFormee: 1,
|
||||
crossFormee2: 2,
|
||||
crossPotent: 2,
|
||||
crossJerusalem: 1,
|
||||
crosslet: 1,
|
||||
crossClechy: 3,
|
||||
crossBottony: 1,
|
||||
crossFleury: 3,
|
||||
crossPatonce: 1,
|
||||
crossPommy: 1,
|
||||
crossGamma: 1,
|
||||
crossArrowed: 1,
|
||||
crossFitchy: 1,
|
||||
crossCercelee: 1,
|
||||
crossMoline: 2,
|
||||
crossFourchy: 1,
|
||||
crossAvellane: 1,
|
||||
crossErminee: 1,
|
||||
crossBiparted: 1,
|
||||
crossMaltese: 3,
|
||||
crossTemplar: 2,
|
||||
crossCeltic: 1,
|
||||
crossCeltic2: 1,
|
||||
crossTriquetra: 1,
|
||||
crossCarolingian: 1,
|
||||
crossOccitan: 1,
|
||||
crossSaltire: 3,
|
||||
crossBurgundy: 1,
|
||||
crossLatin: 3,
|
||||
crossPatriarchal: 1,
|
||||
crossOrthodox: 1,
|
||||
crossCalvary: 1,
|
||||
crossDouble: 1,
|
||||
crossTau: 1,
|
||||
crossSantiago: 1,
|
||||
crossAnkh: 1
|
||||
},
|
||||
animals: {
|
||||
lionRampant: 5, lionPassant: 2, lionPassantGuardant: 1, wolfRampant: 1, wolfPassant: 1, wolfStatant: 1, greyhoundCourant: 1, boarRampant: 1,
|
||||
horseRampant: 2, horseSalient: 1, bearRampant: 2, bearPassant: 1, bullPassant: 1, goat: 1, lamb: 1, elephant: 1, camel: 1
|
||||
lionRampant: 5,
|
||||
lionPassant: 2,
|
||||
lionPassantGuardant: 1,
|
||||
wolfRampant: 1,
|
||||
wolfPassant: 1,
|
||||
wolfStatant: 1,
|
||||
greyhoundCourant: 1,
|
||||
boarRampant: 1,
|
||||
horseRampant: 2,
|
||||
horseSalient: 1,
|
||||
bearRampant: 2,
|
||||
bearPassant: 1,
|
||||
bullPassant: 1,
|
||||
goat: 1,
|
||||
lamb: 1,
|
||||
elephant: 1,
|
||||
camel: 1
|
||||
},
|
||||
animalHeads: { wolfHeadErased: 1, bullHeadCaboshed: 1, deerHeadCaboshed: 1, lionHeadCaboshed: 2 },
|
||||
fantastic: { dragonPassant: 2, dragonRampant: 2, wyvern: 1, wyvernWithWingsDisplayed: 1, griffinPassant: 1, griffinRampant: 1, eagleTwoHeards: 2, unicornRampant: 1, pegasus: 1, serpent: 1 },
|
||||
birds: { eagle: 9, raven: 1, cock: 3, parrot: 1, swan: 2, swanErased: 1, heron: 1, owl: 1 },
|
||||
plants: { tree: 1, oak: 1, cinquefoil: 1, rose: 1 },
|
||||
agriculture: { garb: 1, rake: 1 },
|
||||
arms: { sword: 5, sabre: 1, sabresCrossed: 1, hatchet: 2, axe: 2, lochaberAxe: 1, mallet: 1, bowWithArrow: 2, bow: 1, arrow: 1, arrowsSheaf: 1, helmet: 2 },
|
||||
bodyparts: { hand: 4, head: 1, headWreathed: 1 },
|
||||
people: { cavalier: 3, monk: 1, angel: 2 },
|
||||
architecture: { tower: 1, castle: 1 },
|
||||
animalHeads: {wolfHeadErased: 1, bullHeadCaboshed: 1, deerHeadCaboshed: 1, lionHeadCaboshed: 2},
|
||||
fantastic: {dragonPassant: 2, dragonRampant: 2, wyvern: 1, wyvernWithWingsDisplayed: 1, griffinPassant: 1, griffinRampant: 1, eagleTwoHeards: 2, unicornRampant: 1, pegasus: 1, serpent: 1},
|
||||
birds: {eagle: 9, raven: 1, cock: 3, parrot: 1, swan: 2, swanErased: 1, heron: 1, owl: 1},
|
||||
plants: {tree: 1, oak: 1, cinquefoil: 1, rose: 1},
|
||||
agriculture: {garb: 1, rake: 1},
|
||||
arms: {sword: 5, sabre: 1, sabresCrossed: 1, hatchet: 2, axe: 2, lochaberAxe: 1, mallet: 1, bowWithArrow: 2, bow: 1, arrow: 1, arrowsSheaf: 1, helmet: 2},
|
||||
bodyparts: {hand: 4, head: 1, headWreathed: 1},
|
||||
people: {cavalier: 3, monk: 1, angel: 2},
|
||||
architecture: {tower: 1, castle: 1},
|
||||
miscellaneous: {
|
||||
crown: 3, orb: 1, chalice: 1, key: 1, buckle: 1, bugleHorn: 1, bugleHorn2: 1, bell: 2, pot: 1, bucket: 1, horseshoe: 3,
|
||||
attire: 1, stagsAttires: 1, ramsHorn: 1, cowHorns: 2, wing: 1, wingSword: 1, lute: 1, harp: 1, wheel: 2, crosier: 1, fasces: 1, log: 1
|
||||
crown: 3,
|
||||
orb: 1,
|
||||
chalice: 1,
|
||||
key: 1,
|
||||
buckle: 1,
|
||||
bugleHorn: 1,
|
||||
bugleHorn2: 1,
|
||||
bell: 2,
|
||||
pot: 1,
|
||||
bucket: 1,
|
||||
horseshoe: 3,
|
||||
attire: 1,
|
||||
stagsAttires: 1,
|
||||
ramsHorn: 1,
|
||||
cowHorns: 2,
|
||||
wing: 1,
|
||||
wingSword: 1,
|
||||
lute: 1,
|
||||
harp: 1,
|
||||
wheel: 2,
|
||||
crosier: 1,
|
||||
fasces: 1,
|
||||
log: 1
|
||||
},
|
||||
// selection based on culture type:
|
||||
Naval: { anchor: 3, boat: 1, lymphad: 2, armillarySphere: 1, escallop: 1, dolphin: 1 },
|
||||
Highland: { tower: 1, raven: 1, wolfHeadErased: 1, wolfPassant: 1, goat: 1, axe: 1 },
|
||||
River: { tower: 1, garb: 1, rake: 1, boat: 1, pike: 2, bullHeadCaboshed: 1 },
|
||||
Lake: { cancer: 2, escallop: 1, pike: 2, heron: 1, boat: 1, boat2: 2 },
|
||||
Nomadic: { pot: 1, buckle: 1, wheel: 2, sabre: 2, sabresCrossed: 1, bow: 2, arrow: 1, horseRampant: 1, horseSalient: 1, crescent: 1, camel: 3 },
|
||||
Hunting: { bugleHorn: 2, bugleHorn2: 1, stagsAttires: 2, attire: 2, hatchet: 1, bowWithArrow: 1, arrowsSheaf: 1, deerHeadCaboshed: 1, wolfStatant: 1, oak: 1 },
|
||||
Naval: {anchor: 3, boat: 1, lymphad: 2, armillarySphere: 1, escallop: 1, dolphin: 1},
|
||||
Highland: {tower: 1, raven: 1, wolfHeadErased: 1, wolfPassant: 1, goat: 1, axe: 1},
|
||||
River: {tower: 1, garb: 1, rake: 1, boat: 1, pike: 2, bullHeadCaboshed: 1},
|
||||
Lake: {cancer: 2, escallop: 1, pike: 2, heron: 1, boat: 1, boat2: 2},
|
||||
Nomadic: {pot: 1, buckle: 1, wheel: 2, sabre: 2, sabresCrossed: 1, bow: 2, arrow: 1, horseRampant: 1, horseSalient: 1, crescent: 1, camel: 3},
|
||||
Hunting: {bugleHorn: 2, bugleHorn2: 1, stagsAttires: 2, attire: 2, hatchet: 1, bowWithArrow: 1, arrowsSheaf: 1, deerHeadCaboshed: 1, wolfStatant: 1, oak: 1},
|
||||
// selection based on type
|
||||
City: { key: 3, bell: 2, lute: 1, tower: 1, castle: 1, mallet: 1 },
|
||||
Capital: { crown: 4, orb: 1, lute: 1, castle: 3, tower: 1 },
|
||||
Сathedra: { chalice: 1, orb: 1, crosier: 2, lamb: 1, monk: 2, angel: 3, crossLatin: 2, crossPatriarchal: 1, crossOrthodox: 1, crossCalvary: 1 },
|
||||
City: {key: 3, bell: 2, lute: 1, tower: 1, castle: 1, mallet: 1},
|
||||
Capital: {crown: 4, orb: 1, lute: 1, castle: 3, tower: 1},
|
||||
Сathedra: {chalice: 1, orb: 1, crosier: 2, lamb: 1, monk: 2, angel: 3, crossLatin: 2, crossPatriarchal: 1, crossOrthodox: 1, crossCalvary: 1},
|
||||
// specific cases
|
||||
natural: { fountain: "azure", garb: "or", raven: "sable" }, // charges to mainly use predefined colours
|
||||
sinister: [ // charges that can be sinister
|
||||
"crossGamma", "lionRampant", "lionPassant", "wolfRampant", "wolfPassant", "wolfStatant", "wolfHeadErased", "greyhoundСourant", "boarRampant",
|
||||
"horseRampant", "horseSalient", "bullPassant", "bearRampant", "bearPassant", "goat", "lamb", "elephant", "eagle", "raven", "cock", "parrot",
|
||||
"swan", "swanErased", "heron", "pike", "dragonPassant", "dragonRampant", "wyvern", "wyvernWithWingsDisplayed", "griffinPassant", "griffinRampant",
|
||||
"unicornRampant", "pegasus", "serpent", "hatchet", "lochaberAxe", "hand", "wing", "wingSword", "lute", "harp", "bow", "head", "headWreathed",
|
||||
"knight", "lymphad", "log", "crosier", "dolphin", "sabre", "monk", "owl", "axe", "camel", "fasces", "lionPassantGuardant", "helmet"],
|
||||
reversed: [ // charges that can be reversed
|
||||
"goutte", "mullet", "mullet7", "crescent", "crossTau", "cancer", "sword", "sabresCrossed", "hand",
|
||||
"horseshoe", "bowWithArrow", "arrow", "arrowsSheaf", "rake", "crossTriquetra", "crossLatin", "crossTau"
|
||||
natural: {fountain: "azure", garb: "or", raven: "sable"}, // charges to mainly use predefined colours
|
||||
sinister: [
|
||||
// charges that can be sinister
|
||||
"crossGamma",
|
||||
"lionRampant",
|
||||
"lionPassant",
|
||||
"wolfRampant",
|
||||
"wolfPassant",
|
||||
"wolfStatant",
|
||||
"wolfHeadErased",
|
||||
"greyhoundСourant",
|
||||
"boarRampant",
|
||||
"horseRampant",
|
||||
"horseSalient",
|
||||
"bullPassant",
|
||||
"bearRampant",
|
||||
"bearPassant",
|
||||
"goat",
|
||||
"lamb",
|
||||
"elephant",
|
||||
"eagle",
|
||||
"raven",
|
||||
"cock",
|
||||
"parrot",
|
||||
"swan",
|
||||
"swanErased",
|
||||
"heron",
|
||||
"pike",
|
||||
"dragonPassant",
|
||||
"dragonRampant",
|
||||
"wyvern",
|
||||
"wyvernWithWingsDisplayed",
|
||||
"griffinPassant",
|
||||
"griffinRampant",
|
||||
"unicornRampant",
|
||||
"pegasus",
|
||||
"serpent",
|
||||
"hatchet",
|
||||
"lochaberAxe",
|
||||
"hand",
|
||||
"wing",
|
||||
"wingSword",
|
||||
"lute",
|
||||
"harp",
|
||||
"bow",
|
||||
"head",
|
||||
"headWreathed",
|
||||
"knight",
|
||||
"lymphad",
|
||||
"log",
|
||||
"crosier",
|
||||
"dolphin",
|
||||
"sabre",
|
||||
"monk",
|
||||
"owl",
|
||||
"axe",
|
||||
"camel",
|
||||
"fasces",
|
||||
"lionPassantGuardant",
|
||||
"helmet"
|
||||
],
|
||||
reversed: [
|
||||
// charges that can be reversed
|
||||
"goutte",
|
||||
"mullet",
|
||||
"mullet7",
|
||||
"crescent",
|
||||
"crossTau",
|
||||
"cancer",
|
||||
"sword",
|
||||
"sabresCrossed",
|
||||
"hand",
|
||||
"horseshoe",
|
||||
"bowWithArrow",
|
||||
"arrow",
|
||||
"arrowsSheaf",
|
||||
"rake",
|
||||
"crossTriquetra",
|
||||
"crossLatin",
|
||||
"crossTau"
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const positions = {
|
||||
conventional: { e: 20, abcdefgzi: 3, beh: 3, behdf: 2, acegi: 1, kn: 3, bhdf: 1, jeo: 1, abc: 3, jln: 6, jlh: 3, kmo: 2, jleh: 1, def: 3, abcpqh: 4, ABCDEFGHIJKL: 1 },
|
||||
complex: { e: 40, beh: 1, kn: 1, jeo: 1, abc: 2, jln: 7, jlh: 2, def: 1, abcpqh: 1 },
|
||||
conventional: {e: 20, abcdefgzi: 3, beh: 3, behdf: 2, acegi: 1, kn: 3, bhdf: 1, jeo: 1, abc: 3, jln: 6, jlh: 3, kmo: 2, jleh: 1, def: 3, abcpqh: 4, ABCDEFGHIJKL: 1},
|
||||
complex: {e: 40, beh: 1, kn: 1, jeo: 1, abc: 2, jln: 7, jlh: 2, def: 1, abcpqh: 1},
|
||||
divisions: {
|
||||
perPale: { e: 15, pq: 5, jo: 2, jl: 2, ABCDEFGHIJKL: 1 },
|
||||
perFess: { e: 12, kn: 4, jkl: 2, gizgiz: 1, jlh: 3, kmo: 1, ABCDEFGHIJKL: 1 },
|
||||
perBend: { e: 5, lm: 5, bcfdgh: 1 },
|
||||
perBendSinister: { e: 1, jo: 1 },
|
||||
perCross: { e: 4, jlmo: 1, j: 1, jo: 2, jl: 1 },
|
||||
perChevron: { e: 1, jlh: 1, dfk: 1, dfbh: 2, bdefh: 1 },
|
||||
perChevronReversed: { e: 1, mok: 2, dfh: 2, dfbh: 1, bdefh: 1 },
|
||||
perSaltire: { bhdf: 8, e: 3, abcdefgzi: 1, bh: 1, df: 1, ABCDEFGHIJKL: 1 },
|
||||
perPile: { ee: 3, be: 2, abceh: 1, abcabc: 1, jleh: 1 }
|
||||
perPale: {e: 15, pq: 5, jo: 2, jl: 2, ABCDEFGHIJKL: 1},
|
||||
perFess: {e: 12, kn: 4, jkl: 2, gizgiz: 1, jlh: 3, kmo: 1, ABCDEFGHIJKL: 1},
|
||||
perBend: {e: 5, lm: 5, bcfdgh: 1},
|
||||
perBendSinister: {e: 1, jo: 1},
|
||||
perCross: {e: 4, jlmo: 1, j: 1, jo: 2, jl: 1},
|
||||
perChevron: {e: 1, jlh: 1, dfk: 1, dfbh: 2, bdefh: 1},
|
||||
perChevronReversed: {e: 1, mok: 2, dfh: 2, dfbh: 1, bdefh: 1},
|
||||
perSaltire: {bhdf: 8, e: 3, abcdefgzi: 1, bh: 1, df: 1, ABCDEFGHIJKL: 1},
|
||||
perPile: {ee: 3, be: 2, abceh: 1, abcabc: 1, jleh: 1}
|
||||
},
|
||||
ordinariesOn: {
|
||||
pale: { ee: 12, beh: 10, kn: 3, bb: 1 },
|
||||
fess: { ee: 1, def: 3 },
|
||||
bar: { defdefdef: 1 },
|
||||
fessCotissed: { ee: 1, def: 3 },
|
||||
fessDoubleCotissed: { ee: 1, defdef: 3 },
|
||||
bend: { ee: 2, jo: 1, joe: 1 },
|
||||
bendSinister: { ee: 1, lm: 1, lem: 4 },
|
||||
bendlet: { joejoejoe: 1 },
|
||||
bendletSinister: { lemlemlem: 1 },
|
||||
bordure: { ABCDEFGHIJKL: 1 },
|
||||
chief: { abc: 5, bbb: 1 },
|
||||
quarter: { jjj: 1 },
|
||||
canton: { yyyy: 1 },
|
||||
cross: { eeee: 1, behdfbehdf: 3, behbehbeh: 2 },
|
||||
crossParted: { e: 5, ee: 1 },
|
||||
saltire: { ee: 5, jlemo: 1 },
|
||||
saltireParted: { e: 5, ee: 1 },
|
||||
pall: { ee: 1, jleh: 5, jlhh: 3 },
|
||||
pallReversed: { ee: 1, bemo: 5 },
|
||||
pile: { bbb: 1 },
|
||||
pileInBend: { eeee: 1, eeoo: 1 },
|
||||
pileInBendSinister: { eeee: 1, eemm: 1 }
|
||||
pale: {ee: 12, beh: 10, kn: 3, bb: 1},
|
||||
fess: {ee: 1, def: 3},
|
||||
bar: {defdefdef: 1},
|
||||
fessCotissed: {ee: 1, def: 3},
|
||||
fessDoubleCotissed: {ee: 1, defdef: 3},
|
||||
bend: {ee: 2, jo: 1, joe: 1},
|
||||
bendSinister: {ee: 1, lm: 1, lem: 4},
|
||||
bendlet: {joejoejoe: 1},
|
||||
bendletSinister: {lemlemlem: 1},
|
||||
bordure: {ABCDEFGHIJKL: 1},
|
||||
chief: {abc: 5, bbb: 1},
|
||||
quarter: {jjj: 1},
|
||||
canton: {yyyy: 1},
|
||||
cross: {eeee: 1, behdfbehdf: 3, behbehbeh: 2},
|
||||
crossParted: {e: 5, ee: 1},
|
||||
saltire: {ee: 5, jlemo: 1},
|
||||
saltireParted: {e: 5, ee: 1},
|
||||
pall: {ee: 1, jleh: 5, jlhh: 3},
|
||||
pallReversed: {ee: 1, bemo: 5},
|
||||
pile: {bbb: 1},
|
||||
pileInBend: {eeee: 1, eeoo: 1},
|
||||
pileInBendSinister: {eeee: 1, eemm: 1}
|
||||
},
|
||||
ordinariesOff: {
|
||||
pale: { yyy: 1 },
|
||||
fess: { abc: 3, abcz: 1 },
|
||||
bar: { abc: 2, abcgzi: 1, jlh: 5, bgi: 2, ach: 1 },
|
||||
gemelle: { abc: 1 },
|
||||
bend: { ccg: 2, ccc: 1 },
|
||||
bendSinister: { aai: 2, aaa: 1 },
|
||||
bendlet: { ccg: 2, ccc: 1 },
|
||||
bendletSinister: { aai: 2, aaa: 1 },
|
||||
bordure: { e: 4, jleh:2, kenken: 1, peqpeq: 1 },
|
||||
orle: { e: 4, jleh: 1, kenken: 1, peqpeq: 1 },
|
||||
chief: { emo: 2, emoz: 1, ez: 2 },
|
||||
terrace: { e: 5, def: 1, bdf: 3 },
|
||||
mount: { e: 5, def: 1, bdf: 3 },
|
||||
point: { e: 2, def: 1, bdf: 3, acbdef: 1 },
|
||||
flaunches: { e: 3, kn: 1, beh: 3 },
|
||||
gyron: { bh: 1 },
|
||||
quarter: { e: 1 },
|
||||
canton: { e: 5, beh: 1, def: 1, bdefh: 1, kn: 1 },
|
||||
cross: { acgi: 1 },
|
||||
pall: { BCKFEILGJbdmfo: 1 },
|
||||
pallReversed: { aczac: 1 },
|
||||
chevron: { ach: 3, hhh: 1 },
|
||||
chevronReversed: { bbb: 1 },
|
||||
pile: { acdfgi: 1, acac: 1 },
|
||||
pileInBend: { cg: 1 },
|
||||
pileInBendSinister: { ai: 1 },
|
||||
label: { defgzi: 2, eh: 3, defdefhmo: 1, egiegi: 1, pqn: 5 }
|
||||
pale: {yyy: 1},
|
||||
fess: {abc: 3, abcz: 1},
|
||||
bar: {abc: 2, abcgzi: 1, jlh: 5, bgi: 2, ach: 1},
|
||||
gemelle: {abc: 1},
|
||||
bend: {ccg: 2, ccc: 1},
|
||||
bendSinister: {aai: 2, aaa: 1},
|
||||
bendlet: {ccg: 2, ccc: 1},
|
||||
bendletSinister: {aai: 2, aaa: 1},
|
||||
bordure: {e: 4, jleh: 2, kenken: 1, peqpeq: 1},
|
||||
orle: {e: 4, jleh: 1, kenken: 1, peqpeq: 1},
|
||||
chief: {emo: 2, emoz: 1, ez: 2},
|
||||
terrace: {e: 5, def: 1, bdf: 3},
|
||||
mount: {e: 5, def: 1, bdf: 3},
|
||||
point: {e: 2, def: 1, bdf: 3, acbdef: 1},
|
||||
flaunches: {e: 3, kn: 1, beh: 3},
|
||||
gyron: {bh: 1},
|
||||
quarter: {e: 1},
|
||||
canton: {e: 5, beh: 1, def: 1, bdefh: 1, kn: 1},
|
||||
cross: {acgi: 1},
|
||||
pall: {BCKFEILGJbdmfo: 1},
|
||||
pallReversed: {aczac: 1},
|
||||
chevron: {ach: 3, hhh: 1},
|
||||
chevronReversed: {bbb: 1},
|
||||
pile: {acdfgi: 1, acac: 1},
|
||||
pileInBend: {cg: 1},
|
||||
pileInBendSinister: {ai: 1},
|
||||
label: {defgzi: 2, eh: 3, defdefhmo: 1, egiegi: 1, pqn: 5}
|
||||
},
|
||||
// charges
|
||||
inescutcheon: { e: 4, jln: 1 },
|
||||
mascle: { e: 15, abcdefgzi: 3, beh: 3, bdefh: 4, acegi: 1, kn: 3, joe: 2, abc: 3, jlh: 8, jleh: 1, df: 3, abcpqh: 4, pqe: 3, eknpq: 3 },
|
||||
lionRampant: { e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1 },
|
||||
lionPassant: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
wolfPassant: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
greyhoundСourant: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
griffinRampant: { e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1 },
|
||||
griffinPassant: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
|
||||
boarRampant: { e: 12, beh: 1, kn: 1, jln: 2 },
|
||||
eagle: { e: 15, beh: 1, kn: 1, abc: 1, jlh: 2, def: 2, pq: 1 },
|
||||
raven: { e: 15, beh: 1, kn: 1, jeo: 1, abc: 3, jln: 3, def: 1 },
|
||||
wyvern: { e: 10, jln: 1 },
|
||||
garb: { e: 1, def: 3, abc: 2, beh: 1, kn: 1, jln: 3, jleh: 1, abcpqh: 1, joe: 1, lme: 1 },
|
||||
crown: { e: 10, abcdefgzi: 1, beh: 3, behdf: 2, acegi: 1, kn: 1, pq: 2, abc: 1, jln: 4, jleh: 1, def: 2, abcpqh: 3 },
|
||||
hand: { e: 10, jln: 2, kn: 1, jeo: 1, abc: 2, pqe: 1 },
|
||||
inescutcheon: {e: 4, jln: 1},
|
||||
mascle: {e: 15, abcdefgzi: 3, beh: 3, bdefh: 4, acegi: 1, kn: 3, joe: 2, abc: 3, jlh: 8, jleh: 1, df: 3, abcpqh: 4, pqe: 3, eknpq: 3},
|
||||
lionRampant: {e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1},
|
||||
lionPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
|
||||
wolfPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
|
||||
greyhoundСourant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
|
||||
griffinRampant: {e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1},
|
||||
griffinPassant: {e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1},
|
||||
boarRampant: {e: 12, beh: 1, kn: 1, jln: 2},
|
||||
eagle: {e: 15, beh: 1, kn: 1, abc: 1, jlh: 2, def: 2, pq: 1},
|
||||
raven: {e: 15, beh: 1, kn: 1, jeo: 1, abc: 3, jln: 3, def: 1},
|
||||
wyvern: {e: 10, jln: 1},
|
||||
garb: {e: 1, def: 3, abc: 2, beh: 1, kn: 1, jln: 3, jleh: 1, abcpqh: 1, joe: 1, lme: 1},
|
||||
crown: {e: 10, abcdefgzi: 1, beh: 3, behdf: 2, acegi: 1, kn: 1, pq: 2, abc: 1, jln: 4, jleh: 1, def: 2, abcpqh: 3},
|
||||
hand: {e: 10, jln: 2, kn: 1, jeo: 1, abc: 2, pqe: 1},
|
||||
armillarySphere: {e: 1},
|
||||
tree: {e: 1},
|
||||
lymphad: {e: 1},
|
||||
|
|
@ -175,33 +369,92 @@
|
|||
};
|
||||
|
||||
const lines = {
|
||||
straight: 50, wavy: 8, engrailed: 4, invecked: 3, rayonne: 3, embattled: 1, raguly: 1, urdy: 1, dancetty: 1, indented: 2,
|
||||
dentilly: 1, bevilled: 1, angled: 1, flechy: 1, barby: 1, enclavy: 1, escartely: 1, arched: 2, archedReversed: 1, nowy: 1, nowyReversed: 1,
|
||||
embattledGhibellin: 1, embattledNotched: 1, embattledGrady: 1, dovetailedIndented: 1, dovetailed: 1,
|
||||
potenty: 1, potentyDexter: 1, potentySinister: 1, nebuly: 2, seaWaves: 1, dragonTeeth: 1, firTrees: 1
|
||||
straight: 50,
|
||||
wavy: 8,
|
||||
engrailed: 4,
|
||||
invecked: 3,
|
||||
rayonne: 3,
|
||||
embattled: 1,
|
||||
raguly: 1,
|
||||
urdy: 1,
|
||||
dancetty: 1,
|
||||
indented: 2,
|
||||
dentilly: 1,
|
||||
bevilled: 1,
|
||||
angled: 1,
|
||||
flechy: 1,
|
||||
barby: 1,
|
||||
enclavy: 1,
|
||||
escartely: 1,
|
||||
arched: 2,
|
||||
archedReversed: 1,
|
||||
nowy: 1,
|
||||
nowyReversed: 1,
|
||||
embattledGhibellin: 1,
|
||||
embattledNotched: 1,
|
||||
embattledGrady: 1,
|
||||
dovetailedIndented: 1,
|
||||
dovetailed: 1,
|
||||
potenty: 1,
|
||||
potentyDexter: 1,
|
||||
potentySinister: 1,
|
||||
nebuly: 2,
|
||||
seaWaves: 1,
|
||||
dragonTeeth: 1,
|
||||
firTrees: 1
|
||||
};
|
||||
|
||||
const divisions = {
|
||||
variants: { perPale: 5, perFess: 5, perBend: 2, perBendSinister: 1, perChevron: 1, perChevronReversed: 1, perCross: 5, perPile: 1, perSaltire: 1, gyronny: 1, chevronny: 1 },
|
||||
variants: {perPale: 5, perFess: 5, perBend: 2, perBendSinister: 1, perChevron: 1, perChevronReversed: 1, perCross: 5, perPile: 1, perSaltire: 1, gyronny: 1, chevronny: 1},
|
||||
perPale: lines,
|
||||
perFess: lines,
|
||||
perBend: lines,
|
||||
perBendSinister: lines,
|
||||
perChevron: lines,
|
||||
perChevronReversed: lines,
|
||||
perCross: { straight: 20, wavy: 5, engrailed: 4, invecked: 3, rayonne: 1, embattled: 1, raguly: 1, urdy: 1, indented: 2, dentilly: 1, bevilled: 1, angled: 1, embattledGhibellin: 1, embattledGrady: 1, dovetailedIndented: 1, dovetailed: 1, potenty: 1, potentyDexter: 1, potentySinister: 1, nebuly: 1 },
|
||||
perCross: {straight: 20, wavy: 5, engrailed: 4, invecked: 3, rayonne: 1, embattled: 1, raguly: 1, urdy: 1, indented: 2, dentilly: 1, bevilled: 1, angled: 1, embattledGhibellin: 1, embattledGrady: 1, dovetailedIndented: 1, dovetailed: 1, potenty: 1, potentyDexter: 1, potentySinister: 1, nebuly: 1},
|
||||
perPile: lines
|
||||
};
|
||||
|
||||
const ordinaries = {
|
||||
lined: {
|
||||
pale: 7, fess: 5, bend: 3, bendSinister: 2, chief: 5, bar: 2, gemelle: 1, fessCotissed: 1, fessDoubleCotissed: 1,
|
||||
bendlet: 2, bendletSinister: 1, terrace: 3, cross: 6, crossParted: 1, saltire: 2, saltireParted: 1
|
||||
pale: 7,
|
||||
fess: 5,
|
||||
bend: 3,
|
||||
bendSinister: 2,
|
||||
chief: 5,
|
||||
bar: 2,
|
||||
gemelle: 1,
|
||||
fessCotissed: 1,
|
||||
fessDoubleCotissed: 1,
|
||||
bendlet: 2,
|
||||
bendletSinister: 1,
|
||||
terrace: 3,
|
||||
cross: 6,
|
||||
crossParted: 1,
|
||||
saltire: 2,
|
||||
saltireParted: 1
|
||||
},
|
||||
straight: {
|
||||
bordure: 8, orle: 4, mount: 1, point: 2, flaunches: 1, gore: 1,
|
||||
gyron: 1, quarter: 1, canton: 2, pall: 3, pallReversed: 2, chevron: 4, chevronReversed: 3,
|
||||
pile: 2, pileInBend: 2, pileInBendSinister: 1, piles: 1, pilesInPoint: 2, label: 1
|
||||
bordure: 8,
|
||||
orle: 4,
|
||||
mount: 1,
|
||||
point: 2,
|
||||
flaunches: 1,
|
||||
gore: 1,
|
||||
gyron: 1,
|
||||
quarter: 1,
|
||||
canton: 2,
|
||||
pall: 3,
|
||||
pallReversed: 2,
|
||||
chevron: 4,
|
||||
chevronReversed: 3,
|
||||
pile: 2,
|
||||
pileInBend: 2,
|
||||
pileInBendSinister: 1,
|
||||
piles: 1,
|
||||
pilesInPoint: 2,
|
||||
label: 1
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -215,60 +468,61 @@
|
|||
simple: {round: 12, oval: 6, vesicaPiscis: 1, square: 1, diamond: 2, no: 0},
|
||||
fantasy: {fantasy1: 2, fantasy2: 2, fantasy3: 1, fantasy4: 1, fantasy5: 3},
|
||||
middleEarth: {noldor: 1, gondor: 1, easterling: 1, erebor: 1, ironHills: 1, urukHai: 1, moriaOrc: 1}
|
||||
}
|
||||
};
|
||||
|
||||
const generate = function(parent, kinship, dominion, type) {
|
||||
const generate = function (parent, kinship, dominion, type) {
|
||||
if (!parent || parent === "custom") {
|
||||
parent = null;
|
||||
kinship = 0;
|
||||
dominion = 0;
|
||||
}
|
||||
let usedPattern = null, usedTinctures = [];
|
||||
let usedPattern = null,
|
||||
usedTinctures = [];
|
||||
|
||||
const t1 = P(kinship) ? parent.t1 : getTincture("field");
|
||||
if (t1.includes("-")) usedPattern = t1;
|
||||
const coa = {t1};
|
||||
|
||||
let charge = P(usedPattern ? .5 : .93) ? true : false; // 80% for charge
|
||||
const linedOrdinary = charge && P(.3) || P(.5) ? parent?.ordinaries && P(kinship) ? parent.ordinaries[0].ordinary : rw(ordinaries.lined) : null;
|
||||
const ordinary = !charge && P(.65) || P(.3) ? linedOrdinary ? linedOrdinary : rw(ordinaries.straight) : null; // 36% for ordinary
|
||||
let charge = P(usedPattern ? 0.5 : 0.93) ? true : false; // 80% for charge
|
||||
const linedOrdinary = (charge && P(0.3)) || P(0.5) ? (parent?.ordinaries && P(kinship) ? parent.ordinaries[0].ordinary : rw(ordinaries.lined)) : null;
|
||||
const ordinary = (!charge && P(0.65)) || P(0.3) ? (linedOrdinary ? linedOrdinary : rw(ordinaries.straight)) : null; // 36% for ordinary
|
||||
const rareDivided = ["chief", "terrace", "chevron", "quarter", "flaunches"].includes(ordinary);
|
||||
const divisioned = rareDivided ? P(.03) : charge && ordinary ? P(.03) : charge ? P(.3) : ordinary ? P(.7) : P(.995); // 33% for division
|
||||
const division = divisioned ? parent?.division && P(kinship - .1) ? parent.division.division : rw(divisions.variants) : null;
|
||||
if (charge) charge =
|
||||
parent?.charges && P(kinship - .1) ? parent.charges[0].charge :
|
||||
type && type !== "Generic" && P(.2) ? rw(charges[type]) :
|
||||
selectCharge();
|
||||
const divisioned = rareDivided ? P(0.03) : charge && ordinary ? P(0.03) : charge ? P(0.3) : ordinary ? P(0.7) : P(0.995); // 33% for division
|
||||
const division = divisioned ? (parent?.division && P(kinship - 0.1) ? parent.division.division : rw(divisions.variants)) : null;
|
||||
if (charge) charge = parent?.charges && P(kinship - 0.1) ? parent.charges[0].charge : type && type !== "Generic" && P(0.2) ? rw(charges[type]) : selectCharge();
|
||||
|
||||
if (division) {
|
||||
const t = getTincture("division", usedTinctures, P(.98) ? coa.t1 : null);
|
||||
const t = getTincture("division", usedTinctures, P(0.98) ? coa.t1 : null);
|
||||
coa.division = {division, t};
|
||||
if (divisions[division]) coa.division.line = usedPattern || (ordinary && P(.7)) ? "straight" : rw(divisions[division]);
|
||||
if (divisions[division]) coa.division.line = usedPattern || (ordinary && P(0.7)) ? "straight" : rw(divisions[division]);
|
||||
}
|
||||
|
||||
if (ordinary) {
|
||||
coa.ordinaries = [{ordinary, t: getTincture("charge", usedTinctures, coa.t1)}];
|
||||
if (linedOrdinary) coa.ordinaries[0].line = usedPattern || (division && P(.7)) ? "straight" : rw(lines);
|
||||
if (division && !charge && !usedPattern && P(.5) && ordinary !== "bordure" && ordinary !== "orle") {
|
||||
if (P(.8)) coa.ordinaries[0].divided = "counter"; // 40%
|
||||
else if (P(.6)) coa.ordinaries[0].divided = "field"; // 6%
|
||||
if (linedOrdinary) coa.ordinaries[0].line = usedPattern || (division && P(0.7)) ? "straight" : rw(lines);
|
||||
if (division && !charge && !usedPattern && P(0.5) && ordinary !== "bordure" && ordinary !== "orle") {
|
||||
if (P(0.8)) coa.ordinaries[0].divided = "counter";
|
||||
// 40%
|
||||
else if (P(0.6)) coa.ordinaries[0].divided = "field";
|
||||
// 6%
|
||||
else coa.ordinaries[0].divided = "division"; // 4%
|
||||
}
|
||||
}
|
||||
|
||||
if (charge) {
|
||||
let p = "e", t = "gules";
|
||||
let p = "e",
|
||||
t = "gules";
|
||||
const ordinaryT = coa.ordinaries ? coa.ordinaries[0].t : null;
|
||||
if (positions.ordinariesOn[ordinary] && P(.8)) {
|
||||
if (positions.ordinariesOn[ordinary] && P(0.8)) {
|
||||
// place charge over ordinary (use tincture of field type)
|
||||
p = rw(positions.ordinariesOn[ordinary]);
|
||||
while (charges.natural[charge] === ordinaryT) charge = selectCharge();
|
||||
t = !usedPattern && P(.3) ? coa.t1 : getTincture("charge", [], ordinaryT);
|
||||
} else if (positions.ordinariesOff[ordinary] && P(.95)) {
|
||||
t = !usedPattern && P(0.3) ? coa.t1 : getTincture("charge", [], ordinaryT);
|
||||
} else if (positions.ordinariesOff[ordinary] && P(0.95)) {
|
||||
// place charge out of ordinary (use tincture of ordinary type)
|
||||
p = rw(positions.ordinariesOff[ordinary]);
|
||||
while (charges.natural[charge] === coa.t1) charge = selectCharge();
|
||||
t = !usedPattern && P(.3) ? ordinaryT : getTincture("charge", usedTinctures, coa.t1);
|
||||
t = !usedPattern && P(0.3) ? ordinaryT : getTincture("charge", usedTinctures, coa.t1);
|
||||
} else if (positions.divisions[division]) {
|
||||
// place charge in fields made by division
|
||||
p = rw(positions.divisions[division]);
|
||||
|
|
@ -289,43 +543,41 @@
|
|||
if (charges.natural[charge]) t = charges.natural[charge]; // natural tincture
|
||||
coa.charges = [{charge, t, p}];
|
||||
|
||||
if (p === "ABCDEFGHIKL" && P(.95)) {
|
||||
if (p === "ABCDEFGHIKL" && P(0.95)) {
|
||||
// add central charge if charge is in bordure
|
||||
coa.charges[0].charge = rw(charges.conventional);
|
||||
const charge = selectCharge(charges.single);
|
||||
const t = getTincture("charge", usedTinctures, coa.t1);
|
||||
coa.charges.push({charge, t, p: "e"});
|
||||
} else if (P(.8) && charge === "inescutcheon") {
|
||||
} else if (P(0.8) && charge === "inescutcheon") {
|
||||
// add charge to inescutcheon
|
||||
const charge = selectCharge(charges.types);
|
||||
const t2 = getTincture("charge", [], t);
|
||||
coa.charges.push({charge, t: t2, p, size:.5});
|
||||
coa.charges.push({charge, t: t2, p, size: 0.5});
|
||||
} else if (division && !ordinary) {
|
||||
const allowCounter = !usedPattern && (!coa.line || coa.line === "straight");
|
||||
|
||||
// dimidiation: second charge at division basic positons
|
||||
if (P(.3) && ["perPale", "perFess"].includes(division) && coa.line === "straight") {
|
||||
if (P(0.3) && ["perPale", "perFess"].includes(division) && coa.line === "straight") {
|
||||
coa.charges[0].divided = "field";
|
||||
if (P(.95)) {
|
||||
const p2 = p === "e" || P(.5) ? "e" : rw(positions.divisions[division]);
|
||||
if (P(0.95)) {
|
||||
const p2 = p === "e" || P(0.5) ? "e" : rw(positions.divisions[division]);
|
||||
const charge = selectCharge(charges.single);
|
||||
const t = getTincture("charge", usedTinctures, coa.division.t);
|
||||
coa.charges.push({charge, t, p: p2, divided: "division"});
|
||||
}
|
||||
}
|
||||
else if (allowCounter && P(.4)) coa.charges[0].divided = "counter"; // counterchanged, 40%
|
||||
else if (["perPale", "perFess", "perBend", "perBendSinister"].includes(division) && P(.8)) { // place 2 charges in division standard positions
|
||||
const [p1, p2] = division === "perPale" ? ["p", "q"] :
|
||||
division === "perFess" ? ["k", "n"] :
|
||||
division === "perBend" ? ["l", "m"] :
|
||||
["j", "o"]; // perBendSinister
|
||||
} else if (allowCounter && P(0.4)) coa.charges[0].divided = "counter";
|
||||
// counterchanged, 40%
|
||||
else if (["perPale", "perFess", "perBend", "perBendSinister"].includes(division) && P(0.8)) {
|
||||
// place 2 charges in division standard positions
|
||||
const [p1, p2] = division === "perPale" ? ["p", "q"] : division === "perFess" ? ["k", "n"] : division === "perBend" ? ["l", "m"] : ["j", "o"]; // perBendSinister
|
||||
coa.charges[0].p = p1;
|
||||
|
||||
const charge = selectCharge(charges.single);
|
||||
const t = getTincture("charge", usedTinctures, coa.division.t);
|
||||
coa.charges.push({charge, t, p: p2});
|
||||
}
|
||||
else if (["perCross", "perSaltire"].includes(division) && P(.5)) { // place 4 charges in division standard positions
|
||||
} else if (["perCross", "perSaltire"].includes(division) && P(0.5)) {
|
||||
// place 4 charges in division standard positions
|
||||
const [p1, p2, p3, p4] = division === "perCross" ? ["j", "l", "m", "o"] : ["b", "d", "f", "h"];
|
||||
coa.charges[0].p = p1;
|
||||
|
||||
|
|
@ -338,8 +590,7 @@
|
|||
const c4 = selectCharge(charges.single);
|
||||
const t4 = getTincture("charge", [], coa.t1);
|
||||
coa.charges.push({charge: c2, t: t2, p: p2}, {charge: c3, t: t3, p: p3}, {charge: c4, t: t4, p: p4});
|
||||
}
|
||||
else if (allowCounter && p.length > 1) coa.charges[0].divided = "counter"; // counterchanged, 40%
|
||||
} else if (allowCounter && p.length > 1) coa.charges[0].divided = "counter"; // counterchanged, 40%
|
||||
}
|
||||
|
||||
coa.charges.forEach(c => defineChargeAttributes(c));
|
||||
|
|
@ -351,8 +602,8 @@
|
|||
c.p = [...new Set(c.p)].join("");
|
||||
|
||||
// define orientation
|
||||
if (P(.02) && charges.sinister.includes(c.charge)) c.sinister = 1;
|
||||
if (P(.02) && charges.reversed.includes(c.charge)) c.reversed = 1;
|
||||
if (P(0.02) && charges.sinister.includes(c.charge)) c.sinister = 1;
|
||||
if (P(0.02) && charges.reversed.includes(c.charge)) c.reversed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -380,24 +631,26 @@
|
|||
if (!coa.charges) coa.charges = [];
|
||||
coa.charges.push({charge, t: t2, p: "y", size: 0.5});
|
||||
|
||||
coa.ordinaries ? coa.ordinaries.push(canton) : coa.ordinaries = [canton];
|
||||
coa.ordinaries ? coa.ordinaries.push(canton) : (coa.ordinaries = [canton]);
|
||||
}
|
||||
|
||||
function selectCharge(set) {
|
||||
const type = set ? rw(set) : ordinary || divisioned ? rw(charges.types): rw(charges.single);
|
||||
const type = set ? rw(set) : ordinary || divisioned ? rw(charges.types) : rw(charges.single);
|
||||
return type === "inescutcheon" ? "inescutcheon" : rw(charges[type]);
|
||||
}
|
||||
|
||||
// select tincture: element type (field, division, charge), used field tinctures, field type to follow RoT
|
||||
function getTincture(element, fields = [], RoT) {
|
||||
const base = RoT ? RoT.includes("-") ? RoT.split("-")[1] : RoT : null;
|
||||
const base = RoT ? (RoT.includes("-") ? RoT.split("-")[1] : RoT) : null;
|
||||
|
||||
let type = rw(tinctures[element]); // metals, colours, stains, patterns
|
||||
if (RoT && type !== "patterns") type = getType(base) === "metals" ? "colours" : "metals"; // follow RoT
|
||||
if (type === "metals" && fields.includes("or") && fields.includes("argent")) type = "colours"; // exclude metals overuse
|
||||
let tincture = rw(tinctures[type]);
|
||||
|
||||
while (tincture === base || fields.includes(tincture)) {tincture = rw(tinctures[type]);} // follow RoT
|
||||
while (tincture === base || fields.includes(tincture)) {
|
||||
tincture = rw(tinctures[type]);
|
||||
} // follow RoT
|
||||
|
||||
if (type !== "patterns" && element !== "charge") usedTinctures.push(tincture); // add field tincture
|
||||
|
||||
|
|
@ -425,39 +678,60 @@
|
|||
if (Object.keys(tinctures.stains).includes(tincture)) return "stains";
|
||||
else return "pattern";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function definePattern(pattern, element, size = "") {
|
||||
let t1 = null, t2 = null;
|
||||
if (P(.1)) size = "-small";
|
||||
else if (P(.1)) size = "-smaller";
|
||||
else if (P(.01)) size = "-big";
|
||||
else if (P(.005)) size = "-smallest";
|
||||
let t1 = null,
|
||||
t2 = null;
|
||||
if (P(0.1)) size = "-small";
|
||||
else if (P(0.1)) size = "-smaller";
|
||||
else if (P(0.01)) size = "-big";
|
||||
else if (P(0.005)) size = "-smallest";
|
||||
|
||||
// apply standard tinctures
|
||||
if (P(.5) && ["vair", "vairInPale", "vairEnPointe"].includes(pattern)) {t1 = "azure"; t2 = "argent";}
|
||||
else if (P(.8) && pattern === "ermine") {t1 = "argent"; t2 = "sable";}
|
||||
else if (pattern === "pappellony") {
|
||||
if (P(.2)) {t1 = "gules"; t2 = "or";}
|
||||
else if (P(.2)) {t1 = "argent"; t2 = "sable";}
|
||||
else if (P(.2)) {t1 = "azure"; t2 = "argent";}
|
||||
if (P(0.5) && ["vair", "vairInPale", "vairEnPointe"].includes(pattern)) {
|
||||
t1 = "azure";
|
||||
t2 = "argent";
|
||||
} else if (P(0.8) && pattern === "ermine") {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
} else if (pattern === "pappellony") {
|
||||
if (P(0.2)) {
|
||||
t1 = "gules";
|
||||
t2 = "or";
|
||||
} else if (P(0.2)) {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
} else if (P(0.2)) {
|
||||
t1 = "azure";
|
||||
t2 = "argent";
|
||||
}
|
||||
else if (pattern === "masoned") {
|
||||
if (P(.3)) {t1 = "gules"; t2 = "argent";}
|
||||
else if (P(.3)) {t1 = "argent"; t2 = "sable";}
|
||||
else if (P(.1)) {t1 = "or"; t2 = "sable";}
|
||||
} else if (pattern === "masoned") {
|
||||
if (P(0.3)) {
|
||||
t1 = "gules";
|
||||
t2 = "argent";
|
||||
} else if (P(0.3)) {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
} else if (P(0.1)) {
|
||||
t1 = "or";
|
||||
t2 = "sable";
|
||||
}
|
||||
else if (pattern === "fretty") {
|
||||
if (t2 === "sable" || P(.35)) {t1 = "argent"; t2 = "gules";}
|
||||
else if (P(.25)) {t1 = "sable"; t2 = "or";}
|
||||
else if (P(.15)) {t1 = "gules"; t2 = "argent";}
|
||||
} else if (pattern === "fretty") {
|
||||
if (t2 === "sable" || P(0.35)) {
|
||||
t1 = "argent";
|
||||
t2 = "gules";
|
||||
} else if (P(0.25)) {
|
||||
t1 = "sable";
|
||||
t2 = "or";
|
||||
} else if (P(0.15)) {
|
||||
t1 = "gules";
|
||||
t2 = "argent";
|
||||
}
|
||||
else if (pattern === "semy") pattern += "_of_" + selectCharge(charges.semy);
|
||||
|
||||
} else if (pattern === "semy") pattern += "_of_" + selectCharge(charges.semy);
|
||||
|
||||
if (!t1 || !t2) {
|
||||
const startWithMetal = P(.7);
|
||||
const startWithMetal = P(0.7);
|
||||
t1 = startWithMetal ? rw(tinctures.metals) : rw(tinctures.colours);
|
||||
t2 = startWithMetal ? rw(tinctures.colours) : rw(tinctures.metals);
|
||||
}
|
||||
|
|
@ -474,28 +748,30 @@
|
|||
|
||||
function replaceTincture(t, n) {
|
||||
const type = getType(t);
|
||||
while (!n || n === t) {n = rw(tinctures[type]);}
|
||||
while (!n || n === t) {
|
||||
n = rw(tinctures[type]);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function getSize(p, o = null, d = null) {
|
||||
if (p === "e" && (o === "bordure" || o === "orle")) return 1.1;
|
||||
if (p === "e") return 1.5;
|
||||
if (p === "jln" || p === "jlh") return .7;
|
||||
if (p === "abcpqh" || p === "ez" || p === "be") return .5;
|
||||
if (["a", "b", "c", "d", "f", "g", "h", "i", "bh", "df"].includes(p)) return .5;
|
||||
if (["j", "l", "m", "o", "jlmo"].includes(p) && d === "perCross") return .6;
|
||||
if (p.length > 10) return .18; // >10 (bordure)
|
||||
if (p.length > 7) return .3; // 8, 9, 10
|
||||
if (p.length > 4) return .4; // 5, 6, 7
|
||||
if (p.length > 2) return .5; // 3, 4
|
||||
return .7; // 1, 2
|
||||
if (p === "jln" || p === "jlh") return 0.7;
|
||||
if (p === "abcpqh" || p === "ez" || p === "be") return 0.5;
|
||||
if (["a", "b", "c", "d", "f", "g", "h", "i", "bh", "df"].includes(p)) return 0.5;
|
||||
if (["j", "l", "m", "o", "jlmo"].includes(p) && d === "perCross") return 0.6;
|
||||
if (p.length > 10) return 0.18; // >10 (bordure)
|
||||
if (p.length > 7) return 0.3; // 8, 9, 10
|
||||
if (p.length > 4) return 0.4; // 5, 6, 7
|
||||
if (p.length > 2) return 0.5; // 3, 4
|
||||
return 0.7; // 1, 2
|
||||
}
|
||||
|
||||
return coa;
|
||||
}
|
||||
};
|
||||
|
||||
const getShield = function(culture, state) {
|
||||
const getShield = function (culture, state) {
|
||||
const emblemShape = document.getElementById("emblemShape");
|
||||
const shapeGroup = emblemShape.selectedOptions[0]?.parentNode.label || "Diversiform";
|
||||
if (shapeGroup !== "Diversiform") return emblemShape.value;
|
||||
|
|
@ -504,11 +780,10 @@
|
|||
if (pack.cultures[culture].shield) return pack.cultures[culture].shield;
|
||||
console.error("Shield shape is not defined on culture level", pack.cultures[culture]);
|
||||
return "heater";
|
||||
}
|
||||
};
|
||||
|
||||
const toString = coa => JSON.stringify(coa).replaceAll("#", "%23");
|
||||
const copy = coa => JSON.parse(JSON.stringify(coa));
|
||||
|
||||
return {generate, toString, copy, getShield, shields};
|
||||
|
||||
})));
|
||||
})();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,10 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.Cultures = factory());
|
||||
}(this, (function () {'use strict';
|
||||
"use strict";
|
||||
|
||||
window.Cultures = (function () {
|
||||
let cells;
|
||||
|
||||
const generate = function() {
|
||||
TIME && console.time('generateCultures');
|
||||
const generate = function () {
|
||||
TIME && console.time("generateCultures");
|
||||
cells = pack.cells;
|
||||
cells.culture = new Uint16Array(cells.i.length); // cell cultures
|
||||
let count = Math.min(+culturesInput.value, +culturesSet.selectedOptions[0].dataset.max);
|
||||
|
|
@ -17,13 +14,19 @@
|
|||
count = Math.floor(populated.length / 50);
|
||||
if (!count) {
|
||||
WARN && console.warn(`There are no populated cells. Cannot generate cultures`);
|
||||
pack.cultures = [{name:"Wildlands", i:0, base:1, shield:"round"}];
|
||||
pack.cultures = [{name: "Wildlands", i: 0, base: 1, shield: "round"}];
|
||||
alertMessage.innerHTML = `
|
||||
The climate is harsh and people cannot live in this world.<br>
|
||||
No cultures, states and burgs will be created.<br>
|
||||
Please consider changing climate settings in the World Configurator`;
|
||||
$("#alert").dialog({resizable: false, title: "Extreme climate warning",
|
||||
buttons: {Ok: function() {$(this).dialog("close");}}
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Extreme climate warning",
|
||||
buttons: {
|
||||
Ok: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
|
|
@ -32,22 +35,28 @@
|
|||
There are only ${populated.length} populated cells and it's insufficient livable area.<br>
|
||||
Only ${count} out of ${culturesInput.value} requested cultures will be generated.<br>
|
||||
Please consider changing climate settings in the World Configurator`;
|
||||
$("#alert").dialog({resizable: false, title: "Extreme climate warning",
|
||||
buttons: {Ok: function() {$(this).dialog("close");}}
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Extreme climate warning",
|
||||
buttons: {
|
||||
Ok: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const cultures = pack.cultures = selectCultures(count);
|
||||
const cultures = (pack.cultures = selectCultures(count));
|
||||
const centers = d3.quadtree();
|
||||
const colors = getColors(count);
|
||||
const emblemShape = document.getElementById("emblemShape").value;
|
||||
|
||||
const codes = [];
|
||||
cultures.forEach(function(c, i) {
|
||||
const cell = c.center = placeCenter(c.sort ? c.sort : (i) => cells.s[i]);
|
||||
cultures.forEach(function (c, i) {
|
||||
const cell = (c.center = placeCenter(c.sort ? c.sort : i => cells.s[i]));
|
||||
centers.add(cells.p[cell]);
|
||||
c.i = i+1;
|
||||
c.i = i + 1;
|
||||
delete c.odd;
|
||||
delete c.sort;
|
||||
c.color = colors[i];
|
||||
|
|
@ -56,20 +65,24 @@
|
|||
c.origin = 0;
|
||||
c.code = abbreviate(c.name, codes);
|
||||
codes.push(c.code);
|
||||
cells.culture[cell] = i+1;
|
||||
cells.culture[cell] = i + 1;
|
||||
if (emblemShape === "random") c.shield = getRandomShield();
|
||||
});
|
||||
|
||||
function placeCenter(v) {
|
||||
let c, spacing = (graphWidth + graphHeight) / 2 / count;
|
||||
const sorted = [...populated].sort((a, b) => v(b) - v(a)), max = Math.floor(sorted.length / 2);
|
||||
do {c = sorted[biased(0, max, 5)]; spacing *= .9;}
|
||||
while (centers.find(cells.p[c][0], cells.p[c][1], spacing) !== undefined);
|
||||
let c,
|
||||
spacing = (graphWidth + graphHeight) / 2 / count;
|
||||
const sorted = [...populated].sort((a, b) => v(b) - v(a)),
|
||||
max = Math.floor(sorted.length / 2);
|
||||
do {
|
||||
c = sorted[biased(0, max, 5)];
|
||||
spacing *= 0.9;
|
||||
} while (centers.find(cells.p[c][0], cells.p[c][1], spacing) !== undefined);
|
||||
return c;
|
||||
}
|
||||
|
||||
// the first culture with id 0 is for wildlands
|
||||
cultures.unshift({name:"Wildlands", i:0, base:1, origin:null, shield:"round"});
|
||||
cultures.unshift({name: "Wildlands", i: 0, base: 1, origin: null, shield: "round"});
|
||||
|
||||
// make sure all bases exist in nameBases
|
||||
if (!nameBases.length) {
|
||||
|
|
@ -77,7 +90,7 @@
|
|||
nameBases = Names.getNameBases();
|
||||
}
|
||||
|
||||
cultures.forEach(c => c.base = c.base % nameBases.length);
|
||||
cultures.forEach(c => (c.base = c.base % nameBases.length));
|
||||
|
||||
function selectCultures(c) {
|
||||
let def = getDefault(c);
|
||||
|
|
@ -87,11 +100,11 @@
|
|||
const count = Math.min(c, def.length);
|
||||
const cultures = [];
|
||||
|
||||
for (let culture, rnd, i=0; cultures.length < count && i < 200; i++) {
|
||||
for (let culture, rnd, i = 0; cultures.length < count && i < 200; i++) {
|
||||
do {
|
||||
rnd = rand(def.length-1);
|
||||
rnd = rand(def.length - 1);
|
||||
culture = def[rnd];
|
||||
} while (!P(culture.odd))
|
||||
} while (!P(culture.odd));
|
||||
cultures.push(culture);
|
||||
def.splice(rnd, 1);
|
||||
}
|
||||
|
|
@ -100,31 +113,31 @@
|
|||
|
||||
// set culture type based on culture center position
|
||||
function defineCultureType(i) {
|
||||
if (cells.h[i] < 70 && [1,2,4].includes(cells.biome[i])) return "Nomadic"; // high penalty in forest biomes and near coastline
|
||||
if (cells.h[i] < 70 && [1, 2, 4].includes(cells.biome[i])) return "Nomadic"; // high penalty in forest biomes and near coastline
|
||||
if (cells.h[i] > 50) return "Highland"; // no penalty for hills and moutains, high for other elevations
|
||||
const f = pack.features[cells.f[cells.haven[i]]]; // opposite feature
|
||||
if (f.type === "lake" && f.cells > 5) return "Lake" // low water cross penalty and high for growth not along coastline
|
||||
if (cells.harbor[i] && f.type !== "lake" && P(.1) || (cells.harbor[i] === 1 && P(.6)) || (pack.features[cells.f[i]].group === "isle" && P(.4))) return "Naval"; // low water cross penalty and high for non-along-coastline growth
|
||||
if (f.type === "lake" && f.cells > 5) return "Lake"; // low water cross penalty and high for growth not along coastline
|
||||
if ((cells.harbor[i] && f.type !== "lake" && P(0.1)) || (cells.harbor[i] === 1 && P(0.6)) || (pack.features[cells.f[i]].group === "isle" && P(0.4))) return "Naval"; // low water cross penalty and high for non-along-coastline growth
|
||||
if (cells.r[i] && cells.fl[i] > 100) return "River"; // no River cross penalty, penalty for non-River growth
|
||||
if (cells.t[i] > 2 && [3,7,8,9,10,12].includes(cells.biome[i])) return "Hunting"; // high penalty in non-native biomes
|
||||
if (cells.t[i] > 2 && [3, 7, 8, 9, 10, 12].includes(cells.biome[i])) return "Hunting"; // high penalty in non-native biomes
|
||||
return "Generic";
|
||||
}
|
||||
|
||||
function defineCultureExpansionism(type) {
|
||||
let base = 1; // Generic
|
||||
if (type === "Lake") base = .8; else
|
||||
if (type === "Naval") base = 1.5; else
|
||||
if (type === "River") base = .9; else
|
||||
if (type === "Nomadic") base = 1.5; else
|
||||
if (type === "Hunting") base = .7; else
|
||||
if (type === "Highland") base = 1.2;
|
||||
return rn((Math.random() * powerInput.value / 2 + 1) * base, 1);
|
||||
if (type === "Lake") base = 0.8;
|
||||
else if (type === "Naval") base = 1.5;
|
||||
else if (type === "River") base = 0.9;
|
||||
else if (type === "Nomadic") base = 1.5;
|
||||
else if (type === "Hunting") base = 0.7;
|
||||
else if (type === "Highland") base = 1.2;
|
||||
return rn(((Math.random() * powerInput.value) / 2 + 1) * base, 1);
|
||||
}
|
||||
|
||||
TIME && console.timeEnd('generateCultures');
|
||||
}
|
||||
TIME && console.timeEnd("generateCultures");
|
||||
};
|
||||
|
||||
const add = function(center) {
|
||||
const add = function (center) {
|
||||
const defaultCultures = getDefault();
|
||||
let culture, base, name;
|
||||
|
||||
|
|
@ -139,7 +152,10 @@
|
|||
name = Names.getCulture(culture, 5, 8, "");
|
||||
base = pack.cultures[culture].base;
|
||||
}
|
||||
const code = abbreviate(name, pack.cultures.map(c => c.code));
|
||||
const code = abbreviate(
|
||||
name,
|
||||
pack.cultures.map(c => c.code)
|
||||
);
|
||||
const i = pack.cultures.length;
|
||||
const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex();
|
||||
|
||||
|
|
@ -148,220 +164,231 @@
|
|||
const emblemShape = document.getElementById("emblemShape").value;
|
||||
if (emblemShape === "random") shield = getRandomShield();
|
||||
|
||||
pack.cultures.push({name, color, base, center, i, expansionism:1, type:"Generic", cells:0, area:0, rural:0, urban:0, origin:0, code, shield});
|
||||
}
|
||||
pack.cultures.push({name, color, base, center, i, expansionism: 1, type: "Generic", cells: 0, area: 0, rural: 0, urban: 0, origin: 0, code, shield});
|
||||
};
|
||||
|
||||
const getDefault = function(count) {
|
||||
const getDefault = function (count) {
|
||||
// generic sorting functions
|
||||
const cells = pack.cells, s = cells.s, sMax = d3.max(s), t = cells.t, h = cells.h, temp = grid.cells.temp;
|
||||
const n = cell => Math.ceil(s[cell] / sMax * 3) // normalized cell score
|
||||
const td = (cell, goal) => {const d = Math.abs(temp[cells.g[cell]] - goal); return d ? d+1 : 1;} // temperature difference fee
|
||||
const bd = (cell, biomes, fee = 4) => biomes.includes(cells.biome[cell]) ? 1 : fee; // biome difference fee
|
||||
const sf = (cell, fee = 4) => cells.haven[cell] && pack.features[cells.f[cells.haven[cell]]].type !== "lake" ? 1 : fee; // not on sea coast fee
|
||||
const cells = pack.cells,
|
||||
s = cells.s,
|
||||
sMax = d3.max(s),
|
||||
t = cells.t,
|
||||
h = cells.h,
|
||||
temp = grid.cells.temp;
|
||||
const n = cell => Math.ceil((s[cell] / sMax) * 3); // normalized cell score
|
||||
const td = (cell, goal) => {
|
||||
const d = Math.abs(temp[cells.g[cell]] - goal);
|
||||
return d ? d + 1 : 1;
|
||||
}; // temperature difference fee
|
||||
const bd = (cell, biomes, fee = 4) => (biomes.includes(cells.biome[cell]) ? 1 : fee); // biome difference fee
|
||||
const sf = (cell, fee = 4) => (cells.haven[cell] && pack.features[cells.f[cells.haven[cell]]].type !== "lake" ? 1 : fee); // not on sea coast fee
|
||||
|
||||
if (culturesSet.value === "european") {
|
||||
return [
|
||||
{name:"Shwazen", base:0, odd:1, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield:"swiss"},
|
||||
{name:"Angshire", base:1, odd:1, sort: i => n(i) / td(i, 10) / sf(i), shield:"wedged"},
|
||||
{name:"Luari", base:2, odd:1, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield:"french"},
|
||||
{name:"Tallian", base:3, odd:1, sort: i => n(i) / td(i, 15), shield:"horsehead"},
|
||||
{name:"Astellian", base:4, odd:1, sort: i => n(i) / td(i, 16), shield:"spanish"},
|
||||
{name:"Slovan", base:5, odd:1, sort: i => n(i) / td(i, 6) * t[i], shield:"polish"},
|
||||
{name:"Norse", base:6, odd:1, sort: i => n(i) / td(i, 5), shield:"heater"},
|
||||
{name:"Elladan", base:7, odd:1, sort: i => n(i) / td(i, 18) * h[i], shield:"boeotian"},
|
||||
{name:"Romian", base:8, odd:.2, sort: i => n(i) / td(i, 15) / t[i], shield:"roman"},
|
||||
{name:"Soumi", base:9, odd:1, sort: i => n(i) / td(i, 5) / bd(i, [9]) * t[i], shield:"pavise"},
|
||||
{name:"Portuzian", base:13, odd:1, sort: i => n(i) / td(i, 17) / sf(i), shield:"renaissance"},
|
||||
{name:"Vengrian", base: 15, odd:1, sort: i => n(i) / td(i, 11) / bd(i, [4]) * t[i], shield:"horsehead2"},
|
||||
{name:"Turchian", base: 16, odd:.05, sort: i => n(i) / td(i, 14), shield:"round"},
|
||||
{name:"Euskati", base: 20, odd:.05, sort: i => n(i) / td(i, 15) * h[i], shield:"oldFrench"},
|
||||
{name:"Keltan", base: 22, odd:.05, sort: i => n(i) / td(i, 11) / bd(i, [6, 8]) * t[i], shield:"oval"}
|
||||
{name: "Shwazen", base: 0, odd: 1, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield: "swiss"},
|
||||
{name: "Angshire", base: 1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield: "wedged"},
|
||||
{name: "Luari", base: 2, odd: 1, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield: "french"},
|
||||
{name: "Tallian", base: 3, odd: 1, sort: i => n(i) / td(i, 15), shield: "horsehead"},
|
||||
{name: "Astellian", base: 4, odd: 1, sort: i => n(i) / td(i, 16), shield: "spanish"},
|
||||
{name: "Slovan", base: 5, odd: 1, sort: i => (n(i) / td(i, 6)) * t[i], shield: "polish"},
|
||||
{name: "Norse", base: 6, odd: 1, sort: i => n(i) / td(i, 5), shield: "heater"},
|
||||
{name: "Elladan", base: 7, odd: 1, sort: i => (n(i) / td(i, 18)) * h[i], shield: "boeotian"},
|
||||
{name: "Romian", base: 8, odd: 0.2, sort: i => n(i) / td(i, 15) / t[i], shield: "roman"},
|
||||
{name: "Soumi", base: 9, odd: 1, sort: i => (n(i) / td(i, 5) / bd(i, [9])) * t[i], shield: "pavise"},
|
||||
{name: "Portuzian", base: 13, odd: 1, sort: i => n(i) / td(i, 17) / sf(i), shield: "renaissance"},
|
||||
{name: "Vengrian", base: 15, odd: 1, sort: i => (n(i) / td(i, 11) / bd(i, [4])) * t[i], shield: "horsehead2"},
|
||||
{name: "Turchian", base: 16, odd: 0.05, sort: i => n(i) / td(i, 14), shield: "round"},
|
||||
{name: "Euskati", base: 20, odd: 0.05, sort: i => (n(i) / td(i, 15)) * h[i], shield: "oldFrench"},
|
||||
{name: "Keltan", base: 22, odd: 0.05, sort: i => (n(i) / td(i, 11) / bd(i, [6, 8])) * t[i], shield: "oval"}
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "oriental") {
|
||||
return [
|
||||
{name:"Koryo", base:10, odd:1, sort: i => n(i) / td(i, 12) / t[i], shield:"round"},
|
||||
{name:"Hantzu", base:11, odd:1, sort: i => n(i) / td(i, 13), shield:"banner"},
|
||||
{name:"Yamoto", base:12, odd:1, sort: i => n(i) / td(i, 15) / t[i], shield:"round"},
|
||||
{name:"Turchian", base: 16, odd:1, sort: i => n(i) / td(i, 12), shield:"round"},
|
||||
{name:"Berberan", base: 17, odd:.2, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i], shield:"oval"},
|
||||
{name:"Eurabic", base: 18, odd:1, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i], shield:"oval"},
|
||||
{name:"Efratic", base: 23, odd:.1, sort: i => n(i) / td(i, 22) * t[i], shield:"round"},
|
||||
{name:"Tehrani", base: 24, odd:1, sort: i => n(i) / td(i, 18) * h[i], shield:"round"},
|
||||
{name:"Maui", base: 25, odd:.2, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield:"vesicaPiscis"},
|
||||
{name:"Carnatic", base: 26, odd:.5, sort: i => n(i) / td(i, 26), shield:"round"},
|
||||
{name:"Vietic", base: 29, odd:.8, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield:"banner"},
|
||||
{name:"Guantzu", base:30, odd:.5, sort: i => n(i) / td(i, 17), shield:"banner"},
|
||||
{name:"Ulus", base:31, odd:1, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i], shield:"banner"}
|
||||
{name: "Koryo", base: 10, odd: 1, sort: i => n(i) / td(i, 12) / t[i], shield: "round"},
|
||||
{name: "Hantzu", base: 11, odd: 1, sort: i => n(i) / td(i, 13), shield: "banner"},
|
||||
{name: "Yamoto", base: 12, odd: 1, sort: i => n(i) / td(i, 15) / t[i], shield: "round"},
|
||||
{name: "Turchian", base: 16, odd: 1, sort: i => n(i) / td(i, 12), shield: "round"},
|
||||
{name: "Berberan", base: 17, odd: 0.2, sort: i => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i], shield: "oval"},
|
||||
{name: "Eurabic", base: 18, odd: 1, sort: i => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: "oval"},
|
||||
{name: "Efratic", base: 23, odd: 0.1, sort: i => (n(i) / td(i, 22)) * t[i], shield: "round"},
|
||||
{name: "Tehrani", base: 24, odd: 1, sort: i => (n(i) / td(i, 18)) * h[i], shield: "round"},
|
||||
{name: "Maui", base: 25, odd: 0.2, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield: "vesicaPiscis"},
|
||||
{name: "Carnatic", base: 26, odd: 0.5, sort: i => n(i) / td(i, 26), shield: "round"},
|
||||
{name: "Vietic", base: 29, odd: 0.8, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield: "banner"},
|
||||
{name: "Guantzu", base: 30, odd: 0.5, sort: i => n(i) / td(i, 17), shield: "banner"},
|
||||
{name: "Ulus", base: 31, odd: 1, sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: "banner"}
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "english") {
|
||||
const getName = () => Names.getBase(1, 5, 9, "", 0);
|
||||
return [
|
||||
{name:getName(), base:1, odd:1, shield:"heater"},
|
||||
{name:getName(), base:1, odd:1, shield:"wedged"},
|
||||
{name:getName(), base:1, odd:1, shield:"swiss"},
|
||||
{name:getName(), base:1, odd:1, shield:"oldFrench"},
|
||||
{name:getName(), base:1, odd:1, shield:"swiss"},
|
||||
{name:getName(), base:1, odd:1, shield:"spanish"},
|
||||
{name:getName(), base:1, odd:1, shield:"hessen"},
|
||||
{name:getName(), base:1, odd:1, shield:"fantasy5"},
|
||||
{name:getName(), base:1, odd:1, shield:"fantasy4"},
|
||||
{name:getName(), base:1, odd:1, shield:"fantasy1"}
|
||||
{name: getName(), base: 1, odd: 1, shield: "heater"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "wedged"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "swiss"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "oldFrench"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "swiss"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "spanish"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "hessen"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "fantasy5"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "fantasy4"},
|
||||
{name: getName(), base: 1, odd: 1, shield: "fantasy1"}
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "antique") {
|
||||
return [
|
||||
{name:"Roman", base:8, odd:1, sort: i => n(i) / td(i, 14) / t[i], shield:"roman"}, // Roman
|
||||
{name:"Roman", base:8, odd:1, sort: i => n(i) / td(i, 15) / sf(i), shield:"roman"}, // Roman
|
||||
{name:"Roman", base:8, odd:1, sort: i => n(i) / td(i, 16) / sf(i), shield:"roman"}, // Roman
|
||||
{name:"Roman", base:8, odd:1, sort: i => n(i) / td(i, 17) / t[i], shield:"roman"}, // Roman
|
||||
{name:"Hellenic", base:7, odd:1, sort: i => n(i) / td(i, 18) / sf(i) * h[i], shield:"boeotian"}, // Greek
|
||||
{name:"Hellenic", base:7, odd:1, sort: i => n(i) / td(i, 19) / sf(i) * h[i], shield:"boeotian"}, // Greek
|
||||
{name:"Macedonian", base:7, odd:.5, sort: i => n(i) / td(i, 12) * h[i], shield:"round"}, // Greek
|
||||
{name:"Celtic", base:22, odd:1, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [6, 8]), shield:"round"},
|
||||
{name:"Germanic", base:0, odd:1, sort: i => n(i) / td(i, 10) ** .5 / bd(i, [6, 8]), shield:"round"},
|
||||
{name:"Persian", base:24, odd:.8, sort: i => n(i) / td(i, 18) * h[i], shield:"oval"}, // Iranian
|
||||
{name:"Scythian", base:24, odd:.5, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [4]), shield:"round"}, // Iranian
|
||||
{name:"Cantabrian", base: 20, odd:.5, sort: i => n(i) / td(i, 16) * h[i], shield:"oval"}, // Basque
|
||||
{name:"Estian", base: 9, odd:.2, sort: i => n(i) / td(i, 5) * t[i], shield:"pavise"}, // Finnic
|
||||
{name:"Carthaginian", base: 17, odd:.3, sort: i => n(i) / td(i, 19) / sf(i), shield:"oval"}, // Berber
|
||||
{name:"Mesopotamian", base: 23, odd:.2, sort: i => n(i) / td(i, 22) / bd(i, [1, 2, 3]), shield:"oval"} // Mesopotamian
|
||||
{name: "Roman", base: 8, odd: 1, sort: i => n(i) / td(i, 14) / t[i], shield: "roman"}, // Roman
|
||||
{name: "Roman", base: 8, odd: 1, sort: i => n(i) / td(i, 15) / sf(i), shield: "roman"}, // Roman
|
||||
{name: "Roman", base: 8, odd: 1, sort: i => n(i) / td(i, 16) / sf(i), shield: "roman"}, // Roman
|
||||
{name: "Roman", base: 8, odd: 1, sort: i => n(i) / td(i, 17) / t[i], shield: "roman"}, // Roman
|
||||
{name: "Hellenic", base: 7, odd: 1, sort: i => (n(i) / td(i, 18) / sf(i)) * h[i], shield: "boeotian"}, // Greek
|
||||
{name: "Hellenic", base: 7, odd: 1, sort: i => (n(i) / td(i, 19) / sf(i)) * h[i], shield: "boeotian"}, // Greek
|
||||
{name: "Macedonian", base: 7, odd: 0.5, sort: i => (n(i) / td(i, 12)) * h[i], shield: "round"}, // Greek
|
||||
{name: "Celtic", base: 22, odd: 1, sort: i => n(i) / td(i, 11) ** 0.5 / bd(i, [6, 8]), shield: "round"},
|
||||
{name: "Germanic", base: 0, odd: 1, sort: i => n(i) / td(i, 10) ** 0.5 / bd(i, [6, 8]), shield: "round"},
|
||||
{name: "Persian", base: 24, odd: 0.8, sort: i => (n(i) / td(i, 18)) * h[i], shield: "oval"}, // Iranian
|
||||
{name: "Scythian", base: 24, odd: 0.5, sort: i => n(i) / td(i, 11) ** 0.5 / bd(i, [4]), shield: "round"}, // Iranian
|
||||
{name: "Cantabrian", base: 20, odd: 0.5, sort: i => (n(i) / td(i, 16)) * h[i], shield: "oval"}, // Basque
|
||||
{name: "Estian", base: 9, odd: 0.2, sort: i => (n(i) / td(i, 5)) * t[i], shield: "pavise"}, // Finnic
|
||||
{name: "Carthaginian", base: 17, odd: 0.3, sort: i => n(i) / td(i, 19) / sf(i), shield: "oval"}, // Berber
|
||||
{name: "Mesopotamian", base: 23, odd: 0.2, sort: i => n(i) / td(i, 22) / bd(i, [1, 2, 3]), shield: "oval"} // Mesopotamian
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "highFantasy") {
|
||||
return [
|
||||
// fantasy races
|
||||
{name:"Quenian (Elfish)", base: 33, odd:1, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i], shield:"gondor"}, // Elves
|
||||
{name:"Eldar (Elfish)", base: 33, odd:1, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i], shield:"noldor"}, // Elves
|
||||
{name:"Trow (Dark Elfish)", base: 34, odd:.9, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i], shield:"hessen"}, // Dark Elves
|
||||
{name:"Lothian (Dark Elfish)", base: 34, odd:.3, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i], shield:"wedged"}, // Dark Elves
|
||||
{name:"Dunirr (Dwarven)", base: 35, odd:1, sort: i => n(i) + h[i], shield:"ironHills"}, // Dwarfs
|
||||
{name:"Khazadur (Dwarven)", base: 35, odd:1, sort: i => n(i) + h[i], shield:"erebor"}, // Dwarfs
|
||||
{name:"Kobold (Goblin)", base: 36, odd:1, sort: i => t[i] - s[i], shield:"moriaOrc"}, // Goblin
|
||||
{name:"Uruk (Orkish)", base: 37, odd:1, sort: i => h[i] * t[i], shield:"urukHai"}, // Orc
|
||||
{name:"Ugluk (Orkish)", base: 37, odd:.5, sort: i => h[i] * t[i] / bd(i, [1,2,10,11]), shield:"moriaOrc"}, // Orc
|
||||
{name:"Yotunn (Giants)", base: 38, odd:.7, sort: i => td(i, -10), shield:"pavise"}, // Giant
|
||||
{name:"Rake (Drakonic)", base: 39, odd:.7, sort: i => -s[i], shield:"fantasy2"}, // Draconic
|
||||
{name:"Arago (Arachnid)", base: 40, odd:.7, sort: i => t[i] - s[i], shield:"horsehead2"}, // Arachnid
|
||||
{name:"Aj'Snaga (Serpents)", base: 41, odd:.7, sort: i => n(i) / bd(i, [12], 10), shield:"fantasy1"}, // Serpents
|
||||
{name: "Quenian (Elfish)", base: 33, odd: 1, sort: i => (n(i) / bd(i, [6, 7, 8, 9], 10)) * t[i], shield: "gondor"}, // Elves
|
||||
{name: "Eldar (Elfish)", base: 33, odd: 1, sort: i => (n(i) / bd(i, [6, 7, 8, 9], 10)) * t[i], shield: "noldor"}, // Elves
|
||||
{name: "Trow (Dark Elfish)", base: 34, odd: 0.9, sort: i => (n(i) / bd(i, [7, 8, 9, 12], 10)) * t[i], shield: "hessen"}, // Dark Elves
|
||||
{name: "Lothian (Dark Elfish)", base: 34, odd: 0.3, sort: i => (n(i) / bd(i, [7, 8, 9, 12], 10)) * t[i], shield: "wedged"}, // Dark Elves
|
||||
{name: "Dunirr (Dwarven)", base: 35, odd: 1, sort: i => n(i) + h[i], shield: "ironHills"}, // Dwarfs
|
||||
{name: "Khazadur (Dwarven)", base: 35, odd: 1, sort: i => n(i) + h[i], shield: "erebor"}, // Dwarfs
|
||||
{name: "Kobold (Goblin)", base: 36, odd: 1, sort: i => t[i] - s[i], shield: "moriaOrc"}, // Goblin
|
||||
{name: "Uruk (Orkish)", base: 37, odd: 1, sort: i => h[i] * t[i], shield: "urukHai"}, // Orc
|
||||
{name: "Ugluk (Orkish)", base: 37, odd: 0.5, sort: i => (h[i] * t[i]) / bd(i, [1, 2, 10, 11]), shield: "moriaOrc"}, // Orc
|
||||
{name: "Yotunn (Giants)", base: 38, odd: 0.7, sort: i => td(i, -10), shield: "pavise"}, // Giant
|
||||
{name: "Rake (Drakonic)", base: 39, odd: 0.7, sort: i => -s[i], shield: "fantasy2"}, // Draconic
|
||||
{name: "Arago (Arachnid)", base: 40, odd: 0.7, sort: i => t[i] - s[i], shield: "horsehead2"}, // Arachnid
|
||||
{name: "Aj'Snaga (Serpents)", base: 41, odd: 0.7, sort: i => n(i) / bd(i, [12], 10), shield: "fantasy1"}, // Serpents
|
||||
// fantasy human
|
||||
{name:"Anor (Human)", base:32, odd:1, sort: i => n(i) / td(i, 10), shield:"fantasy5"},
|
||||
{name:"Dail (Human)", base:32, odd:1, sort: i => n(i) / td(i, 13), shield:"roman"},
|
||||
{name:"Rohand (Human)", base:16, odd:1, sort: i => n(i) / td(i, 16), shield:"round"},
|
||||
{name:"Dulandir (Human)", base:31, odd:1, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i], shield:"easterling"},
|
||||
{name: "Anor (Human)", base: 32, odd: 1, sort: i => n(i) / td(i, 10), shield: "fantasy5"},
|
||||
{name: "Dail (Human)", base: 32, odd: 1, sort: i => n(i) / td(i, 13), shield: "roman"},
|
||||
{name: "Rohand (Human)", base: 16, odd: 1, sort: i => n(i) / td(i, 16), shield: "round"},
|
||||
{name: "Dulandir (Human)", base: 31, odd: 1, sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: "easterling"}
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "darkFantasy") {
|
||||
return [
|
||||
// common real-world English
|
||||
{name:"Angshire", base:1, odd:1, sort: i => n(i) / td(i, 10) / sf(i), shield:"heater"},
|
||||
{name:"Enlandic", base:1, odd:1, sort: i => n(i) / td(i, 12), shield:"heater"},
|
||||
{name:"Westen", base:1, odd:1, sort: i => n(i) / td(i, 10), shield:"heater"},
|
||||
{name:"Nortumbic", base:1, odd:1, sort: i => n(i) / td(i, 7), shield:"heater"},
|
||||
{name:"Mercian", base:1, odd:1, sort: i => n(i) / td(i, 9), shield:"heater"},
|
||||
{name:"Kentian", base:1, odd:1, sort: i => n(i) / td(i, 12), shield:"heater"},
|
||||
{name: "Angshire", base: 1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield: "heater"},
|
||||
{name: "Enlandic", base: 1, odd: 1, sort: i => n(i) / td(i, 12), shield: "heater"},
|
||||
{name: "Westen", base: 1, odd: 1, sort: i => n(i) / td(i, 10), shield: "heater"},
|
||||
{name: "Nortumbic", base: 1, odd: 1, sort: i => n(i) / td(i, 7), shield: "heater"},
|
||||
{name: "Mercian", base: 1, odd: 1, sort: i => n(i) / td(i, 9), shield: "heater"},
|
||||
{name: "Kentian", base: 1, odd: 1, sort: i => n(i) / td(i, 12), shield: "heater"},
|
||||
// rare real-world western
|
||||
{name:"Norse", base:6, odd:.7, sort: i => n(i) / td(i, 5) / sf(i), shield:"oldFrench"},
|
||||
{name:"Schwarzen", base:0, odd:.3, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield:"gonfalon"},
|
||||
{name:"Luarian", base:2, odd:.3, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield:"oldFrench"},
|
||||
{name:"Hetallian", base:3, odd:.3, sort: i => n(i) / td(i, 15), shield:"oval"},
|
||||
{name:"Astellian", base:4, odd:.3, sort: i => n(i) / td(i, 16), shield:"spanish"},
|
||||
{name: "Norse", base: 6, odd: 0.7, sort: i => n(i) / td(i, 5) / sf(i), shield: "oldFrench"},
|
||||
{name: "Schwarzen", base: 0, odd: 0.3, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield: "gonfalon"},
|
||||
{name: "Luarian", base: 2, odd: 0.3, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield: "oldFrench"},
|
||||
{name: "Hetallian", base: 3, odd: 0.3, sort: i => n(i) / td(i, 15), shield: "oval"},
|
||||
{name: "Astellian", base: 4, odd: 0.3, sort: i => n(i) / td(i, 16), shield: "spanish"},
|
||||
// rare real-world exotic
|
||||
{name:"Kiswaili", base:28, odd:.05, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield:"vesicaPiscis"},
|
||||
{name:"Yoruba", base:21, odd:.05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield:"vesicaPiscis"},
|
||||
{name:"Koryo", base:10, odd:.05, sort: i => n(i) / td(i, 12) / t[i], shield:"round"},
|
||||
{name:"Hantzu", base:11, odd:.05, sort: i => n(i) / td(i, 13), shield:"banner"},
|
||||
{name:"Yamoto", base:12, odd:.05, sort: i => n(i) / td(i, 15) / t[i], shield:"round"},
|
||||
{name:"Guantzu", base:30, odd:.05, sort: i => n(i) / td(i, 17), shield:"banner"},
|
||||
{name:"Ulus", base:31, odd:.05, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i], shield:"banner"},
|
||||
{name:"Turan", base: 16, odd:.05, sort: i => n(i) / td(i, 12), shield:"round"},
|
||||
{name:"Berberan", base: 17, odd:.05, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i], shield:"round"},
|
||||
{name:"Eurabic", base: 18, odd:.05, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i], shield:"round"},
|
||||
{name:"Slovan", base:5, odd:.05, sort: i => n(i) / td(i, 6) * t[i], shield:"round"},
|
||||
{name:"Keltan", base: 22, odd:.1, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [6, 8]), shield:"vesicaPiscis"},
|
||||
{name:"Elladan", base:7, odd:.2, sort: i => n(i) / td(i, 18) / sf(i) * h[i], shield:"boeotian"},
|
||||
{name:"Romian", base:8, odd:.2, sort: i => n(i) / td(i, 14) / t[i], shield:"roman"},
|
||||
{name: "Kiswaili", base: 28, odd: 0.05, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield: "vesicaPiscis"},
|
||||
{name: "Yoruba", base: 21, odd: 0.05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield: "vesicaPiscis"},
|
||||
{name: "Koryo", base: 10, odd: 0.05, sort: i => n(i) / td(i, 12) / t[i], shield: "round"},
|
||||
{name: "Hantzu", base: 11, odd: 0.05, sort: i => n(i) / td(i, 13), shield: "banner"},
|
||||
{name: "Yamoto", base: 12, odd: 0.05, sort: i => n(i) / td(i, 15) / t[i], shield: "round"},
|
||||
{name: "Guantzu", base: 30, odd: 0.05, sort: i => n(i) / td(i, 17), shield: "banner"},
|
||||
{name: "Ulus", base: 31, odd: 0.05, sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: "banner"},
|
||||
{name: "Turan", base: 16, odd: 0.05, sort: i => n(i) / td(i, 12), shield: "round"},
|
||||
{name: "Berberan", base: 17, odd: 0.05, sort: i => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i], shield: "round"},
|
||||
{name: "Eurabic", base: 18, odd: 0.05, sort: i => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: "round"},
|
||||
{name: "Slovan", base: 5, odd: 0.05, sort: i => (n(i) / td(i, 6)) * t[i], shield: "round"},
|
||||
{name: "Keltan", base: 22, odd: 0.1, sort: i => n(i) / td(i, 11) ** 0.5 / bd(i, [6, 8]), shield: "vesicaPiscis"},
|
||||
{name: "Elladan", base: 7, odd: 0.2, sort: i => (n(i) / td(i, 18) / sf(i)) * h[i], shield: "boeotian"},
|
||||
{name: "Romian", base: 8, odd: 0.2, sort: i => n(i) / td(i, 14) / t[i], shield: "roman"},
|
||||
// fantasy races
|
||||
{name:"Eldar", base: 33, odd:.5, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i], shield:"fantasy5"}, // Elves
|
||||
{name:"Trow", base: 34, odd:.8, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i], shield:"hessen"}, // Dark Elves
|
||||
{name:"Durinn", base: 35, odd:.8, sort: i => n(i) + h[i], shield:"erebor"}, // Dwarven
|
||||
{name:"Kobblin", base: 36, odd:.8, sort: i => t[i] - s[i], shield:"moriaOrc"}, // Goblin
|
||||
{name:"Uruk", base: 37, odd:.8, sort: i => h[i] * t[i] / bd(i, [1,2,10,11]), shield:"urukHai"}, // Orc
|
||||
{name:"Yotunn", base: 38, odd:.8, sort: i => td(i, -10), shield:"pavise"}, // Giant
|
||||
{name:"Drake", base: 39, odd:.9, sort: i => -s[i], shield:"fantasy2"}, // Draconic
|
||||
{name:"Rakhnid", base: 40, odd:.9, sort: i => t[i] - s[i], shield:"horsehead2"}, // Arachnid
|
||||
{name:"Aj'Snaga", base: 41, odd:.9, sort: i => n(i) / bd(i, [12], 10), shield:"fantasy1"}, // Serpents
|
||||
]
|
||||
{name: "Eldar", base: 33, odd: 0.5, sort: i => (n(i) / bd(i, [6, 7, 8, 9], 10)) * t[i], shield: "fantasy5"}, // Elves
|
||||
{name: "Trow", base: 34, odd: 0.8, sort: i => (n(i) / bd(i, [7, 8, 9, 12], 10)) * t[i], shield: "hessen"}, // Dark Elves
|
||||
{name: "Durinn", base: 35, odd: 0.8, sort: i => n(i) + h[i], shield: "erebor"}, // Dwarven
|
||||
{name: "Kobblin", base: 36, odd: 0.8, sort: i => t[i] - s[i], shield: "moriaOrc"}, // Goblin
|
||||
{name: "Uruk", base: 37, odd: 0.8, sort: i => (h[i] * t[i]) / bd(i, [1, 2, 10, 11]), shield: "urukHai"}, // Orc
|
||||
{name: "Yotunn", base: 38, odd: 0.8, sort: i => td(i, -10), shield: "pavise"}, // Giant
|
||||
{name: "Drake", base: 39, odd: 0.9, sort: i => -s[i], shield: "fantasy2"}, // Draconic
|
||||
{name: "Rakhnid", base: 40, odd: 0.9, sort: i => t[i] - s[i], shield: "horsehead2"}, // Arachnid
|
||||
{name: "Aj'Snaga", base: 41, odd: 0.9, sort: i => n(i) / bd(i, [12], 10), shield: "fantasy1"} // Serpents
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "random") {
|
||||
return d3.range(count).map(function() {
|
||||
const rnd = rand(nameBases.length-1);
|
||||
return d3.range(count).map(function () {
|
||||
const rnd = rand(nameBases.length - 1);
|
||||
const name = Names.getBaseShort(rnd);
|
||||
return {name, base:rnd, odd:1, shield:getRandomShield()}
|
||||
return {name, base: rnd, odd: 1, shield: getRandomShield()};
|
||||
});
|
||||
}
|
||||
|
||||
// all-world
|
||||
return [
|
||||
{name:"Shwazen", base:0, odd:.7, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield:"hessen"},
|
||||
{name:"Angshire", base:1, odd:1, sort: i => n(i) / td(i, 10) / sf(i), shield:"heater"},
|
||||
{name:"Luari", base:2, odd:.6, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield:"oldFrench"},
|
||||
{name:"Tallian", base:3, odd:.6, sort: i => n(i) / td(i, 15), shield:"horsehead2"},
|
||||
{name:"Astellian", base:4, odd:.6, sort: i => n(i) / td(i, 16), shield:"spanish"},
|
||||
{name:"Slovan", base:5, odd:.7, sort: i => n(i) / td(i, 6) * t[i], shield:"round"},
|
||||
{name:"Norse", base:6, odd:.7, sort: i => n(i) / td(i, 5), shield:"heater"},
|
||||
{name:"Elladan", base:7, odd:.7, sort: i => n(i) / td(i, 18) * h[i], shield:"boeotian"},
|
||||
{name:"Romian", base:8, odd:.7, sort: i => n(i) / td(i, 15), shield:"roman"},
|
||||
{name:"Soumi", base:9, odd:.3, sort: i => n(i) / td(i, 5) / bd(i, [9]) * t[i], shield:"pavise"},
|
||||
{name:"Koryo", base:10, odd:.1, sort: i => n(i) / td(i, 12) / t[i], shield:"round"},
|
||||
{name:"Hantzu", base:11, odd:.1, sort: i => n(i) / td(i, 13), shield:"banner"},
|
||||
{name:"Yamoto", base:12, odd:.1, sort: i => n(i) / td(i, 15) / t[i], shield:"round"},
|
||||
{name:"Portuzian", base:13, odd:.4, sort: i => n(i) / td(i, 17) / sf(i), shield:"spanish"},
|
||||
{name:"Nawatli", base:14, odd:.1, sort: i => h[i] / td(i, 18) / bd(i, [7]), shield:"square"},
|
||||
{name:"Vengrian", base: 15, odd:.2, sort: i => n(i) / td(i, 11) / bd(i, [4]) * t[i], shield:"wedged"},
|
||||
{name:"Turchian", base: 16, odd:.2, sort: i => n(i) / td(i, 13), shield:"round"},
|
||||
{name:"Berberan", base: 17, odd:.1, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i], shield:"round"},
|
||||
{name:"Eurabic", base: 18, odd:.2, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i], shield:"round"},
|
||||
{name:"Inuk", base: 19, odd:.05, sort: i => td(i, -1) / bd(i, [10, 11]) / sf(i), shield:"square"},
|
||||
{name:"Euskati", base: 20, odd:.05, sort: i => n(i) / td(i, 15) * h[i], shield:"spanish"},
|
||||
{name:"Yoruba", base: 21, odd:.05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield:"vesicaPiscis"},
|
||||
{name:"Keltan", base: 22, odd:.05, sort: i => n(i) / td(i, 11) / bd(i, [6, 8]) * t[i], shield:"vesicaPiscis"},
|
||||
{name:"Efratic", base: 23, odd:.05, sort: i => n(i) / td(i, 22) * t[i], shield:"diamond"},
|
||||
{name:"Tehrani", base: 24, odd:.1, sort: i => n(i) / td(i, 18) * h[i], shield:"round"},
|
||||
{name:"Maui", base: 25, odd:.05, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield:"round"},
|
||||
{name:"Carnatic", base: 26, odd:.05, sort: i => n(i) / td(i, 26), shield:"round"},
|
||||
{name:"Inqan", base: 27, odd:.05, sort: i => h[i] / td(i, 13), shield:"square"},
|
||||
{name:"Kiswaili", base: 28, odd:.1, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield:"vesicaPiscis"},
|
||||
{name:"Vietic", base: 29, odd:.1, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield:"banner"},
|
||||
{name:"Guantzu", base:30, odd:.1, sort: i => n(i) / td(i, 17), shield:"banner"},
|
||||
{name:"Ulus", base:31, odd:.1, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i], shield:"banner"}
|
||||
{name: "Shwazen", base: 0, odd: 0.7, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield: "hessen"},
|
||||
{name: "Angshire", base: 1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield: "heater"},
|
||||
{name: "Luari", base: 2, odd: 0.6, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield: "oldFrench"},
|
||||
{name: "Tallian", base: 3, odd: 0.6, sort: i => n(i) / td(i, 15), shield: "horsehead2"},
|
||||
{name: "Astellian", base: 4, odd: 0.6, sort: i => n(i) / td(i, 16), shield: "spanish"},
|
||||
{name: "Slovan", base: 5, odd: 0.7, sort: i => (n(i) / td(i, 6)) * t[i], shield: "round"},
|
||||
{name: "Norse", base: 6, odd: 0.7, sort: i => n(i) / td(i, 5), shield: "heater"},
|
||||
{name: "Elladan", base: 7, odd: 0.7, sort: i => (n(i) / td(i, 18)) * h[i], shield: "boeotian"},
|
||||
{name: "Romian", base: 8, odd: 0.7, sort: i => n(i) / td(i, 15), shield: "roman"},
|
||||
{name: "Soumi", base: 9, odd: 0.3, sort: i => (n(i) / td(i, 5) / bd(i, [9])) * t[i], shield: "pavise"},
|
||||
{name: "Koryo", base: 10, odd: 0.1, sort: i => n(i) / td(i, 12) / t[i], shield: "round"},
|
||||
{name: "Hantzu", base: 11, odd: 0.1, sort: i => n(i) / td(i, 13), shield: "banner"},
|
||||
{name: "Yamoto", base: 12, odd: 0.1, sort: i => n(i) / td(i, 15) / t[i], shield: "round"},
|
||||
{name: "Portuzian", base: 13, odd: 0.4, sort: i => n(i) / td(i, 17) / sf(i), shield: "spanish"},
|
||||
{name: "Nawatli", base: 14, odd: 0.1, sort: i => h[i] / td(i, 18) / bd(i, [7]), shield: "square"},
|
||||
{name: "Vengrian", base: 15, odd: 0.2, sort: i => (n(i) / td(i, 11) / bd(i, [4])) * t[i], shield: "wedged"},
|
||||
{name: "Turchian", base: 16, odd: 0.2, sort: i => n(i) / td(i, 13), shield: "round"},
|
||||
{name: "Berberan", base: 17, odd: 0.1, sort: i => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i], shield: "round"},
|
||||
{name: "Eurabic", base: 18, odd: 0.2, sort: i => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: "round"},
|
||||
{name: "Inuk", base: 19, odd: 0.05, sort: i => td(i, -1) / bd(i, [10, 11]) / sf(i), shield: "square"},
|
||||
{name: "Euskati", base: 20, odd: 0.05, sort: i => (n(i) / td(i, 15)) * h[i], shield: "spanish"},
|
||||
{name: "Yoruba", base: 21, odd: 0.05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield: "vesicaPiscis"},
|
||||
{name: "Keltan", base: 22, odd: 0.05, sort: i => (n(i) / td(i, 11) / bd(i, [6, 8])) * t[i], shield: "vesicaPiscis"},
|
||||
{name: "Efratic", base: 23, odd: 0.05, sort: i => (n(i) / td(i, 22)) * t[i], shield: "diamond"},
|
||||
{name: "Tehrani", base: 24, odd: 0.1, sort: i => (n(i) / td(i, 18)) * h[i], shield: "round"},
|
||||
{name: "Maui", base: 25, odd: 0.05, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield: "round"},
|
||||
{name: "Carnatic", base: 26, odd: 0.05, sort: i => n(i) / td(i, 26), shield: "round"},
|
||||
{name: "Inqan", base: 27, odd: 0.05, sort: i => h[i] / td(i, 13), shield: "square"},
|
||||
{name: "Kiswaili", base: 28, odd: 0.1, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield: "vesicaPiscis"},
|
||||
{name: "Vietic", base: 29, odd: 0.1, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield: "banner"},
|
||||
{name: "Guantzu", base: 30, odd: 0.1, sort: i => n(i) / td(i, 17), shield: "banner"},
|
||||
{name: "Ulus", base: 31, odd: 0.1, sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: "banner"}
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
// expand cultures across the map (Dijkstra-like algorithm)
|
||||
const expand = function() {
|
||||
TIME && console.time('expandCultures');
|
||||
const expand = function () {
|
||||
TIME && console.time("expandCultures");
|
||||
cells = pack.cells;
|
||||
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
pack.cultures.forEach(function(c) {
|
||||
pack.cultures.forEach(function (c) {
|
||||
if (!c.i || c.removed) return;
|
||||
queue.queue({e:c.center, p:0, c:c.i});
|
||||
queue.queue({e: c.center, p: 0, c: c.i});
|
||||
});
|
||||
|
||||
const neutral = cells.i.length / 5000 * 3000 * neutralInput.value; // limit cost for culture growth
|
||||
const neutral = (cells.i.length / 5000) * 3000 * neutralInput.value; // limit cost for culture growth
|
||||
const cost = [];
|
||||
while (queue.length) {
|
||||
const next = queue.dequeue(), n = next.e, p = next.p, c = next.c;
|
||||
const next = queue.dequeue(),
|
||||
n = next.e,
|
||||
p = next.p,
|
||||
c = next.c;
|
||||
const type = pack.cultures[c].type;
|
||||
cells.c[n].forEach(function(e) {
|
||||
cells.c[n].forEach(function (e) {
|
||||
const biome = cells.biome[e];
|
||||
const biomeCost = getBiomeCost(c, biome, type);
|
||||
const biomeChangeCost = biome === cells.biome[n] ? 0 : 20; // penalty on biome change
|
||||
|
|
@ -375,13 +402,13 @@
|
|||
if (!cost[e] || totalCost < cost[e]) {
|
||||
if (cells.s[e] > 0) cells.culture[e] = c; // assign culture to populated cell
|
||||
cost[e] = totalCost;
|
||||
queue.queue({e, p:totalCost, c});
|
||||
queue.queue({e, p: totalCost, c});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TIME && console.timeEnd('expandCultures');
|
||||
}
|
||||
TIME && console.timeEnd("expandCultures");
|
||||
};
|
||||
|
||||
function getBiomeCost(c, biome, type) {
|
||||
if (cells.biome[pack.cultures[c].center] === biome) return 10; // tiny penalty for native biome
|
||||
|
|
@ -391,7 +418,8 @@
|
|||
}
|
||||
|
||||
function getHeightCost(i, h, type) {
|
||||
const f = pack.features[cells.f[i]], a = cells.area[i];
|
||||
const f = pack.features[cells.f[i]],
|
||||
a = cells.area[i];
|
||||
if (type === "Lake" && f.type === "lake") return 10; // no lake crossing penalty for Lake cultures
|
||||
if (type === "Naval" && h < 20) return a * 2; // low sea/lake crossing penalty for Naval cultures
|
||||
if (type === "Nomadic" && h < 20) return a * 50; // giant sea/lake crossing penalty for Nomads
|
||||
|
|
@ -407,7 +435,7 @@
|
|||
function getRiverCost(r, i, type) {
|
||||
if (type === "River") return r ? 0 : 100; // penalty for river cultures
|
||||
if (!r) return 0; // no penalty for others if there is no river
|
||||
return Math.min(Math.max(cells.fl[i] / 10, 20), 100) // river penalty from 20 to 100 based on flux
|
||||
return Math.min(Math.max(cells.fl[i] / 10, 20), 100); // river penalty from 20 to 100 based on flux
|
||||
}
|
||||
|
||||
function getTypeCost(t, type) {
|
||||
|
|
@ -417,11 +445,10 @@
|
|||
return 0;
|
||||
}
|
||||
|
||||
const getRandomShield = function() {
|
||||
const getRandomShield = function () {
|
||||
const type = rw(COA.shields.types);
|
||||
return rw(COA.shields[type]);
|
||||
}
|
||||
};
|
||||
|
||||
return {generate, add, expand, getDefault, getRandomShield};
|
||||
|
||||
})));
|
||||
})();
|
||||
|
|
|
|||
141
modules/fonts.js
Normal file
141
modules/fonts.js
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// helper finctions to work with fonts
|
||||
|
||||
async function addFonts(url) {
|
||||
$("head").append('<link rel="stylesheet" type="text/css" href="' + url + '">');
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
const text = await resp.text();
|
||||
let s = document.createElement("style");
|
||||
s.innerHTML = text;
|
||||
document.head.appendChild(s);
|
||||
let styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0];
|
||||
let FontRule = rule_1 => {
|
||||
let family = rule_1.style.getPropertyValue("font-family");
|
||||
let font = family.replace(/['"]+/g, "").replace(/ /g, "+");
|
||||
let weight = rule_1.style.getPropertyValue("font-weight");
|
||||
if (weight && weight !== "400") font += ":" + weight;
|
||||
if (fonts.indexOf(font) == -1) {
|
||||
fonts.push(font);
|
||||
fetched++;
|
||||
}
|
||||
};
|
||||
let fetched = 0;
|
||||
for (let r of styleSheet.cssRules) {
|
||||
FontRule(r);
|
||||
}
|
||||
document.head.removeChild(s);
|
||||
return fetched;
|
||||
} catch (err) {
|
||||
return ERROR && console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function loadUsedFonts() {
|
||||
const fontsInUse = getFontsList(svg);
|
||||
const fontsToLoad = fontsInUse.filter(font => !fonts.includes(font));
|
||||
if (fontsToLoad?.length) {
|
||||
const url = "https://fonts.googleapis.com/css?family=" + fontsToLoad.join("|");
|
||||
addFonts(url);
|
||||
}
|
||||
}
|
||||
|
||||
function getFontsList(svg) {
|
||||
const fontsInUse = [];
|
||||
|
||||
svg.selectAll("#labels > g").each(function () {
|
||||
if (!this.hasChildNodes()) return;
|
||||
const font = this.dataset.font;
|
||||
if (font) fontsInUse.push(font);
|
||||
});
|
||||
if (legend?.node()?.hasChildNodes()) fontsInUse.push(legend.attr("data-font"));
|
||||
|
||||
return [...new Set(fontsInUse)];
|
||||
}
|
||||
|
||||
// code from Kaiido's answer https://stackoverflow.com/questions/42402584/how-to-use-google-fonts-in-canvas-when-drawing-dom-objects-in-svg
|
||||
function GFontToDataURI(url) {
|
||||
if (!url) return Promise.resolve();
|
||||
return fetch(url) // first fecth the embed stylesheet page
|
||||
.then(resp => resp.text()) // we only need the text of it
|
||||
.then(text => {
|
||||
let s = document.createElement("style");
|
||||
s.innerHTML = text;
|
||||
document.head.appendChild(s);
|
||||
const styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0];
|
||||
|
||||
const FontRule = rule => {
|
||||
const src = rule.style.getPropertyValue("src");
|
||||
const url = src ? src.split("url(")[1].split(")")[0] : "";
|
||||
return {rule, src, url: url.substring(url.length - 1, 1)};
|
||||
};
|
||||
const fontProms = [];
|
||||
|
||||
for (const r of styleSheet.cssRules) {
|
||||
let fR = FontRule(r);
|
||||
if (!fR.url) continue;
|
||||
|
||||
fontProms.push(
|
||||
fetch(fR.url) // fetch the actual font-file (.woff)
|
||||
.then(resp => resp.blob())
|
||||
.then(blob => {
|
||||
return new Promise(resolve => {
|
||||
let f = new FileReader();
|
||||
f.onload = e => resolve(f.result);
|
||||
f.readAsDataURL(blob);
|
||||
});
|
||||
})
|
||||
.then(dataURL => fR.rule.cssText.replace(fR.url, dataURL))
|
||||
);
|
||||
}
|
||||
document.head.removeChild(s); // clean up
|
||||
return Promise.all(fontProms); // wait for all this has been done
|
||||
});
|
||||
}
|
||||
|
||||
// fetch default fonts if not done before
|
||||
function loadDefaultFonts() {
|
||||
if (!$('link[href="fonts.css"]').length) {
|
||||
$("head").append('<link rel="stylesheet" type="text/css" href="fonts.css">');
|
||||
const fontsToAdd = ["Amatic+SC:700", "IM+Fell+English", "Great+Vibes", "MedievalSharp", "Metamorphous", "Nova+Script", "Uncial+Antiqua", "Underdog", "Caesar+Dressing", "Bitter", "Yellowtail", "Montez", "Shadows+Into+Light", "Fredericka+the+Great", "Orbitron", "Dancing+Script:700", "Architects+Daughter", "Kaushan+Script", "Gloria+Hallelujah", "Satisfy", "Comfortaa:700", "Cinzel"];
|
||||
fontsToAdd.forEach(function (f) {
|
||||
if (fonts.indexOf(f) === -1) fonts.push(f);
|
||||
});
|
||||
updateFontOptions();
|
||||
}
|
||||
}
|
||||
|
||||
function fetchFonts(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (url === "") return tip("Use a direct link to any @font-face declaration or just font name to fetch from Google Fonts");
|
||||
|
||||
if (url.indexOf("http") === -1) {
|
||||
url = url.replace(url.charAt(0), url.charAt(0).toUpperCase()).split(" ").join("+");
|
||||
url = "https://fonts.googleapis.com/css?family=" + url;
|
||||
}
|
||||
|
||||
addFonts(url).then(fetched => {
|
||||
if (fetched === undefined) return tip("Cannot fetch font for this value!", false, "error");
|
||||
if (fetched === 0) return tip("Already in the fonts list!", false, "error");
|
||||
|
||||
updateFontOptions();
|
||||
if (fetched === 1) {
|
||||
tip("Font " + fonts[fonts.length - 1] + " is fetched");
|
||||
} else if (fetched > 1) {
|
||||
tip(fetched + " fonts are added to the list");
|
||||
}
|
||||
resolve(fetched);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update font list for Label and Burg Editors
|
||||
function updateFontOptions() {
|
||||
styleSelectFont.innerHTML = "";
|
||||
for (let i = 0; i < fonts.length; i++) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = i;
|
||||
const font = fonts[i].split(":")[0].replace(/\+/g, " ");
|
||||
opt.style.fontFamily = opt.innerHTML = font;
|
||||
styleSelectFont.add(opt);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.HeightmapGenerator = factory());
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
window.HeightmapGenerator = (function () {
|
||||
let cells, p;
|
||||
|
||||
const generate = function () {
|
||||
|
|
@ -12,262 +10,63 @@
|
|||
cells.h = new Uint8Array(grid.points.length);
|
||||
|
||||
const template = document.getElementById("templateInput").value;
|
||||
switch (template) {
|
||||
case "Volcano":
|
||||
templateVolcano();
|
||||
break;
|
||||
case "High Island":
|
||||
templateHighIsland();
|
||||
break;
|
||||
case "Low Island":
|
||||
templateLowIsland();
|
||||
break;
|
||||
case "Continents":
|
||||
templateContinents();
|
||||
break;
|
||||
case "Archipelago":
|
||||
templateArchipelago();
|
||||
break;
|
||||
case "Atoll":
|
||||
templateAtoll();
|
||||
break;
|
||||
case "Mediterranean":
|
||||
templateMediterranean();
|
||||
break;
|
||||
case "Peninsula":
|
||||
templatePeninsula();
|
||||
break;
|
||||
case "Pangea":
|
||||
templatePangea();
|
||||
break;
|
||||
case "Isthmus":
|
||||
templateIsthmus();
|
||||
break;
|
||||
case "Shattered":
|
||||
templateShattered();
|
||||
break;
|
||||
const templateString = HeightmapTemplates[template];
|
||||
const steps = templateString.split("\n");
|
||||
|
||||
if (!steps.length) throw new Error(`Heightmap template: no steps. Template: ${template}. Steps: ${steps}`);
|
||||
|
||||
for (const step of steps) {
|
||||
const elements = step.trim().split(" ");
|
||||
if (elements.length < 2) throw new Error(`Heightmap template: steps < 2. Template: ${template}. Step: ${elements}`);
|
||||
addStep(...elements);
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("generateHeightmap");
|
||||
};
|
||||
|
||||
// parse template step
|
||||
function addStep(a1, a2, a3, a4, a5) {
|
||||
if (a1 === "Hill") return addHill(a2, a3, a4, a5);
|
||||
if (a1 === "Pit") return addPit(a2, a3, a4, a5);
|
||||
if (a1 === "Range") return addRange(a2, a3, a4, a5);
|
||||
if (a1 === "Trough") return addTrough(a2, a3, a4, a5);
|
||||
if (a1 === "Strait") return addStrait(a2, a3);
|
||||
if (a1 === "Add") return modify(a3, a2, 1);
|
||||
if (a1 === "Multiply") return modify(a3, 0, a2);
|
||||
if (a1 === "Add") return modify(a3, +a2, 1);
|
||||
if (a1 === "Multiply") return modify(a3, 0, +a2);
|
||||
if (a1 === "Smooth") return smooth(a2);
|
||||
}
|
||||
|
||||
// Heighmap Template: Volcano
|
||||
function templateVolcano() {
|
||||
addStep("Hill", "1", "90-100", "44-56", "40-60");
|
||||
addStep("Multiply", 0.8, "50-100");
|
||||
addStep("Range", "1.5", "30-55", "45-55", "40-60");
|
||||
addStep("Smooth", 2);
|
||||
addStep("Hill", "1.5", "25-35", "25-30", "20-75");
|
||||
addStep("Hill", "1", "25-35", "75-80", "25-75");
|
||||
addStep("Hill", "0.5", "20-25", "10-15", "20-25");
|
||||
}
|
||||
|
||||
// Heighmap Template: High Island
|
||||
function templateHighIsland() {
|
||||
addStep("Hill", "1", "90-100", "65-75", "47-53");
|
||||
addStep("Add", 5, "all");
|
||||
addStep("Hill", "6", "20-23", "25-55", "45-55");
|
||||
addStep("Range", "1", "40-50", "45-55", "45-55");
|
||||
addStep("Smooth", 2);
|
||||
addStep("Trough", "2-3", "20-30", "20-30", "20-30");
|
||||
addStep("Trough", "2-3", "20-30", "60-80", "70-80");
|
||||
addStep("Hill", "1", "10-15", "60-60", "50-50");
|
||||
addStep("Hill", "1.5", "13-16", "15-20", "20-75");
|
||||
addStep("Multiply", 0.8, "20-100");
|
||||
addStep("Range", "1.5", "30-40", "15-85", "30-40");
|
||||
addStep("Range", "1.5", "30-40", "15-85", "60-70");
|
||||
addStep("Pit", "2-3", "10-15", "15-85", "20-80");
|
||||
}
|
||||
|
||||
// Heighmap Template: Low Island
|
||||
function templateLowIsland() {
|
||||
addStep("Hill", "1", "90-99", "60-80", "45-55");
|
||||
addStep("Hill", "4-5", "25-35", "20-65", "40-60");
|
||||
addStep("Range", "1", "40-50", "45-55", "45-55");
|
||||
addStep("Smooth", 3);
|
||||
addStep("Trough", "1.5", "20-30", "15-85", "20-30");
|
||||
addStep("Trough", "1.5", "20-30", "15-85", "70-80");
|
||||
addStep("Hill", "1.5", "10-15", "5-15", "20-80");
|
||||
addStep("Hill", "1", "10-15", "85-95", "70-80");
|
||||
addStep("Pit", "3-5", "10-15", "15-85", "20-80");
|
||||
addStep("Multiply", 0.4, "20-100");
|
||||
}
|
||||
|
||||
// Heighmap Template: Continents
|
||||
function templateContinents() {
|
||||
addStep("Hill", "1", "80-85", "75-80", "40-60");
|
||||
addStep("Hill", "1", "80-85", "20-25", "40-60");
|
||||
addStep("Multiply", 0.22, "20-100");
|
||||
addStep("Hill", "5-6", "15-20", "25-75", "20-82");
|
||||
addStep("Range", ".8", "30-60", "5-15", "20-45");
|
||||
addStep("Range", ".8", "30-60", "5-15", "55-80");
|
||||
addStep("Range", "0-3", "30-60", "80-90", "20-80");
|
||||
addStep("Trough", "3-4", "15-20", "15-85", "20-80");
|
||||
addStep("Strait", "2", "vertical");
|
||||
addStep("Smooth", 2);
|
||||
addStep("Trough", "1-2", "5-10", "45-55", "45-55");
|
||||
addStep("Pit", "3-4", "10-15", "15-85", "20-80");
|
||||
addStep("Hill", "1", "5-10", "40-60", "40-60");
|
||||
}
|
||||
|
||||
// Heighmap Template: Archipelago
|
||||
function templateArchipelago() {
|
||||
addStep("Add", 11, "all");
|
||||
addStep("Range", "2-3", "40-60", "20-80", "20-80");
|
||||
addStep("Hill", "5", "15-20", "10-90", "30-70");
|
||||
addStep("Hill", "2", "10-15", "10-30", "20-80");
|
||||
addStep("Hill", "2", "10-15", "60-90", "20-80");
|
||||
addStep("Smooth", 3);
|
||||
addStep("Trough", "10", "20-30", "5-95", "5-95");
|
||||
addStep("Strait", "2", "vertical");
|
||||
addStep("Strait", "2", "horizontal");
|
||||
}
|
||||
|
||||
// Heighmap Template: Atoll
|
||||
function templateAtoll() {
|
||||
addStep("Hill", "1", "75-80", "50-60", "45-55");
|
||||
addStep("Hill", "1.5", "30-50", "25-75", "30-70");
|
||||
addStep("Hill", ".5", "30-50", "25-35", "30-70");
|
||||
addStep("Smooth", 1);
|
||||
addStep("Multiply", 0.2, "25-100");
|
||||
addStep("Hill", ".5", "10-20", "50-55", "48-52");
|
||||
}
|
||||
|
||||
// Heighmap Template: Mediterranean
|
||||
function templateMediterranean() {
|
||||
addStep("Range", "3-4", "30-50", "0-100", "0-10");
|
||||
addStep("Range", "3-4", "30-50", "0-100", "90-100");
|
||||
addStep("Hill", "5-6", "30-70", "0-100", "0-5");
|
||||
addStep("Hill", "5-6", "30-70", "0-100", "95-100");
|
||||
addStep("Smooth", 1);
|
||||
addStep("Hill", "2-3", "30-70", "0-5", "20-80");
|
||||
addStep("Hill", "2-3", "30-70", "95-100", "20-80");
|
||||
addStep("Multiply", 0.8, "land");
|
||||
addStep("Trough", "3-5", "40-50", "0-100", "0-10");
|
||||
addStep("Trough", "3-5", "40-50", "0-100", "90-100");
|
||||
}
|
||||
|
||||
// Heighmap Template: Peninsula
|
||||
function templatePeninsula() {
|
||||
addStep("Range", "2-3", "20-35", "40-50", "0-15");
|
||||
addStep("Add", 5, "all");
|
||||
addStep("Hill", "1", "90-100", "10-90", "0-5");
|
||||
addStep("Add", 13, "all");
|
||||
addStep("Hill", "3-4", "3-5", "5-95", "80-100");
|
||||
addStep("Hill", "1-2", "3-5", "5-95", "40-60");
|
||||
addStep("Trough", "5-6", "10-25", "5-95", "5-95");
|
||||
addStep("Smooth", 3);
|
||||
}
|
||||
|
||||
// Heighmap Template: Pangea
|
||||
function templatePangea() {
|
||||
addStep("Hill", "1-2", "25-40", "15-50", "0-10");
|
||||
addStep("Hill", "1-2", "5-40", "50-85", "0-10");
|
||||
addStep("Hill", "1-2", "25-40", "50-85", "90-100");
|
||||
addStep("Hill", "1-2", "5-40", "15-50", "90-100");
|
||||
addStep("Hill", "8-12", "20-40", "20-80", "48-52");
|
||||
addStep("Smooth", 2);
|
||||
addStep("Multiply", 0.7, "land");
|
||||
addStep("Trough", "3-4", "25-35", "5-95", "10-20");
|
||||
addStep("Trough", "3-4", "25-35", "5-95", "80-90");
|
||||
addStep("Range", "5-6", "30-40", "10-90", "35-65");
|
||||
}
|
||||
|
||||
// Heighmap Template: Isthmus
|
||||
function templateIsthmus() {
|
||||
addStep("Hill", "5-10", "15-30", "0-30", "0-20");
|
||||
addStep("Hill", "5-10", "15-30", "10-50", "20-40");
|
||||
addStep("Hill", "5-10", "15-30", "30-70", "40-60");
|
||||
addStep("Hill", "5-10", "15-30", "50-90", "60-80");
|
||||
addStep("Hill", "5-10", "15-30", "70-100", "80-100");
|
||||
addStep("Smooth", 2);
|
||||
addStep("Trough", "4-8", "15-30", "0-30", "0-20");
|
||||
addStep("Trough", "4-8", "15-30", "10-50", "20-40");
|
||||
addStep("Trough", "4-8", "15-30", "30-70", "40-60");
|
||||
addStep("Trough", "4-8", "15-30", "50-90", "60-80");
|
||||
addStep("Trough", "4-8", "15-30", "70-100", "80-100");
|
||||
}
|
||||
|
||||
// Heighmap Template: Shattered
|
||||
function templateShattered() {
|
||||
addStep("Hill", "8", "35-40", "15-85", "30-70");
|
||||
addStep("Trough", "10-20", "40-50", "5-95", "5-95");
|
||||
addStep("Range", "5-7", "30-40", "10-90", "20-80");
|
||||
addStep("Pit", "12-20", "30-40", "15-85", "20-80");
|
||||
}
|
||||
|
||||
function getBlobPower() {
|
||||
switch (+pointsInput.dataset.cells) {
|
||||
case 1000:
|
||||
return 0.93;
|
||||
case 2000:
|
||||
return 0.95;
|
||||
case 5000:
|
||||
return 0.96;
|
||||
case 10000:
|
||||
return 0.98;
|
||||
case 20000:
|
||||
return 0.985;
|
||||
case 30000:
|
||||
return 0.987;
|
||||
case 40000:
|
||||
return 0.9892;
|
||||
case 50000:
|
||||
return 0.9911;
|
||||
case 60000:
|
||||
return 0.9921;
|
||||
case 70000:
|
||||
return 0.9934;
|
||||
case 80000:
|
||||
return 0.9942;
|
||||
case 90000:
|
||||
return 0.9946;
|
||||
case 100000:
|
||||
return 0.995;
|
||||
}
|
||||
const cells = +pointsInput.dataset.cells;
|
||||
if (cells === 1000) return 0.93;
|
||||
if (cells === 2000) return 0.95;
|
||||
if (cells === 5000) return 0.96;
|
||||
if (cells === 10000) return 0.98;
|
||||
if (cells === 20000) return 0.985;
|
||||
if (cells === 30000) return 0.987;
|
||||
if (cells === 40000) return 0.9892;
|
||||
if (cells === 50000) return 0.9911;
|
||||
if (cells === 60000) return 0.9921;
|
||||
if (cells === 70000) return 0.9934;
|
||||
if (cells === 80000) return 0.9942;
|
||||
if (cells === 90000) return 0.9946;
|
||||
if (cells === 100000) return 0.995;
|
||||
}
|
||||
|
||||
function getLinePower() {
|
||||
switch (+pointsInput.dataset.cells) {
|
||||
case 1000:
|
||||
return 0.74;
|
||||
case 2000:
|
||||
return 0.75;
|
||||
case 5000:
|
||||
return 0.78;
|
||||
case 10000:
|
||||
return 0.81;
|
||||
case 20000:
|
||||
return 0.82;
|
||||
case 30000:
|
||||
return 0.83;
|
||||
case 40000:
|
||||
return 0.84;
|
||||
case 50000:
|
||||
return 0.855;
|
||||
case 60000:
|
||||
return 0.87;
|
||||
case 70000:
|
||||
return 0.885;
|
||||
case 80000:
|
||||
return 0.91;
|
||||
case 90000:
|
||||
return 0.92;
|
||||
case 100000:
|
||||
return 0.93;
|
||||
}
|
||||
const cells = +pointsInput.dataset.cells;
|
||||
if (cells === 1000) return 0.74;
|
||||
if (cells === 2000) return 0.75;
|
||||
if (cells === 5000) return 0.78;
|
||||
if (cells === 10000) return 0.81;
|
||||
if (cells === 20000) return 0.82;
|
||||
if (cells === 30000) return 0.83;
|
||||
if (cells === 40000) return 0.84;
|
||||
if (cells === 50000) return 0.855;
|
||||
if (cells === 60000) return 0.87;
|
||||
if (cells === 70000) return 0.885;
|
||||
if (cells === 80000) return 0.91;
|
||||
if (cells === 90000) return 0.92;
|
||||
if (cells === 100000) return 0.93;
|
||||
}
|
||||
|
||||
const addHill = function (count, height, rangeX, rangeY) {
|
||||
|
|
@ -422,7 +221,6 @@
|
|||
if (d % 6 !== 0) return;
|
||||
for (const l of d3.range(i)) {
|
||||
const min = cells.c[cur][d3.scan(cells.c[cur], (a, b) => cells.h[a] - cells.h[b])]; // downhill cell
|
||||
//debug.append("circle").attr("cx", p[min][0]).attr("cy", p[min][1]).attr("r", 1);
|
||||
cells.h[min] = (cells.h[cur] * 2 + cells.h[min]) / 3;
|
||||
cur = min;
|
||||
}
|
||||
|
|
@ -611,4 +409,4 @@
|
|||
}
|
||||
|
||||
return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify};
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
126
modules/heightmap-templates.js
Normal file
126
modules/heightmap-templates.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
"use strict";
|
||||
|
||||
window.HeightmapTemplates = (function () {
|
||||
const volcano = `Hill 1 90-100 44-56 40-60
|
||||
Multiply 0.8 50-100 0 0
|
||||
Range 1.5 30-55 45-55 40-60
|
||||
Smooth 2 0 0 0
|
||||
Hill 1.5 25-35 25-30 20-75
|
||||
Hill 1 25-35 75-80 25-75
|
||||
Hill 0.5 20-25 10-15 20-25`;
|
||||
|
||||
const highIsland = `Hill 1 90-100 65-75 47-53
|
||||
Add 5 all 0 0
|
||||
Hill 6 20-23 25-55 45-55
|
||||
Range 1 40-50 45-55 45-55
|
||||
Smooth 2 0 0 0
|
||||
Trough 2-3 20-30 20-30 20-30
|
||||
Trough 2-3 20-30 60-80 70-80
|
||||
Hill 1 10-15 60-60 50-50
|
||||
Hill 1.5 13-16 15-20 20-75
|
||||
Multiply 0.8 20-100 0 0
|
||||
Range 1.5 30-40 15-85 30-40
|
||||
Range 1.5 30-40 15-85 60-70
|
||||
Pit 2-3 10-15 15-85 20-80`;
|
||||
|
||||
const lowIsland = `Hill 1 90-99 60-80 45-55
|
||||
Hill 4-5 25-35 20-65 40-60
|
||||
Range 1 40-50 45-55 45-55
|
||||
Smooth 3 0 0 0
|
||||
Trough 1.5 20-30 15-85 20-30
|
||||
Trough 1.5 20-30 15-85 70-80
|
||||
Hill 1.5 10-15 5-15 20-80
|
||||
Hill 1 10-15 85-95 70-80
|
||||
Pit 3-5 10-15 15-85 20-80
|
||||
Multiply 0.4 20-100 0 0`;
|
||||
|
||||
const continents = `Hill 1 80-85 75-80 40-60
|
||||
Hill 1 80-85 20-25 40-60
|
||||
Multiply 0.22 20-100 0 0
|
||||
Hill 5-6 15-20 25-75 20-82
|
||||
Range .8 30-60 5-15 20-45
|
||||
Range .8 30-60 5-15 55-80
|
||||
Range 0-3 30-60 80-90 20-80
|
||||
Trough 3-4 15-20 15-85 20-80
|
||||
Strait 2 vertical 0 0
|
||||
Smooth 2 0 0 0
|
||||
Trough 1-2 5-10 45-55 45-55
|
||||
Pit 3-4 10-15 15-85 20-80
|
||||
Hill 1 5-10 40-60 40-60`;
|
||||
|
||||
const archipelago = `Add 11 all 0 0
|
||||
Range 2-3 40-60 20-80 20-80
|
||||
Hill 5 15-20 10-90 30-70
|
||||
Hill 2 10-15 10-30 20-80
|
||||
Hill 2 10-15 60-90 20-80
|
||||
Smooth 3 0 0 0
|
||||
Trough 10 20-30 5-95 5-95
|
||||
Strait 2 vertical 0 0
|
||||
Strait 2 horizontal 0 0`;
|
||||
|
||||
const atoll = `Add 11 all 0 0
|
||||
Range 2-3 40-60 20-80 20-80
|
||||
Hill 5 15-20 10-90 30-70
|
||||
Hill 2 10-15 10-30 20-80
|
||||
Hill 2 10-15 60-90 20-80
|
||||
Smooth 3 0 0 0
|
||||
Trough 10 20-30 5-95 5-95
|
||||
Strait 2 vertical 0 0
|
||||
Strait 2 horizontal 0 0`;
|
||||
|
||||
const mediterranean = `Range 3-4 30-50 0-100 0-10
|
||||
Range 3-4 30-50 0-100 90-100
|
||||
Hill 5-6 30-70 0-100 0-5
|
||||
Hill 5-6 30-70 0-100 95-100
|
||||
Smooth 1 0 0 0
|
||||
Hill 2-3 30-70 0-5 20-80
|
||||
Hill 2-3 30-70 95-100 20-80
|
||||
Multiply 0.8 land 0 0
|
||||
Trough 3-5 40-50 0-100 0-10
|
||||
Trough 3-5 40-50 0-100 90-100`;
|
||||
|
||||
const peninsula = `Range 2-3 20-35 40-50 0-15
|
||||
Add 5 all 0 0
|
||||
Hill 1 90-100 10-90 0-5
|
||||
Add 13 all 0 0
|
||||
Hill 3-4 3-5 5-95 80-100
|
||||
Hill 1-2 3-5 5-95 40-60
|
||||
Trough 5-6 10-25 5-95 5-95
|
||||
Smooth 3 0 0 0`;
|
||||
|
||||
const pangea = `Hill 1-2 25-40 15-50 0-10
|
||||
Hill 1-2 5-40 50-85 0-10
|
||||
Hill 1-2 25-40 50-85 90-100
|
||||
Hill 1-2 5-40 15-50 90-100
|
||||
Hill 8-12 20-40 20-80 48-52
|
||||
Smooth 2 0 0 0
|
||||
Multiply 0.7 land 0 0
|
||||
Trough 3-4 25-35 5-95 10-20
|
||||
Trough 3-4 25-35 5-95 80-90
|
||||
Range 5-6 30-40 10-90 35-65`;
|
||||
|
||||
const isthmus = `Hill 5-10 15-30 0-30 0-20
|
||||
Hill 5-10 15-30 10-50 20-40
|
||||
Hill 5-10 15-30 30-70 40-60
|
||||
Hill 5-10 15-30 50-90 60-80
|
||||
Hill 5-10 15-30 70-100 80-100
|
||||
Smooth 2 0 0 0
|
||||
Trough 4-8 15-30 0-30 0-20
|
||||
Trough 4-8 15-30 10-50 20-40
|
||||
Trough 4-8 15-30 30-70 40-60
|
||||
Trough 4-8 15-30 50-90 60-80
|
||||
Trough 4-8 15-30 70-100 80-100`;
|
||||
|
||||
const shattered = `Hill 8 35-40 15-85 30-70
|
||||
Trough 10-20 40-50 5-95 5-95
|
||||
Range 5-7 30-40 10-90 20-80
|
||||
Pit 12-20 30-40 15-85 20-80`;
|
||||
|
||||
const taklamakan = `Hill 1-3 20-30 30-70 30-70
|
||||
Hill 2-4 60-85 0-5 0-100
|
||||
Hill 2-4 60-85 95-100 0-100
|
||||
Hill 3-4 60-85 20-80 0-5
|
||||
Hill 3-4 60-85 20-80 95-100`;
|
||||
|
||||
return {volcano, highIsland, lowIsland, continents, archipelago, atoll, mediterranean, peninsula, peninsula, pangea, isthmus, shattered, taklamakan};
|
||||
})();
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Lakes = factory());
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
window.Lakes = (function () {
|
||||
const setClimateData = function (h) {
|
||||
const cells = pack.cells;
|
||||
const lakeOutCells = new Uint16Array(cells.i.length);
|
||||
|
|
@ -10,8 +8,8 @@
|
|||
pack.features.forEach(f => {
|
||||
if (f.type !== "lake") return;
|
||||
|
||||
// default flux: sum of precipition around lake first cell
|
||||
f.flux = rn(d3.sum(f.shoreline.map(c => grid.cells.prec[cells.g[c]])) / 2);
|
||||
// default flux: sum of precipitation around lake
|
||||
f.flux = f.shoreline.reduce((acc, c) => acc + grid.cells.prec[cells.g[c]], 0);
|
||||
|
||||
// temperature and evaporation to detect closed lakes
|
||||
f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
|
||||
|
|
@ -96,7 +94,6 @@
|
|||
if (feature.type !== "lake") continue;
|
||||
delete feature.river;
|
||||
delete feature.enteringFlux;
|
||||
delete feature.shoreline;
|
||||
delete feature.outCell;
|
||||
delete feature.closed;
|
||||
feature.height = rn(feature.height, 3);
|
||||
|
|
@ -140,7 +137,7 @@
|
|||
if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 10 === 0) return "lava";
|
||||
|
||||
if (!feature.inlets && !feature.outlet) {
|
||||
if (feature.evaporation / 2 > feature.flux) return "dry";
|
||||
if (feature.evaporation > feature.flux * 4) return "dry";
|
||||
if (feature.cells < 3 && feature.firstCell % 10 === 0) return "sinkhole";
|
||||
}
|
||||
|
||||
|
|
@ -150,4 +147,4 @@
|
|||
}
|
||||
|
||||
return {setClimateData, cleanupLakeData, prepareLakeData, defineGroup, generateName, getName, getShoreline};
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -141,6 +141,8 @@ function parseLoadedData(data) {
|
|||
if (settings[19]) options = JSON.parse(settings[19]);
|
||||
if (settings[20]) mapName.value = settings[20];
|
||||
if (settings[21]) hideLabels.checked = +settings[21];
|
||||
if (settings[22]) stylePreset.value = settings[22];
|
||||
if (settings[23]) rescaleLabels.checked = settings[23];
|
||||
})();
|
||||
|
||||
void (function parseConfiguration() {
|
||||
|
|
@ -220,6 +222,8 @@ function parseLoadedData(data) {
|
|||
burgLabels = labels.select('#burgLabels');
|
||||
})();
|
||||
|
||||
loadUsedFonts();
|
||||
|
||||
void (function parseGridData() {
|
||||
grid = JSON.parse(data[6]);
|
||||
calculateVoronoi(grid, grid.points);
|
||||
|
|
@ -270,12 +274,13 @@ function parseLoadedData(data) {
|
|||
}
|
||||
})();
|
||||
|
||||
void (function restoreLayersState() {
|
||||
// helper functions
|
||||
const notHidden = (selection) => selection.node() && selection.style('display') !== 'none';
|
||||
const hasChildren = (selection) => selection.node()?.hasChildNodes();
|
||||
const hasChild = (selection, selector) => selection.node()?.querySelector(selector);
|
||||
const turnOn = (el) => document.getElementById(el).classList.remove('buttonoff');
|
||||
|
||||
void (function restoreLayersState() {
|
||||
// turn all layers off
|
||||
document
|
||||
.getElementById('mapLayers')
|
||||
|
|
@ -290,7 +295,7 @@ function parseLoadedData(data) {
|
|||
if (hasChildren(gridOverlay)) turnOn('toggleGrid');
|
||||
if (hasChildren(coordinates)) turnOn('toggleCoordinates');
|
||||
if (notHidden(compass) && hasChild(compass, 'use')) turnOn('toggleCompass');
|
||||
if (notHidden(rivers)) turnOn('toggleRivers');
|
||||
if (hasChildren(rivers)) turnOn('toggleRivers');
|
||||
if (notHidden(terrain) && hasChildren(terrain)) turnOn('toggleRelief');
|
||||
if (hasChildren(relig)) turnOn('toggleReligions');
|
||||
if (hasChildren(cults)) turnOn('toggleCultures');
|
||||
|
|
@ -303,7 +308,6 @@ function parseLoadedData(data) {
|
|||
if (hasChild(population, 'line')) turnOn('togglePopulation');
|
||||
if (hasChildren(ice)) turnOn('toggleIce');
|
||||
if (hasChild(prec, 'circle')) turnOn('togglePrec');
|
||||
if (hasChildren(goods)) turnOn('toggleResources');
|
||||
if (notHidden(emblems) && hasChild(emblems, 'use')) turnOn('toggleEmblems');
|
||||
if (notHidden(labels)) turnOn('toggleLabels');
|
||||
if (notHidden(icons)) turnOn('toggleIcons');
|
||||
|
|
@ -694,7 +698,7 @@ function parseLoadedData(data) {
|
|||
}
|
||||
|
||||
if (version < 1.63) {
|
||||
// v.1.63 change ocean pattern opacity element
|
||||
// v.1.63 changed ocean pattern opacity element
|
||||
const oceanPattern = document.getElementById('oceanPattern');
|
||||
if (oceanPattern) oceanPattern.removeAttribute('opacity');
|
||||
const oceanicPattern = document.getElementById('oceanicPattern');
|
||||
|
|
@ -713,6 +717,50 @@ function parseLoadedData(data) {
|
|||
defs.append('g').attr('id', 'defs-icons');
|
||||
Resources.generate();
|
||||
}
|
||||
|
||||
if (version < 1.64) {
|
||||
// v.1.64 change states style
|
||||
const opacity = regions.attr('opacity');
|
||||
const filter = regions.attr('filter');
|
||||
statesBody.attr('opacity', opacity).attr('filter', filter);
|
||||
statesHalo.attr('opacity', opacity).attr('filter', 'blur(5px)');
|
||||
regions.attr('opacity', null).attr('filter', null);
|
||||
}
|
||||
|
||||
if (version < 1.65) {
|
||||
// v 1.65 changed rivers data
|
||||
rivers.attr('style', null); // remove style to unhide layer
|
||||
|
||||
for (const river of pack.rivers) {
|
||||
const node = document.getElementById('river' + river.i);
|
||||
if (node && !river.cells) {
|
||||
const riverCells = new Set();
|
||||
const length = node.getTotalLength() / 2;
|
||||
const segments = Math.ceil(length / 6);
|
||||
const increment = length / segments;
|
||||
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i);
|
||||
const p2 = node.getPointAtLength(c);
|
||||
const x = (p1.x + p2.x) / 2;
|
||||
const y = (p1.y + p2.y) / 2;
|
||||
const cell = findCell(x, y, 6);
|
||||
if (cell) riverCells.add(cell);
|
||||
}
|
||||
|
||||
river.cells = Array.from(riverCells);
|
||||
}
|
||||
|
||||
pack.cells.i.forEach((i) => {
|
||||
if (pack.cells.r[i] && pack.cells.h[i] < 20) pack.cells.r[i] = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (version < 1.652) {
|
||||
// remove style to unhide layers
|
||||
rivers.attr('style', null);
|
||||
borders.attr('style', null);
|
||||
}
|
||||
})();
|
||||
|
||||
void (function checkDataIntegrity() {
|
||||
|
|
@ -812,6 +860,7 @@ function parseLoadedData(data) {
|
|||
// set options
|
||||
yearInput.value = options.year;
|
||||
eraInput.value = options.era;
|
||||
shapeRendering.value = viewbox.attr('shape-rendering') || 'geometricPrecision';
|
||||
|
||||
if (window.restoreDefaultEvents) restoreDefaultEvents();
|
||||
focusOn(); // based on searchParams focus on point, cell or burg
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Military = factory());
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
window.Military = (function () {
|
||||
const generate = function () {
|
||||
TIME && console.time("generateMilitaryForces");
|
||||
const cells = pack.cells,
|
||||
|
|
@ -371,4 +369,4 @@
|
|||
};
|
||||
|
||||
return {generate, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, moveRegiment, getTotal, getEmblem};
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Names = factory());
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
window.Names = (function () {
|
||||
let chains = [];
|
||||
|
||||
// calculate Markov chain for a namesbase
|
||||
|
|
@ -294,4 +293,4 @@
|
|||
};
|
||||
|
||||
return {getBase, getCulture, getCultureShort, getBaseShort, getState, updateChain, clearChains, getNameBases, getMapName, calculateChain};
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.OceanLayers = factory());
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
window.OceanLayers = (function () {
|
||||
let cells, vertices, pointsN, used;
|
||||
|
||||
const OceanLayers = function OceanLayers() {
|
||||
|
|
@ -91,4 +89,4 @@
|
|||
}
|
||||
|
||||
return OceanLayers;
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.ReliefIcons = factory());
|
||||
}(this, (function () {'use strict';
|
||||
"use strict";
|
||||
|
||||
const ReliefIcons = function() {
|
||||
TIME && console.time('drawRelief');
|
||||
window.ReliefIcons = (function () {
|
||||
const ReliefIcons = function () {
|
||||
TIME && console.time("drawRelief");
|
||||
terrain.selectAll("*").remove();
|
||||
const density = terrain.attr("density") || .4;
|
||||
const size = 1.6 * (terrain.attr("size") || 1);
|
||||
const mod = .2 * size; // size modifier;s
|
||||
const relief = []; // t: type, c: cell, x: centerX, y: centerY, s: size;
|
||||
|
||||
const cells = pack.cells;
|
||||
const density = terrain.attr("density") || 0.4;
|
||||
const size = 2 * (terrain.attr("size") || 1);
|
||||
const mod = 0.2 * size; // size modifier
|
||||
const relief = [];
|
||||
|
||||
for (const i of cells.i) {
|
||||
const height = cells.h[i];
|
||||
if (height < 20) continue; // no icons on water
|
||||
if (cells.r[i]) continue; // no icons on rivers
|
||||
const b = cells.biome[i];
|
||||
if (height < 50 && biomesData.iconsDensity[b] === 0) continue; // no icons for this biome
|
||||
const polygon = getPackPolygon(i);
|
||||
const x = d3.extent(polygon, p => p[0]), y = d3.extent(polygon, p => p[1]);
|
||||
const e = [Math.ceil(x[0]), Math.ceil(y[0]), Math.floor(x[1]), Math.floor(y[1])]; // polygon box
|
||||
const biome = cells.biome[i];
|
||||
if (height < 50 && biomesData.iconsDensity[biome] === 0) continue; // no icons for this biome
|
||||
|
||||
if (height < 50) placeBiomeIcons(i, b); else placeReliefIcons(i);
|
||||
const polygon = getPackPolygon(i);
|
||||
const [minX, maxX] = d3.extent(polygon, p => p[0]);
|
||||
const [minY, maxY] = d3.extent(polygon, p => p[1]);
|
||||
|
||||
if (height < 50) placeBiomeIcons(i, biome);
|
||||
else placeReliefIcons(i);
|
||||
|
||||
function placeBiomeIcons() {
|
||||
const iconsDensity = biomesData.iconsDensity[b] / 100;
|
||||
const iconsDensity = biomesData.iconsDensity[biome] / 100;
|
||||
const radius = 2 / iconsDensity / density;
|
||||
if (Math.random() > iconsDensity * 10) return;
|
||||
|
||||
for (const [cx, cy] of poissonDiscSampler(e[0], e[1], e[2], e[3], radius)) {
|
||||
for (const [cx, cy] of poissonDiscSampler(minX, minY, maxX, maxY, radius)) {
|
||||
if (!d3.polygonContains(polygon, [cx, cy])) continue;
|
||||
let h = rn((4 + Math.random()) * size, 2);
|
||||
const icon = getBiomeIcon(i, biomesData.icons[b]);
|
||||
if (icon === "#relief-grass-1") h *= 1.3;
|
||||
relief.push({i: icon, x: rn(cx-h, 2), y: rn(cy-h, 2), s: rn(h*2, 2)});
|
||||
let h = (4 + Math.random()) * size;
|
||||
const icon = getBiomeIcon(i, biomesData.icons[biome]);
|
||||
if (icon === "#relief-grass-1") h *= 1.2;
|
||||
relief.push({i: icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2)});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,9 +43,9 @@
|
|||
const radius = 2 / density;
|
||||
const [icon, h] = getReliefIcon(i, height);
|
||||
|
||||
for (const [cx, cy] of poissonDiscSampler(e[0], e[1], e[2], e[3], radius)) {
|
||||
for (const [cx, cy] of poissonDiscSampler(minX, minY, maxX, maxY, radius)) {
|
||||
if (!d3.polygonContains(polygon, [cx, cy])) continue;
|
||||
relief.push({i: icon, x: rn(cx-h, 2), y: rn(cy-h, 2), s: rn(h*2, 2)});
|
||||
relief.push({i: icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2)});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,17 +58,16 @@
|
|||
}
|
||||
|
||||
// sort relief icons by y+size
|
||||
relief.sort((a, b) => (a.y + a.s) - (b.y + b.s));
|
||||
relief.sort((a, b) => a.y + a.s - (b.y + b.s));
|
||||
|
||||
// append relief icons at once using pure js
|
||||
let reliefHTML = "";
|
||||
for (const r of relief) {
|
||||
reliefHTML += `<use href="${r.i}" x="${r.x}" y="${r.y}" width="${r.s}" height="${r.s}"/>`;
|
||||
}
|
||||
terrain.html(reliefHTML);
|
||||
|
||||
TIME && console.timeEnd('drawRelief');
|
||||
}
|
||||
TIME && console.timeEnd("drawRelief");
|
||||
};
|
||||
|
||||
function getBiomeIcon(i, b) {
|
||||
let type = b[Math.floor(Math.random() * b.length)];
|
||||
|
|
@ -78,27 +77,42 @@
|
|||
}
|
||||
|
||||
function getVariant(type) {
|
||||
switch(type) {
|
||||
case "mount": return rand(2,7);
|
||||
case "mountSnow": return rand(1,6);
|
||||
case "hill": return rand(2,5);
|
||||
case "conifer": return 2;
|
||||
case "coniferSnow": return 1;
|
||||
case "swamp": return rand(2,3);
|
||||
case "cactus": return rand(1,3);
|
||||
case "deadTree": return rand(1,2);
|
||||
default: return 2;
|
||||
switch (type) {
|
||||
case "mount":
|
||||
return rand(2, 7);
|
||||
case "mountSnow":
|
||||
return rand(1, 6);
|
||||
case "hill":
|
||||
return rand(2, 5);
|
||||
case "conifer":
|
||||
return 2;
|
||||
case "coniferSnow":
|
||||
return 1;
|
||||
case "swamp":
|
||||
return rand(2, 3);
|
||||
case "cactus":
|
||||
return rand(1, 3);
|
||||
case "deadTree":
|
||||
return rand(1, 2);
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
function getOldIcon(type) {
|
||||
switch(type) {
|
||||
case "mountSnow": return "mount";
|
||||
case "vulcan": return "mount";
|
||||
case "coniferSnow": return "conifer";
|
||||
case "cactus": return "dune";
|
||||
case "deadTree": return "dune";
|
||||
default: return type;
|
||||
switch (type) {
|
||||
case "mountSnow":
|
||||
return "mount";
|
||||
case "vulcan":
|
||||
return "mount";
|
||||
case "coniferSnow":
|
||||
return "conifer";
|
||||
case "cactus":
|
||||
return "dune";
|
||||
case "deadTree":
|
||||
return "dune";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,5 +125,4 @@
|
|||
}
|
||||
|
||||
return ReliefIcons;
|
||||
|
||||
})));
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.Religions = factory());
|
||||
}(this, (function () {'use strict';
|
||||
"use strict";
|
||||
|
||||
window.Religions = (function () {
|
||||
// name generation approach and relative chance to be selected
|
||||
const approach = {"Number":1, "Being":3, "Adjective":5, "Color + Animal":5,
|
||||
"Adjective + Animal":5, "Adjective + Being":5, "Adjective + Genitive":1,
|
||||
"Color + Being":3, "Color + Genitive":3, "Being + of + Genitive":2, "Being + of the + Genitive":1,
|
||||
"Animal + of + Genitive":1, "Adjective + Being + of + Genitive":2, "Adjective + Animal + of + Genitive":2};
|
||||
const approach = {Number: 1, Being: 3, Adjective: 5, "Color + Animal": 5, "Adjective + Animal": 5, "Adjective + Being": 5, "Adjective + Genitive": 1, "Color + Being": 3, "Color + Genitive": 3, "Being + of + Genitive": 2, "Being + of the + Genitive": 1, "Animal + of + Genitive": 1, "Adjective + Being + of + Genitive": 2, "Adjective + Animal + of + Genitive": 2};
|
||||
|
||||
// turn weighted array into simple array
|
||||
const approaches = [];
|
||||
for (const a in approach) {
|
||||
for (let j=0; j < approach[a]; j++) {
|
||||
for (let j = 0; j < approach[a]; j++) {
|
||||
approaches.push(a);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,64 +23,70 @@
|
|||
};
|
||||
|
||||
const forms = {
|
||||
Folk:{"Shamanism":2, "Animism":2, "Ancestor worship":1, "Polytheism":2},
|
||||
Organized:{"Polytheism":5, "Dualism":1, "Monotheism":4, "Non-theism":1},
|
||||
Cult:{"Cult":1, "Dark Cult":1},
|
||||
Heresy:{"Heresy":1}
|
||||
Folk: {Shamanism: 2, Animism: 2, "Ancestor worship": 1, Polytheism: 2},
|
||||
Organized: {Polytheism: 5, Dualism: 1, Monotheism: 4, "Non-theism": 1},
|
||||
Cult: {Cult: 1, "Dark Cult": 1},
|
||||
Heresy: {Heresy: 1}
|
||||
};
|
||||
|
||||
const methods = {"Random + type":3, "Random + ism":1, "Supreme + ism":5, "Faith of + Supreme":5, "Place + ism":1, "Culture + ism":2, "Place + ian + type":6, "Culture + type":4};
|
||||
const methods = {"Random + type": 3, "Random + ism": 1, "Supreme + ism": 5, "Faith of + Supreme": 5, "Place + ism": 1, "Culture + ism": 2, "Place + ian + type": 6, "Culture + type": 4};
|
||||
|
||||
const types = {
|
||||
"Shamanism":{"Beliefs":3, "Shamanism":2, "Spirits":1},
|
||||
"Animism":{"Spirits":1, "Beliefs":1},
|
||||
"Ancestor worship":{"Beliefs":1, "Forefathers":2, "Ancestors":2},
|
||||
"Polytheism":{"Deities":3, "Faith":1, "Gods":1, "Pantheon":1},
|
||||
Shamanism: {Beliefs: 3, Shamanism: 2, Spirits: 1},
|
||||
Animism: {Spirits: 1, Beliefs: 1},
|
||||
"Ancestor worship": {Beliefs: 1, Forefathers: 2, Ancestors: 2},
|
||||
Polytheism: {Deities: 3, Faith: 1, Gods: 1, Pantheon: 1},
|
||||
|
||||
"Dualism":{"Religion":3, "Faith":1, "Cult":1},
|
||||
"Monotheism":{"Religion":1, "Church":1},
|
||||
"Non-theism":{"Beliefs":3, "Spirits":1},
|
||||
Dualism: {Religion: 3, Faith: 1, Cult: 1},
|
||||
Monotheism: {Religion: 1, Church: 1},
|
||||
"Non-theism": {Beliefs: 3, Spirits: 1},
|
||||
|
||||
"Cult":{"Cult":4, "Sect":4, "Worship":1, "Orden":1, "Coterie":1, "Arcanum":1},
|
||||
"Dark Cult":{"Cult":2, "Sect":2, "Occultism":1, "Idols":1, "Coven":1, "Circle":1, "Blasphemy":1},
|
||||
Cult: {Cult: 4, Sect: 4, Worship: 1, Orden: 1, Coterie: 1, Arcanum: 1},
|
||||
"Dark Cult": {Cult: 2, Sect: 2, Occultism: 1, Idols: 1, Coven: 1, Circle: 1, Blasphemy: 1},
|
||||
|
||||
"Heresy":{"Heresy":3, "Sect":2, "Schism":1, "Dissenters":1, "Circle":1, "Brotherhood":1, "Society":1, "Iconoclasm":1, "Dissent":1, "Apostates":1}
|
||||
Heresy: {Heresy: 3, Sect: 2, Schism: 1, Dissenters: 1, Circle: 1, Brotherhood: 1, Society: 1, Iconoclasm: 1, Dissent: 1, Apostates: 1}
|
||||
};
|
||||
|
||||
const generate = function() {
|
||||
TIME && console.time('generateReligions');
|
||||
const cells = pack.cells, states = pack.states, cultures = pack.cultures;
|
||||
const religions = pack.religions = [];
|
||||
const generate = function () {
|
||||
TIME && console.time("generateReligions");
|
||||
const cells = pack.cells,
|
||||
states = pack.states,
|
||||
cultures = pack.cultures;
|
||||
const religions = (pack.religions = []);
|
||||
cells.religion = new Uint16Array(cells.culture); // cell religion; initially based on culture
|
||||
|
||||
// add folk religions
|
||||
pack.cultures.forEach(c => {
|
||||
if (!c.i) {religions.push({i: 0, name: "No religion"}); return;}
|
||||
if (c.removed) {religions.push({i: c.i, name: "Extinct religion for "+c.name, color:getMixedColor(c.color, .1, 0), removed:true}); return;}
|
||||
if (!c.i) {
|
||||
religions.push({i: 0, name: "No religion"});
|
||||
return;
|
||||
}
|
||||
if (c.removed) {
|
||||
religions.push({i: c.i, name: "Extinct religion for " + c.name, color: getMixedColor(c.color, 0.1, 0), removed: true});
|
||||
return;
|
||||
}
|
||||
const form = rw(forms.Folk);
|
||||
const name = c.name + " " + rw(types[form]);
|
||||
const deity = form === "Animism" ? null : getDeityName(c.i);
|
||||
const color = getMixedColor(c.color, .1, 0); // `url(#hatch${rand(8,13)})`;
|
||||
religions.push({i: c.i, name, color, culture: c.i, type:"Folk", form, deity, center: c.center, origin:0});
|
||||
const color = getMixedColor(c.color, 0.1, 0); // `url(#hatch${rand(8,13)})`;
|
||||
religions.push({i: c.i, name, color, culture: c.i, type: "Folk", form, deity, center: c.center, origin: 0});
|
||||
});
|
||||
|
||||
if (religionsInput.value == 0 || pack.cultures.length < 2) {
|
||||
religions.filter(r => r.i).forEach(r => r.code = abbreviate(r.name));
|
||||
religions.filter(r => r.i).forEach(r => (r.code = abbreviate(r.name)));
|
||||
return;
|
||||
}
|
||||
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
const sorted = burgs.length > +religionsInput.value
|
||||
? burgs.sort((a, b) => b.population - a.population).map(b => b.cell)
|
||||
: cells.i.filter(i => cells.s[i] > 2).sort((a, b) => cells.s[b] - cells.s[a]);
|
||||
const sorted = burgs.length > +religionsInput.value ? burgs.sort((a, b) => b.population - a.population).map(b => b.cell) : cells.i.filter(i => cells.s[i] > 2).sort((a, b) => cells.s[b] - cells.s[a]);
|
||||
const religionsTree = d3.quadtree();
|
||||
const spacing = (graphWidth + graphHeight) / 6 / religionsInput.value; // base min distance between towns
|
||||
const cultsCount = Math.floor(rand(10, 40) / 100 * religionsInput.value);
|
||||
const cultsCount = Math.floor((rand(10, 40) / 100) * religionsInput.value);
|
||||
const count = +religionsInput.value - cultsCount + religions.length;
|
||||
|
||||
// generate organized religions
|
||||
for (let i=0; religions.length < count && i < 1000; i++) {
|
||||
let center = sorted[biased(0, sorted.length-1, 5)]; // religion center
|
||||
for (let i = 0; religions.length < count && i < 1000; i++) {
|
||||
let center = sorted[biased(0, sorted.length - 1, 5)]; // religion center
|
||||
const form = rw(forms.Organized);
|
||||
const state = cells.state[center];
|
||||
const culture = cells.culture[center];
|
||||
|
|
@ -96,34 +96,36 @@
|
|||
if (expansion === "state" && !state) expansion = "global";
|
||||
if (expansion === "culture" && !culture) expansion = "global";
|
||||
|
||||
if (expansion === "state" && Math.random() > .5) center = states[state].center;
|
||||
if (expansion === "culture" && Math.random() > .5) center = cultures[culture].center;
|
||||
if (expansion === "state" && Math.random() > 0.5) center = states[state].center;
|
||||
if (expansion === "culture" && Math.random() > 0.5) center = cultures[culture].center;
|
||||
|
||||
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]);
|
||||
const x = cells.p[center][0], y = cells.p[center][1];
|
||||
const x = cells.p[center][0],
|
||||
y = cells.p[center][1];
|
||||
|
||||
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make the placement not uniform
|
||||
const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform
|
||||
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
|
||||
|
||||
// add "Old" to name of the folk religion on this culture
|
||||
const folk = religions.find(r => r.culture === culture && r.type === "Folk");
|
||||
if (folk && expansion === "culture" && folk.name.slice(0,3) !== "Old") folk.name = "Old " + folk.name;
|
||||
if (folk && expansion === "culture" && folk.name.slice(0, 3) !== "Old") folk.name = "Old " + folk.name;
|
||||
const origin = folk ? folk.i : 0;
|
||||
|
||||
const expansionism = rand(3, 8);
|
||||
const color = getMixedColor(religions[origin].color, .3, 0); // `url(#hatch${rand(0,5)})`;
|
||||
religions.push({i: religions.length, name, color, culture, type:"Organized", form, deity, expansion, expansionism, center, origin});
|
||||
const color = getMixedColor(religions[origin].color, 0.3, 0); // `url(#hatch${rand(0,5)})`;
|
||||
religions.push({i: religions.length, name, color, culture, type: "Organized", form, deity, expansion, expansionism, center, origin});
|
||||
religionsTree.add([x, y]);
|
||||
}
|
||||
|
||||
// generate cults
|
||||
for (let i=0; religions.length < count + cultsCount && i < 1000; i++) {
|
||||
for (let i = 0; religions.length < count + cultsCount && i < 1000; i++) {
|
||||
const form = rw(forms.Cult);
|
||||
let center = sorted[biased(0, sorted.length-1, 1)]; // religion center
|
||||
let center = sorted[biased(0, sorted.length - 1, 1)]; // religion center
|
||||
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]);
|
||||
const x = cells.p[center][0], y = cells.p[center][1];
|
||||
const x = cells.p[center][0],
|
||||
y = cells.p[center][1];
|
||||
|
||||
const s = spacing * gauss(2, .3, 1, 3, 2); // randomize to make the placement not uniform
|
||||
const s = spacing * gauss(2, 0.3, 1, 3, 2); // randomize to make the placement not uniform
|
||||
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
|
||||
|
||||
const culture = cells.culture[center];
|
||||
|
|
@ -131,9 +133,9 @@
|
|||
const origin = folk ? folk.i : 0;
|
||||
const deity = getDeityName(culture);
|
||||
const name = getCultName(form, center);
|
||||
const expansionism = gauss(1.1, .5, 0, 5);
|
||||
const color = getMixedColor(cultures[culture].color, .5, 0); // "url(#hatch7)";
|
||||
religions.push({i: religions.length, name, color, culture, type:"Cult", form, deity, expansion:"global", expansionism, center, origin});
|
||||
const expansionism = gauss(1.1, 0.5, 0, 5);
|
||||
const color = getMixedColor(cultures[culture].color, 0.5, 0); // "url(#hatch7)";
|
||||
religions.push({i: religions.length, name, color, culture, type: "Cult", form, deity, expansion: "global", expansionism, center, origin});
|
||||
religionsTree.add([x, y]);
|
||||
//debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).attr("fill", "red");
|
||||
}
|
||||
|
|
@ -141,21 +143,24 @@
|
|||
expandReligions();
|
||||
|
||||
// generate heresies
|
||||
religions.filter(r => r.type === "Organized").forEach(r => {
|
||||
religions
|
||||
.filter(r => r.type === "Organized")
|
||||
.forEach(r => {
|
||||
if (r.expansionism < 3) return;
|
||||
const count = gauss(0, 1, 0, 3);
|
||||
for (let i=0; i < count; i++) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
let center = ra(cells.i.filter(i => cells.religion[i] === r.i && cells.c[i].some(c => cells.religion[c] !== r.i)));
|
||||
if (!center) continue;
|
||||
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]);
|
||||
const x = cells.p[center][0], y = cells.p[center][1];
|
||||
const x = cells.p[center][0],
|
||||
y = cells.p[center][1];
|
||||
if (religionsTree.find(x, y, spacing / 10) !== undefined) continue; // to close to other
|
||||
|
||||
const culture = cells.culture[center];
|
||||
const name = getCultName("Heresy", center);
|
||||
const expansionism = gauss(1.2, .5, 0, 5);
|
||||
const color = getMixedColor(r.color, .4, .2); // "url(#hatch6)";
|
||||
religions.push({i: religions.length, name, color, culture, type:"Heresy", form:r.form, deity: r.deity, expansion:"global", expansionism, center, origin:r.i});
|
||||
const expansionism = gauss(1.2, 0.5, 0, 5);
|
||||
const color = getMixedColor(r.color, 0.4, 0.2); // "url(#hatch6)";
|
||||
religions.push({i: religions.length, name, color, culture, type: "Heresy", form: r.form, deity: r.deity, expansion: "global", expansionism, center, origin: r.i});
|
||||
religionsTree.add([x, y]);
|
||||
//debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).attr("fill", "green");
|
||||
}
|
||||
|
|
@ -164,49 +169,64 @@
|
|||
expandHeresies();
|
||||
checkCenters();
|
||||
|
||||
TIME && console.timeEnd('generateReligions');
|
||||
}
|
||||
TIME && console.timeEnd("generateReligions");
|
||||
};
|
||||
|
||||
const add = function(center) {
|
||||
const cells = pack.cells, religions = pack.religions;
|
||||
const add = function (center) {
|
||||
const cells = pack.cells,
|
||||
religions = pack.religions;
|
||||
const r = cells.religion[center];
|
||||
const i = religions.length;
|
||||
const culture = cells.culture[center];
|
||||
const color = getMixedColor(religions[r].color, .3, 0);
|
||||
const color = getMixedColor(religions[r].color, 0.3, 0);
|
||||
|
||||
const type = religions[r].type === "Organized" ? rw({Organized:4, Cult:1, Heresy:2}) : rw({Organized:5, Cult:2});
|
||||
const type = religions[r].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2}) : rw({Organized: 5, Cult: 2});
|
||||
const form = rw(forms[type]);
|
||||
const deity = type === "Heresy" ? religions[r].deity : form === "Non-theism" ? null : getDeityName(culture);
|
||||
|
||||
let name, expansion;
|
||||
if (type === "Organized") [name, expansion] = getReligionName(form, deity, center)
|
||||
else {name = getCultName(form, center); expansion = "global";}
|
||||
const formName = type === "Heresy" ? religions[r].form : form;
|
||||
const code = abbreviate(name, religions.map(r => r.code));
|
||||
religions.push({i, name, color, culture, type, form:formName, deity, expansion, expansionism:0, center, cells:0, area:0, rural:0, urban:0, origin:r, code});
|
||||
cells.religion[center] = i;
|
||||
if (type === "Organized") [name, expansion] = getReligionName(form, deity, center);
|
||||
else {
|
||||
name = getCultName(form, center);
|
||||
expansion = "global";
|
||||
}
|
||||
const formName = type === "Heresy" ? religions[r].form : form;
|
||||
const code = abbreviate(
|
||||
name,
|
||||
religions.map(r => r.code)
|
||||
);
|
||||
religions.push({i, name, color, culture, type, form: formName, deity, expansion, expansionism: 0, center, cells: 0, area: 0, rural: 0, urban: 0, origin: r, code});
|
||||
cells.religion[center] = i;
|
||||
};
|
||||
|
||||
// growth algorithm to assign cells to religions
|
||||
const expandReligions = function() {
|
||||
const cells = pack.cells, religions = pack.religions;
|
||||
const expandReligions = function () {
|
||||
const cells = pack.cells,
|
||||
religions = pack.religions;
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
|
||||
religions.filter(r => r.type === "Organized" || r.type === "Cult").forEach(r => {
|
||||
religions
|
||||
.filter(r => r.type === "Organized" || r.type === "Cult")
|
||||
.forEach(r => {
|
||||
cells.religion[r.center] = r.i;
|
||||
queue.queue({e:r.center, p:0, r:r.i, s: cells.state[r.center], c:r.culture});
|
||||
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center], c: r.culture});
|
||||
cost[r.center] = 1;
|
||||
});
|
||||
|
||||
const neutral = cells.i.length / 5000 * 200 * gauss(1, .3, .2, 2, 2) * neutralInput.value; // limit cost for organized religions growth
|
||||
const neutral = (cells.i.length / 5000) * 200 * gauss(1, 0.3, 0.2, 2, 2) * neutralInput.value; // limit cost for organized religions growth
|
||||
const popCost = d3.max(cells.pop) / 3; // enougth population to spered religion without penalty
|
||||
|
||||
while (queue.length) {
|
||||
const next = queue.dequeue(), n = next.e, p = next.p, r = next.r, c = next.c, s = next.s;
|
||||
const next = queue.dequeue(),
|
||||
n = next.e,
|
||||
p = next.p,
|
||||
r = next.r,
|
||||
c = next.c,
|
||||
s = next.s;
|
||||
const expansion = religions[r].expansion;
|
||||
|
||||
cells.c[n].forEach(function(e) {
|
||||
cells.c[n].forEach(function (e) {
|
||||
if (expansion === "culture" && c !== cells.culture[e]) return;
|
||||
if (expansion === "state" && s !== cells.state[e]) return;
|
||||
|
||||
|
|
@ -215,88 +235,101 @@
|
|||
const biomeCost = cells.road[e] ? 1 : biomesData.cost[cells.biome[e]];
|
||||
const populationCost = Math.max(rn(popCost - cells.pop[e]), 0);
|
||||
const heightCost = Math.max(cells.h[e], 20) - 20;
|
||||
const waterCost = cells.h[e] < 20 ? cells.road[e] ? 50 : 1000 : 0;
|
||||
const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0;
|
||||
const totalCost = p + (cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism;
|
||||
if (totalCost > neutral) return;
|
||||
|
||||
if (!cost[e] || totalCost < cost[e]) {
|
||||
if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell
|
||||
cost[e] = totalCost;
|
||||
queue.queue({e, p:totalCost, r, c, s});
|
||||
queue.queue({e, p: totalCost, r, c, s});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// growth algorithm to assign cells to heresies
|
||||
const expandHeresies = function() {
|
||||
const cells = pack.cells, religions = pack.religions;
|
||||
const expandHeresies = function () {
|
||||
const cells = pack.cells,
|
||||
religions = pack.religions;
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
|
||||
religions.filter(r => r.type === "Heresy").forEach(r => {
|
||||
religions
|
||||
.filter(r => r.type === "Heresy")
|
||||
.forEach(r => {
|
||||
const b = cells.religion[r.center]; // "base" religion id
|
||||
cells.religion[r.center] = r.i; // heresy id
|
||||
queue.queue({e:r.center, p:0, r:r.i, b});
|
||||
queue.queue({e: r.center, p: 0, r: r.i, b});
|
||||
cost[r.center] = 1;
|
||||
});
|
||||
|
||||
const neutral = cells.i.length / 5000 * 500 * neutralInput.value; // limit cost for heresies growth
|
||||
const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth
|
||||
|
||||
while (queue.length) {
|
||||
const next = queue.dequeue(), n = next.e, p = next.p, r = next.r, b = next.b;
|
||||
const next = queue.dequeue(),
|
||||
n = next.e,
|
||||
p = next.p,
|
||||
r = next.r,
|
||||
b = next.b;
|
||||
|
||||
cells.c[n].forEach(function(e) {
|
||||
cells.c[n].forEach(function (e) {
|
||||
const religionCost = cells.religion[e] === b ? 0 : 2000;
|
||||
const biomeCost = cells.road[e] ? 0 : biomesData.cost[cells.biome[e]];
|
||||
const heightCost = Math.max(cells.h[e], 20) - 20;
|
||||
const waterCost = cells.h[e] < 20 ? cells.road[e] ? 50 : 1000 : 0;
|
||||
const totalCost = p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, .1);
|
||||
const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0;
|
||||
const totalCost = p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1);
|
||||
|
||||
if (totalCost > neutral) return;
|
||||
|
||||
if (!cost[e] || totalCost < cost[e]) {
|
||||
if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell
|
||||
cost[e] = totalCost;
|
||||
queue.queue({e, p:totalCost, r});
|
||||
queue.queue({e, p: totalCost, r});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function checkCenters() {
|
||||
const cells = pack.cells, religions = pack.religions;
|
||||
const cells = pack.cells,
|
||||
religions = pack.religions;
|
||||
|
||||
const codes = religions.map(r => r.code);
|
||||
religions.filter(r => r.i).forEach(r => {
|
||||
religions
|
||||
.filter(r => r.i)
|
||||
.forEach(r => {
|
||||
r.code = abbreviate(r.name, codes);
|
||||
|
||||
// move religion center if it's not within religion area after expansion
|
||||
if (cells.religion[r.center] === r.i) return; // in area
|
||||
const religCells = cells.i.filter(i => cells.religion[i] === r.i);
|
||||
if (!religCells.length) return; // extinct religion
|
||||
r.center = religCells.sort((a,b) => b.pop - a.pop)[0];
|
||||
r.center = religCells.sort((a, b) => b.pop - a.pop)[0];
|
||||
});
|
||||
}
|
||||
|
||||
function updateCultures() {
|
||||
TIME && console.time('updateCulturesForReligions');
|
||||
pack.religions = pack.religions.map( (religion, index) => {
|
||||
if(index === 0) {
|
||||
TIME && console.time("updateCulturesForReligions");
|
||||
pack.religions = pack.religions.map((religion, index) => {
|
||||
if (index === 0) {
|
||||
return religion;
|
||||
}
|
||||
return {...religion, culture: pack.cells.culture[religion.center]};
|
||||
});
|
||||
TIME && console.timeEnd('updateCulturesForReligions');
|
||||
TIME && console.timeEnd("updateCulturesForReligions");
|
||||
}
|
||||
|
||||
// get supreme deity name
|
||||
const getDeityName = function(culture) {
|
||||
if (culture === undefined) {ERROR && console.error("Please define a culture"); return;}
|
||||
const meaning = generateMeaning();
|
||||
const cultureName = Names.getCulture(culture, null, null, "", .8);
|
||||
return cultureName + ", The " + meaning;
|
||||
const getDeityName = function (culture) {
|
||||
if (culture === undefined) {
|
||||
ERROR && console.error("Please define a culture");
|
||||
return;
|
||||
}
|
||||
const meaning = generateMeaning();
|
||||
const cultureName = Names.getCulture(culture, null, null, "", 0.8);
|
||||
return cultureName + ", The " + meaning;
|
||||
};
|
||||
|
||||
function generateMeaning() {
|
||||
const a = ra(approaches); // select generation approach
|
||||
|
|
@ -318,21 +351,29 @@
|
|||
|
||||
function getReligionName(form, deity, center) {
|
||||
const cells = pack.cells;
|
||||
const random = function() {return Names.getCulture(cells.culture[center], null, null, "", 0);}
|
||||
const type = function() {return rw(types[form]);}
|
||||
const supreme = function() {return deity.split(/[ ,]+/)[0];}
|
||||
const place = function(adj) {
|
||||
const random = function () {
|
||||
return Names.getCulture(cells.culture[center], null, null, "", 0);
|
||||
};
|
||||
const type = function () {
|
||||
return rw(types[form]);
|
||||
};
|
||||
const supreme = function () {
|
||||
return deity.split(/[ ,]+/)[0];
|
||||
};
|
||||
const place = function (adj) {
|
||||
const base = cells.burg[center] ? pack.burgs[cells.burg[center]].name : pack.states[cells.state[center]].name;
|
||||
let name = trimVowels(base.split(/[ ,]+/)[0]);
|
||||
return adj ? getAdjective(name) : name;
|
||||
}
|
||||
const culture = function() {return pack.cultures[cells.culture[center]].name;}
|
||||
};
|
||||
const culture = function () {
|
||||
return pack.cultures[cells.culture[center]].name;
|
||||
};
|
||||
|
||||
const m = rw(methods);
|
||||
if (m === "Random + type") return [random() + " " + type(), "global"];
|
||||
if (m === "Random + ism") return [trimVowels(random()) + "ism", "global"];
|
||||
if (m === "Supreme + ism" && deity) return [trimVowels(supreme()) + "ism", "global"];
|
||||
if (m === "Faith of + Supreme" && deity) return [ra(['Faith', 'Way', 'Path', 'Word', 'Witnesses']) + " of " + supreme(), "global"];
|
||||
if (m === "Faith of + Supreme" && deity) return [ra(["Faith", "Way", "Path", "Word", "Witnesses"]) + " of " + supreme(), "global"];
|
||||
if (m === "Place + ism") return [place() + "ism", "state"];
|
||||
if (m === "Culture + ism") return [trimVowels(culture()) + "ism", "culture"];
|
||||
if (m === "Place + ian + type") return [place("adj") + " " + type(), "state"];
|
||||
|
|
@ -342,14 +383,19 @@
|
|||
|
||||
function getCultName(form, center) {
|
||||
const cells = pack.cells;
|
||||
const type = function() {return rw(types[form]);}
|
||||
const random = function() {return trimVowels(Names.getCulture(cells.culture[center], null, null, "", 0).split(/[ ,]+/)[0]);}
|
||||
const burg = function() {return trimVowels(pack.burgs[cells.burg[center]].name.split(/[ ,]+/)[0]);}
|
||||
if (cells.burg[center]) return burg() + "ian " + type();
|
||||
if (Math.random() > .5) return random() + "ian " + type();
|
||||
return type() + " of the " + generateMeaning();
|
||||
const type = function () {
|
||||
return rw(types[form]);
|
||||
};
|
||||
const random = function () {
|
||||
return trimVowels(Names.getCulture(cells.culture[center], null, null, "", 0).split(/[ ,]+/)[0]);
|
||||
};
|
||||
const burg = function () {
|
||||
return trimVowels(pack.burgs[cells.burg[center]].name.split(/[ ,]+/)[0]);
|
||||
};
|
||||
if (cells.burg[center]) return burg() + "ian " + type();
|
||||
if (Math.random() > 0.5) return random() + "ian " + type();
|
||||
return type() + " of the " + generateMeaning();
|
||||
}
|
||||
|
||||
return {generate, add, getDeityName, expandReligions, updateCultures};
|
||||
|
||||
})));
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Rivers = factory());
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
window.Rivers = (function () {
|
||||
const generate = function (allowErosion = true) {
|
||||
TIME && console.time("generateRivers");
|
||||
Math.random = aleaPRNG(seed);
|
||||
const {cells, features} = pack;
|
||||
const p = cells.p;
|
||||
|
||||
const riversData = []; // rivers data
|
||||
const riversData = {}; // rivers data
|
||||
const riverParents = {};
|
||||
const addCellToRiver = function (cell, river) {
|
||||
if (!riversData[river]) riversData[river] = [cell];
|
||||
else riversData[river].push(cell);
|
||||
};
|
||||
|
||||
cells.fl = new Uint16Array(cells.i.length); // water flux array
|
||||
cells.r = new Uint16Array(cells.i.length); // rivers array
|
||||
cells.conf = new Uint8Array(cells.i.length); // confluences array
|
||||
|
|
@ -20,6 +23,7 @@
|
|||
resolveDepressions(h);
|
||||
drainWater();
|
||||
defineRivers();
|
||||
calculateConfluenceFlux();
|
||||
Lakes.cleanupLakeData();
|
||||
|
||||
if (allowErosion) cells.h = Uint8Array.from(h); // apply changed heights as basic one
|
||||
|
|
@ -28,99 +32,98 @@
|
|||
|
||||
function drainWater() {
|
||||
const MIN_FLUX_TO_FORM_RIVER = 30;
|
||||
const prec = grid.cells.prec;
|
||||
const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]);
|
||||
const lakeOutCells = Lakes.setClimateData(h);
|
||||
|
||||
// const flow = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length);
|
||||
// flow[i] = min;
|
||||
// debug.append("path").attr("class", "arrow").attr("d", `M${cells.p[i][0]},${cells.p[i][1]}L${cells.p[min][0]},${cells.p[min][1]}`);
|
||||
|
||||
land.forEach(function (i) {
|
||||
cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation
|
||||
const [x, y] = p[i];
|
||||
cells.fl[i] += prec[cells.g[i]]; // add flux from precipitation
|
||||
|
||||
// create lake outlet if lake is not in deep depression and flux > evaporation
|
||||
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
|
||||
for (const lake of lakes) {
|
||||
const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i);
|
||||
|
||||
cells.fl[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet
|
||||
|
||||
// allow chain lakes to retain identity
|
||||
if (cells.r[lakeCell] !== lake.river) {
|
||||
const sameRiver = cells.c[lakeCell].some(c => cells.r[c] === lake.river);
|
||||
|
||||
if (sameRiver) {
|
||||
cells.r[lakeCell] = lake.river;
|
||||
riversData.push({river: lake.river, cell: lakeCell, x: p[lakeCell][0], y: p[lakeCell][1], flux: cells.fl[lakeCell]});
|
||||
addCellToRiver(lakeCell, lake.river);
|
||||
} else {
|
||||
cells.r[lakeCell] = riverNext;
|
||||
riversData.push({river: riverNext, cell: lakeCell, x: p[lakeCell][0], y: p[lakeCell][1], flux: cells.fl[lakeCell]});
|
||||
addCellToRiver(lakeCell, riverNext);
|
||||
riverNext++;
|
||||
}
|
||||
}
|
||||
|
||||
lake.outlet = cells.r[lakeCell];
|
||||
flowDown(i, cells.fl[i], cells.fl[lakeCell], lake.outlet);
|
||||
flowDown(i, cells.fl[lakeCell], lake.outlet);
|
||||
}
|
||||
|
||||
// assign all tributary rivers to outlet basin
|
||||
for (let outlet = lakes[0]?.outlet, l = 0; l < lakes.length; l++) {
|
||||
lakes[l].inlets?.forEach(fork => (riversData.find(r => r.river === fork).parent = outlet));
|
||||
const outlet = lakes[0]?.outlet;
|
||||
for (const lake of lakes) {
|
||||
if (!Array.isArray(lake.inlets)) continue;
|
||||
for (const inlet of lake.inlets) {
|
||||
riverParents[inlet] = outlet;
|
||||
}
|
||||
}
|
||||
|
||||
// near-border cell: pour water out of the screen
|
||||
if (cells.b[i] && cells.r[i]) {
|
||||
let to = [];
|
||||
const min = Math.min(y, graphHeight - y, x, graphWidth - x);
|
||||
if (min === y) to = [x, 0];
|
||||
else if (min === graphHeight - y) to = [x, graphHeight];
|
||||
else if (min === x) to = [0, y];
|
||||
else if (min === graphWidth - x) to = [graphWidth, y];
|
||||
riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1], flux: cells.fl[i]});
|
||||
return;
|
||||
}
|
||||
if (cells.b[i] && cells.r[i]) return addCellToRiver(-1, cells.r[i]);
|
||||
|
||||
// downhill cell (make sure it's not in the source lake)
|
||||
const filtered = lakeOutCells[i] ? cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c])) : cells.c[i];
|
||||
const min = filtered.sort((a, b) => h[a] - h[b])[0];
|
||||
let min = null;
|
||||
if (lakeOutCells[i]) {
|
||||
const filtered = cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c]));
|
||||
min = filtered.sort((a, b) => h[a] - h[b])[0];
|
||||
} else if (cells.haven[i]) {
|
||||
min = cells.haven[i];
|
||||
} else {
|
||||
min = cells.c[i].sort((a, b) => h[a] - h[b])[0];
|
||||
}
|
||||
|
||||
// cells is depressed
|
||||
if (h[i] <= h[min]) return;
|
||||
|
||||
if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) {
|
||||
// flux is too small to operate as a river
|
||||
if (h[min] >= 20) cells.fl[min] += cells.fl[i];
|
||||
return; // flux is too small to operate as river
|
||||
return;
|
||||
}
|
||||
|
||||
// proclaim a new river
|
||||
if (!cells.r[i]) {
|
||||
cells.r[i] = riverNext;
|
||||
riversData.push({river: riverNext, cell: i, x, y, flux: cells.fl[i]});
|
||||
addCellToRiver(i, riverNext);
|
||||
riverNext++;
|
||||
}
|
||||
|
||||
flowDown(min, cells.fl[min], cells.fl[i], cells.r[i], i);
|
||||
flowDown(min, cells.fl[i], cells.r[i]);
|
||||
});
|
||||
}
|
||||
|
||||
function flowDown(toCell, toFlux, fromFlux, river, fromCell = 0) {
|
||||
if (cells.r[toCell]) {
|
||||
function flowDown(toCell, fromFlux, river) {
|
||||
const toFlux = cells.fl[toCell] - cells.conf[toCell];
|
||||
const toRiver = cells.r[toCell];
|
||||
|
||||
if (toRiver) {
|
||||
// downhill cell already has river assigned
|
||||
if (toFlux < fromFlux) {
|
||||
cells.conf[toCell] = cells.fl[toCell]; // mark confluence
|
||||
if (h[toCell] >= 20) riversData.find(r => r.river === cells.r[toCell]).parent = river; // min river is a tributary of current river
|
||||
if (fromFlux > toFlux) {
|
||||
cells.conf[toCell] += cells.fl[toCell]; // mark confluence
|
||||
if (h[toCell] >= 20) riverParents[toRiver] = river; // min river is a tributary of current river
|
||||
cells.r[toCell] = river; // re-assign river if downhill part has less flux
|
||||
} else {
|
||||
cells.conf[toCell] += fromFlux; // mark confluence
|
||||
if (h[toCell] >= 20) riversData.find(r => r.river === river).parent = cells.r[toCell]; // current river is a tributary of min river
|
||||
if (h[toCell] >= 20) riverParents[river] = toRiver; // current river is a tributary of min river
|
||||
}
|
||||
} else cells.r[toCell] = river; // assign the river to the downhill cell
|
||||
|
||||
if (h[toCell] < 20) {
|
||||
// pour water to the water body
|
||||
const haven = fromCell ? cells.haven[fromCell] : toCell;
|
||||
riversData.push({river, cell: haven, x: p[toCell][0], y: p[toCell][1], flux: fromFlux});
|
||||
|
||||
const waterBody = features[cells.f[toCell]];
|
||||
if (waterBody.type === "lake") {
|
||||
if (!waterBody.river || fromFlux > waterBody.enteringFlux) {
|
||||
|
|
@ -128,58 +131,69 @@
|
|||
waterBody.enteringFlux = fromFlux;
|
||||
}
|
||||
waterBody.flux = waterBody.flux + fromFlux;
|
||||
waterBody.inlets ? waterBody.inlets.push(river) : (waterBody.inlets = [river]);
|
||||
if (!waterBody.inlets) waterBody.inlets = [river];
|
||||
else waterBody.inlets.push(river);
|
||||
}
|
||||
} else {
|
||||
// propagate flux and add next river segment
|
||||
cells.fl[toCell] += fromFlux;
|
||||
riversData.push({river, cell: toCell, x: p[toCell][0], y: p[toCell][1], flux: fromFlux});
|
||||
}
|
||||
|
||||
addCellToRiver(toCell, river);
|
||||
}
|
||||
|
||||
function defineRivers() {
|
||||
cells.r = new Uint16Array(cells.i.length); // re-initiate rivers array
|
||||
pack.rivers = []; // rivers data
|
||||
const riverPaths = [];
|
||||
// re-initialize rivers and confluence arrays
|
||||
cells.r = new Uint16Array(cells.i.length);
|
||||
cells.conf = new Uint16Array(cells.i.length);
|
||||
pack.rivers = [];
|
||||
|
||||
for (let r = 1; r <= riverNext; r++) {
|
||||
const riverSegments = riversData.filter(d => d.river === r);
|
||||
if (riverSegments.length < 3) continue;
|
||||
for (const key in riversData) {
|
||||
const riverCells = riversData[key];
|
||||
if (riverCells.length < 3) continue; // exclude tiny rivers
|
||||
|
||||
for (const segment of riverSegments) {
|
||||
const i = segment.cell;
|
||||
if (cells.r[i]) continue;
|
||||
if (cells.h[i] < 20) continue;
|
||||
cells.r[i] = r;
|
||||
const riverId = +key;
|
||||
for (const cell of riverCells) {
|
||||
if (cell < 0 || cells.h[cell] < 20) continue;
|
||||
|
||||
// mark real confluences and assign river to cells
|
||||
if (cells.r[cell]) cells.conf[cell] = 1;
|
||||
else cells.r[cell] = riverId;
|
||||
}
|
||||
|
||||
const source = riverSegments[0].cell;
|
||||
const mouth = riverSegments[riverSegments.length - 2].cell;
|
||||
const source = riverCells[0];
|
||||
const mouth = riverCells[riverCells.length - 2];
|
||||
const parent = riverParents[key] || 0;
|
||||
|
||||
const widthFactor = rn(0.8 + Math.random() * 0.4, 1); // river width modifier [.8, 1.2]
|
||||
const sourceWidth = cells.h[source] >= 20 ? 0.1 : rn(Math.min(Math.max((cells.fl[source] / 500) ** 0.4, 0.5), 1.7), 2);
|
||||
const widthFactor = !parent || parent === riverId ? 1.2 : 1;
|
||||
const meanderedPoints = addMeandering(riverCells);
|
||||
const discharge = cells.fl[mouth]; // m3 in second
|
||||
const length = rn(getApproximateLength(meanderedPoints), 2);
|
||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
|
||||
|
||||
const riverMeandered = addMeandering(riverSegments, sourceWidth * 10, 0.5);
|
||||
const [path, length, offset] = getPath(riverMeandered, widthFactor, sourceWidth);
|
||||
riverPaths.push([path, r]);
|
||||
|
||||
const parent = riverSegments[0].parent || 0;
|
||||
const width = rn(offset ** 2, 2); // mounth width in km
|
||||
const discharge = last(riverSegments).flux; // in m3/s
|
||||
pack.rivers.push({i: r, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent});
|
||||
pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells});
|
||||
}
|
||||
}
|
||||
|
||||
// draw rivers
|
||||
rivers.html(riverPaths.map(d => `<path id="river${d[1]}" d="${d[0]}"/>`).join(""));
|
||||
function calculateConfluenceFlux() {
|
||||
for (const i of cells.i) {
|
||||
if (!cells.conf[i]) continue;
|
||||
|
||||
const sortedInflux = cells.c[i]
|
||||
.filter(c => cells.r[c] && h[c] > h[i])
|
||||
.map(c => cells.fl[c])
|
||||
.sort((a, b) => b - a);
|
||||
cells.conf[i] = sortedInflux.reduce((acc, flux, index) => (index ? acc + flux : acc), 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// add distance to water value to land cells to make map less depressed
|
||||
const alterHeights = () => {
|
||||
const cells = pack.cells;
|
||||
return Array.from(cells.h).map((h, i) => {
|
||||
if (h < 20 || cells.t[i] < 1) return h;
|
||||
return h + cells.t[i] / 100 + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000;
|
||||
const {h, c, t} = pack.cells;
|
||||
return Array.from(h).map((h, i) => {
|
||||
if (h < 20 || t[i] < 1) return h;
|
||||
return h + t[i] / 100 + d3.mean(c[i].map(c => t[c])) / 10000;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -242,118 +256,132 @@
|
|||
depressions && WARN && console.warn(`Unresolved depressions: ${depressions}. Edit heightmap to fix`);
|
||||
};
|
||||
|
||||
// add more river points on 1/3 and 2/3 of length
|
||||
const addMeandering = function (segments, width = 1, meandering = 0.5) {
|
||||
const riverMeandered = []; // to store enhanced segments
|
||||
// add points at 1/3 and 2/3 of a line between adjacents river cells
|
||||
const addMeandering = function (riverCells, riverPoints = null, meandering = 0.5) {
|
||||
const {fl, conf, h} = pack.cells;
|
||||
const meandered = [];
|
||||
const lastStep = riverCells.length - 1;
|
||||
const points = getRiverPoints(riverCells, riverPoints);
|
||||
let step = h[riverCells[0]] < 20 ? 1 : 10;
|
||||
|
||||
for (let s = 0; s < segments.length; s++, width++) {
|
||||
const sX = segments[s].x,
|
||||
sY = segments[s].y; // segment start coordinates
|
||||
const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence
|
||||
riverMeandered.push([sX, sY, c]);
|
||||
let fluxPrev = 0;
|
||||
const getFlux = (step, flux) => (step === lastStep ? fluxPrev : flux);
|
||||
|
||||
if (s + 1 === segments.length) break; // do not meander last segment
|
||||
for (let i = 0; i <= lastStep; i++, step++) {
|
||||
const cell = riverCells[i];
|
||||
const isLastCell = i === lastStep;
|
||||
|
||||
const eX = segments[s + 1].x,
|
||||
eY = segments[s + 1].y; // segment end coordinates
|
||||
const angle = Math.atan2(eY - sY, eX - sX);
|
||||
const sin = Math.sin(angle),
|
||||
cos = Math.cos(angle);
|
||||
const [x1, y1] = points[i];
|
||||
const flux1 = getFlux(i, fl[cell]);
|
||||
fluxPrev = flux1;
|
||||
|
||||
const meander = meandering + 1 / width + Math.random() * Math.max(meandering - width / 100, 0);
|
||||
const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2; // square distance between segment start and end
|
||||
meandered.push([x1, y1, flux1]);
|
||||
if (isLastCell) break;
|
||||
|
||||
if (width < 10 && (dist2 > 64 || (dist2 > 36 && segments.length < 6))) {
|
||||
const nextCell = riverCells[i + 1];
|
||||
const [x2, y2] = points[i + 1];
|
||||
|
||||
if (nextCell === -1) {
|
||||
meandered.push([x2, y2, fluxPrev]);
|
||||
break;
|
||||
}
|
||||
|
||||
const dist2 = (x2 - x1) ** 2 + (y2 - y1) ** 2; // square distance between cells
|
||||
if (dist2 <= 25 && riverCells.length >= 6) continue;
|
||||
|
||||
const flux2 = getFlux(i + 1, fl[nextCell]);
|
||||
const keepInitialFlux = conf[nextCell] || flux1 === flux2;
|
||||
|
||||
const meander = meandering + 1 / step + Math.max(meandering - step / 100, 0);
|
||||
const angle = Math.atan2(y2 - y1, x2 - x1);
|
||||
const sinMeander = Math.sin(angle) * meander;
|
||||
const cosMeander = Math.cos(angle) * meander;
|
||||
|
||||
if (step < 10 && (dist2 > 64 || (dist2 > 36 && riverCells.length < 5))) {
|
||||
// if dist2 is big or river is small add extra points at 1/3 and 2/3 of segment
|
||||
const p1x = (sX * 2 + eX) / 3 + -sin * meander;
|
||||
const p1y = (sY * 2 + eY) / 3 + cos * meander;
|
||||
const p2x = (sX + eX * 2) / 3 + sin * meander;
|
||||
const p2y = (sY + eY * 2) / 3 + cos * meander;
|
||||
riverMeandered.push([p1x, p1y], [p2x, p2y]);
|
||||
} else if (dist2 > 25 || segments.length < 6) {
|
||||
const p1x = (x1 * 2 + x2) / 3 + -sinMeander;
|
||||
const p1y = (y1 * 2 + y2) / 3 + cosMeander;
|
||||
const p2x = (x1 + x2 * 2) / 3 + sinMeander / 2;
|
||||
const p2y = (y1 + y2 * 2) / 3 - cosMeander / 2;
|
||||
const [p1fl, p2fl] = keepInitialFlux ? [flux1, flux1] : [(flux1 * 2 + flux2) / 3, (flux1 + flux2 * 2) / 3];
|
||||
meandered.push([p1x, p1y, p1fl], [p2x, p2y, p2fl]);
|
||||
} else if (dist2 > 25 || riverCells.length < 6) {
|
||||
// if dist is medium or river is small add 1 extra middlepoint
|
||||
const p1x = (sX + eX) / 2 + -sin * meander;
|
||||
const p1y = (sY + eY) / 2 + cos * meander;
|
||||
riverMeandered.push([p1x, p1y]);
|
||||
const p1x = (x1 + x2) / 2 + -sinMeander;
|
||||
const p1y = (y1 + y2) / 2 + cosMeander;
|
||||
const p1fl = keepInitialFlux ? flux1 : (flux1 + flux2) / 2;
|
||||
meandered.push([p1x, p1y, p1fl]);
|
||||
}
|
||||
}
|
||||
|
||||
return riverMeandered;
|
||||
return meandered;
|
||||
};
|
||||
|
||||
const getPath = function (points, widthFactor = 1, sourceWidth = 0.1) {
|
||||
let offset,
|
||||
extraOffset = sourceWidth; // starting river width (to make river source visible)
|
||||
const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0); // summ of segments length
|
||||
const widening = 1000 + riverLength * 30;
|
||||
const riverPointsLeft = [],
|
||||
riverPointsRight = []; // store points on both sides to build a valid polygon
|
||||
const last = points.length - 1;
|
||||
const factor = riverLength / points.length;
|
||||
const getRiverPoints = (riverCells, riverPoints) => {
|
||||
const {p} = pack.cells;
|
||||
return riverCells.map((cell, i) => {
|
||||
if (riverPoints && riverPoints[i]) return riverPoints[i];
|
||||
if (cell === -1) return getBorderPoint(riverCells[i - 1]);
|
||||
return p[cell];
|
||||
});
|
||||
};
|
||||
|
||||
// first point
|
||||
let x = points[0][0],
|
||||
y = points[0][1],
|
||||
c;
|
||||
let angle = Math.atan2(y - points[1][1], x - points[1][0]);
|
||||
let sin = Math.sin(angle),
|
||||
cos = Math.cos(angle);
|
||||
let xLeft = x + -sin * extraOffset,
|
||||
yLeft = y + cos * extraOffset;
|
||||
riverPointsLeft.push([xLeft, yLeft]);
|
||||
let xRight = x + sin * extraOffset,
|
||||
yRight = y + -cos * extraOffset;
|
||||
riverPointsRight.unshift([xRight, yRight]);
|
||||
const getBorderPoint = i => {
|
||||
const [x, y] = pack.cells.p[i];
|
||||
const min = Math.min(y, graphHeight - y, x, graphWidth - x);
|
||||
if (min === y) return [x, 0];
|
||||
else if (min === graphHeight - y) return [x, graphHeight];
|
||||
else if (min === x) return [0, y];
|
||||
return [graphWidth, y];
|
||||
};
|
||||
|
||||
// middle points
|
||||
for (let p = 1; p < last; p++) {
|
||||
(x = points[p][0]), (y = points[p][1]), (c = points[p][2] || 0);
|
||||
const xPrev = points[p - 1][0],
|
||||
yPrev = points[p - 1][1];
|
||||
const xNext = points[p + 1][0],
|
||||
yNext = points[p + 1][1];
|
||||
angle = Math.atan2(yPrev - yNext, xPrev - xNext);
|
||||
(sin = Math.sin(angle)), (cos = Math.cos(angle));
|
||||
offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2) * widthFactor + extraOffset;
|
||||
const confOffset = Math.atan((c * 5) / widening);
|
||||
extraOffset += confOffset;
|
||||
(xLeft = x + -sin * offset), (yLeft = y + cos * (offset + confOffset));
|
||||
riverPointsLeft.push([xLeft, yLeft]);
|
||||
(xRight = x + sin * offset), (yRight = y + -cos * offset);
|
||||
riverPointsRight.unshift([xRight, yRight]);
|
||||
const FLUX_FACTOR = 500;
|
||||
const MAX_FLUX_WIDTH = 2;
|
||||
const LENGTH_FACTOR = 200;
|
||||
const STEP_WIDTH = 1 / LENGTH_FACTOR;
|
||||
const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR);
|
||||
const MAX_PROGRESSION = last(LENGTH_PROGRESSION);
|
||||
|
||||
const getOffset = (flux, pointNumber, widthFactor = 1, startingWidth = 0) => {
|
||||
const fluxWidth = Math.min(flux ** 0.9 / FLUX_FACTOR, MAX_FLUX_WIDTH);
|
||||
const lengthWidth = pointNumber * STEP_WIDTH + (LENGTH_PROGRESSION[pointNumber] || MAX_PROGRESSION);
|
||||
return widthFactor * (lengthWidth + fluxWidth) + startingWidth;
|
||||
};
|
||||
|
||||
// build polygon from a list of points and calculated offset (width)
|
||||
const getRiverPath = function (points, widthFactor = 1, startingWidth = 0) {
|
||||
const riverPointsLeft = [];
|
||||
const riverPointsRight = [];
|
||||
|
||||
for (let p = 0; p < points.length; p++) {
|
||||
const [x0, y0] = points[p - 1] || points[p];
|
||||
const [x1, y1, flux] = points[p];
|
||||
const [x2, y2] = points[p + 1] || points[p];
|
||||
|
||||
const offset = getOffset(flux, p, widthFactor, startingWidth);
|
||||
const angle = Math.atan2(y0 - y2, x0 - x2);
|
||||
const sinOffset = Math.sin(angle) * offset;
|
||||
const cosOffset = Math.cos(angle) * offset;
|
||||
|
||||
riverPointsLeft.push([x1 - sinOffset, y1 + cosOffset]);
|
||||
riverPointsRight.push([x1 + sinOffset, y1 - cosOffset]);
|
||||
}
|
||||
|
||||
// end point
|
||||
(x = points[last][0]), (y = points[last][1]), (c = points[last][2]);
|
||||
if (c) extraOffset += Math.atan((c * 10) / widening); // add extra width on river confluence
|
||||
angle = Math.atan2(points[last - 1][1] - y, points[last - 1][0] - x);
|
||||
(sin = Math.sin(angle)), (cos = Math.cos(angle));
|
||||
(xLeft = x + -sin * offset), (yLeft = y + cos * offset);
|
||||
riverPointsLeft.push([xLeft, yLeft]);
|
||||
(xRight = x + sin * offset), (yRight = y + -cos * offset);
|
||||
riverPointsRight.unshift([xRight, yRight]);
|
||||
|
||||
// generate polygon path and return
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const right = lineGen(riverPointsRight);
|
||||
const right = lineGen(riverPointsRight.reverse());
|
||||
let left = lineGen(riverPointsLeft);
|
||||
left = left.substring(left.indexOf("C"));
|
||||
return [round(right + left, 2), rn(riverLength, 2), offset];
|
||||
|
||||
return round(right + left, 1);
|
||||
};
|
||||
|
||||
const specify = function () {
|
||||
const rivers = pack.rivers;
|
||||
if (!rivers.length) return;
|
||||
Math.random = aleaPRNG(seed);
|
||||
const thresholdElement = Math.ceil(rivers.length * 0.15);
|
||||
const smallLength = rivers.map(r => r.length || 0).sort((a, b) => a - b)[thresholdElement];
|
||||
const smallType = {Creek: 9, River: 3, Brook: 3, Stream: 1}; // weighted small river types
|
||||
|
||||
for (const r of rivers) {
|
||||
r.basin = getBasin(r.i);
|
||||
r.name = getName(r.mouth);
|
||||
const small = r.length < smallLength;
|
||||
r.type = r.parent && !(r.i % 6) ? (small ? "Branch" : "Fork") : small ? rw(smallType) : "River";
|
||||
for (const river of rivers) {
|
||||
river.basin = getBasin(river.i);
|
||||
river.name = getName(river.mouth);
|
||||
river.type = getType(river);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -361,6 +389,36 @@
|
|||
return Names.getCulture(pack.cells.culture[cell]);
|
||||
};
|
||||
|
||||
// weighted arrays of river type names
|
||||
const riverTypes = {
|
||||
main: {
|
||||
big: {River: 1},
|
||||
small: {Creek: 9, River: 3, Brook: 3, Stream: 1}
|
||||
},
|
||||
fork: {
|
||||
big: {Fork: 1},
|
||||
small: {Branch: 1}
|
||||
}
|
||||
};
|
||||
|
||||
let smallLength = null;
|
||||
const getType = function ({i, length, parent}) {
|
||||
if (smallLength === null) {
|
||||
const threshold = Math.ceil(pack.rivers.length * 0.15);
|
||||
smallLength = pack.rivers.map(r => r.length || 0).sort((a, b) => a - b)[threshold];
|
||||
}
|
||||
|
||||
const isSmall = length < smallLength;
|
||||
const isFork = each(3)(i) && parent && parent !== i;
|
||||
return rw(riverTypes[isFork ? "fork" : "main"][isSmall ? "small" : "big"]);
|
||||
};
|
||||
|
||||
const getApproximateLength = points => points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0);
|
||||
|
||||
// Real mouth width examples: Amazon 6000m, Volga 6000m, Dniepr 3000m, Mississippi 1300m, Themes 900m,
|
||||
// Danube 800m, Daugava 600m, Neva 500m, Nile 450m, Don 400m, Wisla 300m, Pripyat 150m, Bug 140m, Muchavets 40m
|
||||
const getWidth = offset => rn((offset / 1.5) ** 1.8, 2); // mouth width in km
|
||||
|
||||
// remove river and all its tributaries
|
||||
const remove = function (id) {
|
||||
const cells = pack.cells;
|
||||
|
|
@ -381,5 +439,5 @@
|
|||
return getBasin(parent);
|
||||
};
|
||||
|
||||
return {generate, alterHeights, resolveDepressions, addMeandering, getPath, specify, getName, getBasin, remove};
|
||||
});
|
||||
return {generate, alterHeights, resolveDepressions, addMeandering, getRiverPath, specify, getName, getType, getBasin, getWidth, getOffset, getApproximateLength, getRiverPoints, remove};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,25 +1,21 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Routes = factory());
|
||||
})(this, function () {
|
||||
"use strict";
|
||||
|
||||
window.Routes = (function () {
|
||||
const getRoads = function () {
|
||||
TIME && console.time("generateMainRoads");
|
||||
const cells = pack.cells;
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
const capitals = burgs.filter(b => b.capital);
|
||||
const capitals = burgs.filter(b => b.capital).sort((a, b) => a.population - b.population);
|
||||
|
||||
if (capitals.length < 2) return []; // not enough capitals to build main roads
|
||||
const paths = []; // array to store path segments
|
||||
|
||||
for (const b of capitals) {
|
||||
const connect = capitals.filter(c => c.i > b.i && c.feature === b.feature);
|
||||
if (!connect.length) continue;
|
||||
const farthest = d3.scan(connect, (a, c) => (c.y - b.y) ** 2 + (c.x - b.x) ** 2 - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2));
|
||||
const [from, exit] = findLandPath(b.cell, connect[farthest].cell, null);
|
||||
const connect = capitals.filter(c => c.feature === b.feature && c !== b);
|
||||
for (const t of connect) {
|
||||
const [from, exit] = findLandPath(b.cell, t.cell, true);
|
||||
const segments = restorePath(b.cell, exit, "main", from);
|
||||
segments.forEach(s => paths.push(s));
|
||||
}
|
||||
}
|
||||
|
||||
cells.i.forEach(i => (cells.s[i] += cells.road[i] / 2)); // add roads to suitability score
|
||||
TIME && console.timeEnd("generateMainRoads");
|
||||
|
|
@ -41,11 +37,12 @@
|
|||
isle.forEach(function (b, i) {
|
||||
let path = [];
|
||||
if (!i) {
|
||||
// build trail from the first burg on island to the farthest one on the same island
|
||||
// build trail from the first burg on island
|
||||
// to the farthest one on the same island or the closest road
|
||||
const farthest = d3.scan(isle, (a, c) => (c.y - b.y) ** 2 + (c.x - b.x) ** 2 - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2));
|
||||
const to = isle[farthest].cell;
|
||||
if (cells.road[to]) return;
|
||||
const [from, exit] = findLandPath(b.cell, to, null);
|
||||
const [from, exit] = findLandPath(b.cell, to, true);
|
||||
path = restorePath(b.cell, exit, "small", from);
|
||||
} else {
|
||||
// build trail from all other burgs to the closest road on the same island
|
||||
|
|
@ -176,6 +173,7 @@
|
|||
if (cells.h[c] < 20) continue; // ignore water cells
|
||||
const stateChangeCost = cells.state && cells.state[c] !== cells.state[n] ? 400 : 0; // trails tend to lay within the same state
|
||||
const habitability = biomesData.habitability[cells.biome[c]];
|
||||
if (!habitability) continue; // avoid inhabitable cells (eg. lava, glacier)
|
||||
const habitedCost = habitability ? Math.max(100 - habitability, 0) : 400; // routes tend to lay within populated areas
|
||||
const heightChangeCost = Math.abs(cells.h[c] - cells.h[n]) * 10; // routes tend to avoid elevation changes
|
||||
const heightCost = cells.h[c] > 80 ? cells.h[c] : 0; // routes tend to avoid mountainous areas
|
||||
|
|
@ -198,7 +196,7 @@
|
|||
let segment = [],
|
||||
current = end,
|
||||
prev = end;
|
||||
const score = type === "main" ? 5 : 1; // to incrade road score at cell
|
||||
const score = type === "main" ? 5 : 1; // to increase road score at cell
|
||||
|
||||
if (type === "ocean" || !cells.road[prev]) segment.push(end);
|
||||
if (!cells.road[prev]) cells.road[prev] = score;
|
||||
|
|
@ -268,4 +266,4 @@
|
|||
}
|
||||
return [from, exit, false];
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ async function saveJPEG() {
|
|||
async function saveTiles() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// download schema
|
||||
const urlSchema = await getMapURL('tiles', 'schema');
|
||||
const urlSchema = await getMapURL('tiles', {debug: true});
|
||||
const zip = new JSZip();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
|
|
@ -138,20 +138,26 @@ async function saveTiles() {
|
|||
}
|
||||
|
||||
// parse map svg to object url
|
||||
async function getMapURL(type, subtype) {
|
||||
async function getMapURL(type, options = {}) {
|
||||
const {debug = false, globe = false, noLabels = false, noWater = false} = options;
|
||||
const cloneEl = document.getElementById('map').cloneNode(true); // clone svg
|
||||
cloneEl.id = 'fantasyMap';
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
if (subtype !== 'schema') clone.select('#debug').remove();
|
||||
if (!debug) clone.select('#debug').remove();
|
||||
|
||||
const cloneDefs = cloneEl.getElementsByTagName('defs')[0];
|
||||
const svgDefs = document.getElementById('defElements');
|
||||
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
if (isFirefox && type === 'mesh') clone.select('#oceanPattern').remove();
|
||||
if (subtype === 'globe') clone.select('#scaleBar').remove();
|
||||
if (subtype === 'noWater') {
|
||||
if (globe) clone.select('#scaleBar').remove();
|
||||
if (noLabels) {
|
||||
clone.select('#labels #states').remove();
|
||||
clone.select('#labels #burgLabels').remove();
|
||||
clone.select('#icons #burgIcons').remove();
|
||||
}
|
||||
if (noWater) {
|
||||
clone.select('#oceanBase').attr('opacity', 0);
|
||||
clone.select('#oceanPattern').attr('opacity', 0);
|
||||
}
|
||||
|
|
@ -275,8 +281,16 @@ async function getMapURL(type, subtype) {
|
|||
});
|
||||
}
|
||||
|
||||
const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts
|
||||
if (fontStyle) clone.select('defs').append('style').text(fontStyle.join('\n')); // add font to style
|
||||
// load non-standard fonts
|
||||
const usedFonts = getFontsList(clone);
|
||||
const webSafe = ['Georgia', 'Times+New+Roman', 'Comic+Sans+MS', 'Lucida+Sans+Unicode', 'Courier+New', 'Verdana', 'Arial', 'Impact'];
|
||||
const fontsToLoad = usedFonts.filter((font) => !webSafe.includes(font));
|
||||
if (fontsToLoad.length) {
|
||||
const url = 'https://fonts.googleapis.com/css?family=' + fontsToLoad.join('|');
|
||||
const fontStyle = await GFontToDataURI(url);
|
||||
if (fontStyle) clone.select('defs').append('style').text(fontStyle.join('\n'));
|
||||
}
|
||||
|
||||
clone.remove();
|
||||
|
||||
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + new XMLSerializer().serializeToString(cloneEl);
|
||||
|
|
@ -357,63 +371,6 @@ function inlineStyle(clone) {
|
|||
emptyG.remove();
|
||||
}
|
||||
|
||||
// get non-standard fonts used for labels to fetch them from web
|
||||
function getFontsToLoad(clone) {
|
||||
const webSafe = ['Georgia', 'Times+New+Roman', 'Comic+Sans+MS', 'Lucida+Sans+Unicode', 'Courier+New', 'Verdana', 'Arial', 'Impact']; // fonts to not fetch
|
||||
|
||||
const fontsInUse = new Set(); // to store fonts currently in use
|
||||
clone.selectAll('#labels > g').each(function () {
|
||||
if (!this.hasChildNodes()) return;
|
||||
const font = this.dataset.font;
|
||||
if (!font || webSafe.includes(font)) return;
|
||||
fontsInUse.add(font);
|
||||
});
|
||||
const legendFont = legend.attr('data-font');
|
||||
if (legend.node().hasChildNodes() && !webSafe.includes(legendFont)) fontsInUse.add(legendFont);
|
||||
const fonts = [...fontsInUse];
|
||||
return fonts.length ? 'https://fonts.googleapis.com/css?family=' + fonts.join('|') : null;
|
||||
}
|
||||
|
||||
// code from Kaiido's answer https://stackoverflow.com/questions/42402584/how-to-use-google-fonts-in-canvas-when-drawing-dom-objects-in-svg
|
||||
function GFontToDataURI(url) {
|
||||
if (!url) return Promise.resolve();
|
||||
return fetch(url) // first fecth the embed stylesheet page
|
||||
.then((resp) => resp.text()) // we only need the text of it
|
||||
.then((text) => {
|
||||
let s = document.createElement('style');
|
||||
s.innerHTML = text;
|
||||
document.head.appendChild(s);
|
||||
const styleSheet = Array.prototype.filter.call(document.styleSheets, (sS) => sS.ownerNode === s)[0];
|
||||
|
||||
const FontRule = (rule) => {
|
||||
const src = rule.style.getPropertyValue('src');
|
||||
const url = src ? src.split('url(')[1].split(')')[0] : '';
|
||||
return {rule, src, url: url.substring(url.length - 1, 1)};
|
||||
};
|
||||
const fontProms = [];
|
||||
|
||||
for (const r of styleSheet.cssRules) {
|
||||
let fR = FontRule(r);
|
||||
if (!fR.url) continue;
|
||||
|
||||
fontProms.push(
|
||||
fetch(fR.url) // fetch the actual font-file (.woff)
|
||||
.then((resp) => resp.blob())
|
||||
.then((blob) => {
|
||||
return new Promise((resolve) => {
|
||||
let f = new FileReader();
|
||||
f.onload = (e) => resolve(f.result);
|
||||
f.readAsDataURL(blob);
|
||||
});
|
||||
})
|
||||
.then((dataURL) => fR.rule.cssText.replace(fR.url, dataURL))
|
||||
);
|
||||
}
|
||||
document.head.removeChild(s); // clean up
|
||||
return Promise.all(fontProms); // wait for all this has been done
|
||||
});
|
||||
}
|
||||
|
||||
// prepare map data for saving
|
||||
function getMapData() {
|
||||
TIME && console.time('createMapDataBlob');
|
||||
|
|
@ -445,7 +402,9 @@ function getMapData() {
|
|||
precOutput.value,
|
||||
JSON.stringify(options),
|
||||
mapName.value,
|
||||
+hideLabels.checked
|
||||
+hideLabels.checked,
|
||||
stylePreset.value,
|
||||
+rescaleLabels.checked
|
||||
].join('|');
|
||||
const coords = JSON.stringify(mapCoordinates);
|
||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join('|');
|
||||
|
|
@ -667,10 +626,7 @@ function getRiverPoints(node) {
|
|||
}
|
||||
|
||||
async function quickSave() {
|
||||
if (customization) {
|
||||
tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (customization) return tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
const blob = await getMapData();
|
||||
if (blob) ldb.set('lastMap', blob); // auto-save map
|
||||
tip('Map is saved to browser memory. Please also save as .map file to secure progress', true, 'success', 2000);
|
||||
|
|
|
|||
463
modules/ui/3d.js
463
modules/ui/3d.js
File diff suppressed because one or more lines are too long
|
|
@ -14,14 +14,14 @@ function restoreDefaultEvents() {
|
|||
function clicked() {
|
||||
const el = d3.event.target;
|
||||
if (!el || !el.parentElement || !el.parentElement.parentElement) return;
|
||||
const parent = el.parentElement,
|
||||
grand = parent.parentElement,
|
||||
great = grand.parentElement;
|
||||
const parent = el.parentElement;
|
||||
const grand = parent.parentElement;
|
||||
const great = grand.parentElement;
|
||||
const p = d3.mouse(this);
|
||||
const i = findCell(p[0], p[1]);
|
||||
|
||||
if (grand.id === 'emblems') editEmblem();
|
||||
else if (parent.id === 'rivers') editRiver();
|
||||
else if (parent.id === 'rivers') editRiver(el.id);
|
||||
else if (grand.id === 'routes') editRoute();
|
||||
else if (el.tagName === 'tspan' && grand.parentNode.parentNode.id === 'labels') editLabel();
|
||||
else if (grand.id === 'burgLabels') editBurg();
|
||||
|
|
@ -118,33 +118,6 @@ function applySorting(headers) {
|
|||
.forEach((line) => list.appendChild(line));
|
||||
}
|
||||
|
||||
function confirmationDialog(options) {
|
||||
const {
|
||||
title = 'Confirm action',
|
||||
message = 'Are you sure you want to continue? <br>The action cannot be reverted',
|
||||
cancel = 'Cancel',
|
||||
confirm = 'Continue',
|
||||
onCancel = () => {},
|
||||
onConfirm = () => {}
|
||||
} = options;
|
||||
|
||||
alertMessage.innerHTML = message;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title,
|
||||
buttons: {
|
||||
[confirm]: function () {
|
||||
onConfirm();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
[cancel]: function () {
|
||||
onCancel();
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addBurg(point) {
|
||||
const cells = pack.cells;
|
||||
const x = rn(point[0], 2),
|
||||
|
|
@ -405,15 +378,14 @@ function clearLegend() {
|
|||
}
|
||||
|
||||
// draw color (fill) picker
|
||||
function createPicker(hatching) {
|
||||
const COLORS_IN_ROW = 14;
|
||||
function createPicker() {
|
||||
const pos = () => tip('Drag to change the picker position');
|
||||
const cl = () => tip('Click to close the picker');
|
||||
const closePicker = () => container.remove();
|
||||
const closePicker = () => contaiter.style('display', 'none');
|
||||
|
||||
const container = d3.select('body').append('svg').attr('id', 'pickerContainer').attr('width', '100%').attr('height', '100%');
|
||||
container.append('rect').attr('width', '100%').attr('height', '100%').attr('opacity', 0).on('mousemove', cl).on('click', closePicker);
|
||||
const picker = container
|
||||
const contaiter = d3.select('body').append('svg').attr('id', 'pickerContainer').attr('width', '100%').attr('height', '100%');
|
||||
contaiter.append('rect').attr('x', 0).attr('y', 0).attr('width', '100%').attr('height', '100%').attr('opacity', 0.2).on('mousemove', cl).on('click', closePicker);
|
||||
const picker = contaiter
|
||||
.append('g')
|
||||
.attr('id', 'picker')
|
||||
.call(
|
||||
|
|
@ -469,7 +441,11 @@ function createPicker(hatching) {
|
|||
spaces.selectAll('input').on('change', changePickerSpace);
|
||||
|
||||
const colors = picker.append('g').attr('id', 'pickerColors').attr('stroke', '#333333');
|
||||
const clr = d3.range(COLORS_IN_ROW).map((i) => d3.hsl((i / COLORS_IN_ROW) * 360, 0.7, 0.7).hex());
|
||||
const hatches = picker.append('g').attr('id', 'pickerHatches').attr('stroke', '#333333');
|
||||
const hatching = d3.selectAll('g#hatching > pattern');
|
||||
const number = hatching.size();
|
||||
|
||||
const clr = d3.range(number).map((i) => d3.hsl((i / number) * 360, 0.7, 0.7).hex());
|
||||
clr.forEach(function (d, i) {
|
||||
colors
|
||||
.append('rect')
|
||||
|
|
@ -481,14 +457,8 @@ function createPicker(hatching) {
|
|||
.attr('width', 16)
|
||||
.attr('height', 16);
|
||||
});
|
||||
colors
|
||||
.selectAll('rect')
|
||||
.on('click', pickerFillClicked)
|
||||
.on('mousemove', () => tip('Click to fill with the color'));
|
||||
|
||||
if (hatching) {
|
||||
const hatches = picker.append('g').attr('id', 'pickerHatches').attr('stroke', '#333333');
|
||||
d3.selectAll('g#hatching > pattern').each(function (d, i) {
|
||||
hatching.each(function (d, i) {
|
||||
hatches
|
||||
.append('rect')
|
||||
.attr('id', 'picker_' + this.id)
|
||||
|
|
@ -498,11 +468,15 @@ function createPicker(hatching) {
|
|||
.attr('width', 16)
|
||||
.attr('height', 16);
|
||||
});
|
||||
|
||||
colors
|
||||
.selectAll('rect')
|
||||
.on('click', pickerFillClicked)
|
||||
.on('mousemove', () => tip('Click to fill with the color'));
|
||||
hatches
|
||||
.selectAll('rect')
|
||||
.on('click', pickerFillClicked)
|
||||
.on('mousemove', () => tip('Click to fill with the hatching'));
|
||||
}
|
||||
|
||||
// append box
|
||||
const bbox = picker.node().getBBox();
|
||||
|
|
@ -973,6 +947,7 @@ function selectIcon(initial, callback) {
|
|||
|
||||
// Calls the refresh for all currently open editors
|
||||
function refreshAllEditors() {
|
||||
TIME && console.time('refreshAllEditors');
|
||||
if (document.getElementById('culturesEditorRefresh').offsetParent) culturesEditorRefresh.click();
|
||||
if (document.getElementById('biomesEditorRefresh').offsetParent) biomesEditorRefresh.click();
|
||||
if (document.getElementById('diplomacyEditorRefresh').offsetParent) diplomacyEditorRefresh.click();
|
||||
|
|
@ -980,5 +955,5 @@ function refreshAllEditors() {
|
|||
if (document.getElementById('religionsEditorRefresh').offsetParent) religionsEditorRefresh.click();
|
||||
if (document.getElementById('statesEditorRefresh').offsetParent) statesEditorRefresh.click();
|
||||
if (document.getElementById('zonesEditorRefresh').offsetParent) zonesEditorRefresh.click();
|
||||
if (document.getElementById('resourcesEditorRefresh').offsetParent) resourcesEditorRefresh.click();
|
||||
TIME && console.timeEnd('refreshAllEditors');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ document.getElementById('exitCustomization').addEventListener('mousemove', showD
|
|||
/**
|
||||
* @param {string} tip Tooltip text
|
||||
* @param {boolean} main Show above other tooltips
|
||||
* @param {string} type Message type (color): error, warn, success
|
||||
* @param {string} type Message type (color): error / warn / success
|
||||
* @param {number} time Timeout to auto hide, ms
|
||||
*/
|
||||
function tip(tip = 'Tip is undefined', main, type, time) {
|
||||
|
|
@ -96,10 +96,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// specific elements
|
||||
if (group === 'armies') {
|
||||
tip(e.target.parentNode.dataset.name + '. Click to edit');
|
||||
return;
|
||||
}
|
||||
if (group === 'armies') return tip(e.target.parentNode.dataset.name + '. Click to edit');
|
||||
|
||||
if (group === 'emblems' && e.target.tagName === 'use') {
|
||||
const parent = e.target.parentNode;
|
||||
|
|
@ -130,14 +127,11 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (riversOverview.offsetParent) highlightEditorLine(riversOverview, river, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === 'routes') {
|
||||
tip('Click to edit the Route');
|
||||
return;
|
||||
}
|
||||
if (group === 'terrain') {
|
||||
tip('Click to edit the Relief Icon');
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'routes') return tip('Click to edit the Route');
|
||||
|
||||
if (group === 'terrain') return tip('Click to edit the Relief Icon');
|
||||
|
||||
if (subgroup === 'burgLabels' || subgroup === 'burgIcons') {
|
||||
const burg = +path[path.length - 10].dataset.id;
|
||||
const b = pack.burgs[burg];
|
||||
|
|
@ -146,50 +140,25 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (burgsOverview.offsetParent) highlightEditorLine(burgsOverview, burg, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === 'labels') {
|
||||
tip('Click to edit the Label');
|
||||
return;
|
||||
}
|
||||
if (group === 'markers') {
|
||||
tip('Click to edit the Marker');
|
||||
return;
|
||||
}
|
||||
if (group === 'labels') return tip('Click to edit the Label');
|
||||
|
||||
if (group === 'markers') return tip('Click to edit the Marker');
|
||||
|
||||
if (group === 'ruler') {
|
||||
const tag = e.target.tagName;
|
||||
const className = e.target.getAttribute('class');
|
||||
if (tag === 'circle' && className === 'edge') {
|
||||
tip('Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'circle' && className === 'control') {
|
||||
tip('Drag to adjust. Hold Shifta and drag to keep axial direction. Click to remove the point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'circle') {
|
||||
tip('Drag to adjust the measurer');
|
||||
return;
|
||||
}
|
||||
if (tag === 'polyline') {
|
||||
tip('Click on drag to add a control point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'path') {
|
||||
tip('Drag to move the measurer');
|
||||
return;
|
||||
}
|
||||
if (tag === 'text') {
|
||||
tip('Drag to move, click to remove the measurer');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (subgroup === 'burgIcons') {
|
||||
tip('Click to edit the Burg');
|
||||
return;
|
||||
}
|
||||
if (subgroup === 'burgLabels') {
|
||||
tip('Click to edit the Burg');
|
||||
return;
|
||||
if (tag === 'circle' && className === 'edge') return tip('Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point');
|
||||
if (tag === 'circle' && className === 'control') return tip('Drag to adjust. Hold Shift and drag to keep axial direction. Click to remove the point');
|
||||
if (tag === 'circle') return tip('Drag to adjust the measurer');
|
||||
if (tag === 'polyline') return tip('Click on drag to add a control point');
|
||||
if (tag === 'path') return tip('Drag to move the measurer');
|
||||
if (tag === 'text') return tip('Drag to move, click to remove the measurer');
|
||||
}
|
||||
|
||||
if (subgroup === 'burgIcons') return tip('Click to edit the Burg');
|
||||
|
||||
if (subgroup === 'burgLabels') return tip('Click to edit the Burg');
|
||||
|
||||
if (group === 'lakes' && !land) {
|
||||
const lakeId = +e.target.dataset.f;
|
||||
const name = pack.features[lakeId]?.name;
|
||||
|
|
@ -197,20 +166,16 @@ function showMapTooltip(point, e, i, g) {
|
|||
tip(`${fullName} lake. Click to edit`);
|
||||
return;
|
||||
}
|
||||
if (group === 'coastline') {
|
||||
tip('Click to edit the coastline');
|
||||
return;
|
||||
}
|
||||
if (group === 'coastline') return tip('Click to edit the coastline');
|
||||
|
||||
if (group === 'zones') {
|
||||
const zone = path[path.length - 8];
|
||||
tip(zone.dataset.description);
|
||||
if (zonesEditor.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === 'ice') {
|
||||
tip('Click to edit the Ice');
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'ice') return tip('Click to edit the Ice');
|
||||
|
||||
// covering elements
|
||||
if (layerIsOn('togglePrec') && land) tip('Annual Precipitation: ' + getFriendlyPrecipitation(i));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// heightmap-editor module. To be added to window as for now
|
||||
'use strict';
|
||||
|
||||
function editHeightmap() {
|
||||
|
|
@ -134,15 +133,8 @@ function editHeightmap() {
|
|||
|
||||
// Exit customization mode
|
||||
function finalizeHeightmap() {
|
||||
if (viewbox.select('#heights').selectAll('*').size() < 200) {
|
||||
tip('Insufficient land area! There should be at least 200 land cells to finalize the heightmap', null, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.getElementById('imageConverter').offsetParent) {
|
||||
tip('Please exit the Image Conversion mode first', null, 'error');
|
||||
return;
|
||||
}
|
||||
if (viewbox.select('#heights').selectAll('*').size() < 200) return tip('Insufficient land area! There should be at least 200 land cells to finalize the heightmap', null, 'error');
|
||||
if (document.getElementById('imageConverter').offsetParent) return tip('Please exit the Image Conversion mode first', null, 'error');
|
||||
|
||||
delete window.edits; // remove global variable
|
||||
redo.disabled = templateRedo.disabled = true;
|
||||
|
|
@ -207,6 +199,7 @@ function editHeightmap() {
|
|||
}
|
||||
}
|
||||
|
||||
drawRivers();
|
||||
Lakes.defineGroup();
|
||||
defineBiomes();
|
||||
|
||||
|
|
@ -586,6 +579,7 @@ function editHeightmap() {
|
|||
document.getElementById('brushesSliders').style.display = 'none';
|
||||
}
|
||||
|
||||
const dragBrushThrottled = throttle(dragBrush, 100);
|
||||
function toggleBrushMode(e) {
|
||||
if (e.target.classList.contains('pressed')) {
|
||||
exitBrushMode();
|
||||
|
|
@ -594,7 +588,7 @@ function editHeightmap() {
|
|||
exitBrushMode();
|
||||
document.getElementById('brushesSliders').style.display = 'block';
|
||||
e.target.classList.add('pressed');
|
||||
viewbox.style('cursor', 'crosshair').call(d3.drag().on('start', dragBrush));
|
||||
viewbox.style('cursor', 'crosshair').call(d3.drag().on('start', dragBrushThrottled));
|
||||
}
|
||||
|
||||
function dragBrush() {
|
||||
|
|
@ -842,118 +836,15 @@ function editHeightmap() {
|
|||
body.setAttribute('data-changed', 0);
|
||||
body.innerHTML = '';
|
||||
|
||||
if (template === 'templateVolcano') {
|
||||
addStep('Hill', '1', '90-100', '44-56', '40-60');
|
||||
addStep('Multiply', 0.8, '50-100');
|
||||
addStep('Range', '1.5', '30-55', '45-55', '40-60');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Hill', '1.5', '25-35', '25-30', '20-75');
|
||||
addStep('Hill', '1', '25-35', '75-80', '25-75');
|
||||
addStep('Hill', '0.5', '20-25', '10-15', '20-25');
|
||||
} else if (template === 'templateHighIsland') {
|
||||
addStep('Hill', '1', '90-100', '65-75', '47-53');
|
||||
addStep('Add', 5, 'all');
|
||||
addStep('Hill', '6', '20-23', '25-55', '45-55');
|
||||
addStep('Range', '1', '40-50', '45-55', '45-55');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Trough', '2-3', '20-30', '20-30', '20-30');
|
||||
addStep('Trough', '2-3', '20-30', '60-80', '70-80');
|
||||
addStep('Hill', '1', '10-15', '60-60', '50-50');
|
||||
addStep('Hill', '1.5', '13-16', '15-20', '20-75');
|
||||
addStep('Multiply', 0.8, '20-100');
|
||||
addStep('Range', '1.5', '30-40', '15-85', '30-40');
|
||||
addStep('Range', '1.5', '30-40', '15-85', '60-70');
|
||||
addStep('Pit', '2-3', '10-15', '15-85', '20-80');
|
||||
} else if (template === 'templateLowIsland') {
|
||||
addStep('Hill', '1', '90-99', '60-80', '45-55');
|
||||
addStep('Hill', '4-5', '25-35', '20-65', '40-60');
|
||||
addStep('Range', '1', '40-50', '45-55', '45-55');
|
||||
addStep('Smooth', 3);
|
||||
addStep('Trough', '1.5', '20-30', '15-85', '20-30');
|
||||
addStep('Trough', '1.5', '20-30', '15-85', '70-80');
|
||||
addStep('Hill', '1.5', '10-15', '5-15', '20-80');
|
||||
addStep('Hill', '1', '10-15', '85-95', '70-80');
|
||||
addStep('Pit', '3-5', '10-15', '15-85', '20-80');
|
||||
addStep('Multiply', 0.4, '20-100');
|
||||
} else if (template === 'templateContinents') {
|
||||
addStep('Hill', '1', '80-85', '75-80', '40-60');
|
||||
addStep('Hill', '1', '80-85', '20-25', '40-60');
|
||||
addStep('Multiply', 0.22, '20-100');
|
||||
addStep('Hill', '5-6', '15-20', '25-75', '20-82');
|
||||
addStep('Range', '.8', '30-60', '5-15', '20-45');
|
||||
addStep('Range', '.8', '30-60', '5-15', '55-80');
|
||||
addStep('Range', '0-3', '30-60', '80-90', '20-80');
|
||||
addStep('Trough', '3-4', '15-20', '15-85', '20-80');
|
||||
addStep('Strait', '2', 'vertical');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Trough', '1-2', '5-10', '45-55', '45-55');
|
||||
addStep('Pit', '3-4', '10-15', '15-85', '20-80');
|
||||
addStep('Hill', '1', '5-10', '40-60', '40-60');
|
||||
} else if (template === 'templateArchipelago') {
|
||||
addStep('Add', 11, 'all');
|
||||
addStep('Range', '2-3', '40-60', '20-80', '20-80');
|
||||
addStep('Hill', '5', '15-20', '10-90', '30-70');
|
||||
addStep('Hill', '2', '10-15', '10-30', '20-80');
|
||||
addStep('Hill', '2', '10-15', '60-90', '20-80');
|
||||
addStep('Smooth', 3);
|
||||
addStep('Trough', '10', '20-30', '5-95', '5-95');
|
||||
addStep('Strait', '2', 'vertical');
|
||||
addStep('Strait', '2', 'horizontal');
|
||||
} else if (template === 'templateAtoll') {
|
||||
addStep('Hill', '1', '75-80', '50-60', '45-55');
|
||||
addStep('Hill', '1.5', '30-50', '25-75', '30-70');
|
||||
addStep('Hill', '.5', '30-50', '25-35', '30-70');
|
||||
addStep('Smooth', 1);
|
||||
addStep('Multiply', 0.2, '25-100');
|
||||
addStep('Hill', '.5', '10-20', '50-55', '48-52');
|
||||
} else if (template === 'templateMediterranean') {
|
||||
addStep('Range', '3-4', '30-50', '0-100', '0-10');
|
||||
addStep('Range', '3-4', '30-50', '0-100', '90-100');
|
||||
addStep('Hill', '5-6', '30-70', '0-100', '0-5');
|
||||
addStep('Hill', '5-6', '30-70', '0-100', '95-100');
|
||||
addStep('Smooth', 1);
|
||||
addStep('Hill', '2-3', '30-70', '0-5', '20-80');
|
||||
addStep('Hill', '2-3', '30-70', '95-100', '20-80');
|
||||
addStep('Multiply', 0.8, 'land');
|
||||
addStep('Trough', '3-5', '40-50', '0-100', '0-10');
|
||||
addStep('Trough', '3-5', '40-50', '0-100', '90-100');
|
||||
} else if (template === 'templatePeninsula') {
|
||||
addStep('Range', '2-3', '20-35', '40-50', '0-15');
|
||||
addStep('Add', 5, 'all');
|
||||
addStep('Hill', '1', '90-100', '10-90', '0-5');
|
||||
addStep('Add', 13, 'all');
|
||||
addStep('Hill', '3-4', '3-5', '5-95', '80-100');
|
||||
addStep('Hill', '1-2', '3-5', '5-95', '40-60');
|
||||
addStep('Trough', '5-6', '10-25', '5-95', '5-95');
|
||||
addStep('Smooth', 3);
|
||||
} else if (template === 'templatePangea') {
|
||||
addStep('Hill', '1-2', '25-40', '15-50', '0-10');
|
||||
addStep('Hill', '1-2', '5-40', '50-85', '0-10');
|
||||
addStep('Hill', '1-2', '25-40', '50-85', '90-100');
|
||||
addStep('Hill', '1-2', '5-40', '15-50', '90-100');
|
||||
addStep('Hill', '8-12', '20-40', '20-80', '48-52');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Multiply', 0.7, 'land');
|
||||
addStep('Trough', '3-4', '25-35', '5-95', '10-20');
|
||||
addStep('Trough', '3-4', '25-35', '5-95', '80-90');
|
||||
addStep('Range', '5-6', '30-40', '10-90', '35-65');
|
||||
} else if (template === 'templateIsthmus') {
|
||||
addStep('Hill', '5-10', '15-30', '0-30', '0-20');
|
||||
addStep('Hill', '5-10', '15-30', '10-50', '20-40');
|
||||
addStep('Hill', '5-10', '15-30', '30-70', '40-60');
|
||||
addStep('Hill', '5-10', '15-30', '50-90', '60-80');
|
||||
addStep('Hill', '5-10', '15-30', '70-100', '80-100');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Trough', '4-8', '15-30', '0-30', '0-20');
|
||||
addStep('Trough', '4-8', '15-30', '10-50', '20-40');
|
||||
addStep('Trough', '4-8', '15-30', '30-70', '40-60');
|
||||
addStep('Trough', '4-8', '15-30', '50-90', '60-80');
|
||||
addStep('Trough', '4-8', '15-30', '70-100', '80-100');
|
||||
} else if (template === 'templateShattered') {
|
||||
addStep('Hill', '8', '35-40', '15-85', '30-70');
|
||||
addStep('Trough', '10-20', '40-50', '5-95', '5-95');
|
||||
addStep('Range', '5-7', '30-40', '10-90', '20-80');
|
||||
addStep('Pit', '12-20', '30-40', '15-85', '20-80');
|
||||
const templateString = HeightmapTemplates[template];
|
||||
if (!templateString) return;
|
||||
|
||||
const steps = templateString.split('\n');
|
||||
if (!steps.length) return tip(`Heightmap template: no steps defined`, false, 'error');
|
||||
|
||||
for (const step of steps) {
|
||||
const elements = step.trim().split(' ');
|
||||
addStep(...elements);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1119,6 +1010,10 @@ function editHeightmap() {
|
|||
const reader = new FileReader();
|
||||
|
||||
const img = new Image();
|
||||
img.id = 'imageToConvert';
|
||||
img.style.display = 'none';
|
||||
document.body.appendChild(img);
|
||||
|
||||
img.onload = function () {
|
||||
const ctx = document.getElementById('canvas').getContext('2d');
|
||||
ctx.drawImage(img, 0, 0, graphWidth, graphHeight);
|
||||
|
|
@ -1311,10 +1206,7 @@ function editHeightmap() {
|
|||
}
|
||||
|
||||
function applyConversion() {
|
||||
if (colorsAssigned.childElementCount < 3) {
|
||||
tip('Please do the assignment first', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (colorsAssigned.childElementCount < 3) return tip('Please do the assignment first', false, 'error');
|
||||
|
||||
viewbox
|
||||
.select('#heights')
|
||||
|
|
@ -1340,6 +1232,9 @@ function editHeightmap() {
|
|||
const canvas = document.getElementById('canvas');
|
||||
if (canvas) canvas.remove();
|
||||
|
||||
const image = document.getElementById('imageToConvert');
|
||||
if (image) image.remove();
|
||||
|
||||
d3.select('#imageConverter').selectAll('div.color-div').remove();
|
||||
colorsAssigned.style.display = 'none';
|
||||
colorsUnassigned.style.display = 'none';
|
||||
|
|
|
|||
|
|
@ -123,9 +123,10 @@ function restoreLayers() {
|
|||
if (layerIsOn('toggleIce')) drawIce();
|
||||
if (layerIsOn('toggleEmblems')) drawEmblems();
|
||||
|
||||
// states are getting rendered each time, if it's not required than layers should be hidden
|
||||
if (!layerIsOn('toggleBorders')) $('#borders').fadeOut();
|
||||
if (!layerIsOn('toggleStates')) regions.style('display', 'none').selectAll('path').remove();
|
||||
// some layers are rendered each time, remove them if they are not on
|
||||
if (!layerIsOn('toggleBorders')) borders.selectAll('path').remove();
|
||||
if (!layerIsOn('toggleStates')) regions.selectAll('path').remove();
|
||||
if (!layerIsOn('toggleRivers')) rivers.selectAll('*').remove();
|
||||
}
|
||||
|
||||
function toggleHeight(event) {
|
||||
|
|
@ -876,35 +877,80 @@ function toggleStates(event) {
|
|||
}
|
||||
}
|
||||
|
||||
// draw states
|
||||
function drawStates() {
|
||||
TIME && console.time('drawStates');
|
||||
regions.selectAll('path').remove();
|
||||
|
||||
const cells = pack.cells,
|
||||
vertices = pack.vertices,
|
||||
states = pack.states,
|
||||
n = cells.i.length;
|
||||
const {cells, vertices, features} = pack;
|
||||
const states = pack.states;
|
||||
const n = cells.i.length;
|
||||
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
const vArray = new Array(states.length); // store vertices array
|
||||
const body = new Array(states.length).fill(''); // store path around each state
|
||||
const gap = new Array(states.length).fill(''); // store path along water for each state to fill the gaps
|
||||
const body = new Array(states.length).fill(''); // path around each state
|
||||
const gap = new Array(states.length).fill(''); // path along water for each state to fill the gaps
|
||||
const halo = new Array(states.length).fill(''); // path around states, but not lakes
|
||||
|
||||
const getStringPoint = (v) => vertices.p[v[0]].join(',');
|
||||
|
||||
// define inner-state lakes to omit on border render
|
||||
const innerLakes = features.map((feature) => {
|
||||
if (feature.type !== 'lake') return false;
|
||||
if (!feature.shoreline) Lakes.getShoreline(feature);
|
||||
|
||||
const states = feature.shoreline.map((i) => cells.state[i]);
|
||||
return new Set(states).size > 1 ? false : true;
|
||||
});
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (!cells.state[i] || used[i]) continue;
|
||||
const s = cells.state[i];
|
||||
const onborder = cells.c[i].some((n) => cells.state[n] !== s);
|
||||
const state = cells.state[i];
|
||||
|
||||
const onborder = cells.c[i].some((n) => cells.state[n] !== state);
|
||||
if (!onborder) continue;
|
||||
|
||||
const borderWith = cells.c[i].map((c) => cells.state[c]).find((n) => n !== s);
|
||||
const borderWith = cells.c[i].map((c) => cells.state[c]).find((n) => n !== state);
|
||||
const vertex = cells.v[i].find((v) => vertices.c[v].some((i) => cells.state[i] === borderWith));
|
||||
const chain = connectVertices(vertex, s, borderWith);
|
||||
if (chain.length < 3) continue;
|
||||
const points = chain.map((v) => vertices.p[v[0]]);
|
||||
if (!vArray[s]) vArray[s] = [];
|
||||
vArray[s].push(points);
|
||||
body[s] += 'M' + points.join('L');
|
||||
gap[s] += 'M' + vertices.p[chain[0][0]] + chain.reduce((r, v, i, d) => (!i ? r : !v[2] ? r + 'L' + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + 'M' + vertices.p[v[0]] : r), '');
|
||||
const chain = connectVertices(vertex, state);
|
||||
|
||||
const noInnerLakes = chain.filter((v) => v[1] !== 'innerLake');
|
||||
if (noInnerLakes.length < 3) continue;
|
||||
|
||||
// get path around the state
|
||||
if (!vArray[state]) vArray[state] = [];
|
||||
const points = noInnerLakes.map((v) => vertices.p[v[0]]);
|
||||
vArray[state].push(points);
|
||||
body[state] += 'M' + points.join('L');
|
||||
|
||||
// connect path for halo
|
||||
let discontinued = true;
|
||||
halo[state] += noInnerLakes
|
||||
.map((v) => {
|
||||
if (v[1] === 'border') {
|
||||
discontinued = true;
|
||||
return '';
|
||||
}
|
||||
|
||||
const operation = discontinued ? 'M' : 'L';
|
||||
discontinued = false;
|
||||
return `${operation}${getStringPoint(v)}`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
// connect gaps between state and water into a single path
|
||||
discontinued = true;
|
||||
gap[state] += chain
|
||||
.map((v) => {
|
||||
if (v[1] === 'land') {
|
||||
discontinued = true;
|
||||
return '';
|
||||
}
|
||||
|
||||
const operation = discontinued ? 'M' : 'L';
|
||||
discontinued = false;
|
||||
return `${operation}${getStringPoint(v)}`;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
// find state visual center
|
||||
|
|
@ -913,13 +959,14 @@ function drawStates() {
|
|||
states[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility
|
||||
});
|
||||
|
||||
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, states[i].color]).filter((d) => d[0]);
|
||||
const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, states[i].color]).filter((d) => d[0]);
|
||||
const bodyData = body.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter((d) => d[0]);
|
||||
const gapData = gap.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter((d) => d[0]);
|
||||
const haloData = halo.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter((d) => d[0]);
|
||||
|
||||
const bodyString = bodyData.map((d) => `<path id="state${d[1]}" d="${d[0]}" fill="${d[2]}" stroke="none"/>`).join('');
|
||||
const gapString = gapData.map((d) => `<path id="state-gap${d[1]}" d="${d[0]}" fill="none" stroke="${d[2]}"/>`).join('');
|
||||
const clipString = bodyData.map((d) => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`).join('');
|
||||
const haloString = bodyData
|
||||
const haloString = haloData
|
||||
.map((d) => `<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${d3.color(d[2]) ? d3.color(d[2]).darker().hex() : '#666666'}"/>`)
|
||||
.join('');
|
||||
|
||||
|
|
@ -928,57 +975,77 @@ function drawStates() {
|
|||
statesHalo.html(haloString);
|
||||
|
||||
// connect vertices to chain
|
||||
function connectVertices(start, t, state) {
|
||||
function connectVertices(start, state) {
|
||||
const chain = []; // vertices chain to form a path
|
||||
let land = vertices.c[start].some((c) => cells.h[c] >= 20 && cells.state[c] !== t);
|
||||
function check(i) {
|
||||
state = cells.state[i];
|
||||
land = cells.h[i] >= 20;
|
||||
}
|
||||
const getType = (c) => {
|
||||
const borderCell = c.find((i) => cells.b[i]);
|
||||
if (borderCell) return 'border';
|
||||
|
||||
const waterCell = c.find((i) => cells.h[i] < 20);
|
||||
if (!waterCell) return 'land';
|
||||
if (innerLakes[cells.f[waterCell]]) return 'innerLake';
|
||||
return features[cells.f[waterCell]].type;
|
||||
};
|
||||
|
||||
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||
const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||
chain.push([current, state, land]); // add current vertex to sequence
|
||||
const prev = chain.length ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||
|
||||
const c = vertices.c[current]; // cells adjacent to vertex
|
||||
c.filter((c) => cells.state[c] === t).forEach((c) => (used[c] = 1));
|
||||
const c0 = c[0] >= n || cells.state[c[0]] !== t;
|
||||
const c1 = c[1] >= n || cells.state[c[1]] !== t;
|
||||
const c2 = c[2] >= n || cells.state[c[2]] !== t;
|
||||
chain.push([current, getType(c)]); // add current vertex to sequence
|
||||
|
||||
c.filter((c) => cells.state[c] === state).forEach((c) => (used[c] = 1));
|
||||
const c0 = c[0] >= n || cells.state[c[0]] !== state;
|
||||
const c1 = c[1] >= n || cells.state[c[1]] !== state;
|
||||
const c2 = c[2] >= n || cells.state[c[2]] !== state;
|
||||
|
||||
const v = vertices.v[current]; // neighboring vertices
|
||||
if (v[0] !== prev && c0 !== c1) {
|
||||
current = v[0];
|
||||
check(c0 ? c[0] : c[1]);
|
||||
} else if (v[1] !== prev && c1 !== c2) {
|
||||
current = v[1];
|
||||
check(c1 ? c[1] : c[2]);
|
||||
} else if (v[2] !== prev && c0 !== c2) {
|
||||
current = v[2];
|
||||
check(c2 ? c[2] : c[0]);
|
||||
}
|
||||
if (current === chain[chain.length - 1][0]) {
|
||||
|
||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||
|
||||
if (current === prev) {
|
||||
ERROR && console.error('Next vertex is not found');
|
||||
break;
|
||||
}
|
||||
}
|
||||
chain.push([start, state, land]); // add starting vertex to sequence to close the path
|
||||
|
||||
if (chain.length) chain.push(chain[0]);
|
||||
return chain;
|
||||
}
|
||||
|
||||
invokeActiveZooming();
|
||||
TIME && console.timeEnd('drawStates');
|
||||
}
|
||||
|
||||
function toggleBorders(event) {
|
||||
if (!layerIsOn('toggleBorders')) {
|
||||
turnButtonOn('toggleBorders');
|
||||
drawBorders();
|
||||
if (event && isCtrlClick(event)) editStyle('borders');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('borders');
|
||||
return;
|
||||
}
|
||||
turnButtonOff('toggleBorders');
|
||||
borders.selectAll('path').remove();
|
||||
}
|
||||
}
|
||||
|
||||
// draw state and province borders
|
||||
function drawBorders() {
|
||||
TIME && console.time('drawBorders');
|
||||
borders.selectAll('path').remove();
|
||||
|
||||
const cells = pack.cells,
|
||||
vertices = pack.vertices,
|
||||
n = cells.i.length;
|
||||
const sPath = [],
|
||||
pPath = [];
|
||||
const sUsed = new Array(pack.states.length).fill('').map((a) => []);
|
||||
const pUsed = new Array(pack.provinces.length).fill('').map((a) => []);
|
||||
const {cells, vertices} = pack;
|
||||
const n = cells.i.length;
|
||||
|
||||
const sPath = [];
|
||||
const pPath = [];
|
||||
|
||||
const sUsed = new Array(pack.states.length).fill('').map((_) => []);
|
||||
const pUsed = new Array(pack.provinces.length).fill('').map((_) => []);
|
||||
|
||||
for (let i = 0; i < cells.i.length; i++) {
|
||||
if (!cells.state[i]) continue;
|
||||
|
|
@ -1070,21 +1137,6 @@ function drawBorders() {
|
|||
TIME && console.timeEnd('drawBorders');
|
||||
}
|
||||
|
||||
function toggleBorders(event) {
|
||||
if (!layerIsOn('toggleBorders')) {
|
||||
turnButtonOn('toggleBorders');
|
||||
$('#borders').fadeIn();
|
||||
if (event && isCtrlClick(event)) editStyle('borders');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('borders');
|
||||
return;
|
||||
}
|
||||
turnButtonOff('toggleBorders');
|
||||
$('#borders').fadeOut();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleProvinces(event) {
|
||||
if (!layerIsOn('toggleProvinces')) {
|
||||
turnButtonOn('toggleProvinces');
|
||||
|
|
@ -1396,18 +1448,30 @@ function toggleTexture(event) {
|
|||
function toggleRivers(event) {
|
||||
if (!layerIsOn('toggleRivers')) {
|
||||
turnButtonOn('toggleRivers');
|
||||
$('#rivers').fadeIn();
|
||||
drawRivers();
|
||||
if (event && isCtrlClick(event)) editStyle('rivers');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('rivers');
|
||||
return;
|
||||
}
|
||||
$('#rivers').fadeOut();
|
||||
if (event && isCtrlClick(event)) return editStyle('rivers');
|
||||
rivers.selectAll('*').remove();
|
||||
turnButtonOff('toggleRivers');
|
||||
}
|
||||
}
|
||||
|
||||
function drawRivers() {
|
||||
TIME && console.time('drawRivers');
|
||||
const {addMeandering, getRiverPath} = Rivers;
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const riverPaths = pack.rivers.map((river) => {
|
||||
const meanderedPoints = addMeandering(river.cells, river.points);
|
||||
const widthFactor = river.widthFactor || 1;
|
||||
const startingWidth = river.sourceWidth || 0;
|
||||
const path = getRiverPath(meanderedPoints, widthFactor, startingWidth);
|
||||
return `<path id="river${river.i}" d="${path}"/>`;
|
||||
});
|
||||
rivers.html(riverPaths.join(''));
|
||||
TIME && console.timeEnd('drawRivers');
|
||||
}
|
||||
|
||||
function toggleRoutes(event) {
|
||||
if (!layerIsOn('toggleRoutes')) {
|
||||
turnButtonOn('toggleRoutes');
|
||||
|
|
@ -1558,21 +1622,21 @@ function drawEmblems() {
|
|||
const getStateEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 40, 10), 100);
|
||||
const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier
|
||||
const sizeMod = +document.getElementById('styleEmblemsStateSizeInput').value || 1;
|
||||
const sizeMod = +document.getElementById('emblemsStateSizeInput').value || 1;
|
||||
return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states
|
||||
};
|
||||
|
||||
const getProvinceEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 100, 5), 70);
|
||||
const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById('styleEmblemsProvinceSizeInput').value || 1;
|
||||
const sizeMod = +document.getElementById('emblemsProvinceSizeInput').value || 1;
|
||||
return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces
|
||||
};
|
||||
|
||||
const getBurgEmblemSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 185, 2), 50);
|
||||
const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById('styleEmblemsBurgSizeInput').value || 1;
|
||||
const sizeMod = +document.getElementById('emblemsBurgSizeInput').value || 1;
|
||||
return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ function showSupporters() {
|
|||
PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram,
|
||||
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,
|
||||
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,
|
||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray`;
|
||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,
|
||||
"Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas"`;
|
||||
|
||||
const array = supporters
|
||||
.replace(/(?:\r\n|\r|\n)/g, '')
|
||||
|
|
@ -157,6 +158,7 @@ optionsContent.addEventListener('change', function (event) {
|
|||
if (id === 'zoomExtentMin' || id === 'zoomExtentMax') changeZoomExtent(value);
|
||||
else if (id === 'optionsSeed') generateMapWithSeed();
|
||||
else if (id === 'uiSizeInput' || id === 'uiSizeOutput') changeUIsize(value);
|
||||
if (id === 'shapeRendering') viewbox.attr('shape-rendering', value);
|
||||
else if (id === 'yearInput') changeYear();
|
||||
else if (id === 'eraInput') changeEra();
|
||||
});
|
||||
|
|
@ -494,6 +496,9 @@ function applyStoredOptions() {
|
|||
const height = +params.get('height');
|
||||
if (width) mapWidthInput.value = width;
|
||||
if (height) mapHeightInput.value = height;
|
||||
|
||||
// set shape rendering
|
||||
viewbox.attr('shape-rendering', shapeRendering.value);
|
||||
}
|
||||
|
||||
// randomize options if randomization is allowed (not locked or options='default')
|
||||
|
|
@ -537,17 +542,18 @@ function randomizeOptions() {
|
|||
// select heightmap template pseudo-randomly
|
||||
function randomizeHeightmapTemplate() {
|
||||
const templates = {
|
||||
Volcano: 3,
|
||||
'High Island': 22,
|
||||
'Low Island': 9,
|
||||
Continents: 20,
|
||||
Archipelago: 25,
|
||||
Mediterranean: 3,
|
||||
Peninsula: 3,
|
||||
Pangea: 5,
|
||||
Isthmus: 2,
|
||||
Atoll: 1,
|
||||
Shattered: 7
|
||||
volcano: 3,
|
||||
highIsland: 22,
|
||||
lowIsland: 9,
|
||||
continents: 19,
|
||||
archipelago: 23,
|
||||
mediterranean: 5,
|
||||
peninsula: 3,
|
||||
pangea: 5,
|
||||
isthmus: 2,
|
||||
atoll: 1,
|
||||
shattered: 7,
|
||||
taklamakan: 1
|
||||
};
|
||||
document.getElementById('templateInput').value = rw(templates);
|
||||
}
|
||||
|
|
@ -773,6 +779,12 @@ document
|
|||
.forEach((el) => el.addEventListener('input', updateTilesOptions));
|
||||
|
||||
function updateTilesOptions() {
|
||||
if (this?.tagName === 'INPUT') {
|
||||
const {nextElementSibling: next, previousElementSibling: prev} = this;
|
||||
if (next?.tagName === 'INPUT') next.value = this.value;
|
||||
if (prev?.tagName === 'INPUT') prev.value = this.value;
|
||||
}
|
||||
|
||||
const tileSize = document.getElementById('tileSize');
|
||||
const tilesX = +document.getElementById('tileColsOutput').value;
|
||||
const tilesY = +document.getElementById('tileRowsOutput').value;
|
||||
|
|
@ -908,6 +920,7 @@ function toggle3dOptions() {
|
|||
document.getElementById('options3dMeshRotationNumber').addEventListener('change', changeRotation);
|
||||
document.getElementById('options3dGlobeRotationRange').addEventListener('input', changeRotation);
|
||||
document.getElementById('options3dGlobeRotationNumber').addEventListener('change', changeRotation);
|
||||
document.getElementById('options3dMeshLabels3d').addEventListener('change', toggleLabels3d);
|
||||
document.getElementById('options3dMeshSkyMode').addEventListener('change', toggleSkyMode);
|
||||
document.getElementById('options3dMeshSky').addEventListener('input', changeColors);
|
||||
document.getElementById('options3dMeshWater').addEventListener('input', changeColors);
|
||||
|
|
@ -924,6 +937,7 @@ function toggle3dOptions() {
|
|||
options3dSunZ.value = ThreeD.options.sun.z;
|
||||
options3dMeshRotationRange.value = options3dMeshRotationNumber.value = ThreeD.options.rotateMesh;
|
||||
options3dGlobeRotationRange.value = options3dGlobeRotationNumber.value = ThreeD.options.rotateGlobe;
|
||||
options3dMeshLabels3d.value = ThreeD.options.labels3d;
|
||||
options3dMeshSkyMode.value = ThreeD.options.extendedWater;
|
||||
options3dColorSection.style.display = ThreeD.options.extendedWater ? 'block' : 'none';
|
||||
options3dMeshSky.value = ThreeD.options.skyColor;
|
||||
|
|
@ -954,6 +968,10 @@ function toggle3dOptions() {
|
|||
ThreeD.setRotation(speed);
|
||||
}
|
||||
|
||||
function toggleLabels3d() {
|
||||
ThreeD.toggleLabels();
|
||||
}
|
||||
|
||||
function toggleSkyMode() {
|
||||
const hide = ThreeD.options.extendedWater;
|
||||
options3dColorSection.style.display = hide ? 'none' : 'block';
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ function editReliefIcon() {
|
|||
reliefSpacingDiv.style.display = 'none';
|
||||
reliefIconsSeletionAny.style.display = 'none';
|
||||
|
||||
removeCircle();
|
||||
updateReliefSizeInput();
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
|
|
@ -115,10 +116,7 @@ function editReliefIcon() {
|
|||
|
||||
function dragToAdd() {
|
||||
const pressed = reliefIconsDiv.querySelector('svg.pressed');
|
||||
if (!pressed) {
|
||||
tip('Please select an icon', false, error);
|
||||
return;
|
||||
}
|
||||
if (!pressed) return tip('Please select an icon', false, error);
|
||||
|
||||
const type = pressed.dataset.type;
|
||||
const r = +reliefRadiusNumber.value;
|
||||
|
|
@ -188,10 +186,7 @@ function editReliefIcon() {
|
|||
|
||||
function dragToRemove() {
|
||||
const pressed = reliefIconsDiv.querySelector('svg.pressed');
|
||||
if (!pressed) {
|
||||
tip('Please select an icon', false, error);
|
||||
return;
|
||||
}
|
||||
if (!pressed) return tip('Please select an icon', false, error);
|
||||
|
||||
const r = +reliefRadiusNumber.value;
|
||||
const type = pressed.dataset.type;
|
||||
|
|
@ -256,12 +251,32 @@ function editReliefIcon() {
|
|||
}
|
||||
|
||||
function removeIcon() {
|
||||
const message = 'Are you sure you want to remove the relief icon? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
elSelected.remove();
|
||||
let selection = null;
|
||||
const pressed = reliefTools.querySelector('button.pressed');
|
||||
if (pressed.id === 'reliefIndividual') {
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the icon?`;
|
||||
selection = elSelected;
|
||||
} else {
|
||||
const type = reliefIconsDiv.querySelector('svg.pressed')?.dataset.type;
|
||||
selection = type ? terrain.selectAll("use[href='" + type + "']") : terrain.selectAll('use');
|
||||
const size = selection.size();
|
||||
alertMessage.innerHTML = type ? `Are you sure you want to remove all ${type} icons (${size})?` : `Are you sure you want to remove all icons (${size})?`;
|
||||
}
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove relief icons',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
if (selection) selection.remove();
|
||||
$(this).dialog('close');
|
||||
$('#reliefEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove relief icon', message, confirm: 'Remove', onConfirm});
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeReliefEditor() {
|
||||
|
|
|
|||
125
modules/ui/rivers-creator.js
Normal file
125
modules/ui/rivers-creator.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
"use strict";
|
||||
function createRiver() {
|
||||
if (customization) return;
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
|
||||
document.getElementById("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
|
||||
if (!layerIsOn("toggleCells")) toggleCells();
|
||||
|
||||
tip("Click to add river point, click again to remove", true);
|
||||
debug.append("g").attr("id", "controlCells");
|
||||
viewbox.style("cursor", "crosshair").on("click", onCellClick);
|
||||
|
||||
createRiver.cells = [];
|
||||
const body = document.getElementById("riverCreatorBody");
|
||||
|
||||
$("#riverCreator").dialog({
|
||||
title: "Create River",
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"},
|
||||
close: closeRiverCreator
|
||||
});
|
||||
|
||||
if (modules.createRiver) return;
|
||||
modules.createRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("riverCreatorComplete").addEventListener("click", addRiver);
|
||||
document.getElementById("riverCreatorCancel").addEventListener("click", () => $("#riverCreator").dialog("close"));
|
||||
body.addEventListener("click", function (ev) {
|
||||
const el = ev.target;
|
||||
const cl = el.classList;
|
||||
const cell = +el.parentNode.dataset.cell;
|
||||
if (cl.contains("editFlux")) pack.cells.fl[cell] = +el.value;
|
||||
else if (cl.contains("icon-trash-empty")) removeCell(cell);
|
||||
});
|
||||
|
||||
function onCellClick() {
|
||||
const cell = findCell(...d3.mouse(this));
|
||||
|
||||
if (createRiver.cells.includes(cell)) removeCell(cell);
|
||||
else addCell(cell);
|
||||
}
|
||||
|
||||
function addCell(cell) {
|
||||
createRiver.cells.push(cell);
|
||||
drawCells(createRiver.cells);
|
||||
|
||||
const flux = pack.cells.fl[cell];
|
||||
const line = `<div class="editorLine" data-cell="${cell}">
|
||||
<span>Cell ${cell}</span>
|
||||
<span data-tip="Set flux affects river width" style="margin-left: 0.4em">Flux</span>
|
||||
<input type="number" min=0 value="${flux}" class="editFlux" style="width: 5em"/>
|
||||
<span data-tip="Remove the cell" class="icon-trash-empty pointer"></span>
|
||||
</div>`;
|
||||
body.innerHTML += line;
|
||||
}
|
||||
|
||||
function removeCell(cell) {
|
||||
createRiver.cells = createRiver.cells.filter(c => c !== cell);
|
||||
drawCells(createRiver.cells);
|
||||
body.querySelector(`div[data-cell='${cell}']`)?.remove();
|
||||
}
|
||||
|
||||
function drawCells(cells) {
|
||||
debug
|
||||
.select("#controlCells")
|
||||
.selectAll(`polygon`)
|
||||
.data(cells)
|
||||
.join("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("class", "current");
|
||||
}
|
||||
|
||||
function addRiver() {
|
||||
const {rivers, cells} = pack;
|
||||
const {addMeandering, getApproximateLength, getWidth, getOffset, getName, getRiverPath, getBasin} = Rivers;
|
||||
|
||||
const riverCells = createRiver.cells;
|
||||
if (riverCells.length < 2) return tip("Add at least 2 cells", false, "error");
|
||||
|
||||
const riverId = rivers.length ? last(rivers).i + 1 : 1;
|
||||
const parent = cells.r[last(riverCells)] || riverId;
|
||||
|
||||
riverCells.forEach(cell => {
|
||||
if (!cells.r[cell]) cells.r[cell] = riverId;
|
||||
});
|
||||
|
||||
const source = riverCells[0];
|
||||
const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2];
|
||||
const sourceWidth = 0.05;
|
||||
const widthFactor = 1.2;
|
||||
|
||||
const meanderedPoints = addMeandering(riverCells);
|
||||
|
||||
const discharge = cells.fl[mouth]; // m3 in second
|
||||
const length = getApproximateLength(meanderedPoints);
|
||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth));
|
||||
const name = getName(mouth);
|
||||
const basin = getBasin(parent);
|
||||
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"});
|
||||
|
||||
// render river
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
viewbox
|
||||
.select("#rivers")
|
||||
.append("path")
|
||||
.attr("id", "river" + riverId)
|
||||
.attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||
|
||||
editRiver(riverId);
|
||||
}
|
||||
|
||||
function closeRiverCreator() {
|
||||
body.innerHTML = "";
|
||||
debug.select("#controlCells").remove();
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +document.getElementById("toggleCells").dataset.forced;
|
||||
document.getElementById("toggleCells").dataset.forced = 0;
|
||||
if (forced && layerIsOn("toggleCells")) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,31 @@
|
|||
'use strict';
|
||||
function editRiver(id) {
|
||||
if (customization) return;
|
||||
if (elSelected && d3.event && d3.event.target.id === elSelected.attr('id')) return;
|
||||
if (elSelected && id === elSelected.attr('id')) return;
|
||||
closeDialogs('.stable');
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
|
||||
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'));
|
||||
document.getElementById('toggleCells').dataset.forced = +!layerIsOn('toggleCells');
|
||||
if (!layerIsOn('toggleCells')) toggleCells();
|
||||
|
||||
elSelected = d3.select('#' + id);
|
||||
|
||||
tip('Drag control points to change the river course. For major changes please create a new river instead', true);
|
||||
debug.append('g').attr('id', 'controlCells');
|
||||
debug.append('g').attr('id', 'controlPoints');
|
||||
|
||||
updateRiverData();
|
||||
drawControlPoints(node);
|
||||
|
||||
const river = getRiver();
|
||||
const {cells, points} = river;
|
||||
const riverPoints = Rivers.getRiverPoints(cells, points);
|
||||
drawControlPoints(riverPoints, cells);
|
||||
drawCells(cells, 'current');
|
||||
|
||||
$('#riverEditor').dialog({
|
||||
title: 'Edit River',
|
||||
resizable: false,
|
||||
position: {my: 'center top+80', at: 'top', of: node, collision: 'fit'},
|
||||
position: {my: 'left top', at: 'left+10 top+10', of: '#map'},
|
||||
close: closeRiverEditor
|
||||
});
|
||||
|
||||
|
|
@ -23,27 +33,19 @@ function editRiver(id) {
|
|||
modules.editRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById('riverCreateSelectingCells').addEventListener('click', createRiver);
|
||||
document.getElementById('riverEditStyle').addEventListener('click', () => editStyle('rivers'));
|
||||
document.getElementById('riverElevationProfile').addEventListener('click', showElevationProfile);
|
||||
document.getElementById('riverLegend').addEventListener('click', editRiverLegend);
|
||||
document.getElementById('riverRemove').addEventListener('click', removeRiver);
|
||||
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('riverMainstem').addEventListener('change', changeParent);
|
||||
|
||||
document.getElementById('riverSourceWidth').addEventListener('input', changeSourceWidth);
|
||||
document.getElementById('riverWidthFactor').addEventListener('input', changeWidthFactor);
|
||||
|
||||
document.getElementById('riverNew').addEventListener('click', toggleRiverCreationMode);
|
||||
document.getElementById('riverEditStyle').addEventListener('click', () => editStyle('rivers'));
|
||||
document.getElementById('riverElevationProfile').addEventListener('click', showElevationProfile);
|
||||
document.getElementById('riverLegend').addEventListener('click', editRiverLegend);
|
||||
document.getElementById('riverRemove').addEventListener('click', removeRiver);
|
||||
|
||||
function showEditorTips() {
|
||||
showMainTip();
|
||||
if (d3.event.target.parentNode.id === elSelected.attr('id')) tip('Drag to move, click to add a control point');
|
||||
else if (d3.event.target.parentNode.id === 'controlPoints') tip('Drag to move, click to delete the control point');
|
||||
}
|
||||
|
||||
function getRiver() {
|
||||
const riverId = +elSelected.attr('id').slice(5);
|
||||
const river = pack.rivers.find((r) => r.i === riverId);
|
||||
|
|
@ -67,87 +69,107 @@ function editRiver(id) {
|
|||
document.getElementById('riverBasin').value = pack.rivers.find((river) => river.i === r.basin).name;
|
||||
|
||||
document.getElementById('riverDischarge').value = r.discharge + ' m³/s';
|
||||
r.length = elSelected.node().getTotalLength() / 2;
|
||||
const length = rn(r.length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
document.getElementById('riverLength').value = length;
|
||||
const width = rn(r.width * distanceScaleInput.value, 3) + ' ' + distanceUnitInput.value;
|
||||
document.getElementById('riverWidth').value = width;
|
||||
|
||||
document.getElementById('riverSourceWidth').value = r.sourceWidth;
|
||||
document.getElementById('riverWidthFactor').value = r.widthFactor;
|
||||
|
||||
updateRiverLength(r);
|
||||
updateRiverWidth(r);
|
||||
}
|
||||
|
||||
function drawControlPoints(node) {
|
||||
const length = getRiver().length;
|
||||
const segments = Math.ceil(length / 4);
|
||||
const increment = rn((length / segments) * 1e5);
|
||||
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i / 1e5);
|
||||
const p2 = node.getPointAtLength(c / 1e5);
|
||||
addControlPoint([(p1.x + p2.x) / 2, (p1.y + p2.y) / 2]);
|
||||
}
|
||||
function updateRiverLength(river) {
|
||||
river.length = rn(elSelected.node().getTotalLength() / 2, 2);
|
||||
const lengthUI = `${rn(river.length * distanceScaleInput.value)} ${distanceUnitInput.value}`;
|
||||
document.getElementById('riverLength').value = lengthUI;
|
||||
}
|
||||
|
||||
function addControlPoint(point, before = null) {
|
||||
debug.select('#controlPoints').insert('circle', before).attr('cx', point[0]).attr('cy', point[1]).attr('r', 0.6).call(d3.drag().on('drag', dragControlPoint)).on('click', clickControlPoint);
|
||||
function updateRiverWidth(river) {
|
||||
const {addMeandering, getWidth, getOffset} = Rivers;
|
||||
const {cells, discharge, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = addMeandering(cells);
|
||||
river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth));
|
||||
|
||||
const width = `${rn(river.width * distanceScaleInput.value, 3)} ${distanceUnitInput.value}`;
|
||||
document.getElementById('riverWidth').value = width;
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
this.setAttribute('cx', d3.event.x);
|
||||
this.setAttribute('cy', d3.event.y);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function redrawRiver() {
|
||||
const points = [];
|
||||
function drawControlPoints(points, cells) {
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
points.push([+this.getAttribute('cx'), +this.getAttribute('cy')]);
|
||||
.data(points)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('cx', (d) => d[0])
|
||||
.attr('cy', (d) => d[1])
|
||||
.attr('r', 0.6)
|
||||
.attr('data-cell', (d, i) => cells[i])
|
||||
.attr('data-i', (d, i) => i)
|
||||
.call(d3.drag().on('start', dragControlPoint));
|
||||
}
|
||||
|
||||
function drawCells(cells, type) {
|
||||
debug
|
||||
.select('#controlCells')
|
||||
.selectAll(`polygon.${type}`)
|
||||
.data(cells.filter((i) => pack.cells.i[i]))
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('class', type);
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
const {i, r, fl} = pack.cells;
|
||||
const river = getRiver();
|
||||
|
||||
const initCell = +this.dataset.cell;
|
||||
const index = +this.dataset.i;
|
||||
|
||||
let movedToCell = null;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const {x, y} = d3.event;
|
||||
const currentCell = findCell(x, y);
|
||||
|
||||
movedToCell = initCell !== currentCell ? currentCell : null;
|
||||
|
||||
this.setAttribute('cx', x);
|
||||
this.setAttribute('cy', y);
|
||||
this.__data__ = [rn(x, 1), rn(y, 1)];
|
||||
redrawRiver();
|
||||
});
|
||||
|
||||
if (points.length < 2) return;
|
||||
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;
|
||||
d3.event.on('end', () => {
|
||||
if (movedToCell) {
|
||||
this.dataset.cell = movedToCell;
|
||||
river.cells[index] = movedToCell;
|
||||
drawCells(river.cells, 'current');
|
||||
|
||||
if (!r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const widthFactor = +document.getElementById('riverWidthFactor').value;
|
||||
const sourceWidth = +document.getElementById('riverSourceWidth').value;
|
||||
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
|
||||
function redrawRiver() {
|
||||
const river = getRiver();
|
||||
river.points = debug.selectAll('#controlPoints > *').data();
|
||||
const {cells, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(cells, river.points);
|
||||
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||
elSelected.attr('d', path);
|
||||
|
||||
const r = getRiver();
|
||||
if (r) {
|
||||
r.width = rn(offset ** 2, 2);
|
||||
r.length = length;
|
||||
updateRiverData();
|
||||
}
|
||||
|
||||
updateRiverLength(river);
|
||||
if (modules.elevation) showEPForRiver(elSelected.node());
|
||||
}
|
||||
|
||||
function clickControlPoint() {
|
||||
this.remove();
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function addInterimControlPoint() {
|
||||
const point = d3.mouse(this);
|
||||
const controls = document.getElementById('controlPoints').querySelectorAll('circle');
|
||||
const points = Array.from(controls).map((circle) => [+circle.getAttribute('cx'), +circle.getAttribute('cy')]);
|
||||
const index = getSegmentId(points, point, 2);
|
||||
addControlPoint(point, ':nth-child(' + (index + 1) + ')');
|
||||
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
getRiver().name = this.value;
|
||||
}
|
||||
|
|
@ -174,12 +196,16 @@ function editRiver(id) {
|
|||
}
|
||||
|
||||
function changeSourceWidth() {
|
||||
getRiver().sourceWidth = +this.value;
|
||||
const river = getRiver();
|
||||
river.sourceWidth = +this.value;
|
||||
updateRiverWidth(river);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function changeWidthFactor() {
|
||||
getRiver().widthFactor = +this.value;
|
||||
const river = getRiver();
|
||||
river.widthFactor = +this.value;
|
||||
updateRiverWidth(river);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
|
|
@ -194,81 +220,35 @@ function editRiver(id) {
|
|||
editNotes(id, river.name + ' ' + river.type);
|
||||
}
|
||||
|
||||
function toggleRiverCreationMode() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function addPointOnClick() {
|
||||
if (!elSelected.attr('data-new')) {
|
||||
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);
|
||||
}
|
||||
|
||||
// add control point
|
||||
const point = d3.mouse(this);
|
||||
addControlPoint([point[0], point[1]]);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function exitRiverCreationMode() {
|
||||
riverNew.classList.remove('pressed');
|
||||
clearMainTip();
|
||||
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 / 4),
|
||||
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 * 0.15)];
|
||||
const type = length < smallLength ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : 'River';
|
||||
|
||||
const discharge = rn(cells.length * 20 * Math.random());
|
||||
const widthFactor = +document.getElementById('riverWidthFactor').value;
|
||||
const sourceWidth = +document.getElementById('riverSourceWidth').value;
|
||||
|
||||
pack.rivers.push({i: r, source, mouth, discharge, length, width: sourceWidth, widthFactor, sourceWidth, parent: 0, name, type, basin: r});
|
||||
}
|
||||
|
||||
function removeRiver() {
|
||||
const message = 'Are you sure you want to remove the river? <br>All tributaries will be auto-removed';
|
||||
const onConfirm = () => {
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the river and all its tributaries';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Remove river and tributaries',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
const river = +elSelected.attr('id').slice(5);
|
||||
Rivers.remove(river);
|
||||
elSelected.remove(); // if river if missed in pack.rivers
|
||||
elSelected.remove();
|
||||
$('#riverEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove river', message, confirm: 'Remove', onConfirm});
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeRiverEditor() {
|
||||
exitRiverCreationMode();
|
||||
elSelected.on('click', null);
|
||||
debug.select('#controlPoints').remove();
|
||||
debug.select('#controlCells').remove();
|
||||
unselect();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +document.getElementById('toggleCells').dataset.forced;
|
||||
document.getElementById('toggleCells').dataset.forced = 0;
|
||||
if (forced && layerIsOn('toggleCells')) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ function overviewRivers() {
|
|||
// add listeners
|
||||
document.getElementById('riversOverviewRefresh').addEventListener('click', riversOverviewAddLines);
|
||||
document.getElementById('addNewRiver').addEventListener('click', toggleAddRiver);
|
||||
document.getElementById('riverCreateNew').addEventListener('click', createRiver);
|
||||
document.getElementById('riversBasinHighlight').addEventListener('click', toggleBasinsHightlight);
|
||||
document.getElementById('riversExport').addEventListener('click', downloadRiversData);
|
||||
document.getElementById('riversRemoveAll').addEventListener('click', triggerAllRiversRemove);
|
||||
|
|
@ -129,27 +130,53 @@ function overviewRivers() {
|
|||
}
|
||||
|
||||
function openRiverEditor() {
|
||||
editRiver('river' + this.parentNode.dataset.id);
|
||||
const id = 'river' + this.parentNode.dataset.id;
|
||||
editRiver(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`;
|
||||
|
||||
const message = 'Are you sure you want to remove the river? <br>All tributaries will be auto-removed';
|
||||
const onConfirm = () => {
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Remove river',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
Rivers.remove(river);
|
||||
riversOverviewAddLines();
|
||||
};
|
||||
confirmationDialog({title: 'Remove river', message, confirm: 'Remove', onConfirm});
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllRiversRemove() {
|
||||
const message = 'Are you sure you want to remove all rivers? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
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 = [];
|
||||
pack.cells.r = new Uint16Array(pack.cells.i.length);
|
||||
rivers.selectAll('*').remove();
|
||||
riversOverviewAddLines();
|
||||
};
|
||||
confirmationDialog({title: 'Remove all rivers', message, confirm: 'Remove', onConfirm});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -849,18 +849,21 @@ function editStates() {
|
|||
}
|
||||
|
||||
function adjustProvinces(affectedProvinces) {
|
||||
const cells = pack.cells,
|
||||
provinces = pack.provinces,
|
||||
states = pack.states;
|
||||
const {cells, provinces, states} = pack;
|
||||
const form = {Zone: 1, Area: 1, Territory: 2, Province: 1};
|
||||
|
||||
<<<<<<< HEAD
|
||||
affectedProvinces.forEach((p) => {
|
||||
// do nothing if neutral lands are captured
|
||||
if (!p) return;
|
||||
=======
|
||||
affectedProvinces.forEach(p => {
|
||||
if (!p) return; // do nothing if neutral lands are captured
|
||||
const old = provinces[p].state;
|
||||
>>>>>>> 597f9ae038fbcc149315df9b1618e64744fb929d
|
||||
|
||||
// remove province from state provinces list
|
||||
const old = provinces[p].state;
|
||||
if (states[old].provinces.includes(p)) states[old].provinces.splice(states[old].provinces.indexOf(p), 1);
|
||||
if (states[old]?.provinces?.includes(p)) states[old].provinces.splice(states[old].provinces.indexOf(p), 1);
|
||||
|
||||
// find states owning at least 1 province cell
|
||||
const provCells = cells.i.filter((i) => cells.province[i] === p);
|
||||
|
|
@ -871,8 +874,13 @@ function editStates() {
|
|||
if (owner) {
|
||||
const name = provinces[p].name;
|
||||
|
||||
<<<<<<< HEAD
|
||||
// if province is historical part of abouther state province, unite with old province
|
||||
const part = states[owner].provinces.find((n) => name.includes(provinces[n].name));
|
||||
=======
|
||||
// if province is a historical part of another state's province, unite with old province
|
||||
const part = states[owner].provinces.find(n => name.includes(provinces[n].name));
|
||||
>>>>>>> 597f9ae038fbcc149315df9b1618e64744fb929d
|
||||
if (part) {
|
||||
provinces[p].removed = true;
|
||||
provCells.filter((i) => cells.state[i] === owner).forEach((i) => (cells.province[i] = part));
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -551,93 +551,119 @@ function toggleAddRiver() {
|
|||
}
|
||||
|
||||
function addRiverOnClick() {
|
||||
const cells = pack.cells;
|
||||
const point = d3.mouse(this);
|
||||
let i = findCell(point[0], point[1]);
|
||||
if (cells.r[i] || cells.h[i] < 20 || cells.b[i]) return;
|
||||
const {cells, rivers} = pack;
|
||||
let i = findCell(...d3.mouse(this));
|
||||
|
||||
const dataRiver = []; // to store river points
|
||||
let river = +getNextId('river').slice(5); // river id
|
||||
cells.fl[i] = grid.cells.prec[cells.g[i]]; // initial flux
|
||||
if (cells.r[i]) return tip('There is already a river here', false, 'error');
|
||||
if (cells.h[i] < 20) return tip('Cannot create river in water cell', false, 'error');
|
||||
if (cells.b[i]) return;
|
||||
|
||||
const h = Rivers.alterHeights();
|
||||
Lakes.prepareLakeData(h);
|
||||
Rivers.resolveDepressions(h);
|
||||
const {alterHeights, resolveDepressions, addMeandering, getRiverPath, getBasin, getName, getType, getWidth, getOffset, getApproximateLength} = Rivers;
|
||||
const riverCells = [];
|
||||
let riverId = rivers.length ? last(rivers).i + 1 : 1;
|
||||
let parent = riverId;
|
||||
|
||||
const initialFlux = grid.cells.prec[cells.g[i]];
|
||||
cells.fl[i] = initialFlux;
|
||||
|
||||
const h = alterHeights();
|
||||
resolveDepressions(h);
|
||||
|
||||
while (i) {
|
||||
cells.r[i] = river;
|
||||
const [x, y] = cells.p[i];
|
||||
dataRiver.push({x, y, cell: i});
|
||||
cells.r[i] = riverId;
|
||||
riverCells.push(i);
|
||||
|
||||
const min = cells.c[i].sort((a, b) => h[a] - h[b])[0]; // downhill cell
|
||||
if (h[i] <= h[min]) return tip(`Cell ${i} is depressed, river cannot flow further`, false, 'error');
|
||||
|
||||
const [tx, ty] = cells.p[min];
|
||||
|
||||
if (h[min] < 20) {
|
||||
// pour to water body
|
||||
dataRiver.push({x: tx, y: ty, cell: i});
|
||||
if (h[min] < 20) {
|
||||
riverCells.push(min);
|
||||
|
||||
const feature = pack.features[cells.f[min]];
|
||||
if (feature.type === 'lake') {
|
||||
if (feature.outlet) parent = feature.outlet;
|
||||
feature.inlets ? feature.inlets.push(riverId) : (feature.inlets = [riverId]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// pour outside of map from border cell
|
||||
if (cells.b[min]) {
|
||||
cells.fl[min] += cells.fl[i];
|
||||
riverCells.push(-1);
|
||||
break;
|
||||
}
|
||||
|
||||
// continue propagation if min cell has no river
|
||||
if (!cells.r[min]) {
|
||||
// continue if next cell has not river
|
||||
cells.fl[min] += cells.fl[i];
|
||||
i = min;
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle case when lowest cell already has a river
|
||||
const r = cells.r[min];
|
||||
const riverCells = cells.i.filter((i) => cells.r[i] === r);
|
||||
const riverCellsUpper = riverCells.filter((i) => h[i] > h[min]);
|
||||
const oldRiverId = cells.r[min];
|
||||
const oldRiver = rivers.find((river) => river.i === oldRiverId);
|
||||
const oldRiverCells = oldRiver?.cells || cells.i.filter((i) => cells.r[i] === oldRiverId);
|
||||
const oldRiverCellsUpper = oldRiverCells.filter((i) => h[i] > h[min]);
|
||||
|
||||
// finish new river if old river is longer
|
||||
if (dataRiver.length <= riverCellsUpper.length) {
|
||||
// create new river as a tributary
|
||||
if (riverCells.length <= oldRiverCellsUpper.length) {
|
||||
cells.conf[min] += cells.fl[i];
|
||||
dataRiver.push({x: tx, y: ty, cell: min});
|
||||
dataRiver[0].parent = r; // new river is tributary
|
||||
riverCells.push(min);
|
||||
parent = oldRiverId;
|
||||
break;
|
||||
}
|
||||
|
||||
// extend old river
|
||||
rivers.select('#river' + r).remove();
|
||||
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;
|
||||
}
|
||||
|
||||
const points = Rivers.addMeandering(dataRiver, 1, 0.5);
|
||||
const widthFactor = rn(0.8 + Math.random() * 0.4, 1); // river width modifier [.8, 1.2]
|
||||
const sourceWidth = 0.1;
|
||||
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
|
||||
rivers
|
||||
.append('path')
|
||||
.attr('d', path)
|
||||
.attr('id', 'river' + river);
|
||||
|
||||
// add new river to data or change extended river attributes
|
||||
const r = pack.rivers.find((r) => r.i === river);
|
||||
const mouth = last(dataRiver).cell;
|
||||
const discharge = cells.fl[mouth]; // in m3/s
|
||||
|
||||
if (r) {
|
||||
r.source = dataRiver[0].cell;
|
||||
r.length = length;
|
||||
r.discharge = discharge;
|
||||
// continue old river
|
||||
document.getElementById('river' + oldRiverId)?.remove();
|
||||
riverCells.forEach((i) => (cells.r[i] = oldRiverId));
|
||||
oldRiverCells.forEach((cell) => {
|
||||
if (h[cell] > h[min]) {
|
||||
cells.r[cell] = 0;
|
||||
cells.fl[cell] = grid.cells.prec[cells.g[cell]];
|
||||
} else {
|
||||
const parent = dataRiver[0].parent || 0;
|
||||
const basin = Rivers.getBasin(river);
|
||||
const source = dataRiver[0].cell;
|
||||
const width = rn(offset ** 2, 2); // mounth width in km
|
||||
const name = Rivers.getName(mouth);
|
||||
const smallLength = pack.rivers.map((r) => r.length || 0).sort((a, b) => a - b)[Math.ceil(pack.rivers.length * 0.15)];
|
||||
const type = length < smallLength ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : 'River';
|
||||
|
||||
pack.rivers.push({i: river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type});
|
||||
riverCells.push(cell);
|
||||
cells.fl[cell] += cells.fl[i];
|
||||
}
|
||||
});
|
||||
riverId = oldRiverId;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const river = rivers.find((r) => r.i === riverId);
|
||||
|
||||
const source = riverCells[0];
|
||||
const mouth = riverCells[riverCells.length - 2];
|
||||
const widthFactor = river?.widthFactor || (!parent || parent === riverId ? 1.2 : 1);
|
||||
const meanderedPoints = addMeandering(riverCells);
|
||||
|
||||
const discharge = cells.fl[mouth]; // m3 in second
|
||||
const length = getApproximateLength(meanderedPoints);
|
||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor));
|
||||
|
||||
if (river) {
|
||||
river.source = source;
|
||||
river.length = length;
|
||||
river.discharge = discharge;
|
||||
river.width = width;
|
||||
river.cells = riverCells;
|
||||
} else {
|
||||
const basin = getBasin(parent);
|
||||
const name = getName(mouth);
|
||||
const type = getType({i: riverId, length, parent});
|
||||
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells, basin, name, type});
|
||||
}
|
||||
|
||||
// render river
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const path = getRiverPath(meanderedPoints, widthFactor);
|
||||
const id = 'river' + riverId;
|
||||
const riversG = viewbox.select('#rivers');
|
||||
riversG.append('path').attr('id', id).attr('d', path);
|
||||
|
||||
if (d3.event.shiftKey === false) {
|
||||
Lakes.cleanupLakeData();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
function editZones() {
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
|
|
|
|||
373
modules/utils.js
373
modules/utils.js
|
|
@ -11,11 +11,11 @@ function getBoundaryPoints(width, height, spacing) {
|
|||
const numberY = Math.ceil(h / bSpacing) - 1;
|
||||
let points = [];
|
||||
for (let i = 0.5; i < numberX; i++) {
|
||||
let x = Math.ceil(w * i / numberX + offset);
|
||||
let x = Math.ceil((w * i) / numberX + offset);
|
||||
points.push([x, offset], [x, h + offset]);
|
||||
}
|
||||
for (let i = 0.5; i < numberY; i++) {
|
||||
let y = Math.ceil(h * i / numberY + offset);
|
||||
let y = Math.ceil((h * i) / numberY + offset);
|
||||
points.push([offset, y], [w + offset, y]);
|
||||
}
|
||||
return points;
|
||||
|
|
@ -24,7 +24,7 @@ function getBoundaryPoints(width, height, spacing) {
|
|||
// get points on a regular square grid and jitter them a bit
|
||||
function getJitteredGrid(width, height, spacing) {
|
||||
const radius = spacing / 2; // square radius
|
||||
const jittering = radius * .9; // max deviation
|
||||
const jittering = radius * 0.9; // max deviation
|
||||
const jitter = () => Math.random() * 2 * jittering - jittering;
|
||||
|
||||
let points = [];
|
||||
|
|
@ -40,7 +40,7 @@ function getJitteredGrid(width, height, spacing) {
|
|||
|
||||
// return cell index on a regular square grid
|
||||
function findGridCell(x, y) {
|
||||
return Math.floor(Math.min(y / grid.spacing, grid.cellsY -1)) * grid.cellsX + Math.floor(Math.min(x / grid.spacing, grid.cellsX-1));
|
||||
return Math.floor(Math.min(y / grid.spacing, grid.cellsY - 1)) * grid.cellsX + Math.floor(Math.min(x / grid.spacing, grid.cellsX - 1));
|
||||
}
|
||||
|
||||
// return array of cell indexes in radius on a regular square grid
|
||||
|
|
@ -55,14 +55,12 @@ function findGridAll(x, y, radius) {
|
|||
while (r > 1) {
|
||||
let cycle = frontier.slice();
|
||||
frontier = [];
|
||||
cycle.forEach(function(s) {
|
||||
|
||||
c[s].forEach(function(e) {
|
||||
cycle.forEach(function (s) {
|
||||
c[s].forEach(function (e) {
|
||||
if (found.indexOf(e) !== -1) return;
|
||||
found.push(e);
|
||||
frontier.push(e);
|
||||
});
|
||||
|
||||
});
|
||||
r--;
|
||||
}
|
||||
|
|
@ -100,7 +98,7 @@ function getGridPolygon(i) {
|
|||
|
||||
// mbostock's poissonDiscSampler
|
||||
function* poissonDiscSampler(x0, y0, x1, y1, r, k = 3) {
|
||||
if (!(x1 >= x0) || !(y1 >= y0) || !(r > 0)) throw new Error;
|
||||
if (!(x1 >= x0) || !(y1 >= y0) || !(r > 0)) throw new Error();
|
||||
|
||||
const width = x1 - x0;
|
||||
const height = y1 - y0;
|
||||
|
|
@ -113,8 +111,8 @@ function* poissonDiscSampler(x0, y0, x1, y1, r, k = 3) {
|
|||
const queue = [];
|
||||
|
||||
function far(x, y) {
|
||||
const i = x / cellSize | 0;
|
||||
const j = y / cellSize | 0;
|
||||
const i = (x / cellSize) | 0;
|
||||
const j = (y / cellSize) | 0;
|
||||
const i0 = Math.max(i - 2, 0);
|
||||
const j0 = Math.max(j - 2, 0);
|
||||
const i1 = Math.min(i + 3, gridWidth);
|
||||
|
|
@ -134,14 +132,14 @@ function* poissonDiscSampler(x0, y0, x1, y1, r, k = 3) {
|
|||
}
|
||||
|
||||
function sample(x, y) {
|
||||
queue.push(grid[gridWidth * (y / cellSize | 0) + (x / cellSize | 0)] = [x, y]);
|
||||
queue.push((grid[gridWidth * ((y / cellSize) | 0) + ((x / cellSize) | 0)] = [x, y]));
|
||||
return [x + x0, y + y0];
|
||||
}
|
||||
|
||||
yield sample(width / 2, height / 2);
|
||||
|
||||
pick: while (queue.length) {
|
||||
const i = Math.random() * queue.length | 0;
|
||||
const i = (Math.random() * queue.length) | 0;
|
||||
const parent = queue[i];
|
||||
|
||||
for (let j = 0; j < k; ++j) {
|
||||
|
|
@ -171,20 +169,19 @@ function isWater(i) {
|
|||
}
|
||||
|
||||
// convert RGB color string to HEX without #
|
||||
function toHEX(rgb){
|
||||
if (rgb.charAt(0) === "#") {return rgb;}
|
||||
function toHEX(rgb) {
|
||||
if (rgb.charAt(0) === "#") {
|
||||
return rgb;
|
||||
}
|
||||
rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
|
||||
return (rgb && rgb.length === 4) ? "#" +
|
||||
("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
|
||||
("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
|
||||
("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : '';
|
||||
return rgb && rgb.length === 4 ? "#" + ("0" + parseInt(rgb[1], 10).toString(16)).slice(-2) + ("0" + parseInt(rgb[2], 10).toString(16)).slice(-2) + ("0" + parseInt(rgb[3], 10).toString(16)).slice(-2) : "";
|
||||
}
|
||||
|
||||
// return array of standard shuffled colors
|
||||
function getColors(number) {
|
||||
const c12 = ["#dababf","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#c6b9c1","#bc80bd","#ccebc5","#ffed6f","#8dd3c7","#eb8de7"];
|
||||
const c12 = ["#dababf", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#c6b9c1", "#bc80bd", "#ccebc5", "#ffed6f", "#8dd3c7", "#eb8de7"];
|
||||
const cRB = d3.scaleSequential(d3.interpolateRainbow);
|
||||
const colors = d3.shuffle(d3.range(number).map(i => i < 12 ? c12[i] : d3.color(cRB((i-12)/(number-12))).hex()));
|
||||
const colors = d3.shuffle(d3.range(number).map(i => (i < 12 ? c12[i] : d3.color(cRB((i - 12) / (number - 12))).hex())));
|
||||
return colors;
|
||||
}
|
||||
|
||||
|
|
@ -193,30 +190,42 @@ function getRandomColor() {
|
|||
}
|
||||
|
||||
// mix a color with a random color
|
||||
function getMixedColor(color, mix = .2, bright = .3) {
|
||||
function getMixedColor(color, mix = 0.2, bright = 0.3) {
|
||||
const c = color && color[0] === "#" ? color : getRandomColor(); // if provided color is not hex (e.g. harching), generate random one
|
||||
return d3.color(d3.interpolate(c, getRandomColor())(mix)).brighter(bright).hex();
|
||||
}
|
||||
|
||||
// conver temperature from °C to other scales
|
||||
function convertTemperature(c) {
|
||||
switch(temperatureScale.value) {
|
||||
case "°C": return c + "°C";
|
||||
case "°F": return rn(c * 9 / 5 + 32) + "°F";
|
||||
case "K": return rn(c + 273.15) + "K";
|
||||
case "°R": return rn((c + 273.15) * 9 / 5) + "°R";
|
||||
case "°De": return rn((100 - c) * 3 / 2) + "°De";
|
||||
case "°N": return rn(c * 33 / 100) + "°N";
|
||||
case "°Ré": return rn(c * 4 / 5) + "°Ré";
|
||||
case "°Rø": return rn(c * 21 / 40 + 7.5) + "°Rø";
|
||||
default: return c + "°C";
|
||||
switch (temperatureScale.value) {
|
||||
case "°C":
|
||||
return c + "°C";
|
||||
case "°F":
|
||||
return rn((c * 9) / 5 + 32) + "°F";
|
||||
case "K":
|
||||
return rn(c + 273.15) + "K";
|
||||
case "°R":
|
||||
return rn(((c + 273.15) * 9) / 5) + "°R";
|
||||
case "°De":
|
||||
return rn(((100 - c) * 3) / 2) + "°De";
|
||||
case "°N":
|
||||
return rn((c * 33) / 100) + "°N";
|
||||
case "°Ré":
|
||||
return rn((c * 4) / 5) + "°Ré";
|
||||
case "°Rø":
|
||||
return rn((c * 21) / 40 + 7.5) + "°Rø";
|
||||
default:
|
||||
return c + "°C";
|
||||
}
|
||||
}
|
||||
|
||||
// random number in a range
|
||||
function rand(min, max) {
|
||||
if (min === undefined && max === undefined) return Math.random();
|
||||
if (max === undefined) {max = min; min = 0;}
|
||||
if (max === undefined) {
|
||||
max = min;
|
||||
min = 0;
|
||||
}
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
|
|
@ -227,6 +236,10 @@ function P(probability) {
|
|||
return Math.random() < probability;
|
||||
}
|
||||
|
||||
function each(n) {
|
||||
return i => i % n === 0;
|
||||
}
|
||||
|
||||
// random number (normal or gaussian distribution)
|
||||
function gauss(expected = 100, deviation = 30, min = 0, max = 300, round = 0) {
|
||||
return rn(Math.max(Math.min(d3.randomNormal(expected, deviation)(), max), min), round);
|
||||
|
|
@ -245,7 +258,9 @@ function rn(v, d = 0) {
|
|||
|
||||
// round string to d decimals
|
||||
function round(s, d = 1) {
|
||||
return s.replace(/[\d\.-][\d\.e-]*/g, function(n) {return rn(n, d);})
|
||||
return s.replace(/[\d\.-][\d\.e-]*/g, function (n) {
|
||||
return rn(n, d);
|
||||
});
|
||||
}
|
||||
|
||||
// corvent number to short string with SI postfix
|
||||
|
|
@ -279,36 +294,39 @@ function capitalize(string) {
|
|||
|
||||
// transform string to array [translateX,translateY,rotateDeg,rotateX,rotateY,scale]
|
||||
function parseTransform(string) {
|
||||
if (!string) {return [0,0,0,0,0,1];}
|
||||
const a = string.replace(/[a-z()]/g, "").replace(/[ ]/g, ",").split(",");
|
||||
if (!string) {
|
||||
return [0, 0, 0, 0, 0, 1];
|
||||
}
|
||||
const a = string
|
||||
.replace(/[a-z()]/g, "")
|
||||
.replace(/[ ]/g, ",")
|
||||
.split(",");
|
||||
return [a[0] || 0, a[1] || 0, a[2] || 0, a[3] || 0, a[4] || 0, a[5] || 1];
|
||||
}
|
||||
|
||||
// findAll d3.quandtree search from https://bl.ocks.org/lwthatcher/b41479725e0ff2277c7ac90df2de2b5e
|
||||
void function addFindAll() {
|
||||
const Quad = function(node, x0, y0, x1, y1) {
|
||||
void (function addFindAll() {
|
||||
const Quad = function (node, x0, y0, x1, y1) {
|
||||
this.node = node;
|
||||
this.x0 = x0;
|
||||
this.y0 = y0;
|
||||
this.x1 = x1;
|
||||
this.y1 = y1;
|
||||
}
|
||||
};
|
||||
|
||||
const tree_filter = function(x, y, radius) {
|
||||
const tree_filter = function (x, y, radius) {
|
||||
var t = {x, y, x0: this._x0, y0: this._y0, x3: this._x1, y3: this._y1, quads: [], node: this._root};
|
||||
if (t.node) {t.quads.push(new Quad(t.node, t.x0, t.y0, t.x3, t.y3))};
|
||||
if (t.node) {
|
||||
t.quads.push(new Quad(t.node, t.x0, t.y0, t.x3, t.y3));
|
||||
}
|
||||
radiusSearchInit(t, radius);
|
||||
|
||||
var i = 0;
|
||||
while (t.q = t.quads.pop()) {
|
||||
while ((t.q = t.quads.pop())) {
|
||||
i++;
|
||||
|
||||
// Stop searching if this quadrant can’t contain a closer node.
|
||||
if (!(t.node = t.q.node)
|
||||
|| (t.x1 = t.q.x0) > t.x3
|
||||
|| (t.y1 = t.q.y0) > t.y3
|
||||
|| (t.x2 = t.q.x1) < t.x0
|
||||
|| (t.y2 = t.q.y1) < t.y0) continue;
|
||||
if (!(t.node = t.q.node) || (t.x1 = t.q.x0) > t.x3 || (t.y1 = t.q.y0) > t.y3 || (t.x2 = t.q.x1) < t.x0 || (t.y2 = t.q.y1) < t.y0) continue;
|
||||
|
||||
// Bisect the current quadrant.
|
||||
if (t.node.length) {
|
||||
|
|
@ -316,15 +334,10 @@ void function addFindAll() {
|
|||
var xm = (t.x1 + t.x2) / 2,
|
||||
ym = (t.y1 + t.y2) / 2;
|
||||
|
||||
t.quads.push(
|
||||
new Quad(t.node[3], xm, ym, t.x2, t.y2),
|
||||
new Quad(t.node[2], t.x1, ym, xm, t.y2),
|
||||
new Quad(t.node[1], xm, t.y1, t.x2, ym),
|
||||
new Quad(t.node[0], t.x1, t.y1, xm, ym)
|
||||
);
|
||||
t.quads.push(new Quad(t.node[3], xm, ym, t.x2, t.y2), new Quad(t.node[2], t.x1, ym, xm, t.y2), new Quad(t.node[1], xm, t.y1, t.x2, ym), new Quad(t.node[0], t.x1, t.y1, xm, ym));
|
||||
|
||||
// Visit the closest quadrant first.
|
||||
if (t.i = (y >= ym) << 1 | (x >= xm)) {
|
||||
if ((t.i = ((y >= ym) << 1) | (x >= xm))) {
|
||||
t.q = t.quads[t.quads.length - 1];
|
||||
t.quads[t.quads.length - 1] = t.quads[t.quads.length - 1 - t.i];
|
||||
t.quads[t.quads.length - 1 - t.i] = t.q;
|
||||
|
|
@ -340,23 +353,26 @@ void function addFindAll() {
|
|||
}
|
||||
}
|
||||
return t.result;
|
||||
}
|
||||
};
|
||||
d3.quadtree.prototype.findAll = tree_filter;
|
||||
|
||||
var radiusSearchInit = function(t, radius) {
|
||||
var radiusSearchInit = function (t, radius) {
|
||||
t.result = [];
|
||||
t.x0 = t.x - radius, t.y0 = t.y - radius;
|
||||
t.x3 = t.x + radius, t.y3 = t.y + radius;
|
||||
(t.x0 = t.x - radius), (t.y0 = t.y - radius);
|
||||
(t.x3 = t.x + radius), (t.y3 = t.y + radius);
|
||||
t.radius = radius * radius;
|
||||
}
|
||||
};
|
||||
|
||||
var radiusSearchVisit = function(t, d2) {
|
||||
var radiusSearchVisit = function (t, d2) {
|
||||
t.node.data.scanned = true;
|
||||
if (d2 < t.radius) {
|
||||
do {t.result.push(t.node.data); t.node.data.selected = true;} while (t.node = t.node.next);
|
||||
do {
|
||||
t.result.push(t.node.data);
|
||||
t.node.data.selected = true;
|
||||
} while ((t.node = t.node.next));
|
||||
}
|
||||
}
|
||||
}()
|
||||
};
|
||||
})();
|
||||
|
||||
// get segment of any point on polyline
|
||||
function getSegmentId(points, point, step = 10) {
|
||||
|
|
@ -366,23 +382,23 @@ function getSegmentId(points, point, step = 10) {
|
|||
let minSegment = 1;
|
||||
let minDist = Infinity;
|
||||
|
||||
for (let i=0; i < points.length-1; i++) {
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const p1 = points[i];
|
||||
const p2 = points[i+1];
|
||||
const p2 = points[i + 1];
|
||||
|
||||
const length = Math.sqrt(d2(p1, p2));
|
||||
const segments = Math.ceil(length / step);
|
||||
const dx = (p2[0] - p1[0]) / segments;
|
||||
const dy = (p2[1] - p1[1]) / segments;
|
||||
|
||||
for (let s=0; s < segments; s++) {
|
||||
for (let s = 0; s < segments; s++) {
|
||||
const x = p1[0] + s * dx;
|
||||
const y = p1[1] + s * dy;
|
||||
const dist2 = d2(point, [x, y]);
|
||||
|
||||
if (dist2 >= minDist) continue;
|
||||
minDist = dist2;
|
||||
minSegment = i+1;
|
||||
minSegment = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -418,7 +434,9 @@ function vowel(c) {
|
|||
|
||||
// remove vowels from the end of the string
|
||||
function trimVowels(string) {
|
||||
while (string.length > 3 && vowel(last(string))) {string = string.slice(0,-1);}
|
||||
while (string.length > 3 && vowel(last(string))) {
|
||||
string = string.slice(0, -1);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +445,7 @@ function getAdjective(string) {
|
|||
// special cases for some suffixes
|
||||
if (string.length > 8 && string.slice(-6) === "orszag") return string.slice(0, -6);
|
||||
if (string.length > 6 && string.slice(-4) === "stan") return string.slice(0, -4);
|
||||
if (P(.5) && string.slice(-4) === "land") return string + "ic";
|
||||
if (P(0.5) && string.slice(-4) === "land") return string + "ic";
|
||||
if (string.slice(-4) === " Guo") string = string.slice(0, -4);
|
||||
|
||||
// don't change is name ends on suffix
|
||||
|
|
@ -436,16 +454,16 @@ function getAdjective(string) {
|
|||
if (string.slice(-1) === "i") return string;
|
||||
|
||||
const end = string.slice(-1); // last letter of string
|
||||
if (end === "a") return string += "n";
|
||||
if (end === "o") return string = trimVowels(string) + "an";
|
||||
if (vowel(end) || end === "c") return string += "an"; // ceiuy
|
||||
if (end === "m" || end === "n") return string += "ese";
|
||||
if (end === "q") return string += "i";
|
||||
if (end === "a") return (string += "n");
|
||||
if (end === "o") return (string = trimVowels(string) + "an");
|
||||
if (vowel(end) || end === "c") return (string += "an"); // ceiuy
|
||||
if (end === "m" || end === "n") return (string += "ese");
|
||||
if (end === "q") return (string += "i");
|
||||
return trimVowels(string) + "ian";
|
||||
}
|
||||
|
||||
// get ordinal out of integer: 1 => 1st
|
||||
const nth = n => n+(["st","nd","rd"][((n+90)%100-10)%10-1]||"th");
|
||||
const nth = n => n + (["st", "nd", "rd"][((((n + 90) % 100) - 10) % 10) - 1] || "th");
|
||||
|
||||
// get two-letters code (abbreviation) from string
|
||||
function abbreviate(name, restricted = []) {
|
||||
|
|
@ -453,8 +471,8 @@ function abbreviate(name, restricted = []) {
|
|||
const words = parsed.split(" ");
|
||||
const letters = words.join("");
|
||||
|
||||
let code = words.length === 2 ? words[0][0]+words[1][0] : letters.slice(0,2);
|
||||
for (let i = 1; i < letters.length-1 && restricted.includes(code); i++) {
|
||||
let code = words.length === 2 ? words[0][0] + words[1][0] : letters.slice(0, 2);
|
||||
for (let i = 1; i < letters.length - 1 && restricted.includes(code); i++) {
|
||||
code = letters[0] + letters[i].toUpperCase();
|
||||
}
|
||||
return code;
|
||||
|
|
@ -463,7 +481,7 @@ function abbreviate(name, restricted = []) {
|
|||
// conjunct array: [A,B,C] => "A, B and C"
|
||||
function list(array) {
|
||||
if (!Intl.ListFormat) return array.join(", ");
|
||||
const conjunction = new Intl.ListFormat(window.lang || "en", {style:"long", type:"conjunction"});
|
||||
const conjunction = new Intl.ListFormat(window.lang || "en", {style: "long", type: "conjunction"});
|
||||
return conjunction.format(array);
|
||||
}
|
||||
|
||||
|
|
@ -472,7 +490,10 @@ function splitInTwo(str) {
|
|||
const half = str.length / 2;
|
||||
const ar = str.split(" ");
|
||||
if (ar.length < 2) return ar; // only one word
|
||||
let first = "", last = "", middle = "", rest = "";
|
||||
let first = "",
|
||||
last = "",
|
||||
middle = "",
|
||||
rest = "";
|
||||
|
||||
ar.forEach((w, d) => {
|
||||
if (d + 1 !== ar.length) w += " ";
|
||||
|
|
@ -501,10 +522,10 @@ function ra(array) {
|
|||
function rw(object) {
|
||||
const array = [];
|
||||
for (const key in object) {
|
||||
for (let i=0; i < object[key]; i++) {
|
||||
for (let i = 0; i < object[key]; i++) {
|
||||
array.push(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
|
|
@ -515,33 +536,69 @@ function lim(v) {
|
|||
|
||||
// get number from string in format "1-3" or "2" or "0.5"
|
||||
function getNumberInRange(r) {
|
||||
if (typeof r !== "string") {ERROR && console.error("The value should be a string", r); return 0;}
|
||||
if (typeof r !== "string") {
|
||||
ERROR && console.error("The value should be a string", r);
|
||||
return 0;
|
||||
}
|
||||
if (!isNaN(+r)) return ~~r + +P(r - ~~r);
|
||||
const sign = r[0] === "-" ? -1 : 1;
|
||||
if (isNaN(+r[0])) r = r.slice(1);
|
||||
const range = r.includes("-") ? r.split("-") : null;
|
||||
if (!range) {ERROR && console.error("Cannot parse the number. Check the format", r); return 0;}
|
||||
if (!range) {
|
||||
ERROR && console.error("Cannot parse the number. Check the format", r);
|
||||
return 0;
|
||||
}
|
||||
const count = rand(range[0] * sign, +range[1]);
|
||||
if (isNaN(count) || count < 0) {ERROR && console.error("Cannot parse number. Check the format", r); return 0;}
|
||||
if (isNaN(count) || count < 0) {
|
||||
ERROR && console.error("Cannot parse number. Check the format", r);
|
||||
return 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// return center point of common edge of 2 pack cells
|
||||
function getMiddlePoint(cell1, cell2) {
|
||||
const {cells, vertices} = pack;
|
||||
|
||||
const commonVertices = cells.v[cell1].filter(vertex => vertices.c[vertex].some(cell => cell === cell2));
|
||||
const [x1, y1] = vertices.p[commonVertices[0]];
|
||||
const [x2, y2] = vertices.p[commonVertices[1]];
|
||||
|
||||
const x = (x1 + x2) / 2;
|
||||
const y = (y1 + y2) / 2;
|
||||
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
// helper function non-used for the generation
|
||||
function drawCellsValue(data) {
|
||||
debug.selectAll("text").remove();
|
||||
debug.selectAll("text").data(data).enter().append("text")
|
||||
.attr("x", (d,i) => pack.cells.p[i][0]).attr("y", (d,i) => pack.cells.p[i][1]).text(d => d);
|
||||
debug
|
||||
.selectAll("text")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("x", (d, i) => pack.cells.p[i][0])
|
||||
.attr("y", (d, i) => pack.cells.p[i][1])
|
||||
.text(d => d);
|
||||
}
|
||||
|
||||
// helper function non-used for the generation
|
||||
function drawPolygons(data) {
|
||||
const max = d3.max(data), min = d3.min(data), scheme = getColorScheme();
|
||||
const max = d3.max(data),
|
||||
min = d3.min(data),
|
||||
scheme = getColorScheme();
|
||||
data = data.map(d => 1 - normalize(d, min, max));
|
||||
|
||||
debug.selectAll("polygon").remove();
|
||||
debug.selectAll("polygon").data(data).enter().append("polygon")
|
||||
.attr("points", (d,i) => getPackPolygon(i))
|
||||
.attr("fill", d => scheme(d)).attr("stroke", d => scheme(d));
|
||||
debug
|
||||
.selectAll("polygon")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("polygon")
|
||||
.attr("points", (d, i) => getPackPolygon(i))
|
||||
.attr("fill", d => scheme(d))
|
||||
.attr("stroke", d => scheme(d));
|
||||
}
|
||||
|
||||
// polyfill for composedPath
|
||||
|
|
@ -552,56 +609,86 @@ function getComposedPath(node) {
|
|||
else if (node.defaultView) parent = node.defaultView;
|
||||
if (parent !== undefined) return [node].concat(getComposedPath(parent));
|
||||
return [node];
|
||||
};
|
||||
}
|
||||
|
||||
// polyfill for replaceAll
|
||||
if (!String.prototype.replaceAll) {
|
||||
String.prototype.replaceAll = function(str, newStr){
|
||||
if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') return this.replace(str, newStr);
|
||||
return this.replace(new RegExp(str, 'g'), newStr);
|
||||
String.prototype.replaceAll = function (str, newStr) {
|
||||
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") return this.replace(str, newStr);
|
||||
return this.replace(new RegExp(str, "g"), newStr);
|
||||
};
|
||||
}
|
||||
|
||||
// get next unused id
|
||||
function getNextId(core, i = 1) {
|
||||
while (document.getElementById(core+i)) i++;
|
||||
while (document.getElementById(core + i)) i++;
|
||||
return core + i;
|
||||
}
|
||||
|
||||
function debounce(f, ms) {
|
||||
function debounce(func, ms) {
|
||||
let isCooldown = false;
|
||||
|
||||
return function() {
|
||||
return function () {
|
||||
if (isCooldown) return;
|
||||
f.apply(this, arguments);
|
||||
func.apply(this, arguments);
|
||||
isCooldown = true;
|
||||
setTimeout(() => isCooldown = false, ms);
|
||||
setTimeout(() => (isCooldown = false), ms);
|
||||
};
|
||||
}
|
||||
|
||||
function throttle(func, ms) {
|
||||
let isThrottled = false;
|
||||
let savedArgs;
|
||||
let savedThis;
|
||||
|
||||
function wrapper() {
|
||||
if (isThrottled) {
|
||||
savedArgs = arguments;
|
||||
savedThis = this;
|
||||
return;
|
||||
}
|
||||
|
||||
func.apply(this, arguments);
|
||||
isThrottled = true;
|
||||
|
||||
setTimeout(function () {
|
||||
isThrottled = false;
|
||||
if (savedArgs) {
|
||||
wrapper.apply(savedThis, savedArgs);
|
||||
savedArgs = savedThis = null;
|
||||
}
|
||||
}, ms);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
// parse error to get the readable string in Chrome and Firefox
|
||||
function parseError(error) {
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
||||
const errorString = isFirefox ? error.toString() + " " + error.stack : error.stack;
|
||||
const regex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||
const errorNoURL = errorString.replace(regex, url => '<i>' + last(url.split("/")) + '</i>');
|
||||
const errorParsed = errorNoURL.replace(/at /ig, "<br> at ");
|
||||
const regex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi;
|
||||
const errorNoURL = errorString.replace(regex, url => "<i>" + last(url.split("/")) + "</i>");
|
||||
const errorParsed = errorNoURL.replace(/at /gi, "<br> at ");
|
||||
return errorParsed;
|
||||
}
|
||||
|
||||
// polyfills
|
||||
if (Array.prototype.flat === undefined) {
|
||||
Array.prototype.flat = function() {
|
||||
return this.reduce((acc, val) => Array.isArray(val) ? acc.concat(val.flat()) : acc.concat(val), []);
|
||||
}
|
||||
Array.prototype.flat = function () {
|
||||
return this.reduce((acc, val) => (Array.isArray(val) ? acc.concat(val.flat()) : acc.concat(val)), []);
|
||||
};
|
||||
}
|
||||
|
||||
// check if string is a valid for JSON parse
|
||||
JSON.isValid = str => {
|
||||
try {JSON.parse(str);}
|
||||
catch(e) {return false;}
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
function getBase64(url, callback) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
|
@ -640,16 +727,16 @@ function wiki(page) {
|
|||
|
||||
// wrap URL into html a element
|
||||
function link(URL, description) {
|
||||
return `<a href="${URL}" rel="noopener" target="_blank">${description}</a>`
|
||||
return `<a href="${URL}" rel="noopener" target="_blank">${description}</a>`;
|
||||
}
|
||||
|
||||
function isCtrlClick(event) {
|
||||
// meta key is cmd key on MacOs
|
||||
return event.ctrlKey || event.metaKey;
|
||||
return event.ctrlKey || event.metaKey;
|
||||
}
|
||||
|
||||
function generateDate(from = 100, to = 1000) {
|
||||
return new Date(rand(from, to),rand(12),rand(31)).toLocaleDateString("en", {year:'numeric', month:'long', day:'numeric'});
|
||||
return new Date(rand(from, to), rand(12), rand(31)).toLocaleDateString("en", {year: "numeric", month: "long", day: "numeric"});
|
||||
}
|
||||
|
||||
function getQGIScoordinates(x, y) {
|
||||
|
|
@ -659,15 +746,18 @@ function getQGIScoordinates(x, y) {
|
|||
}
|
||||
|
||||
// prompt replacer (prompt does not work in Electron)
|
||||
void function() {
|
||||
void (function () {
|
||||
const prompt = document.getElementById("prompt");
|
||||
const form = prompt.querySelector("#promptForm");
|
||||
|
||||
window.prompt = function(promptText = "Please provide an input", options = {default:1, step:.01, min:0, max:100}, callback) {
|
||||
if (options.default === undefined) {ERROR && console.error("Prompt: options object does not have default value defined"); return;}
|
||||
window.prompt = function (promptText = "Please provide an input", options = {default: 1, step: 0.01, min: 0, max: 100}, callback) {
|
||||
if (options.default === undefined) {
|
||||
ERROR && console.error("Prompt: options object does not have default value defined");
|
||||
return;
|
||||
}
|
||||
const input = prompt.querySelector("#promptInput");
|
||||
prompt.querySelector("#promptText").innerHTML = promptText;
|
||||
const type = typeof(options.default) === "number" ? "number" : "text";
|
||||
const type = typeof options.default === "number" ? "number" : "text";
|
||||
input.type = type;
|
||||
if (options.step !== undefined) input.step = options.step;
|
||||
if (options.min !== undefined) input.min = options.min;
|
||||
|
|
@ -676,17 +766,56 @@ void function() {
|
|||
input.value = options.default;
|
||||
prompt.style.display = "block";
|
||||
|
||||
form.addEventListener("submit", event => {
|
||||
form.addEventListener(
|
||||
"submit",
|
||||
event => {
|
||||
prompt.style.display = "none";
|
||||
const v = type === "number" ? +input.value : input.value;
|
||||
event.preventDefault();
|
||||
if (callback) callback(v);
|
||||
}, {once: true});
|
||||
}
|
||||
},
|
||||
{once: true}
|
||||
);
|
||||
};
|
||||
|
||||
const cancel = prompt.querySelector("#promptCancel");
|
||||
cancel.addEventListener("click", () => prompt.style.display = "none");
|
||||
}()
|
||||
cancel.addEventListener("click", () => (prompt.style.display = "none"));
|
||||
})();
|
||||
|
||||
// indexedDB; ldb object
|
||||
!function(){function e(t,o){return n?void(n.transaction("s").objectStore("s").get(t).onsuccess=function(e){var t=e.target.result&&e.target.result.v||null;o(t)}):void setTimeout(function(){e(t,o)},100)}var t=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;if(!t)return void ERROR && console.error("indexedDB not supported");var n,o={k:"",v:""},r=t.open("d2",1);r.onsuccess=function(e){n=this.result},r.onerror=function(e){ERROR && console.error("indexedDB request error"),INFO && console.log(e)},r.onupgradeneeded=function(e){n=null;var t=e.target.result.createObjectStore("s",{keyPath:"k"});t.transaction.oncomplete=function(e){n=e.target.db}},window.ldb={get:e,set:function(e,t){o.k=e,o.v=t,n.transaction("s","readwrite").objectStore("s").put(o)}}}();
|
||||
!(function () {
|
||||
function e(t, o) {
|
||||
return n
|
||||
? void (n.transaction("s").objectStore("s").get(t).onsuccess = function (e) {
|
||||
var t = (e.target.result && e.target.result.v) || null;
|
||||
o(t);
|
||||
})
|
||||
: void setTimeout(function () {
|
||||
e(t, o);
|
||||
}, 100);
|
||||
}
|
||||
var t = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
if (!t) return void ERROR && console.error("indexedDB not supported");
|
||||
var n,
|
||||
o = {k: "", v: ""},
|
||||
r = t.open("d2", 1);
|
||||
(r.onsuccess = function (e) {
|
||||
n = this.result;
|
||||
}),
|
||||
(r.onerror = function (e) {
|
||||
ERROR && console.error("indexedDB request error"), INFO && console.log(e);
|
||||
}),
|
||||
(r.onupgradeneeded = function (e) {
|
||||
n = null;
|
||||
var t = e.target.result.createObjectStore("s", {keyPath: "k"});
|
||||
t.transaction.oncomplete = function (e) {
|
||||
n = e.target.db;
|
||||
};
|
||||
}),
|
||||
(window.ldb = {
|
||||
get: e,
|
||||
set: function (e, t) {
|
||||
(o.k = e), (o.v = t), n.transaction("s", "readwrite").objectStore("s").put(o);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue