Skip to content

Commit

Permalink
feat: move biomes code to a separate module, reduce deserts amount
Browse files Browse the repository at this point in the history
  • Loading branch information
Azgaar committed Aug 11, 2023
1 parent 71cc739 commit 1bb9025
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 135 deletions.
13 changes: 7 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7943,6 +7943,7 @@
<script src="modules/ocean-layers.js?v=1.91.02"></script>
<script src="modules/river-generator.js?v=1.89.13"></script>
<script src="modules/lakes.js"></script>
<script src="modules/biomes.js"></script>
<script src="modules/names-generator.js?v=1.87.14"></script>
<script src="modules/cultures-generator.js?v=1.89.10"></script>
<script src="modules/burgs-and-states.js?v=1.89.37"></script>
Expand All @@ -7951,7 +7952,7 @@
<script src="modules/military-generator.js"></script>
<script src="modules/markers-generator.js?v=1.89.34"></script>
<script src="modules/coa-generator.js?v=1.91.00"></script>
<script src="modules/submap.js?v=1.88.05"></script>
<script src="modules/submap.js?v=1.91.05"></script>
<script src="libs/polylabel.min.js"></script>
<script src="libs/lineclip.min.js"></script>
<script src="libs/alea.min.js"></script>
Expand All @@ -7962,16 +7963,16 @@

<script src="modules/ui/general.js?v=1.87.03"></script>
<script src="modules/ui/options.js?v=1.91.00"></script>
<script src="main.js?v=1.90.00"></script>
<script src="main.js?v=1.91.05"></script>

<script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/style.js"></script>
<script defer src="modules/ui/editors.js?v=1.91.00"></script>
<script defer src="modules/ui/tools.js?v=1.90.00"></script>
<script defer src="modules/ui/world-configurator.js?v=1.90.00"></script>
<script defer src="modules/ui/heightmap-editor.js?v=1.90.04"></script>
<script defer src="modules/ui/world-configurator.js?v=1.91.05"></script>
<script defer src="modules/ui/heightmap-editor.js?v=1.91.05"></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?v=1.91.05"></script>
<script defer src="modules/ui/namesbase-editor.js?v=1.89.26"></script>
<script defer src="modules/ui/elevation-profile.js"></script>
<script defer src="modules/ui/temperature-graph.js?v=1.90.03"></script>
Expand Down Expand Up @@ -8005,7 +8006,7 @@
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>

<script defer src="modules/io/save.js?v=1.91.04"></script>
<script defer src="modules/io/load.js?v=1.91.04"></script>
<script defer src="modules/io/load.js?v=1.91.05"></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/formats.js"></script>
Expand Down
116 changes: 2 additions & 114 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ let notes = [];
let rulers = new Rulers();
let customization = 0;

let biomesData = applyDefaultBiomesSystem();
let biomesData = Biomes.getDefault();
let nameBases = Names.getNameBases(); // cultures-related data

let color = d3.scaleSequential(d3.interpolateSpectral); // default color scheme
Expand Down Expand Up @@ -425,79 +425,6 @@ function findBurgForMFCG(params) {
tip("Here stands the glorious city of " + b.name, true, "success", 15000);
}

// apply default biomes data
function applyDefaultBiomesSystem() {
const name = [
"Marine",
"Hot desert",
"Cold desert",
"Savanna",
"Grassland",
"Tropical seasonal forest",
"Temperate deciduous forest",
"Tropical rainforest",
"Temperate rainforest",
"Taiga",
"Tundra",
"Glacier",
"Wetland"
];
const color = [
"#466eab",
"#fbe79f",
"#b5b887",
"#d2d082",
"#c8d68f",
"#b6d95d",
"#29bc56",
"#7dcb35",
"#409c43",
"#4b6b32",
"#96784b",
"#d5e7eb",
"#0b9131"
];
const habitability = [0, 4, 10, 22, 30, 50, 100, 80, 90, 12, 4, 0, 12];
const iconsDensity = [0, 3, 2, 120, 120, 120, 120, 150, 150, 100, 5, 0, 150];
const icons = [
{},
{dune: 3, cactus: 6, deadTree: 1},
{dune: 9, deadTree: 1},
{acacia: 1, grass: 9},
{grass: 1},
{acacia: 8, palm: 1},
{deciduous: 1},
{acacia: 5, palm: 3, deciduous: 1, swamp: 1},
{deciduous: 6, swamp: 1},
{conifer: 1},
{grass: 1},
{},
{swamp: 1}
];
const cost = [10, 200, 150, 60, 50, 70, 70, 80, 90, 200, 1000, 5000, 150]; // biome movement cost
const biomesMartix = [
// 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, 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, 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])
];

// parse icons weighted array into a simple array
for (let i = 0; i < icons.length; i++) {
const parsed = [];
for (const icon in icons[i]) {
for (let j = 0; j < icons[i][icon]; j++) {
parsed.push(icon);
}
}
icons[i] = parsed;
}

return {i: d3.range(0, name.length), name, color, biomesMartix, habitability, iconsDensity, icons, cost};
}

function handleZoom(isScaleChanged, isPositionChanged) {
viewbox.attr("transform", `translate(${viewX} ${viewY}) scale(${scale})`);

Expand Down Expand Up @@ -708,7 +635,7 @@ async function generate(options) {
Rivers.generate();
drawRivers();
Lakes.defineGroup();
defineBiomes();
Biomes.define();

rankCells();
Cultures.generate();
Expand Down Expand Up @@ -1499,45 +1426,6 @@ function isWetLand(moisture, temperature, height) {
return false;
}

// assign biome id for each cell
function defineBiomes() {
TIME && console.time("defineBiomes");
const {cells} = pack;
const {temp, prec} = grid.cells;
cells.biome = new Uint8Array(cells.i.length); // biomes array

for (const i of cells.i) {
const temperature = temp[cells.g[i]];
const height = cells.h[i];
const moisture = height < 20 ? 0 : calculateMoisture(i);
cells.biome[i] = getBiomeId(moisture, temperature, height);
}

function calculateMoisture(i) {
let moist = prec[cells.g[i]];
if (cells.r[i]) moist += Math.max(cells.fl[i] / 20, 2);

const n = cells.c[i]
.filter(isLand)
.map(c => prec[cells.g[c]])
.concat([moist]);
return rn(4 + d3.mean(n));
}

TIME && console.timeEnd("defineBiomes");
}

// assign biome id to a cell
function getBiomeId(moisture, temperature, height) {
if (height < 20) return 0; // marine biome: all water cells
if (temperature < -5) return 11; // permafrost biome
if (isWetLand(moisture, temperature, height)) return 12; // wetland biome

const moistureBand = Math.min((moisture / 5) | 0, 4); // [0-4]
const temperatureBand = Math.min(Math.max(20 - temperature, 0), 25); // [0-25]
return biomesData.biomesMartix[moistureBand][temperatureBand];
}

// assess cells suitability to calculate population and rand cells for culture center and burgs placement
function rankCells() {
TIME && console.time("rankCells");
Expand Down
144 changes: 144 additions & 0 deletions modules/biomes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"use strict";

const MIN_LAND_HEIGHT = 20;

const names = [
"Marine",
"Hot desert",
"Cold desert",
"Savanna",
"Grassland",
"Tropical seasonal forest",
"Temperate deciduous forest",
"Tropical rainforest",
"Temperate rainforest",
"Taiga",
"Tundra",
"Glacier",
"Wetland"
];

window.Biomes = (function () {
const getDefault = () => {
const name = [
"Marine",
"Hot desert",
"Cold desert",
"Savanna",
"Grassland",
"Tropical seasonal forest",
"Temperate deciduous forest",
"Tropical rainforest",
"Temperate rainforest",
"Taiga",
"Tundra",
"Glacier",
"Wetland"
];

const color = [
"#466eab",
"#fbe79f",
"#b5b887",
"#d2d082",
"#c8d68f",
"#b6d95d",
"#29bc56",
"#7dcb35",
"#409c43",
"#4b6b32",
"#96784b",
"#d5e7eb",
"#0b9131"
];
const habitability = [0, 4, 10, 22, 30, 50, 100, 80, 90, 12, 4, 0, 12];
const iconsDensity = [0, 3, 2, 120, 120, 120, 120, 150, 150, 100, 5, 0, 150];
const icons = [
{},
{dune: 3, cactus: 6, deadTree: 1},
{dune: 9, deadTree: 1},
{acacia: 1, grass: 9},
{grass: 1},
{acacia: 8, palm: 1},
{deciduous: 1},
{acacia: 5, palm: 3, deciduous: 1, swamp: 1},
{deciduous: 6, swamp: 1},
{conifer: 1},
{grass: 1},
{},
{swamp: 1}
];
const cost = [10, 200, 150, 60, 50, 70, 70, 80, 90, 200, 1000, 5000, 150]; // biome movement cost
const biomesMartix = [
// 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([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([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([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])
];

// parse icons weighted array into a simple array
for (let i = 0; i < icons.length; i++) {
const parsed = [];
for (const icon in icons[i]) {
for (let j = 0; j < icons[i][icon]; j++) {
parsed.push(icon);
}
}
icons[i] = parsed;
}

return {i: d3.range(0, name.length), name, color, biomesMartix, habitability, iconsDensity, icons, cost};
};

// assign biome id for each cell
function define() {
TIME && console.time("defineBiomes");

const {fl: flux, r: riverIds, h: heights, c: neighbors, g: gridReference} = pack.cells;
const {temp, prec} = grid.cells;
pack.cells.biome = new Uint8Array(pack.cells.i.length); // biomes array

for (let cellId = 0; cellId < heights.length; cellId++) {
const height = heights[cellId];
const moisture = height < MIN_LAND_HEIGHT ? 0 : calculateMoisture(cellId);
const temperature = temp[gridReference[cellId]];
pack.cells.biome[cellId] = getId(moisture, temperature, height, Boolean(riverIds[cellId]));
}

function calculateMoisture(cellId) {
let moisture = prec[gridReference[cellId]];
if (riverIds[cellId]) moisture += Math.max(flux[cellId] / 10, 2);

const moistAround = neighbors[cellId]
.filter(neibCellId => heights[neibCellId] >= MIN_LAND_HEIGHT)
.map(c => prec[gridReference[c]])
.concat([moisture]);
return rn(4 + d3.mean(moistAround));
}

TIME && console.timeEnd("defineBiomes");
}

function getId(moisture, temperature, height, hasRiver) {
if (height < 20) return 0; // all water cells: marine biome
if (temperature < -5) return 11; // too cold: permafrost biome
if (temperature >= 25 && !hasRiver && moisture < 8) return 1; // too hot and dry: hot desert biome
if (isWetland(moisture, temperature, height)) return 12; // too wet: wetland biome

// in other cases use biome matrix
const moistureBand = Math.min((moisture / 5) | 0, 4); // [0-4]
const temperatureBand = Math.min(Math.max(20 - temperature, 0), 25); // [0-25]
return biomesData.biomesMartix[moistureBand][temperatureBand];
}

function isWetland(moisture, temperature, height) {
if (temperature <= -2) return false; // too cold
if (moisture > 40 && height < 25) return true; // near coast
if (moisture > 24 && height > 24 && height < 60) return true; // off coast
return false;
}

return {getDefault, define, getId};
})();
2 changes: 1 addition & 1 deletion modules/io/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ async function parseLoadedData(data) {
}

const biomes = data[3].split("|");
biomesData = applyDefaultBiomesSystem();
biomesData = Biomes.getDefault();
biomesData.color = biomes[0].split(",");
biomesData.habitability = biomes[1].split(",").map(h => +h);
biomesData.name = biomes[2].split(",");
Expand Down
2 changes: 1 addition & 1 deletion modules/submap.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ window.Submap = (function () {
// biome calculation based on (resampled) grid.cells.temp and prec
// it's safe to recalculate.
stage("Regenerating Biome.");
defineBiomes();
Biomes.define();
// recalculate suitability and population
// TODO: normalize according to the base-map
rankCells();
Expand Down
Loading

0 comments on commit 1bb9025

Please sign in to comment.