diff --git a/src/layers/renderers/drawBiomes.js b/src/layers/renderers/drawBiomes.js index 91856940..5e406cb5 100644 --- a/src/layers/renderers/drawBiomes.js +++ b/src/layers/renderers/drawBiomes.js @@ -1,4 +1,5 @@ import {clipPoly} from "utils/lineUtils"; +import {TIME} from "config/logging"; export function drawBiomes() { TIME && console.time("drawBiomes"); @@ -6,6 +7,7 @@ export function drawBiomes() { const {cells, vertices} = pack; const n = cells.i.length; + const used = new Uint8Array(cells.i.length); const paths = new Array(biomesData.i.length).fill(""); diff --git a/src/layers/renderers/drawRivers.ts b/src/layers/renderers/drawRivers.ts index 43ea0b1f..e1c71c06 100644 --- a/src/layers/renderers/drawRivers.ts +++ b/src/layers/renderers/drawRivers.ts @@ -1,6 +1,6 @@ import {pick} from "utils/functionUtils"; -export function drawRivers(pack: IPack) { +export function drawRivers() { rivers.selectAll("*").remove(); const {addMeandering, getRiverPath} = window.Rivers; diff --git a/src/modules/biomes.js b/src/modules/biomes.js index 7295b58c..e06e1db8 100644 --- a/src/modules/biomes.js +++ b/src/modules/biomes.js @@ -1,8 +1,8 @@ import * as d3 from "d3"; import {TIME} from "config/logging"; -import {isLand} from "utils/graphUtils"; import {rn} from "utils/numberUtils"; +import {MIN_LAND_HEIGHT} from "config/generation"; window.Biomes = (function () { const getDefault = () => { @@ -78,40 +78,40 @@ window.Biomes = (function () { return {i: d3.range(0, name.length), name, color, biomesMartix, habitability, iconsDensity, icons, cost}; }; + // assign biome id for each cell + function define({temp, prec, flux, riverIds, heights, neighbors, gridReference}) { + TIME && console.time("defineBiomes"); + + const biome = new Uint8Array(heights.length); // biomes array + + for (let cellId = 0; cellId < heights.length; cellId++) { + const temperature = temp[gridReference[cellId]]; + const height = heights[cellId]; + const moisture = height < MIN_LAND_HEIGHT ? 0 : calculateMoisture(cellId); + biome[cellId] = getId(moisture, temperature, height); + } + + function calculateMoisture(cellId) { + let moist = prec[gridReference[cellId]]; + if (riverIds[cellId]) moist += Math.max(flux[cellId] / 20, 2); + + const moistAround = neighbors[cellId] + .filter(neibCellId => heights[neibCellId] >= MIN_LAND_HEIGHT) + .map(c => prec[gridReference[c]]) + .concat([moist]); + return rn(4 + d3.mean(moistAround)); + } + + TIME && console.timeEnd("defineBiomes"); + return biome; + } + function isWetLand(moisture, temperature, height) { if (moisture > 40 && temperature > -2 && height < 25) return true; //near coast if (moisture > 24 && temperature > -2 && height > 24 && height < 60) return true; //off coast return false; } - // assign biome id for each cell - function define(pack, grid) { - 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] = getId(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 getId(moisture, temperature, height) { if (height < 20) return 0; // marine biome: all water cells diff --git a/src/scripts/generation/generation.ts b/src/scripts/generation/generation.ts index 23fd7609..33cbb513 100644 --- a/src/scripts/generation/generation.ts +++ b/src/scripts/generation/generation.ts @@ -65,7 +65,8 @@ async function generate(options?: IGenerationOptions) { // renderLayer("cells"); renderLayer("features"); renderLayer("heightmap"); - renderLayer("rivers", pack); + renderLayer("rivers"); + // renderLayer("biomes"); WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`); // showStatistics(); diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index ace524a1..e400ca98 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -15,9 +15,10 @@ import {rn} from "utils/numberUtils"; import {generateRivers} from "./rivers"; const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD; -// const {Lakes, OceanLayers, Biomes, Cultures, BurgsAndStates, Religions, Military, Markers, Names} = window; +const {Biomes} = window; export function createPack(grid: IGrid): IPack { + const {temp, prec} = grid.cells; const {vertices, cells} = repackGrid(grid); const {features, featureIds, distanceField, haven, harbor} = markupPackFeatures( @@ -29,18 +30,26 @@ export function createPack(grid: IGrid): IPack { const { heights, flux, - r, + riverIds, conf, rivers: rawRivers, mergedFeatures } = generateRivers( {...pick(cells, "i", "c", "b", "g", "h", "p"), f: featureIds, t: distanceField, haven}, features, - grid.cells.prec, - grid.cells.temp + prec, + temp ); - // Biomes.define(newPack, grid); + const biome: IPack["cells"]["biome"] = Biomes.define({ + temp, + prec, + flux, + riverIds, + heights, + neighbors: cells.c, + gridReference: cells.g + }); // const rankCellsData = pick(newPack.cells, "i", "f", "fl", "conf", "r", "h", "area", "biome", "haven", "harbor"); // rankCells(newPack.features!, rankCellsData); @@ -79,8 +88,9 @@ export function createPack(grid: IGrid): IPack { haven, harbor, fl: flux, - r, - conf + r: riverIds, + conf, + biome }, features: mergedFeatures, rivers: rawRivers diff --git a/src/scripts/generation/pack/rivers.ts b/src/scripts/generation/pack/rivers.ts index f6e489ae..d72b6e4d 100644 --- a/src/scripts/generation/pack/rivers.ts +++ b/src/scripts/generation/pack/rivers.ts @@ -40,19 +40,19 @@ export function generateRivers( const cellsNumberModifier = (points / 10000) ** 0.25; const {flux, lakeData} = drainWater(); - const {r, conf, rivers} = defineRivers(); + const {riverIds, conf, rivers} = defineRivers(); const heights = downcutRivers(currentCellHeights); const mergedFeatures = mergeLakeData(features, lakeData, rivers); TIME && console.timeEnd("generateRivers"); - return {heights, flux, r, conf, rivers, mergedFeatures}; + return {heights, flux, riverIds, conf, rivers, mergedFeatures}; function drainWater() { const MIN_FLUX_TO_FORM_RIVER = 30; - const riverIds = new Uint16Array(cellsNumber); + const tempRiverIds = new Uint16Array(cellsNumber); const confluence = new Uint8Array(cellsNumber); const flux = new Uint16Array(cellsNumber); @@ -82,20 +82,20 @@ export function generateRivers( flux[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet // allow to chain lakes to keep river identity - if (riverIds[lakeCell] !== lake.river) { - const sameRiver = cells.c[lakeCell].some(c => riverIds[c] === lake.river); + if (tempRiverIds[lakeCell] !== lake.river) { + const sameRiver = cells.c[lakeCell].some(c => tempRiverIds[c] === lake.river); if (lake.river && sameRiver) { - riverIds[lakeCell] = lake.river; + tempRiverIds[lakeCell] = lake.river; addCellToRiver(lakeCell, lake.river); } else { - riverIds[lakeCell] = nextRiverId; + tempRiverIds[lakeCell] = nextRiverId; addCellToRiver(lakeCell, nextRiverId); nextRiverId++; } } - lake.outlet = riverIds[lakeCell]; + lake.outlet = tempRiverIds[lakeCell]; flowDown(cellId, flux[lakeCell], lake.outlet); } @@ -111,7 +111,7 @@ export function generateRivers( } // near-border cell: pour water out of the screen - if (cells.b[cellId] && riverIds[cellId]) return addCellToRiver(-1, riverIds[cellId]); + if (cells.b[cellId] && tempRiverIds[cellId]) return addCellToRiver(-1, tempRiverIds[cellId]); // downhill cell (make sure it's not in the source lake) let min = null; @@ -136,20 +136,20 @@ export function generateRivers( } // create a new river - if (!riverIds[cellId]) { - riverIds[cellId] = nextRiverId; + if (!tempRiverIds[cellId]) { + tempRiverIds[cellId] = nextRiverId; addCellToRiver(cellId, nextRiverId); nextRiverId++; } - flowDown(min, flux[cellId], riverIds[cellId]); + flowDown(min, flux[cellId], tempRiverIds[cellId]); }); return {flux, lakeData}; function flowDown(toCell: number, fromFlux: number, riverId: number) { const toFlux = flux[toCell] - confluence[toCell]; - const toRiver = riverIds[toCell]; + const toRiver = tempRiverIds[toCell]; if (toRiver) { // downhill cell already has river assigned @@ -159,7 +159,7 @@ export function generateRivers( // min river is a tributary of current river riverParents[toRiver] = riverId; } - riverIds[toCell] = riverId; // re-assign river if downhill part has less flux + tempRiverIds[toCell] = riverId; // re-assign river if downhill part has less flux } else { confluence[toCell] += fromFlux; // mark confluence if (currentCellHeights[toCell] >= MIN_LAND_HEIGHT) { @@ -167,7 +167,7 @@ export function generateRivers( riverParents[riverId] = toRiver; } } - } else riverIds[toCell] = riverId; // assign the river to the downhill cell + } else tempRiverIds[toCell] = riverId; // assign the river to the downhill cell if (currentCellHeights[toCell] < MIN_LAND_HEIGHT) { // pour water to the water body @@ -197,7 +197,7 @@ export function generateRivers( } function defineRivers() { - const r = new Uint16Array(cellsNumber); + const riverIds = new Uint16Array(cellsNumber); const conf = new Uint16Array(cellsNumber); const rivers: Omit[] = []; @@ -213,8 +213,8 @@ export function generateRivers( if (cell < 0 || cells.h[cell] < MIN_LAND_HEIGHT) continue; // mark confluences and assign river to cells - if (r[cell]) conf[cell] = 1; - else r[cell] = riverId; + if (riverIds[cell]) conf[cell] = 1; + else riverIds[cell] = riverId; } const source = riverCells[0]; @@ -246,13 +246,13 @@ export function generateRivers( if (!conf[i]) continue; const sortedInflux = cells.c[i] - .filter(c => r[c] && currentCellHeights[c] > currentCellHeights[i]) + .filter(c => riverIds[c] && currentCellHeights[c] > currentCellHeights[i]) .map(c => flux[c]) .sort((a, b) => b - a); conf[i] = sortedInflux.reduce((acc, flux, index) => (index ? acc + flux : acc), 0); } - return {r, conf, rivers}; + return {riverIds, conf, rivers}; } function downcutRivers(heights: Float32Array) { diff --git a/src/types/pack/pack.d.ts b/src/types/pack/pack.d.ts index 758572f1..2d43378f 100644 --- a/src/types/pack/pack.d.ts +++ b/src/types/pack/pack.d.ts @@ -20,7 +20,7 @@ interface IPackCells { fl: Uint16Array; // flux volume, defined by drainWater() in river-generator.ts r: Uint16Array; // river id, defined by defineRivers() in river-generator.ts conf: Uint16Array; // conluence, defined by defineRivers() in river-generator.ts - biome: UintArray; + biome: Uint8Array; area: UintArray; state: UintArray; culture: UintArray;