Independent North and South Poles temperature (#972)

* Poles to have Different Temperature (Ref: Axial Tilt suggestion) (#964)

* Initial Idea

* Changed Names:
Currently still only on NorthTemperature reliant, compadible version

* Restored Generation of Temperature

* Temperature Function found

* Version Bump

* Scuffed Saving solution

* Current Version(without the save changes)

* Globe Temperature Display

* Individual Regeneration of Temperatures

* Fixed Loading and Saving
New Maps save a Dummy 0 at settings[17]

* Final Version Bump
(currently no description for the Update)

---------

Co-authored-by: Azgaar <maxganiev@yandex.com>

* chore: formatting

* refactor: temperature inputs

* feat: rework temperature generation alg

* style: respore winds button

* refactor: update options on load, don't update temperature UI

* refactor: no need to keep compatibility here

* fix: load temp setting from .map file

---------

Co-authored-by: Leo <leonard.krusch@gmx.de>
This commit is contained in:
Azgaar 2023-08-06 22:59:33 +04:00 committed by GitHub
parent 7b3f897bdb
commit b3e2aa00e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 240 additions and 155 deletions

View file

@ -2346,29 +2346,58 @@
<div id="dialogs"> <div id="dialogs">
<div id="worldConfigurator" class="dialog stable" style="display: none"> <div id="worldConfigurator" class="dialog stable" style="display: none">
<div> <div style="display: flex">
<div id="worldControls"> <div id="worldControls">
<div> <div>
<i data-locked="0" id="lock_temperatureEquator" class="icon-lock-open"></i> <i data-locked="0" id="lock_temperatureEquator" class="icon-lock-open"></i>
<label data-tip="Set temperature at equator"> <label data-tip="Set temperature at equator">
<i>Equator:</i> <i>Equator:</i>
<input <input id="temperatureEquatorInput" data-stored="temperatureEquator" type="number" min="-50" max="50" />
id="temperatureEquatorInput" <span>°C = <span id="temperatureEquatorF"></span></span>
data-stored="temperatureEquator"
type="number"
min="-50"
max="50"
/>°C = <span id="temperatureEquatorF"></span>°F
<input id="temperatureEquatorOutput" data-stored="temperatureEquator" type="range" min="-50" max="50" /> <input id="temperatureEquatorOutput" data-stored="temperatureEquator" type="range" min="-50" max="50" />
</label> </label>
</div> </div>
<div> <div>
<i data-locked="0" id="lock_temperaturePole" class="icon-lock-open"></i> <label data-tip="Set the North Pole average yearly temperature">
<label data-tip="Set temperature near poles"> <i data-locked="0" id="lock_temperatureNorthPole" class="icon-lock-open"></i>
<i>Poles:</i> <i>North Pole:</i>
<input id="temperaturePoleInput" data-stored="temperaturePole" type="number" min="-50" max="50" />°C = <input
<span id="temperaturePoleF"></span>°F id="temperatureNorthPoleInput"
<input id="temperaturePoleOutput" data-stored="temperaturePole" type="range" min="-50" max="50" /> data-stored="temperatureNorthPole"
type="number"
min="-50"
max="50"
/>
<span>°C = <span id="temperatureNorthPoleF"></span></span>
<input
id="temperatureNorthPoleOutput"
data-stored="temperatureNorthPole"
type="range"
min="-50"
max="50"
/>
</label>
</div>
<div>
<label data-tip="Set the South Pole average yearly temperature">
<i data-locked="0" id="lock_temperatureSouthPole" class="icon-lock-open"></i>
<i>South Pole:</i>
<input
id="temperatureSouthPoleInput"
data-stored="temperatureSouthPole"
type="number"
min="-50"
max="50"
/>
<span>°C = <span id="temperatureSouthPoleF"></span></span>
<input
id="temperatureSouthPoleOutput"
data-stored="temperatureSouthPole"
type="range"
min="-50"
max="50"
/>
</label> </label>
</div> </div>
<div> <div>
@ -2424,59 +2453,65 @@
<div data-tip="Map coordinates on globe"><i>Coords:</i> <span id="mapCoordinates"></span></div> <div data-tip="Map coordinates on globe"><i>Coords:</i> <span id="mapCoordinates"></span></div>
</div> </div>
<svg id="globe" width="22em" height="22em" viewBox="-20 -25 240 240"> <div style="display: flex; flex-direction: column; align-items: flex-end">
<defs> <svg id="globe" width="22em" viewBox="-20 -25 240 240">
<linearGradient id="temperatureGradient" x1="0" x2="0" y1="0" y2="1"> <defs>
<stop class="tempGradient90" offset="0%" stop-color="blue" /> <linearGradient id="temperatureGradient" x1="0" x2="0" y1="0" y2="1">
<stop class="tempGradient60" offset="16.6%" stop-color="green" /> <stop id="grad90" offset="0%" stop-color="blue" />
<stop class="tempGradient30" offset="33.3%" stop-color="yellow" /> <stop id="grad60" offset="16.6%" stop-color="green" />
<stop class="tempGradient0" offset="50%" stop-color="red" /> <stop id="grad30" offset="33.3%" stop-color="yellow" />
<stop class="tempGradient30" offset="66.6%" stop-color="yellow" /> <stop id="grad0" offset="50%" stop-color="red" />
<stop class="tempGradient60" offset="83.3%" stop-color="green" /> <stop id="grad-30" offset="66.6%" stop-color="yellow" />
<stop class="tempGradient90" offset="100%" stop-color="blue" /> <stop id="grad-60" offset="83.3%" stop-color="green" />
</linearGradient> <stop id="grad-90" offset="100%" stop-color="blue" />
</defs> </linearGradient>
<g id="globeNoteLines"> </defs>
<line x1="5" x2="220" y1="0" y2="0" /> <g id="globeNoteLines">
<line x1="5" x2="220" y1="13" y2="13" /> <line x1="5" x2="220" y1="0" y2="0" />
<line x1="5" x2="220" y1="49.5" y2="49.5" /> <line x1="5" x2="220" y1="13" y2="13" />
<line x1="-5" x2="220" y1="100" y2="100" /> <line x1="5" x2="220" y1="49.5" y2="49.5" />
<line x1="5" x2="220" y1="150.5" y2="150.5" /> <line x1="-5" x2="220" y1="100" y2="100" />
<line x1="5" x2="220" y1="187" y2="187" /> <line x1="5" x2="220" y1="150.5" y2="150.5" />
<line x1="5" x2="220" y1="200" y2="200" /> <line x1="5" x2="220" y1="187" y2="187" />
</g> <line x1="5" x2="220" y1="200" y2="200" />
<g id="globeWindArrows" data-tip="Click to change wind direction" stroke-linejoin="round"> </g>
<circle cx="210" cy="6" r="12" /> <g id="globeWindArrows" data-tip="Click to change wind direction" stroke-linejoin="round">
<path data-tier="0" d="M210,11 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(225 210 6)" /> <circle cx="210" cy="6" r="12" />
<circle cx="210" cy="30" r="12" /> <path data-tier="0" d="M210,11 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(225 210 6)" />
<path data-tier="1" d="M210,35 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(45 210 30)" /> <circle cx="210" cy="30" r="12" />
<circle cx="210" cy="75" r="12" /> <path data-tier="1" d="M210,35 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(45 210 30)" />
<path data-tier="2" d="M210,80 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(225 210 75)" /> <circle cx="210" cy="75" r="12" />
<circle cx="210" cy="130" r="12" /> <path data-tier="2" d="M210,80 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(225 210 75)" />
<path data-tier="3" d="M210,135 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(315 210 130)" /> <circle cx="210" cy="130" r="12" />
<circle cx="210" cy="173" r="12" /> <path data-tier="3" d="M210,135 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(315 210 130)" />
<path data-tier="4" d="M210,178 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(135 210 173)" /> <circle cx="210" cy="173" r="12" />
<circle cx="210" cy="194" r="12" /> <path data-tier="4" d="M210,178 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(135 210 173)" />
<path data-tier="5" d="M210,199 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(315 210 194)" /> <circle cx="210" cy="194" r="12" />
</g> <path data-tier="5" d="M210,199 v-10 l-3,3 m6,0 l-3,-3" transform="rotate(315 210 194)" />
<g id="globaAxisLabels"> </g>
<text x="82%" y="-4%">wind</text> <g id="globaAxisLabels">
<text x="-8%" y="-4%">latitude</text> <text x="82%" y="-4%">wind</text>
</g> <text x="-8%" y="-4%">latitude</text>
<g id="globeLatLabels"> </g>
<text x="-15" y="5">90°</text> <g id="globeLatLabels">
<text x="-15" y="18">60°</text> <text x="-15" y="5">90°</text>
<text x="-15" y="53">30°</text> <text x="-15" y="18">60°</text>
<text x="-15" y="103"></text> <text x="-15" y="53">30°</text>
<text x="-15" y="153">30°</text> <text x="-15" y="103"></text>
<text x="-15" y="190">60°</text> <text x="-15" y="153">30°</text>
<text x="-15" y="204">90°</text> <text x="-15" y="190">60°</text>
</g> <text x="-15" y="204">90°</text>
<circle id="globeOutline" cx="100" cy="100" r="100" /> </g>
<line id="globeEquator" x1="1" x2="199" y1="100" y2="100" /> <circle id="globeOutline" cx="100" cy="100" r="100" />
<path id="globeGraticule" /> <line id="globeEquator" x1="1" x2="199" y1="100" y2="100" />
<path id="globeArea" /> <path id="globeGraticule" />
</svg> <path id="globeArea" />
</svg>
<button id="restoreWinds" data-tip="Click to restore default (Earth-based) wind directions">
Restore winds
</button>
</div>
</div> </div>
</div> </div>
@ -7924,14 +7959,14 @@
<script src="modules/ui/stylePresets.js?v=1.89.11"></script> <script src="modules/ui/stylePresets.js?v=1.89.11"></script>
<script src="modules/ui/general.js?v=1.87.03"></script> <script src="modules/ui/general.js?v=1.87.03"></script>
<script src="modules/ui/options.js?v=1.89.36"></script> <script src="modules/ui/options.js?v=1.90.00"></script>
<script src="main.js?v=1.89.32"></script> <script src="main.js?v=1.90.00"></script>
<script defer src="modules/relief-icons.js"></script> <script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/style.js"></script> <script defer src="modules/ui/style.js"></script>
<script defer src="modules/ui/editors.js?v=1.89.35"></script> <script defer src="modules/ui/editors.js?v=1.89.35"></script>
<script defer src="modules/ui/tools.js?v=1.89.38"></script> <script defer src="modules/ui/tools.js?v=1.90.00"></script>
<script defer src="modules/ui/world-configurator.js"></script> <script defer src="modules/ui/world-configurator.js?v=1.90.00"></script>
<script defer src="modules/ui/heightmap-editor.js?v=1.89.06"></script> <script defer src="modules/ui/heightmap-editor.js?v=1.89.06"></script>
<script defer src="modules/ui/provinces-editor.js?v=1.89.00"></script> <script defer src="modules/ui/provinces-editor.js?v=1.89.00"></script>
<script defer src="modules/ui/biomes-editor.js"></script> <script defer src="modules/ui/biomes-editor.js"></script>
@ -7967,8 +8002,8 @@
<script defer src="libs/rgbquant.min.js"></script> <script defer src="libs/rgbquant.min.js"></script>
<script defer src="libs/jquery.ui.touch-punch.min.js"></script> <script defer src="libs/jquery.ui.touch-punch.min.js"></script>
<script defer src="modules/io/save.js?v=1.89.29"></script> <script defer src="modules/io/save.js?v=1.90.00"></script>
<script defer src="modules/io/load.js?v=1.89.30"></script> <script defer src="modules/io/load.js?v=1.90.00"></script>
<script defer src="modules/io/cloud.js"></script> <script defer src="modules/io/cloud.js"></script>
<script defer src="modules/io/export.js?v=1.89.36"></script> <script defer src="modules/io/export.js?v=1.89.36"></script>
<script defer src="modules/io/formats.js"></script> <script defer src="modules/io/formats.js"></script>

63
main.js
View file

@ -5,8 +5,8 @@
// set debug options // set debug options
const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1"; const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
const DEBUG = localStorage.getItem("debug"); const DEBUG = localStorage.getItem("debug");
const INFO = DEBUG || !PRODUCTION; const INFO = true;
const TIME = DEBUG || !PRODUCTION; const TIME = true;
const WARN = true; const WARN = true;
const ERROR = true; const ERROR = true;
@ -179,13 +179,17 @@ function onZoom() {
const onZoomDebouced = debounce(onZoom, 50); const onZoomDebouced = debounce(onZoom, 50);
const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", onZoomDebouced); const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", onZoomDebouced);
// default options // default options, based on Earth data
let options = { let options = {
pinNotes: false, pinNotes: false,
showMFCGMap: true, showMFCGMap: true,
winds: [225, 45, 225, 315, 135, 315], winds: [225, 45, 225, 315, 135, 315],
temperatureEquator: 27,
temperatureNorthPole: -30,
temperatureSouthPole: -15,
stateLabelsMode: "auto" stateLabelsMode: "auto"
}; };
let mapCoordinates = {}; // map coordinates on globe let mapCoordinates = {}; // map coordinates on globe
let populationRate = +document.getElementById("populationRateInput").value; let populationRate = +document.getElementById("populationRateInput").value;
let distanceScale = +document.getElementById("distanceScaleInput").value; let distanceScale = +document.getElementById("distanceScaleInput").value;
@ -467,7 +471,7 @@ function applyDefaultBiomesSystem() {
const biomesMartix = [ const biomesMartix = [
// hot ↔ cold [>19°C; <-4°C]; dry ↕ wet // hot ↔ cold [>19°C; <-4°C]; dry ↕ wet
new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10]), new Uint8Array([1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10]),
new Uint8Array([3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 10, 10, 10]), new Uint8Array([1, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 10, 10, 10]),
new Uint8Array([5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 9, 9, 9, 9, 10, 10, 10]), new Uint8Array([5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 9, 9, 9, 9, 10, 10, 10]),
new Uint8Array([5, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10]), new Uint8Array([5, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10]),
new Uint8Array([7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10, 10]) new Uint8Array([7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10, 10])
@ -968,7 +972,7 @@ function defineMapSize() {
const part = grid.features.some(f => f.land && f.border); // if land goes over map borders const part = grid.features.some(f => f.land && f.border); // if land goes over map borders
const max = part ? 80 : 100; // max size const max = part ? 80 : 100; // max size
const lat = () => gauss(P(0.5) ? 40 : 60, 15, 25, 75); // latitude shift const lat = () => gauss(P(0.5) ? 40 : 60, 20, 25, 75); // latitude shift
if (!part) { if (!part) {
if (template === "Pangea") return [100, 50]; if (template === "Pangea") return [100, 50];
@ -1003,30 +1007,49 @@ function calculateMapCoordinates() {
mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon}; mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon};
} }
// temperature model // temperature model, trying to follow real-world data
// based on http://www-das.uwyo.edu/~geerts/cwx/notes/chap16/Image64.gif
function calculateTemperatures() { function calculateTemperatures() {
TIME && console.time("calculateTemperatures"); TIME && console.time("calculateTemperatures");
const cells = grid.cells; const cells = grid.cells;
cells.temp = new Int8Array(cells.i.length); // temperature array cells.temp = new Int8Array(cells.i.length); // temperature array
const tEq = +temperatureEquatorInput.value; const {temperatureEquator, temperatureNorthPole, temperatureSouthPole} = options;
const tPole = +temperaturePoleInput.value; const tropics = [16, -20]; // tropics zone
const tDelta = tEq - tPole; const tropicalGradient = 0.15;
const int = d3.easePolyInOut.exponent(0.5); // interpolation function
d3.range(0, cells.i.length, grid.cellsX).forEach(function (r) { const tempNorthTropic = temperatureEquator - tropics[0] * tropicalGradient;
const y = grid.points[r][1]; const northernGradient = (tempNorthTropic - temperatureNorthPole) / (90 - tropics[0]);
const lat = Math.abs(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT); // [0; 90]
const initTemp = tEq - int(lat / 90) * tDelta; const tempSouthTropic = temperatureEquator + tropics[1] * tropicalGradient;
for (let i = r; i < r + grid.cellsX; i++) { const southernGradient = (tempSouthTropic - temperatureSouthPole) / (90 + tropics[1]);
cells.temp[i] = minmax(initTemp - convertToFriendly(cells.h[i]), -128, 127);
const exponent = +heightExponentInput.value;
for (let rowCellId = 0; rowCellId < cells.i.length; rowCellId += grid.cellsX) {
const [, y] = grid.points[rowCellId];
const rowLatitude = mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT; // [90; -90]
const tempSeaLevel = calculateSeaLevelTemp(rowLatitude);
DEBUG && console.info(`${rn(rowLatitude)}° sea temperature: ${rn(tempSeaLevel)}°C`);
for (let cellId = rowCellId; cellId < rowCellId + grid.cellsX; cellId++) {
const tempAltitudeDrop = getAltitudeTemperatureDrop(cells.h[cellId]);
cells.temp[cellId] = minmax(tempSeaLevel - tempAltitudeDrop, -128, 127);
} }
}); }
// temperature decreases by 6.5 degree C per 1km function calculateSeaLevelTemp(latitude) {
function convertToFriendly(h) { const isTropical = latitude <= 16 && latitude >= -20;
if (isTropical) return temperatureEquator - Math.abs(latitude) * tropicalGradient;
return latitude > 0
? tempNorthTropic - (latitude - tropics[0]) * northernGradient
: tempSouthTropic + (latitude - tropics[1]) * southernGradient;
}
// temperature drops by 6.5°C per 1km of altitude
function getAltitudeTemperatureDrop(h) {
if (h < 20) return 0; if (h < 20) return 0;
const exponent = +heightExponentInput.value;
const height = Math.pow(h - 18, exponent); const height = Math.pow(h - 18, exponent);
return rn((height / 1000) * 6.5); return rn((height / 1000) * 6.5);
} }

View file

@ -108,8 +108,6 @@ function getSettings() {
urbanization: urbanization, urbanization: urbanization,
mapSize: mapSizeOutput.value, mapSize: mapSizeOutput.value,
latitudeO: latitudeOutput.value, latitudeO: latitudeOutput.value,
temperatureEquator: temperatureEquatorOutput.value,
temperaturePole: temperaturePoleOutput.value,
prec: precOutput.value, prec: precOutput.value,
options: options, options: options,
mapName: mapName.value, mapName: mapName.value,

View file

@ -218,10 +218,13 @@ async function parseLoadedData(data) {
if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13]; if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13];
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100); if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100);
if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100); if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100);
if (settings[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = settings[16];
if (settings[17]) temperaturePoleInput.value = temperaturePoleOutput.value = settings[17];
if (settings[18]) precInput.value = precOutput.value = settings[18]; if (settings[18]) precInput.value = precOutput.value = settings[18];
if (settings[19]) options = JSON.parse(settings[19]); if (settings[19]) options = JSON.parse(settings[19]);
// setting 16 and 17 (temperature) are part of options now
if (settings[16]) options.temperatureEquator = Number(settings[16]);
if (settings[17]) options.temperatureNorthPole = options.temperatureSouthPole = Number(settings[17]);
if (settings[20]) mapName.value = settings[20]; if (settings[20]) mapName.value = settings[20];
if (settings[21]) hideLabels.checked = +settings[21]; if (settings[21]) hideLabels.checked = +settings[21];
if (settings[22]) stylePreset.value = settings[22]; if (settings[22]) stylePreset.value = settings[22];
@ -231,6 +234,9 @@ async function parseLoadedData(data) {
void (function applyOptionsToUI() { void (function applyOptionsToUI() {
stateLabelsModeInput.value = options.stateLabelsMode; stateLabelsModeInput.value = options.stateLabelsMode;
yearInput.value = options.year;
eraInput.value = options.era;
shapeRendering.value = viewbox.attr("shape-rendering") || "geometricPrecision";
})(); })();
void (function parseConfiguration() { void (function parseConfiguration() {
@ -586,11 +592,6 @@ async function parseLoadedData(data) {
if (rulers && layerIsOn("toggleRulers")) rulers.draw(); if (rulers && layerIsOn("toggleRulers")) rulers.draw();
if (layerIsOn("toggleGrid")) drawGrid(); if (layerIsOn("toggleGrid")) drawGrid();
// set options
yearInput.value = options.year;
eraInput.value = options.era;
shapeRendering.value = viewbox.attr("shape-rendering") || "geometricPrecision";
if (window.restoreDefaultEvents) restoreDefaultEvents(); if (window.restoreDefaultEvents) restoreDefaultEvents();
focusOn(); // based on searchParams focus on point, cell or burg focusOn(); // based on searchParams focus on point, cell or burg
invokeActiveZooming(); invokeActiveZooming();

View file

@ -24,8 +24,8 @@ function getMapData() {
urbanization, urbanization,
mapSizeOutput.value, mapSizeOutput.value,
latitudeOutput.value, latitudeOutput.value,
temperatureEquatorOutput.value, 0, // previously used for temperatureEquatorOutput.value
temperaturePoleOutput.value, 0, // previously used for tempNorthOutput.value
precOutput.value, precOutput.value,
JSON.stringify(options), JSON.stringify(options),
mapName.value, mapName.value,

View file

@ -559,11 +559,10 @@ function applyStoredOptions() {
if (key.slice(0, 5) === "style") applyOption(stylePreset, key, key.slice(5)); if (key.slice(0, 5) === "style") applyOption(stylePreset, key, key.slice(5));
} }
if (stored("winds")) if (stored("winds")) options.winds = localStorage.getItem("winds").split(",").map(Number);
options.winds = localStorage if (stored("temperatureEquator")) options.temperatureEquator = +localStorage.getItem("temperatureEquator");
.getItem("winds") if (stored("temperatureNorthPole")) options.temperatureNorthPole = +localStorage.getItem("temperatureNorthPole");
.split(",") if (stored("temperatureSouthPole")) options.temperatureSouthPole = +localStorage.getItem("temperatureSouthPole");
.map(w => +w);
if (stored("military")) options.military = JSON.parse(stored("military")); if (stored("military")) options.military = JSON.parse(stored("military"));
if (stored("tooltipSize")) changeTooltipSize(stored("tooltipSize")); if (stored("tooltipSize")) changeTooltipSize(stored("tooltipSize"));
@ -607,13 +606,10 @@ function randomizeOptions() {
if (randomize || !locked("culturesSet")) randomizeCultureSet(); if (randomize || !locked("culturesSet")) randomizeCultureSet();
// 'Configure World' settings // 'Configure World' settings
if (randomize || !locked("temperatureEquator")) options.temperatureEquator = gauss(25, 7, 20, 35, 0);
if (randomize || !locked("temperatureNorthPole")) options.temperatureNorthPole = gauss(-25, 7, -40, 10, 0);
if (randomize || !locked("temperatureSouthPole")) options.temperatureSouthPole = gauss(-15, 7, -40, 10, 0);
if (randomize || !locked("prec")) precInput.value = precOutput.value = gauss(100, 40, 5, 500); if (randomize || !locked("prec")) precInput.value = precOutput.value = gauss(100, 40, 5, 500);
const tMax = 30,
tMin = -30; // temperature extremes
if (randomize || !locked("temperatureEquator"))
temperatureEquatorOutput.value = temperatureEquatorInput.value = rand(tMax - 10, tMax);
if (randomize || !locked("temperaturePole"))
temperaturePoleOutput.value = temperaturePoleInput.value = rand(tMin, tMin + 30);
// 'Units Editor' settings // 'Units Editor' settings
const US = navigator.language === "en-US"; const US = navigator.language === "en-US";
@ -1072,8 +1068,7 @@ function toggle3dOptions() {
document.getElementById("options3dGlobeResolution").addEventListener("change", changeResolution); document.getElementById("options3dGlobeResolution").addEventListener("change", changeResolution);
// document.getElementById("options3dMeshWireframeMode").addEventListener("change",toggleWireframe3d); // document.getElementById("options3dMeshWireframeMode").addEventListener("change",toggleWireframe3d);
document.getElementById("options3dSunColor").addEventListener("input", changeSunColor); document.getElementById("options3dSunColor").addEventListener("input", changeSunColor);
document.getElementById("options3dSubdivide").addEventListener("change",toggle3dSubdivision); document.getElementById("options3dSubdivide").addEventListener("change", toggle3dSubdivision);
function updateValues() { function updateValues() {
const globe = document.getElementById("canvas3d").dataset.type === "viewGlobe"; const globe = document.getElementById("canvas3d").dataset.type === "viewGlobe";
@ -1113,7 +1108,7 @@ function toggle3dOptions() {
ThreeD.setLightness(this.value / 100); ThreeD.setLightness(this.value / 100);
} }
function changeSunColor(){ function changeSunColor() {
ThreeD.setSunColor(options3dSunColor.value); ThreeD.setSunColor(options3dSunColor.value);
} }
@ -1134,7 +1129,7 @@ function toggle3dOptions() {
ThreeD.toggleLabels(); ThreeD.toggleLabels();
} }
function toggle3dSubdivision(){ function toggle3dSubdivision() {
ThreeD.toggle3dSubdivision(); ThreeD.toggle3dSubdivision();
} }

View file

@ -1,5 +1,6 @@
function editWorld() { function editWorld() {
if (customization) return; if (customization) return;
$("#worldConfigurator").dialog({ $("#worldConfigurator").dialog({
title: "Configure World", title: "Configure World",
resizable: false, resizable: false,
@ -8,8 +9,7 @@ function editWorld() {
"Whole World": () => applyWorldPreset(100, 50), "Whole World": () => applyWorldPreset(100, 50),
Northern: () => applyWorldPreset(33, 25), Northern: () => applyWorldPreset(33, 25),
Tropical: () => applyWorldPreset(33, 50), Tropical: () => applyWorldPreset(33, 50),
Southern: () => applyWorldPreset(33, 75), Southern: () => applyWorldPreset(33, 75)
"Restore Winds": restoreDefaultWinds
}, },
open: function () { open: function () {
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button"); const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
@ -17,7 +17,6 @@ function editWorld() {
buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes")); buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes"));
buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes")); buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes"));
buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes")); buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes"));
buttons[4].addEventListener("mousemove", () => tip("Click to restore default wind directions"));
}, },
close: function () { close: function () {
$(this).dialog("destroy"); $(this).dialog("destroy");
@ -25,28 +24,56 @@ function editWorld() {
}); });
const globe = d3.select("#globe"); const globe = d3.select("#globe");
const clr = d3.scaleSequential(d3.interpolateSpectral);
const tMax = 30,
tMin = -25; // temperature extremes
const projection = d3.geoOrthographic().translate([100, 100]).scale(100); const projection = d3.geoOrthographic().translate([100, 100]).scale(100);
const path = d3.geoPath(projection); const path = d3.geoPath(projection);
updateInputValues();
updateGlobeTemperature(); updateGlobeTemperature();
updateGlobePosition(); updateGlobePosition();
if (modules.editWorld) return; if (modules.editWorld) return;
modules.editWorld = true; modules.editWorld = true;
document.getElementById("worldControls").addEventListener("input", e => updateWorld(e.target)); byId("worldControls").addEventListener("input", e => updateWorld(e.target));
globe.select("#globeWindArrows").on("click", changeWind); globe.select("#globeWindArrows").on("click", changeWind);
globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule
updateWindDirections(); updateWindDirections();
byId("restoreWinds").addEventListener("click", restoreDefaultWinds);
function updateInputValues() {
byId("temperatureEquatorInput").value = options.temperatureEquator;
byId("temperatureEquatorOutput").value = options.temperatureEquator;
byId("temperatureEquatorF").innerText = convertTemperature(options.temperatureEquator, "°F");
byId("temperatureNorthPoleInput").value = options.temperatureNorthPole;
byId("temperatureNorthPoleOutput").value = options.temperatureNorthPole;
byId("temperatureNorthPoleF").innerText = convertTemperature(options.temperatureNorthPole, "°F");
byId("temperatureSouthPoleInput").value = options.temperatureSouthPole;
byId("temperatureSouthPoleOutput").value = options.temperatureSouthPole;
byId("temperatureSouthPoleF").innerText = convertTemperature(options.temperatureSouthPole, "°F");
}
function updateWorld(el) { function updateWorld(el) {
if (el) { if (el?.dataset.stored) {
document.getElementById(el.dataset.stored + "Input").value = el.value; const stored = el.dataset.stored;
document.getElementById(el.dataset.stored + "Output").value = el.value; byId(stored + "Input").value = el.value;
if (el.dataset.stored) lock(el.dataset.stored); byId(stored + "Output").value = el.value;
lock(el.dataset.stored);
if (stored === "temperatureEquator") {
options.temperatureEquator = Number(el.value);
byId("temperatureEquatorF").innerText = convertTemperature(options.temperatureEquator, "°F");
}
if (stored === "temperatureNorthPole") {
options.temperatureNorthPole = Number(el.value);
byId("temperatureNorthPoleF").innerText = convertTemperature(options.temperatureNorthPole, "°F");
}
if (stored === "temperatureSouthPole") {
options.temperatureSouthPole = Number(el.value);
byId("temperatureSouthPoleF").innerText = convertTemperature(options.temperatureSouthPole, "°F");
}
} }
updateGlobeTemperature(); updateGlobeTemperature();
@ -65,11 +92,11 @@ function editWorld() {
if (layerIsOn("toggleBiomes")) drawBiomes(); if (layerIsOn("toggleBiomes")) drawBiomes();
if (layerIsOn("toggleCoordinates")) drawCoordinates(); if (layerIsOn("toggleCoordinates")) drawCoordinates();
if (layerIsOn("toggleRivers")) drawRivers(); if (layerIsOn("toggleRivers")) drawRivers();
if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500); if (byId("canvas3d")) setTimeout(ThreeD.update(), 500);
} }
function updateGlobePosition() { function updateGlobePosition() {
const size = +document.getElementById("mapSizeOutput").value; const size = +byId("mapSizeOutput").value;
const eqD = ((graphHeight / 2) * 100) / size; const eqD = ((graphHeight / 2) * 100) / size;
calculateMapCoordinates(); calculateMapCoordinates();
@ -77,12 +104,12 @@ function editWorld() {
const scale = +distanceScaleInput.value; const scale = +distanceScaleInput.value;
const unit = distanceUnitInput.value; const unit = distanceUnitInput.value;
const meridian = toKilometer(eqD * 2 * scale); const meridian = toKilometer(eqD * 2 * scale);
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`; byId("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`; byId("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
document.getElementById("meridianLength").innerHTML = rn(eqD * 2); byId("meridianLength").innerHTML = rn(eqD * 2);
document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`; byId("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : ""; byId("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
document.getElementById("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`; byId("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
function toKilometer(v) { function toKilometer(v) {
if (unit === "km") return v; if (unit === "km") return v;
@ -104,15 +131,24 @@ function editWorld() {
globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area
} }
// update temperatures on globe (visual-only)
function updateGlobeTemperature() { function updateGlobeTemperature() {
const tEq = +document.getElementById("temperatureEquatorOutput").value; const tEq = options.temperatureEquator;
document.getElementById("temperatureEquatorF").innerHTML = rn((tEq * 9) / 5 + 32); const tNP = options.temperatureNorthPole;
const tPole = +document.getElementById("temperaturePoleOutput").value; const tSP = options.temperatureSouthPole;
document.getElementById("temperaturePoleF").innerHTML = rn((tPole * 9) / 5 + 32);
globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin))); const scale = d3.scaleSequential(d3.interpolateSpectral);
globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 2) / 3 - tMin) / (tMax - tMin))); const getColor = value => scale(1 - value);
globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin))); const [tMin, tMax] = [-25, 30]; // temperature extremes
globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin))); const tDelta = tMax - tMin;
globe.select("#grad90").attr("stop-color", getColor((tNP - tMin) / tDelta));
globe.select("#grad60").attr("stop-color", getColor((tEq - ((tEq - tNP) * 2) / 3 - tMin) / tDelta));
globe.select("#grad30").attr("stop-color", getColor((tEq - ((tEq - tNP) * 1) / 4 - tMin) / tDelta));
globe.select("#grad0").attr("stop-color", getColor((tEq - tMin) / tDelta));
globe.select("#grad-30").attr("stop-color", getColor((tEq - ((tEq - tSP) * 1) / 4 - tMin) / tDelta));
globe.select("#grad-60").attr("stop-color", getColor((tEq - ((tEq - tSP) * 2) / 3 - tMin) / tDelta));
globe.select("#grad-90").attr("stop-color", getColor((tSP - tMin) / tDelta));
} }
function updateWindDirections() { function updateWindDirections() {
@ -146,8 +182,8 @@ function editWorld() {
} }
function applyWorldPreset(size, lat) { function applyWorldPreset(size, lat) {
document.getElementById("mapSizeInput").value = document.getElementById("mapSizeOutput").value = size; byId("mapSizeInput").value = byId("mapSizeOutput").value = size;
document.getElementById("latitudeInput").value = document.getElementById("latitudeOutput").value = lat; byId("latitudeInput").value = byId("latitudeOutput").value = lat;
lock("mapSize"); lock("mapSize");
lock("latitude"); lock("latitude");
updateWorld(); updateWorld();

View file

@ -13,8 +13,7 @@ const temperatureConversionMap = {
"°Rø": temp => rn((temp * 21) / 40 + 7.5) + "°Rø" "°Rø": temp => rn((temp * 21) / 40 + 7.5) + "°Rø"
}; };
function convertTemperature(temp) { function convertTemperature(temp, scale = temperatureScale.value || "°C") {
const scale = temperatureScale.value || "°C";
return temperatureConversionMap[scale](temp); return temperatureConversionMap[scale](temp);
} }

View file

@ -1,8 +1,7 @@
"use strict"; "use strict";
// version and caching control // version and caching control
const version = "1.90.00"; // generator version, update each time
const version = "1.89.39"; // generator version, update each time
{ {
document.title += " v" + version; document.title += " v" + version;
@ -29,17 +28,16 @@ const version = "1.89.39"; // generator version, update each time
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<li>North and South Poles temperature can be set independently</li>
<li>More than 70 new heraldic charges</li> <li>More than 70 new heraldic charges</li>
<li>Multi-color heraldic charges support</li> <li>Multi-color heraldic charges support</li>
<li>New 3D scene options</li> <li>New 3D scene options and improvements</li>
<li>Autosave feature (in Options)</li> <li>Autosave feature (in Options)</li>
<li>Google translation support (in Options)</li> <li>Google translation support (in Options)</li>
<li>Religions can be edited and redrawn like cultures</li> <li>Religions can be edited and redrawn like cultures</li>
<li>Lock states, provinces, cultures, and religions from regeneration</li> <li>Lock states, provinces, cultures, and religions from regeneration</li>
<li>Heightmap brushes: linear edit option</li> <li>Heightmap brushes: linear edit option</li>
<li>Data Charts screen</li> <li>Data Charts screen</li>
<li>Сultures and religions can have multiple parents in hierarchy tree</li>
<li>Heightmap selection screen</li>
</ul> </ul>
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p> <p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>