refactor(es modules): replace PrioriryQueue with faster FlatQueue

This commit is contained in:
Azgaar 2022-07-01 16:10:36 +03:00
parent 6a7ec6513a
commit 6ea6e52b75
9 changed files with 267 additions and 242 deletions

View file

@ -7659,7 +7659,6 @@
<script src="/src/libs/jquery-ui.min.js"></script>
<script src="/src/versioning.js"></script>
<script src="/src/libs/d3.min.js"></script>
<script src="/src/libs/priority-queue.min.js"></script>
<script src="/src/libs/delaunator.min.js"></script>
<script type="module" src="/src/modules/heightmap-generator.js"></script>

View file

@ -12,6 +12,7 @@
"vite": "^3.0.0-beta.3"
},
"dependencies": {
"flatqueue": "^2.0.3",
"lineclip": "^1.1.5",
"polylabel": "^1.1.0"
}

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,8 @@
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License
// https://github.com/Azgaar/Fantasy-Map-Generator
import FlatQueue from "flatqueue";
import "./components";
import {ERROR, INFO, TIME, WARN} from "./config/logging";
import {UINT16_MAX} from "./constants";
@ -1401,26 +1403,29 @@ function addZones(number = 1) {
const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg
if (!burg) return;
const cellsArray = [],
cost = [],
power = rand(20, 37);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
queue.queue({e: burg.cell, p: 0});
const cellsArray = [];
const costs = [];
const power = rand(20, 37);
const queue = new FlatQueue();
queue.push(burg.cell, 0);
while (queue.length) {
const next = queue.dequeue();
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
used[next.e] = 1;
const priority = queue.peekValue();
const next = queue.pop();
cells.c[next.e].forEach(function (e) {
const r = cells.road[next.e];
const c = r ? Math.max(10 - r, 1) : 100;
const p = next.p + c;
if (p > power) return;
if (cells.burg[next] || cells.pop[next]) cellsArray.push(next);
used[next] = 1;
if (!cost[e] || p < cost[e]) {
cost[e] = p;
queue.queue({e, p});
cells.c[next].forEach(neibCellId => {
const roadValue = cells.road[next];
const cost = roadValue ? Math.max(10 - roadValue, 1) : 100;
const totalPriority = priority + cost;
if (totalPriority > power) return;
if (!costs[neibCellId] || totalPriority < costs[neibCellId]) {
costs[neibCellId] = totalPriority;
queue.push(neibCellId, totalPriority);
}
});
}
@ -1481,25 +1486,28 @@ function addZones(number = 1) {
const burg = ra(burgs.filter(b => !used[b.cell] && b.i && !b.removed)); // random burg
if (!burg) return;
const cellsArray = [],
cost = [],
power = rand(5, 25);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
queue.queue({e: burg.cell, p: 0});
const cellsArray = [];
const costs = [];
const power = rand(5, 25);
const queue = new FlatQueue();
queue.push(burg.cell, 0);
while (queue.length) {
const next = queue.dequeue();
if (cells.burg[next.e] || cells.pop[next.e]) cellsArray.push(next.e);
used[next.e] = 1;
const priority = queue.peekValue();
const next = queue.pop();
cells.c[next.e].forEach(function (e) {
const c = rand(1, 10);
const p = next.p + c;
if (p > power) return;
if (cells.burg[next] || cells.pop[next]) cellsArray.push(next);
used[next] = 1;
if (!cost[e] || p < cost[e]) {
cost[e] = p;
queue.queue({e, p});
cells.c[next].forEach(neibCellId => {
const cost = rand(1, 10);
const totalPriority = priority + cost;
if (totalPriority > power) return;
if (!costs[neibCellId] || totalPriority < costs[neibCellId]) {
costs[neibCellId] = totalPriority;
queue.push(neibCellId, totalPriority);
}
});
}
@ -1522,9 +1530,9 @@ function addZones(number = 1) {
if (note[0]) note[0].legend = note[0].legend.replace("Active volcano", "Erupting volcano");
const name = note[0] ? note[0].name.replace(" Volcano", "") + " Eruption" : "Volcano Eruption";
const cellsArray = [],
queue = [cell],
power = rand(10, 30);
const cellsArray = [];
const queue = [cell];
const power = rand(10, 30);
while (queue.length) {
const q = P(0.5) ? queue.shift() : queue.pop();
@ -1545,9 +1553,9 @@ function addZones(number = 1) {
if (!roads.length) return;
const cell = +ra(roads);
const cellsArray = [],
queue = [cell],
power = rand(3, 15);
const cellsArray = [];
const queue = [cell];
const power = rand(3, 15);
while (queue.length) {
const q = P(0.3) ? queue.shift() : queue.pop();
@ -1570,9 +1578,9 @@ function addZones(number = 1) {
if (!elevated.length) return;
const cell = ra(elevated);
const cellsArray = [],
queue = [cell],
power = rand(3, 15);
const cellsArray = [];
const queue = [cell];
const power = rand(3, 15);
while (queue.length) {
const q = queue.pop();
@ -1602,9 +1610,9 @@ function addZones(number = 1) {
const cell = +ra(rivers),
river = cells.r[cell];
const cellsArray = [],
queue = [cell],
power = rand(5, 30);
const cellsArray = [];
const queue = [cell];
const power = rand(5, 30);
while (queue.length) {
const q = queue.pop();
@ -1627,9 +1635,9 @@ function addZones(number = 1) {
if (!coastal.length) return;
const cell = +ra(coastal);
const cellsArray = [],
queue = [cell],
power = rand(10, 30);
const cellsArray = [];
const queue = [cell];
const power = rand(10, 30);
while (queue.length) {
const q = queue.shift();

View file

@ -1,3 +1,5 @@
import FlatQueue from "flatqueue";
import {TIME} from "config/logging";
import {layerIsOn} from "layers";
import {Voronoi} from "/src/modules/voronoi";
@ -375,44 +377,46 @@ window.BurgsAndStates = (function () {
const {cells, states, cultures, burgs} = pack;
cells.state = new Uint16Array(cells.i.length);
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const queue = new FlatQueue();
const cost = [];
const neutral = (cells.i.length / 5000) * 2500 * neutralInput.value * statesNeutral; // limit cost for state growth
states
.filter(s => s.i && !s.removed)
.forEach(s => {
const capitalCell = burgs[s.capital].cell;
cells.state[capitalCell] = s.i;
const cultureCenter = cultures[s.culture].center;
const b = cells.biome[cultureCenter]; // state native biome
queue.queue({e: s.center, p: 0, s: s.i, b});
cost[s.center] = 1;
.forEach(state => {
const capitalCell = burgs[state.capital].cell;
cells.state[capitalCell] = state.i;
const cultureCenter = cultures[state.culture].center;
const biome = cells.biome[cultureCenter]; // state native biome
queue.push({cellId: state.center, stateId: state.i, b: biome}, 0);
cost[state.center] = 1;
});
while (queue.length) {
const next = queue.dequeue();
const {e, p, s, b} = next;
const {type, culture} = states[s];
const priority = queue.peekValue();
const {cellId, stateId, biome} = queue.pop();
const {type, culture} = states[stateId];
cells.c[e].forEach(e => {
if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells
cells.c[cellId].forEach(neibCellId => {
if (cells.state[neibCellId] && neibCellId === states[cells.state[neibCellId]].center) return; // do not overwrite capital cells
const cultureCost = culture === cells.culture[e] ? -9 : 100;
const populationCost = cells.h[e] < 20 ? 0 : cells.s[e] ? Math.max(20 - cells.s[e], 0) : 5000;
const biomeCost = getBiomeCost(b, cells.biome[e], type);
const heightCost = getHeightCost(pack.features[cells.f[e]], cells.h[e], type);
const riverCost = getRiverCost(cells.r[e], e, type);
const typeCost = getTypeCost(cells.t[e], type);
const cultureCost = culture === cells.culture[neibCellId] ? -9 : 100;
const populationCost =
cells.h[neibCellId] < 20 ? 0 : cells.s[neibCellId] ? Math.max(20 - cells.s[neibCellId], 0) : 5000;
const biomeCost = getBiomeCost(biome, cells.biome[neibCellId], type);
const heightCost = getHeightCost(pack.features[cells.f[neibCellId]], cells.h[neibCellId], type);
const riverCost = getRiverCost(cells.r[neibCellId], neibCellId, type);
const typeCost = getTypeCost(cells.t[neibCellId], type);
const cellCost = Math.max(cultureCost + populationCost + biomeCost + heightCost + riverCost + typeCost, 0);
const totalCost = p + 10 + cellCost / states[s].expansionism;
const totalCost = priority + 10 + cellCost / states[stateId].expansionism;
if (totalCost > neutral) return;
if (!cost[e] || totalCost < cost[e]) {
if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell
cost[e] = totalCost;
queue.queue({e, p: totalCost, s, b});
if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.h[neibCellId] >= 20) cells.state[neibCellId] = stateId; // assign state to cell
cost[neibCellId] = totalCost;
queue.push({cellId: neibCellId, stateId, biome}, totalCost);
}
});
}
@ -571,24 +575,24 @@ window.BurgsAndStates = (function () {
]; // right point
// connect leftmost and rightmost points with shortest path
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = [],
from = [];
queue.queue({e: start, p: 0});
const queue = new FlatQueue();
const cost = [];
const from = [];
queue.push(start, 0);
while (queue.length) {
const next = queue.dequeue(),
n = next.e,
p = next.p;
if (n === end) break;
const priority = queue.peekValue();
const next = queue.pop();
for (const v of c.v[n]) {
if (v === -1) continue;
const totalCost = p + (inside[v] ? 1 : 100);
if (from[v] || totalCost >= cost[v]) continue;
cost[v] = totalCost;
from[v] = n;
queue.queue({e: v, p: totalCost});
if (next === end) break;
for (const vertex of c.v[next]) {
if (vertex === -1) continue;
const totalCost = priority + (inside[vertex] ? 1 : 100);
if (from[vertex] || totalCost >= cost[vertex]) continue;
cost[vertex] = totalCost;
from[vertex] = next;
queue.push(vertex, totalCost);
}
}
@ -1212,33 +1216,34 @@ window.BurgsAndStates = (function () {
});
// expand generated provinces
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const queue = new FlatQueue();
const cost = [];
provinces.forEach(function (p) {
if (!p.i || p.removed) return;
cells.province[p.center] = p.i;
queue.queue({e: p.center, p: 0, province: p.i, state: p.state});
cost[p.center] = 1;
provinces.forEach(province => {
const {i, center, state, removed} = province;
if (!i || removed) return;
cells.province[center] = i;
queue.push({cellId: center, provinceId: i, stateId: state}, 0);
cost[province.center] = 1;
});
while (queue.length) {
const next = queue.dequeue(),
n = next.e,
p = next.p,
province = next.province,
state = next.state;
cells.c[n].forEach(function (e) {
const land = cells.h[e] >= 20;
if (!land && !cells.t[e]) return; // cannot pass deep ocean
if (land && cells.state[e] !== state) return;
const evevation = cells.h[e] >= 70 ? 100 : cells.h[e] >= 50 ? 30 : cells.h[e] >= 20 ? 10 : 100;
const totalCost = p + evevation;
const priority = queue.peekValue();
const {cellId, provinceId, stateId} = queue.pop();
cells.c[cellId].forEach(neibCellId => {
const land = cells.h[neibCellId] >= 20;
if (!land && !cells.t[neibCellId]) return; // cannot pass deep ocean
if (land && cells.state[neibCellId] !== stateId) return;
const evevation =
cells.h[neibCellId] >= 70 ? 100 : cells.h[neibCellId] >= 50 ? 30 : cells.h[neibCellId] >= 20 ? 10 : 100;
const totalCost = priority + evevation;
if (totalCost > max) return;
if (!cost[e] || totalCost < cost[e]) {
if (land) cells.province[e] = province; // assign province to a cell
cost[e] = totalCost;
queue.queue({e, p: totalCost, province, state});
if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (land) cells.province[neibCellId] = provinceId;
cost[neibCellId] = totalCost;
queue.push({cellId: neibCellId, provinceId, stateId}, totalCost);
}
});
}
@ -1282,26 +1287,26 @@ window.BurgsAndStates = (function () {
cells.province[center] = province;
// expand province
const cost = [];
cost[center] = 1;
queue.queue({e: center, p: 0});
while (queue.length) {
const next = queue.dequeue(),
n = next.e,
p = next.p;
const costs = [];
costs[center] = 1;
queue.push(center, 0);
cells.c[n].forEach(function (e) {
if (cells.province[e]) return;
const land = cells.h[e] >= 20;
if (cells.state[e] && cells.state[e] !== s.i) return;
const ter = land ? (cells.state[e] === s.i ? 3 : 20) : cells.t[e] ? 10 : 30;
const totalCost = p + ter;
while (queue.length) {
const priority = queue.peekValue();
const next = queue.pop();
cells.c[next].forEach(neibCellId => {
if (cells.province[neibCellId]) return;
const land = cells.h[neibCellId] >= 20;
if (cells.state[neibCellId] && cells.state[neibCellId] !== s.i) return;
const cost = land ? (cells.state[neibCellId] === s.i ? 3 : 20) : cells.t[neibCellId] ? 10 : 30;
const totalCost = priority + cost;
if (totalCost > max) return;
if (!cost[e] || totalCost < cost[e]) {
if (land && cells.state[e] === s.i) cells.province[e] = province; // assign province to a cell
cost[e] = totalCost;
queue.queue({e, p: totalCost});
if (!costs[neibCellId] || totalCost < costs[neibCellId]) {
if (land && cells.state[neibCellId] === s.i) cells.province[neibCellId] = province; // assign province to a cell
costs[neibCellId] = totalCost;
queue.push(neibCellId, totalCost);
}
});
}
@ -1344,9 +1349,11 @@ window.BurgsAndStates = (function () {
// check if there is a land way within the same state between two cells
function isPassable(from, to) {
if (cells.f[from] !== cells.f[to]) return false; // on different islands
const queue = [from],
used = new Uint8Array(cells.i.length),
state = cells.state[from];
const queue = [from];
const used = new Uint8Array(cells.i.length);
const state = cells.state[from];
while (queue.length) {
const current = queue.pop();
if (current === to) return true; // way is found

View file

@ -1,3 +1,5 @@
import FlatQueue from "flatqueue";
import {TIME} from "config/logging";
import {getColors} from "utils/colorUtils";
import {rn, minmax} from "utils/numberUtils";
@ -481,36 +483,37 @@ window.Cultures = (function () {
TIME && console.time("expandCultures");
cells = pack.cells;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
pack.cultures.forEach(function (c) {
if (!c.i || c.removed) return;
queue.queue({e: c.center, p: 0, c: c.i});
const queue = new FlatQueue();
pack.cultures.forEach(culture => {
if (!culture.i || culture.removed) return;
queue.push({cellId: culture.center, cultureId: culture.i}, 0);
});
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 type = pack.cultures[c].type;
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
const heightCost = getHeightCost(e, cells.h[e], type);
const riverCost = getRiverCost(cells.r[e], e, type);
const typeCost = getTypeCost(cells.t[e], type);
const priority = queue.peekValue();
const {cellId, cultureId} = queue.pop();
const type = pack.cultures[cultureId].type;
cells.c[cellId].forEach(neibCellId => {
const biome = cells.biome[neibCellId];
const biomeCost = getBiomeCost(cultureId, biome, type);
const biomeChangeCost = biome === cells.biome[cellId] ? 0 : 20; // penalty on biome change
const heightCost = getHeightCost(neibCellId, cells.h[neibCellId], type);
const riverCost = getRiverCost(cells.r[neibCellId], neibCellId, type);
const typeCost = getTypeCost(cells.t[neibCellId], type);
const totalCost =
p + (biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / pack.cultures[c].expansionism;
priority +
(biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / pack.cultures[cultureId].expansionism;
if (totalCost > neutral) return;
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});
if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.s[neibCellId] > 0) cells.culture[neibCellId] = cultureId; // assign culture to populated cell
cost[neibCellId] = totalCost;
queue.push({cellId: neibCellId, cultureId}, totalCost);
}
});
}

View file

@ -1,3 +1,5 @@
import FlatQueue from "flatqueue";
import {TIME} from "config/logging";
import {findAll} from "utils/graphUtils";
import {unique} from "utils/arrayUtils";
@ -574,50 +576,48 @@ window.Religions = (function () {
// growth algorithm to assign cells to religions
const expandReligions = function () {
const cells = pack.cells,
religions = pack.religions;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const {cells, religions} = pack;
const queue = new FlatQueue();
const cost = [];
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});
cost[r.center] = 1;
.forEach(({i, center, culture}) => {
cells.religion[center] = i;
const stateId = cells.state[center];
queue.push({cellId: center, religionId: i, stateId, cultureId: culture}, 0);
cost[center] = 1;
});
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
const popCost = d3.max(cells.pop) / 3; // enough 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 expansion = religions[r].expansion;
const priority = queue.peekValue();
const {cellId, religionId, stateId, cultureId, biome} = queue.pop();
cells.c[n].forEach(function (e) {
if (expansion === "culture" && c !== cells.culture[e]) return;
if (expansion === "state" && s !== cells.state[e]) return;
const expansion = religions[religionId].expansion;
const cultureCost = c !== cells.culture[e] ? 10 : 0;
const stateCost = s !== cells.state[e] ? 10 : 0;
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;
cells.c[cellId].forEach(neibCellId => {
if (expansion === "culture" && cultureId !== cells.culture[neibCellId]) return;
if (expansion === "state" && stateId !== cells.state[neibCellId]) return;
const cultureCost = cultureId !== cells.culture[neibCellId] ? 10 : 0;
const stateCost = stateId !== cells.state[neibCellId] ? 10 : 0;
const biomeCost = cells.road[neibCellId] ? 1 : biomesData.cost[cells.biome[neibCellId]];
const populationCost = Math.max(rn(popCost - cells.pop[neibCellId]), 0);
const heightCost = Math.max(cells.h[neibCellId], 20) - 20;
const waterCost = cells.h[neibCellId] < 20 ? (cells.road[neibCellId] ? 50 : 1000) : 0;
const totalCost =
p +
(cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism;
priority +
(cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) /
religions[religionId].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});
if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.h[neibCellId] >= 20 && cells.culture[neibCellId]) cells.religion[neibCellId] = religionId; // assign religion to cell
cost[neibCellId] = totalCost;
queue.push({cellId: neibCellId, religionId, cultureId, stateId}, totalCost);
}
});
}
@ -625,43 +625,40 @@ window.Religions = (function () {
// growth algorithm to assign cells to heresies
const expandHeresies = function () {
const cells = pack.cells,
religions = pack.religions;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const {cells, religions} = pack;
const queue = new FlatQueue();
const cost = [];
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});
cost[r.center] = 1;
.forEach(({i, center}) => {
const baseReligionId = cells.religion[center];
cells.religion[center] = i; // heresy id
queue.push({cellId: center, religionId: i, baseReligionId}, 0);
cost[center] = 1;
});
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 priority = queue.peekValue();
const {cellId, religionId, baseReligionId} = queue.pop();
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;
cells.c[cellId].forEach(neibCellId => {
const religionCost = cells.religion[neibCellId] === baseReligionId ? 0 : 2000;
const biomeCost = cells.road[neibCellId] ? 0 : biomesData.cost[cells.biome[neibCellId]];
const heightCost = Math.max(cells.h[neibCellId], 20) - 20;
const waterCost = cells.h[neibCellId] < 20 ? (cells.road[neibCellId] ? 50 : 1000) : 0;
const totalCost =
p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1);
priority +
(religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[religionId].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});
if (!cost[neibCellId] || totalCost < cost[neibCellId]) {
if (cells.h[neibCellId] >= 20 && cells.culture[neibCellId]) cells.religion[neibCellId] = religionId; // assign religion to cell
cost[neibCellId] = totalCost;
queue.push({cellId: neibCellId, religionId, baseReligionId}, totalCost);
}
});
}

View file

@ -1,3 +1,5 @@
import FlatQueue from "flatqueue";
import {TIME} from "config/logging";
import {findCell} from "utils/graphUtils";
import {last} from "utils/arrayUtils";
@ -169,33 +171,33 @@ window.Routes = (function () {
// Find a land path to a specific cell (exit), to a closest road (toRoad), or to all reachable cells (null, null)
function findLandPath(start, exit = null, toRoad = null) {
const cells = pack.cells;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = [],
from = [];
queue.queue({e: start, p: 0});
const queue = new FlatQueue();
const cost = [];
const from = [];
queue.push(start, 0);
while (queue.length) {
const next = queue.dequeue(),
n = next.e,
p = next.p;
if (toRoad && cells.road[n]) return [from, n];
const priority = queue.peekValue();
const next = queue.pop();
for (const c of cells.c[n]) {
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 (toRoad && cells.road[next]) return [from, next];
for (const neibCellId of cells.c[next]) {
if (cells.h[neibCellId] < 20) continue; // ignore water cells
const stateChangeCost = cells.state && cells.state[neibCellId] !== cells.state[next] ? 400 : 0; // trails tend to lay within the same state
const habitability = biomesData.habitability[cells.biome[neibCellId]];
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
const heightChangeCost = Math.abs(cells.h[neibCellId] - cells.h[next]) * 10; // routes tend to avoid elevation changes
const heightCost = cells.h[neibCellId] > 80 ? cells.h[neibCellId] : 0; // routes tend to avoid mountainous areas
const cellCoast = 10 + stateChangeCost + habitedCost + heightChangeCost + heightCost;
const totalCost = p + (cells.road[c] || cells.burg[c] ? cellCoast / 3 : cellCoast);
const totalCost = priority + (cells.road[neibCellId] || cells.burg[neibCellId] ? cellCoast / 3 : cellCoast);
if (from[c] || totalCost >= cost[c]) continue;
from[c] = n;
if (c === exit) return [from, exit];
cost[c] = totalCost;
queue.queue({e: c, p: totalCost});
if (from[neibCellId] || totalCost >= cost[neibCellId]) continue;
from[neibCellId] = next;
if (neibCellId === exit) return [from, exit];
cost[neibCellId] = totalCost;
queue.push(neibCellId, totalCost);
}
}
return [from, exit];
@ -247,32 +249,36 @@ window.Routes = (function () {
// find water paths
function findOceanPath(start, exit = null, toRoute = null) {
const cells = pack.cells,
temp = grid.cells.temp;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
const cost = [],
from = [];
queue.queue({e: start, p: 0});
const cells = pack.cells;
const temp = grid.cells.temp;
const queue = new FlatQueue();
const cost = [];
const from = [];
queue.push(start, 0);
while (queue.length) {
const next = queue.dequeue(),
n = next.e,
p = next.p;
if (toRoute && n !== start && cells.road[n]) return [from, n, true];
const priority = queue.peekValue();
const next = queue.pop();
for (const c of cells.c[n]) {
if (c === exit) {
from[c] = n;
if (toRoute && next !== start && cells.road[next]) return [from, next, true];
for (const neibCellId of cells.c[next]) {
if (neibCellId === exit) {
from[neibCellId] = next;
return [from, exit, true];
}
if (cells.h[c] >= 20) continue; // ignore land cells
if (temp[cells.g[c]] <= -5) continue; // ignore cells with term <= -5
const dist2 = (cells.p[c][1] - cells.p[n][1]) ** 2 + (cells.p[c][0] - cells.p[n][0]) ** 2;
const totalCost = p + (cells.road[c] ? 1 + dist2 / 2 : dist2 + (cells.t[c] ? 1 : 100));
if (from[c] || totalCost >= cost[c]) continue;
(from[c] = n), (cost[c] = totalCost);
queue.queue({e: c, p: totalCost});
if (cells.h[neibCellId] >= 20) continue; // ignore land cells
if (temp[cells.g[neibCellId]] <= -5) continue; // ignore cells with term <= -5
const dist2 =
(cells.p[neibCellId][1] - cells.p[next][1]) ** 2 + (cells.p[neibCellId][0] - cells.p[next][0]) ** 2;
const totalCost = priority + (cells.road[neibCellId] ? 1 + dist2 / 2 : dist2 + (cells.t[neibCellId] ? 1 : 100));
if (from[neibCellId] || totalCost >= cost[neibCellId]) continue;
(from[neibCellId] = next), (cost[neibCellId] = totalCost);
queue.push(neibCellId, totalCost);
}
}
return [from, exit, false];

View file

@ -128,6 +128,11 @@ esbuild@^0.14.43:
esbuild-windows-64 "0.14.47"
esbuild-windows-arm64 "0.14.47"
flatqueue@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/flatqueue/-/flatqueue-2.0.3.tgz#dd23673cb478e25d5a9acb9dcbb85c89e9e8e64e"
integrity sha512-RZCWZNkmxzUOh8jqEcEGZCycb3B8KAfpPwg3H//cURasunYxsg1eIvE+QDSjX+ZPHTIVfINfK1aLTrVKKO0i4g==
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"