mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 02:01:22 +01:00
feat: drawFeatures
This commit is contained in:
parent
6d9c86ba74
commit
4b071730f7
16 changed files with 233 additions and 416 deletions
|
|
@ -768,7 +768,7 @@ fieldset {
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tabcontent .buttonoff {
|
||||
.tabcontent li.buttonoff {
|
||||
background-color: var(--bg-disabled);
|
||||
color: #444444aa;
|
||||
}
|
||||
|
|
|
|||
33
index.html
33
index.html
|
|
@ -344,6 +344,10 @@
|
|||
</g>
|
||||
|
||||
<g id="deftemp">
|
||||
<g id="featurePaths"></g>
|
||||
<g id="textPaths"></g>
|
||||
<g id="statePaths"></g>
|
||||
<g id="defs-emblems"></g>
|
||||
<mask id="land"></mask>
|
||||
<mask id="water">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="white" />
|
||||
|
|
@ -351,9 +355,6 @@
|
|||
<mask id="fog" style="stroke-width: 10; stroke: black; stroke-linejoin: round; stroke-opacity: 0.1">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="white" stroke="none" />
|
||||
</mask>
|
||||
<g id="textPaths"></g>
|
||||
<g id="statePaths"></g>
|
||||
<g id="defs-emblems"></g>
|
||||
</g>
|
||||
|
||||
<pattern id="oceanic" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||
|
|
@ -438,7 +439,7 @@
|
|||
<select
|
||||
data-tip="Select a map layers preset"
|
||||
id="layersPreset"
|
||||
onchange="changePreset(this.value)"
|
||||
onchange="changeLayersPreset(this.value)"
|
||||
style="width: 45%"
|
||||
>
|
||||
<option value="political" selected>Political map</option>
|
||||
|
|
@ -478,7 +479,6 @@
|
|||
id="toggleTexture"
|
||||
data-tip="Texture overlay: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="X"
|
||||
class="buttonoff"
|
||||
onclick="toggleTexture(event)"
|
||||
>
|
||||
Te<u>x</u>ture
|
||||
|
|
@ -487,7 +487,6 @@
|
|||
id="toggleHeight"
|
||||
data-tip="Heightmap: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="H"
|
||||
class="buttonoff"
|
||||
onclick="toggleHeight(event)"
|
||||
>
|
||||
<u>H</u>eightmap
|
||||
|
|
@ -496,7 +495,6 @@
|
|||
id="toggleBiomes"
|
||||
data-tip="Biomes: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="B"
|
||||
class="buttonoff"
|
||||
onclick="toggleBiomes(event)"
|
||||
>
|
||||
<u>B</u>iomes
|
||||
|
|
@ -505,7 +503,6 @@
|
|||
id="toggleCells"
|
||||
data-tip="Cells structure: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="E"
|
||||
class="buttonoff"
|
||||
onclick="toggleCells(event)"
|
||||
>
|
||||
C<u>e</u>lls
|
||||
|
|
@ -514,7 +511,6 @@
|
|||
id="toggleGrid"
|
||||
data-tip="Grid: click to toggle, drag to raise or lower. Ctrl + click to edit layer style and select type"
|
||||
data-shortcut="G"
|
||||
class="buttonoff"
|
||||
onclick="toggleGrid(event)"
|
||||
>
|
||||
<u>G</u>rid
|
||||
|
|
@ -523,7 +519,6 @@
|
|||
id="toggleCoordinates"
|
||||
data-tip="Coordinate grid: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="O"
|
||||
class="buttonoff"
|
||||
onclick="toggleCoordinates(event)"
|
||||
>
|
||||
C<u>o</u>ordinates
|
||||
|
|
@ -532,7 +527,6 @@
|
|||
id="toggleCompass"
|
||||
data-tip="Wind (Compass) Rose: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="W"
|
||||
class="buttonoff"
|
||||
onclick="toggleCompass(event)"
|
||||
>
|
||||
<u>W</u>ind Rose
|
||||
|
|
@ -541,7 +535,6 @@
|
|||
id="toggleRivers"
|
||||
data-tip="Rivers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="V"
|
||||
class="buttonoff"
|
||||
onclick="toggleRivers(event)"
|
||||
>
|
||||
Ri<u>v</u>ers
|
||||
|
|
@ -550,7 +543,6 @@
|
|||
id="toggleRelief"
|
||||
data-tip="Relief and biome icons: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="F"
|
||||
class="buttonoff"
|
||||
onclick="toggleRelief(event)"
|
||||
>
|
||||
Relie<u>f</u>
|
||||
|
|
@ -559,7 +551,6 @@
|
|||
id="toggleReligions"
|
||||
data-tip="Religions: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="R"
|
||||
class="buttonoff"
|
||||
onclick="toggleReligions(event)"
|
||||
>
|
||||
<u>R</u>eligions
|
||||
|
|
@ -568,7 +559,6 @@
|
|||
id="toggleCultures"
|
||||
data-tip="Cultures: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="C"
|
||||
class="buttonoff"
|
||||
onclick="toggleCultures(event)"
|
||||
>
|
||||
<u>C</u>ultures
|
||||
|
|
@ -577,7 +567,6 @@
|
|||
id="toggleStates"
|
||||
data-tip="States: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="S"
|
||||
class="buttonoff"
|
||||
onclick="toggleStates(event)"
|
||||
>
|
||||
<u>S</u>tates
|
||||
|
|
@ -586,7 +575,6 @@
|
|||
id="toggleProvinces"
|
||||
data-tip="Provinces: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="P"
|
||||
class="buttonoff"
|
||||
onclick="toggleProvinces(event)"
|
||||
>
|
||||
<u>P</u>rovinces
|
||||
|
|
@ -595,7 +583,6 @@
|
|||
id="toggleZones"
|
||||
data-tip="Zones: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="Z"
|
||||
class="buttonoff"
|
||||
onclick="toggleZones(event)"
|
||||
>
|
||||
<u>Z</u>ones
|
||||
|
|
@ -604,7 +591,6 @@
|
|||
id="toggleBorders"
|
||||
data-tip="State borders: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="D"
|
||||
class="buttonoff"
|
||||
onclick="toggleBorders(event)"
|
||||
>
|
||||
Bor<u>d</u>ers
|
||||
|
|
@ -613,7 +599,6 @@
|
|||
id="toggleRoutes"
|
||||
data-tip="Trade routes: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="U"
|
||||
class="buttonoff"
|
||||
onclick="toggleRoutes(event)"
|
||||
>
|
||||
Ro<u>u</u>tes
|
||||
|
|
@ -622,7 +607,6 @@
|
|||
id="toggleTemperature"
|
||||
data-tip="Temperature map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="T"
|
||||
class="buttonoff"
|
||||
onclick="toggleTemperature(event)"
|
||||
>
|
||||
<u>T</u>emperature
|
||||
|
|
@ -631,7 +615,6 @@
|
|||
id="togglePopulation"
|
||||
data-tip="Population map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="N"
|
||||
class="buttonoff"
|
||||
onclick="togglePopulation(event)"
|
||||
>
|
||||
Populatio<u>n</u>
|
||||
|
|
@ -640,7 +623,6 @@
|
|||
id="toggleIce"
|
||||
data-tip="Icebergs and glaciers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="J"
|
||||
class="buttonoff"
|
||||
onclick="toggleIce(event)"
|
||||
>
|
||||
Ice
|
||||
|
|
@ -649,7 +631,6 @@
|
|||
id="togglePrecipitation"
|
||||
data-tip="Precipitation map: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="A"
|
||||
class="buttonoff"
|
||||
onclick="togglePrecipitation(event)"
|
||||
>
|
||||
Precipit<u>a</u>tion
|
||||
|
|
@ -658,7 +639,6 @@
|
|||
id="toggleEmblems"
|
||||
data-tip="Emblems: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="Y"
|
||||
class="buttonoff"
|
||||
onclick="toggleEmblems(event)"
|
||||
>
|
||||
Emblems
|
||||
|
|
@ -683,7 +663,6 @@
|
|||
id="toggleMilitary"
|
||||
data-tip="Military forces: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="M"
|
||||
class="buttonoff"
|
||||
onclick="toggleMilitary(event)"
|
||||
>
|
||||
<u>M</u>ilitary
|
||||
|
|
@ -692,7 +671,6 @@
|
|||
id="toggleMarkers"
|
||||
data-tip="Markers: click to toggle, drag to raise or lower the layer. Ctrl + click to edit layer style"
|
||||
data-shortcut="K"
|
||||
class="buttonoff"
|
||||
onclick="toggleMarkers(event)"
|
||||
>
|
||||
Mar<u>k</u>ers
|
||||
|
|
@ -701,7 +679,6 @@
|
|||
id="toggleRulers"
|
||||
data-tip="Rulers: click to toggle, drag to move, click on label to delete. Ctrl + click to edit layer style"
|
||||
data-shortcut="= (equal sign)"
|
||||
class="buttonoff"
|
||||
onclick="toggleRulers(event)"
|
||||
>
|
||||
Rulers
|
||||
|
|
|
|||
11
main.js
11
main.js
|
|
@ -14,6 +14,7 @@ const ERROR = true;
|
|||
const MOBILE = window.innerWidth < 600 || navigator.userAgentData?.mobile;
|
||||
|
||||
// typed arrays max values
|
||||
const INT8_MAX = 127;
|
||||
const UINT8_MAX = 255;
|
||||
const UINT16_MAX = 65535;
|
||||
const UINT32_MAX = 4294967295;
|
||||
|
|
@ -313,9 +314,9 @@ async function checkLoadParameters() {
|
|||
async function generateMapOnLoad() {
|
||||
await applyStyleOnLoad(); // apply previously selected default or custom style
|
||||
await generate(); // generate map
|
||||
applyPreset(); // apply saved layers preset
|
||||
applyLayersPreset(); // apply saved layers preset and reder layers
|
||||
fitMapToScreen();
|
||||
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
||||
focusOn(); // focus on point, cell or burg from MFCG based on url searchParams
|
||||
}
|
||||
|
||||
// focus on coordinates, cell or burg provided in searchParams
|
||||
|
|
@ -638,11 +639,9 @@ async function generate(options) {
|
|||
|
||||
reGraph();
|
||||
Features.markupPack();
|
||||
drawCoastline();
|
||||
createDefaultRuler();
|
||||
|
||||
Rivers.generate();
|
||||
Lakes.defineGroup();
|
||||
Biomes.define();
|
||||
|
||||
rankCells();
|
||||
|
|
@ -659,7 +658,7 @@ async function generate(options) {
|
|||
drawStateLabels();
|
||||
|
||||
Rivers.specify();
|
||||
Lakes.generateName();
|
||||
Features.specify();
|
||||
|
||||
Military.generate();
|
||||
Markers.generate();
|
||||
|
|
@ -1243,7 +1242,7 @@ const regenerateMap = debounce(async function (options) {
|
|||
resetZoom(1000);
|
||||
undraw();
|
||||
await generate(options);
|
||||
restoreLayers();
|
||||
drawLayers();
|
||||
if (ThreeD.options.isOn) ThreeD.redraw();
|
||||
if ($("#worldConfigurator").is(":visible")) editWorld();
|
||||
|
||||
|
|
|
|||
|
|
@ -203,7 +203,6 @@ export function resolveVersionConflicts(mapVersion) {
|
|||
lakes.selectAll("path").remove();
|
||||
|
||||
Features.markupPack();
|
||||
drawCoastline();
|
||||
createDefaultRuler();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ window.Features = (function () {
|
|||
|
||||
while (queue.length) {
|
||||
const cellId = queue.pop();
|
||||
if (borderCells[cellId]) border = true;
|
||||
if (!border && borderCells[cellId]) border = true;
|
||||
|
||||
for (const neighborId of neighbors[cellId]) {
|
||||
const isNeibLand = heights[neighborId] >= 20;
|
||||
|
|
@ -81,43 +81,30 @@ window.Features = (function () {
|
|||
function markupPack() {
|
||||
TIME && console.time("markupPack");
|
||||
|
||||
const gridCellsNumber = grid.cells.i.length;
|
||||
const OCEAN_MIN_SIZE = gridCellsNumber / 25;
|
||||
const SEA_MIN_SIZE = gridCellsNumber / 1000;
|
||||
const CONTINENT_MIN_SIZE = gridCellsNumber / 10;
|
||||
const ISLAND_MIN_SIZE = gridCellsNumber / 1000;
|
||||
const {cells, vertices} = pack;
|
||||
const {h: heights, c: neighbors, b: borderCells, i} = cells;
|
||||
const packCellsNumber = i.length;
|
||||
if (!packCellsNumber) return; // no cells -> there is nothing to do
|
||||
|
||||
const {h: heights, c: neighbors, b: borderCells, i} = pack.cells;
|
||||
const cellsNumber = i.length;
|
||||
if (!cellsNumber) return; // no cells -> there is nothing to do
|
||||
|
||||
const distanceField = new Int8Array(cellsNumber); // pack.cells.t
|
||||
const featureIds = new Uint16Array(cellsNumber); // pack.cells.f
|
||||
const haven = createTypedArray({maxValue: cellsNumber, length: cellsNumber}); // haven: opposite water cell
|
||||
const harbor = new Uint8Array(cellsNumber); // harbor: number of adjacent water cells
|
||||
const distanceField = new Int8Array(packCellsNumber); // pack.cells.t
|
||||
const featureIds = new Uint16Array(packCellsNumber); // pack.cells.f
|
||||
const haven = createTypedArray({maxValue: packCellsNumber, length: packCellsNumber}); // haven: opposite water cell
|
||||
const harbor = new Uint8Array(packCellsNumber); // harbor: number of adjacent water cells
|
||||
const features = [0];
|
||||
|
||||
const defineHaven = cellId => {
|
||||
const waterCells = neighbors[cellId].filter(isWater);
|
||||
const distances = waterCells.map(c => dist2(cells.p[cellId], cells.p[c]));
|
||||
const closest = distances.indexOf(Math.min.apply(Math, distances));
|
||||
|
||||
haven[cellId] = waterCells[closest];
|
||||
harbor[cellId] = waterCells.length;
|
||||
};
|
||||
|
||||
const queue = [0];
|
||||
for (let featureId = 1; queue[0] !== -1; featureId++) {
|
||||
const firstCell = queue[0];
|
||||
featureIds[firstCell] = featureId;
|
||||
|
||||
const land = isLand(firstCell);
|
||||
let border = false; // true if feature touches map border
|
||||
let border = Boolean(borderCells[firstCell]); // true if feature touches map border
|
||||
let totalCells = 1; // count cells in a feature
|
||||
|
||||
while (queue.length) {
|
||||
const cellId = queue.pop();
|
||||
if (borderCells[cellId]) border = true;
|
||||
if (!border && borderCells[cellId]) border = true;
|
||||
|
||||
for (const neighborId of neighbors[cellId]) {
|
||||
const isNeibLand = isLand(neighborId);
|
||||
|
|
@ -141,10 +128,7 @@ window.Features = (function () {
|
|||
}
|
||||
}
|
||||
|
||||
const featureVertices = getFeatureVertices({firstCell, vertices, cells, featureIds, featureId});
|
||||
const points = clipPoly(featureVertices.map(vertex => vertices.p[vertex]));
|
||||
const area = d3.polygonArea(points); // feature perimiter area
|
||||
features.push(addFeature({firstCell, land, border, featureVertices, featureId, totalCells, area}));
|
||||
features.push(addFeature({firstCell, land, border, featureId, totalCells}));
|
||||
|
||||
queue[0] = featureIds.findIndex(f => f === UNMARKED); // find unmarked cell
|
||||
}
|
||||
|
|
@ -160,100 +144,112 @@ window.Features = (function () {
|
|||
|
||||
TIME && console.timeEnd("markupPack");
|
||||
|
||||
function addFeature({firstCell, land, border, featureVertices, featureId, totalCells, area}) {
|
||||
function defineHaven(cellId) {
|
||||
const waterCells = neighbors[cellId].filter(isWater);
|
||||
const distances = waterCells.map(neibCellId => dist2(cells.p[cellId], cells.p[neibCellId]));
|
||||
const closest = distances.indexOf(Math.min.apply(Math, distances));
|
||||
|
||||
haven[cellId] = waterCells[closest];
|
||||
harbor[cellId] = waterCells.length;
|
||||
}
|
||||
|
||||
function addFeature({firstCell, land, border, featureId, totalCells}) {
|
||||
const type = land ? "island" : border ? "ocean" : "lake";
|
||||
const featureVertices = type === "ocean" ? [] : getFeatureVertices(firstCell);
|
||||
const points = clipPoly(featureVertices.map(vertex => vertices.p[vertex]));
|
||||
const area = d3.polygonArea(points); // feature perimiter area
|
||||
const absArea = Math.abs(rn(area));
|
||||
|
||||
if (land) return addIsland();
|
||||
if (border) return addOcean();
|
||||
return addLake();
|
||||
const feature = {
|
||||
i: featureId,
|
||||
type,
|
||||
land,
|
||||
border,
|
||||
firstCell,
|
||||
cells: totalCells,
|
||||
vertices: featureVertices,
|
||||
area: absArea
|
||||
};
|
||||
|
||||
function addIsland() {
|
||||
const group = defineIslandGroup();
|
||||
const feature = {
|
||||
i: featureId,
|
||||
type: "island",
|
||||
group,
|
||||
land: true,
|
||||
border,
|
||||
cells: totalCells,
|
||||
firstCell,
|
||||
vertices: featureVertices,
|
||||
area: absArea
|
||||
};
|
||||
return feature;
|
||||
if (type === "lake") {
|
||||
if (area > 0) feature.vertices = feature.vertices.reverse();
|
||||
feature.shoreline = unique(feature.vertices.map(vertex => vertices.c[vertex].filter(isLand)).flat());
|
||||
feature.height = Lakes.getHeight(feature);
|
||||
}
|
||||
|
||||
function addOcean() {
|
||||
const group = defineOceanGroup();
|
||||
const feature = {
|
||||
i: featureId,
|
||||
type: "ocean",
|
||||
group,
|
||||
land: false,
|
||||
border: false,
|
||||
cells: totalCells,
|
||||
firstCell,
|
||||
vertices: featureVertices,
|
||||
area: absArea
|
||||
};
|
||||
return feature;
|
||||
}
|
||||
return feature;
|
||||
|
||||
function addLake() {
|
||||
const group = "freshwater"; // temp, to be defined later
|
||||
const name = ""; // temp, to be defined later
|
||||
function getFeatureVertices(firstCell) {
|
||||
const getType = cellId => featureIds[cellId];
|
||||
const type = getType(firstCell);
|
||||
const ofSameType = cellId => getType(cellId) === type;
|
||||
const ofDifferentType = cellId => borderCells[cellId] || getType(cellId) !== type;
|
||||
|
||||
// ensure lake ring is clockwise (to form a hole)
|
||||
const lakeVertices = area > 0 ? featureVertices.reverse() : featureVertices;
|
||||
const isOnBorder = neighbors[firstCell].some(ofDifferentType);
|
||||
if (!isOnBorder) throw new Error(`Markup: firstCell ${firstCell} is not on the feature or map border`);
|
||||
|
||||
const shoreline = getShoreline(); // land cells around lake
|
||||
const height = getLakeElevation();
|
||||
const startingVertex = cells.v[firstCell].find(v => vertices.c[v].some(ofDifferentType));
|
||||
if (startingVertex === undefined) throw new Error(`Markup: startingVertex for cell ${firstCell} is not found`);
|
||||
|
||||
function getShoreline() {
|
||||
const isLand = cellId => heights[cellId] >= MIN_LAND_HEIGHT;
|
||||
const cellsAround = lakeVertices.map(vertex => vertices.c[vertex].filter(isLand)).flat();
|
||||
return unique(cellsAround);
|
||||
}
|
||||
|
||||
function getLakeElevation() {
|
||||
const MIN_ELEVATION_DELTA = 0.1;
|
||||
const minShoreHeight = d3.min(shoreline.map(cellId => heights[cellId])) || MIN_LAND_HEIGHT;
|
||||
return rn(minShoreHeight - MIN_ELEVATION_DELTA, 2);
|
||||
}
|
||||
|
||||
const feature = {
|
||||
i: featureId,
|
||||
type: "lake",
|
||||
group,
|
||||
name,
|
||||
land: false,
|
||||
border: false,
|
||||
cells: totalCells,
|
||||
firstCell,
|
||||
vertices: lakeVertices,
|
||||
shoreline: shoreline,
|
||||
height,
|
||||
area: absArea
|
||||
};
|
||||
return feature;
|
||||
}
|
||||
|
||||
function defineOceanGroup() {
|
||||
if (totalCells > OCEAN_MIN_SIZE) return "ocean";
|
||||
if (totalCells > SEA_MIN_SIZE) return "sea";
|
||||
return "gulf";
|
||||
}
|
||||
|
||||
function defineIslandGroup() {
|
||||
const prevFeature = features[featureIds[firstCell - 1]];
|
||||
|
||||
if (prevFeature && prevFeature.type === "lake") return "lake_island";
|
||||
if (totalCells > CONTINENT_MIN_SIZE) return "continent";
|
||||
if (totalCells > ISLAND_MIN_SIZE) return "island";
|
||||
return "isle";
|
||||
return connectVertices({vertices, startingVertex, ofSameType, closeRing: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {markupGrid, markupPack};
|
||||
// add properties to pack features
|
||||
function specify() {
|
||||
const gridCellsNumber = grid.cells.i.length;
|
||||
const OCEAN_MIN_SIZE = gridCellsNumber / 25;
|
||||
const SEA_MIN_SIZE = gridCellsNumber / 1000;
|
||||
const CONTINENT_MIN_SIZE = gridCellsNumber / 10;
|
||||
const ISLAND_MIN_SIZE = gridCellsNumber / 1000;
|
||||
|
||||
for (const feature of pack.features) {
|
||||
if (!feature || feature.type === "ocean") continue;
|
||||
|
||||
feature.group = defineGroup(feature);
|
||||
|
||||
if (feature.type === "lake") {
|
||||
feature.height = Lakes.getHeight(feature);
|
||||
feature.name = Lakes.getName(feature);
|
||||
}
|
||||
}
|
||||
|
||||
function defineGroup(feature) {
|
||||
if (feature.type === "island") return defineIslandGroup(feature);
|
||||
if (feature.type === "ocean") return defineOceanGroup();
|
||||
if (feature.type === "lake") return defineLakeGroup(feature);
|
||||
throw new Error(`Markup: unknown feature type ${feature.type}`);
|
||||
}
|
||||
|
||||
function defineOceanGroup(feature) {
|
||||
if (feature.cells > OCEAN_MIN_SIZE) return "ocean";
|
||||
if (feature.cells > SEA_MIN_SIZE) return "sea";
|
||||
return "gulf";
|
||||
}
|
||||
|
||||
function defineIslandGroup(feature) {
|
||||
const prevFeature = pack.features[pack.cells.f[feature.firstCell - 1]];
|
||||
if (prevFeature && prevFeature.type === "lake") return "lake_island";
|
||||
if (feature.cells > CONTINENT_MIN_SIZE) return "continent";
|
||||
if (feature.cells > ISLAND_MIN_SIZE) return "island";
|
||||
return "isle";
|
||||
}
|
||||
|
||||
function defineLakeGroup(feature) {
|
||||
if (feature.temp < -3) return "frozen";
|
||||
if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 10 === 0) return "lava";
|
||||
|
||||
if (!feature.inlets && !feature.outlet) {
|
||||
if (feature.evaporation > feature.flux * 4) return "dry";
|
||||
if (feature.cells < 3 && feature.firstCell % 10 === 0) return "sinkhole";
|
||||
}
|
||||
|
||||
if (!feature.outlet && feature.evaporation > feature.flux) return "salt";
|
||||
|
||||
return "freshwater";
|
||||
}
|
||||
}
|
||||
|
||||
return {markupGrid, markupPack, specify};
|
||||
})();
|
||||
|
|
|
|||
174
modules/lakes.js
174
modules/lakes.js
|
|
@ -1,98 +1,87 @@
|
|||
"use strict";
|
||||
|
||||
window.Lakes = (function () {
|
||||
const setClimateData = function (h) {
|
||||
const cells = pack.cells;
|
||||
const lakeOutCells = new Uint16Array(cells.i.length);
|
||||
const LAKE_ELEVATION_DELTA = 0.1;
|
||||
|
||||
pack.features.forEach(f => {
|
||||
if (f.type !== "lake") return;
|
||||
// check if lake can be potentially open (not in deep depression)
|
||||
const detectCloseLakes = h => {
|
||||
const {cells} = pack;
|
||||
const ELEVATION_LIMIT = +byId("lakeElevationLimitOutput").value;
|
||||
|
||||
// default flux: sum of precipitation around lake
|
||||
f.flux = f.shoreline.reduce((acc, c) => acc + grid.cells.prec[cells.g[c]], 0);
|
||||
pack.features.forEach(feature => {
|
||||
if (feature.type !== "lake") return;
|
||||
delete feature.closed;
|
||||
|
||||
// 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);
|
||||
const height = (f.height - 18) ** heightExponentInput.value; // height in meters
|
||||
const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11]
|
||||
f.evaporation = rn(evaporation * f.cells);
|
||||
|
||||
// no outlet for lakes in depressed areas
|
||||
if (f.closed) return;
|
||||
|
||||
// lake outlet cell
|
||||
f.outCell = f.shoreline[d3.scan(f.shoreline, (a, b) => h[a] - h[b])];
|
||||
lakeOutCells[f.outCell] = f.i;
|
||||
});
|
||||
|
||||
return lakeOutCells;
|
||||
};
|
||||
|
||||
// get array of land cells aroound lake
|
||||
const getShoreline = function (lake) {
|
||||
const uniqueCells = new Set();
|
||||
if (!lake.vertices) lake.vertices = [];
|
||||
lake.vertices.forEach(v => pack.vertices.c[v].forEach(c => pack.cells.h[c] >= 20 && uniqueCells.add(c)));
|
||||
lake.shoreline = [...uniqueCells];
|
||||
};
|
||||
|
||||
const prepareLakeData = h => {
|
||||
const cells = pack.cells;
|
||||
const ELEVATION_LIMIT = +document.getElementById("lakeElevationLimitOutput").value;
|
||||
|
||||
pack.features.forEach(f => {
|
||||
if (f.type !== "lake") return;
|
||||
delete f.flux;
|
||||
delete f.inlets;
|
||||
delete f.outlet;
|
||||
delete f.height;
|
||||
delete f.closed;
|
||||
!f.shoreline && Lakes.getShoreline(f);
|
||||
|
||||
// lake surface height is as lowest land cells around
|
||||
const min = f.shoreline.sort((a, b) => h[a] - h[b])[0];
|
||||
f.height = h[min] - 0.1;
|
||||
|
||||
// check if lake can be open (not in deep depression)
|
||||
if (ELEVATION_LIMIT === 80) {
|
||||
f.closed = false;
|
||||
const MAX_ELEVATION = feature.height + ELEVATION_LIMIT;
|
||||
if (MAX_ELEVATION > 99) {
|
||||
feature.closed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let deep = true;
|
||||
const threshold = f.height + ELEVATION_LIMIT;
|
||||
const queue = [min];
|
||||
let isDeep = true;
|
||||
const lowestShorelineCell = feature.shoreline.sort((a, b) => h[a] - h[b])[0];
|
||||
const queue = [lowestShorelineCell];
|
||||
const checked = [];
|
||||
checked[min] = true;
|
||||
checked[lowestShorelineCell] = true;
|
||||
|
||||
// check if elevated lake can potentially pour to another water body
|
||||
while (deep && queue.length) {
|
||||
const q = queue.pop();
|
||||
while (queue.length && isDeep) {
|
||||
const cellId = queue.pop();
|
||||
|
||||
for (const n of cells.c[q]) {
|
||||
if (checked[n]) continue;
|
||||
if (h[n] >= threshold) continue;
|
||||
for (const neibCellId of cells.c[cellId]) {
|
||||
if (checked[neibCellId]) continue;
|
||||
if (h[neibCellId] >= MAX_ELEVATION) continue;
|
||||
|
||||
if (h[n] < 20) {
|
||||
const nFeature = pack.features[cells.f[n]];
|
||||
if (nFeature.type === "ocean" || f.height > nFeature.height) {
|
||||
deep = false;
|
||||
break;
|
||||
}
|
||||
if (h[neibCellId] < 20) {
|
||||
const nFeature = pack.features[cells.f[neibCellId]];
|
||||
if (nFeature.type === "ocean" || feature.height > nFeature.height) isDeep = false;
|
||||
}
|
||||
|
||||
checked[n] = true;
|
||||
queue.push(n);
|
||||
checked[neibCellId] = true;
|
||||
queue.push(neibCellId);
|
||||
}
|
||||
}
|
||||
|
||||
f.closed = deep;
|
||||
feature.closed = isDeep;
|
||||
});
|
||||
};
|
||||
|
||||
const defineClimateData = function (heights) {
|
||||
const {cells, features} = pack;
|
||||
const lakeOutCells = new Uint16Array(cells.i.length);
|
||||
|
||||
features.forEach(feature => {
|
||||
if (feature.type !== "lake") return;
|
||||
feature.flux = getFlux(feature);
|
||||
feature.temp = getLakeTemp(feature);
|
||||
feature.evaporation = getLakeEvaporation(feature);
|
||||
if (feature.closed) return; // no outlet for lakes in depressed areas
|
||||
|
||||
feature.outCell = getLowestShoreCell(feature);
|
||||
lakeOutCells[feature.outCell] = feature.i;
|
||||
});
|
||||
|
||||
return lakeOutCells;
|
||||
|
||||
function getFlux(lake) {
|
||||
return lake.shoreline.reduce((acc, c) => acc + grid.cells.prec[cells.g[c]], 0);
|
||||
}
|
||||
|
||||
function getLakeTemp(lake) {
|
||||
if (lake.cells < 6) return grid.cells.temp[cells.g[lake.firstCell]];
|
||||
return rn(d3.mean(lake.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
|
||||
}
|
||||
|
||||
function getLakeEvaporation(lake) {
|
||||
const height = (lake.height - 18) ** heightExponentInput.value; // height in meters
|
||||
const evaporation = ((700 * (lake.temp + 0.006 * height)) / 50 + 75) / (80 - lake.temp); // based on Penman formula, [1-11]
|
||||
return rn(evaporation * lake.cells);
|
||||
}
|
||||
|
||||
function getLowestShoreCell(lake) {
|
||||
return lake.shoreline.sort((a, b) => heights[a] - heights[b])[0];
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupLakeData = function () {
|
||||
for (const feature of pack.features) {
|
||||
if (feature.type !== "lake") continue;
|
||||
|
|
@ -111,23 +100,10 @@ window.Lakes = (function () {
|
|||
}
|
||||
};
|
||||
|
||||
const defineGroup = function () {
|
||||
for (const feature of pack.features) {
|
||||
if (feature.type !== "lake") continue;
|
||||
const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node();
|
||||
if (!lakeEl) continue;
|
||||
|
||||
feature.group = getGroup(feature);
|
||||
document.getElementById(feature.group).appendChild(lakeEl);
|
||||
}
|
||||
};
|
||||
|
||||
const generateName = function () {
|
||||
Math.random = aleaPRNG(seed);
|
||||
for (const feature of pack.features) {
|
||||
if (feature.type !== "lake") continue;
|
||||
feature.name = getName(feature);
|
||||
}
|
||||
const getHeight = function (feature) {
|
||||
const heights = pack.cells.h;
|
||||
const minShoreHeight = d3.min(feature.shoreline.map(cellId => heights[cellId])) || 20;
|
||||
return rn(minShoreHeight - LAKE_ELEVATION_DELTA, 2);
|
||||
};
|
||||
|
||||
const getName = function (feature) {
|
||||
|
|
@ -136,19 +112,5 @@ window.Lakes = (function () {
|
|||
return Names.getCulture(culture);
|
||||
};
|
||||
|
||||
function getGroup(feature) {
|
||||
if (feature.temp < -3) return "frozen";
|
||||
if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 10 === 0) return "lava";
|
||||
|
||||
if (!feature.inlets && !feature.outlet) {
|
||||
if (feature.evaporation > feature.flux * 4) return "dry";
|
||||
if (feature.cells < 3 && feature.firstCell % 10 === 0) return "sinkhole";
|
||||
}
|
||||
|
||||
if (!feature.outlet && feature.evaporation > feature.flux) return "salt";
|
||||
|
||||
return "freshwater";
|
||||
}
|
||||
|
||||
return {setClimateData, cleanupLakeData, prepareLakeData, defineGroup, generateName, getName, getShoreline};
|
||||
return {defineClimateData, cleanupLakeData, detectCloseLakes, getHeight, getName};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,119 +1,10 @@
|
|||
"use strict";
|
||||
|
||||
function drawCoastline() {
|
||||
TIME && console.time("drawCoastline");
|
||||
|
||||
const {cells, vertices, features} = pack;
|
||||
const n = cells.i.length;
|
||||
|
||||
const used = new Uint8Array(features.length); // store connected features
|
||||
|
||||
const landMask = defs.select("#land");
|
||||
const waterMask = defs.select("#water");
|
||||
lineGen.curve(d3.curveBasisClosed);
|
||||
|
||||
for (const i of cells.i) {
|
||||
const startFromEdge = !i && cells.h[i] >= 20;
|
||||
if (!startFromEdge && cells.t[i] !== -1 && cells.t[i] !== 1) continue; // non-edge cell
|
||||
|
||||
const f = cells.f[i];
|
||||
if (used[f]) continue; // already connected
|
||||
if (features[f].type === "ocean") continue; // ocean cell
|
||||
|
||||
const type = features[f].type === "lake" ? 1 : -1; // type value to search for
|
||||
const ofSameType = cellId => cells.t[cellId] === type || cellId >= n;
|
||||
|
||||
const startingVertex = findStart(i, type);
|
||||
if (startingVertex === -1) continue; // cannot start here
|
||||
|
||||
let vchain = connectVertices({vertices, startingVertex, ofSameType});
|
||||
if (features[f].type === "lake") relax(vchain, 1.2);
|
||||
used[f] = 1;
|
||||
|
||||
let points = clipPoly(
|
||||
vchain.map(v => vertices.p[v]),
|
||||
1
|
||||
);
|
||||
|
||||
const area = d3.polygonArea(points); // area with lakes/islands
|
||||
if (area > 0 && features[f].type === "lake") {
|
||||
points = points.reverse();
|
||||
vchain = vchain.reverse();
|
||||
}
|
||||
|
||||
features[f].area = Math.abs(area);
|
||||
features[f].vertices = vchain;
|
||||
|
||||
const path = round(lineGen(points));
|
||||
|
||||
if (features[f].type === "lake") {
|
||||
landMask
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "black")
|
||||
.attr("id", "land_" + f);
|
||||
lakes
|
||||
.select("#freshwater")
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("id", "lake_" + f)
|
||||
.attr("data-f", f); // draw the lake
|
||||
} else {
|
||||
landMask
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "white")
|
||||
.attr("id", "land_" + f);
|
||||
waterMask
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "black")
|
||||
.attr("id", "water_" + f);
|
||||
const g = features[f].group === "lake_island" ? "lake_island" : "sea_island";
|
||||
coastline
|
||||
.select("#" + g)
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("id", "island_" + f)
|
||||
.attr("data-f", f); // draw the coastline
|
||||
}
|
||||
}
|
||||
|
||||
// find cell vertex to start path detection
|
||||
function findStart(i, t) {
|
||||
if (t === -1 && cells.b[i]) return cells.v[i].find(v => vertices.c[v].some(c => c >= n)); // map border cell
|
||||
const filtered = cells.c[i].filter(c => cells.t[c] === t);
|
||||
const index = cells.c[i].indexOf(d3.min(filtered));
|
||||
return index === -1 ? index : cells.v[i][index];
|
||||
}
|
||||
|
||||
// move vertices that are too close to already added ones
|
||||
function relax(vchain, r) {
|
||||
const p = vertices.p,
|
||||
tree = d3.quadtree();
|
||||
|
||||
for (let i = 0; i < vchain.length; i++) {
|
||||
const v = vchain[i];
|
||||
let [x, y] = [p[v][0], p[v][1]];
|
||||
if (i && vchain[i + 1] && tree.find(x, y, r) !== undefined) {
|
||||
const v1 = vchain[i - 1],
|
||||
v2 = vchain[i + 1];
|
||||
const [x1, y1] = [p[v1][0], p[v1][1]];
|
||||
const [x2, y2] = [p[v2][0], p[v2][1]];
|
||||
[x, y] = [(x1 + x2) / 2, (y1 + y2) / 2];
|
||||
p[v] = [x, y];
|
||||
}
|
||||
tree.add([x, y]);
|
||||
}
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("drawCoastline");
|
||||
}
|
||||
|
||||
function drawFeatures() {
|
||||
TIME && console.time("drawFeatures");
|
||||
const {vertices, features} = pack;
|
||||
|
||||
const featurePaths = defs.select("#featurePaths");
|
||||
const landMask = defs.select("#land");
|
||||
const waterMask = defs.select("#water");
|
||||
const lineGen = d3.line().curve(d3.curveBasisClosed);
|
||||
|
|
@ -126,37 +17,39 @@ function drawFeatures() {
|
|||
const clippedPoints = clipPoly(simplifiedPoints, 1);
|
||||
const path = round(lineGen(clippedPoints));
|
||||
|
||||
featurePaths
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("id", "feature_" + feature.i)
|
||||
.attr("data-f", feature.i);
|
||||
|
||||
if (feature.type === "lake") {
|
||||
landMask
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "black")
|
||||
.attr("id", "land_" + feature.i);
|
||||
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i)
|
||||
.attr("fill", "black");
|
||||
lakes
|
||||
.select(`#${feature.group}`)
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("id", "lake_" + feature.i)
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i);
|
||||
} else {
|
||||
landMask
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "white")
|
||||
.attr("id", "land_" + feature.i);
|
||||
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i)
|
||||
.attr("fill", "white");
|
||||
waterMask
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "black")
|
||||
.attr("id", "water_" + feature.i);
|
||||
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i)
|
||||
.attr("fill", "black");
|
||||
const coastlineGroup = feature.group === "lake_island" ? "#lake_island" : "#sea_island";
|
||||
coastline
|
||||
.select(`#${feature.group}`)
|
||||
.append("path")
|
||||
.attr("d", path)
|
||||
.attr("id", "island_" + feature.i)
|
||||
.select(coastlineGroup)
|
||||
.append("use")
|
||||
.attr("href", "#feature_" + feature.i)
|
||||
.attr("data-f", feature.i);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ window.Rivers = (function () {
|
|||
|
||||
const riversData = {}; // rivers data
|
||||
const riverParents = {};
|
||||
|
||||
const addCellToRiver = function (cell, river) {
|
||||
if (!riversData[river]) riversData[river] = [cell];
|
||||
else riversData[river].push(cell);
|
||||
|
|
@ -19,7 +20,7 @@ window.Rivers = (function () {
|
|||
let riverNext = 1; // first river id is 1
|
||||
|
||||
const h = alterHeights();
|
||||
Lakes.prepareLakeData(h);
|
||||
Lakes.detectCloseLakes(h);
|
||||
resolveDepressions(h);
|
||||
drainWater();
|
||||
defineRivers();
|
||||
|
|
@ -39,9 +40,8 @@ window.Rivers = (function () {
|
|||
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
|
||||
|
||||
const prec = grid.cells.prec;
|
||||
const area = pack.cells.area;
|
||||
const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]);
|
||||
const lakeOutCells = Lakes.setClimateData(h);
|
||||
const lakeOutCells = Lakes.defineClimateData(h);
|
||||
|
||||
land.forEach(function (i) {
|
||||
cells.fl[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ window.Submap = (function () {
|
|||
// remove misclassified cells
|
||||
stage("Define coastline");
|
||||
Features.markupPack();
|
||||
drawCoastline();
|
||||
createDefaultRuler();
|
||||
|
||||
// Packed Graph
|
||||
|
|
@ -203,7 +202,6 @@ window.Submap = (function () {
|
|||
|
||||
stage("Regenerating river network");
|
||||
Rivers.generate();
|
||||
Lakes.defineGroup();
|
||||
|
||||
// biome calculation based on (resampled) grid.cells.temp and prec
|
||||
// it's safe to recalculate.
|
||||
|
|
@ -270,7 +268,7 @@ window.Submap = (function () {
|
|||
drawStateLabels();
|
||||
|
||||
Rivers.specify();
|
||||
Lakes.generateName();
|
||||
Features.specify();
|
||||
|
||||
stage("Porting military");
|
||||
for (const s of pack.states) {
|
||||
|
|
|
|||
|
|
@ -225,7 +225,6 @@ function editHeightmap(options) {
|
|||
generatePrecipitation();
|
||||
reGraph();
|
||||
Features.markupPack();
|
||||
drawCoastline();
|
||||
|
||||
Rivers.generate(erosionAllowed);
|
||||
|
||||
|
|
@ -237,7 +236,6 @@ function editHeightmap(options) {
|
|||
}
|
||||
}
|
||||
|
||||
Lakes.defineGroup();
|
||||
Biomes.define();
|
||||
rankCells();
|
||||
|
||||
|
|
@ -255,7 +253,7 @@ function editHeightmap(options) {
|
|||
drawStateLabels();
|
||||
|
||||
Rivers.specify();
|
||||
Lakes.generateName();
|
||||
Features.specify();
|
||||
|
||||
Military.generate();
|
||||
Markers.generate();
|
||||
|
|
@ -343,7 +341,6 @@ function editHeightmap(options) {
|
|||
generatePrecipitation();
|
||||
reGraph();
|
||||
Features.markupPack();
|
||||
drawCoastline();
|
||||
|
||||
if (erosionAllowed) Rivers.generate(true);
|
||||
|
||||
|
|
|
|||
|
|
@ -92,28 +92,23 @@ function restoreCustomPresets() {
|
|||
}
|
||||
|
||||
// run on map generation
|
||||
function applyPreset() {
|
||||
function applyLayersPreset() {
|
||||
const preset = localStorage.getItem("preset") || byId("layersPreset").value;
|
||||
changePreset(preset);
|
||||
changeLayersPreset(preset);
|
||||
}
|
||||
|
||||
// toggle layers on preset change
|
||||
function changePreset(preset) {
|
||||
function changeLayersPreset(preset) {
|
||||
const layers = presets[preset]; // layers to be turned on
|
||||
document
|
||||
.getElementById("mapLayers")
|
||||
.querySelectorAll("li")
|
||||
.forEach(function (e) {
|
||||
if (layers.includes(e.id) && !layerIsOn(e.id)) e.click();
|
||||
else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click();
|
||||
});
|
||||
layersPreset.value = preset;
|
||||
localStorage.setItem("preset", preset);
|
||||
|
||||
const isDefault = getDefaultPresets()[preset];
|
||||
removePresetButton.style.display = isDefault ? "none" : "inline-block";
|
||||
savePresetButton.style.display = "none";
|
||||
if (byId("canvas3d")) setTimeout(ThreeD.update(), 400);
|
||||
byId("removePresetButton").style.display = isDefault ? "none" : "inline-block";
|
||||
byId("savePresetButton").style.display = "none";
|
||||
|
||||
document.querySelectorAll("#mapLayers > li").forEach(e => (e.className = layers.includes(e.id) ? null : "buttonoff"));
|
||||
drawLayers();
|
||||
if (byId("canvas3d")) setTimeout(() => ThreeD.update(), 400);
|
||||
}
|
||||
|
||||
function savePreset() {
|
||||
|
|
@ -161,8 +156,9 @@ function getCurrentPreset() {
|
|||
savePresetButton.style.display = "inline-block";
|
||||
}
|
||||
|
||||
// run on map regeneration
|
||||
function restoreLayers() {
|
||||
// run on each map generation
|
||||
function drawLayers() {
|
||||
drawFeatures();
|
||||
if (layerIsOn("toggleTexture")) drawTexture();
|
||||
if (layerIsOn("toggleHeight")) drawHeightmap();
|
||||
if (layerIsOn("toggleCells")) drawCells();
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ window.UISubmap = (function () {
|
|||
|
||||
oldstate = null; // destroy old state to free memory
|
||||
|
||||
restoreLayers();
|
||||
drawLayers();
|
||||
if (ThreeD.options.isOn) ThreeD.redraw();
|
||||
if ($("#worldConfigurator").is(":visible")) editWorld();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ function regenerateRoutes() {
|
|||
|
||||
function regenerateRivers() {
|
||||
Rivers.generate();
|
||||
Lakes.defineGroup();
|
||||
Rivers.specify();
|
||||
Features.specify();
|
||||
if (layerIsOn("toggleRivers")) drawRivers();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,10 +86,10 @@ function editWorld() {
|
|||
generatePrecipitation();
|
||||
const heights = new Uint8Array(pack.cells.h);
|
||||
Rivers.generate();
|
||||
Lakes.defineGroup();
|
||||
Rivers.specify();
|
||||
pack.cells.h = new Float32Array(heights);
|
||||
Biomes.define();
|
||||
Features.specify();
|
||||
|
||||
if (layerIsOn("toggleTemperature")) drawTemperature();
|
||||
if (layerIsOn("togglePrecipitation")) drawPrecipitation();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
// clip polygon by graph bbox
|
||||
function clipPoly(points, secure = 0) {
|
||||
if (points.length < 2) return points;
|
||||
return polygonclip(points, [0, 0, graphWidth, graphHeight], secure);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,8 +110,7 @@ function getVertexPath(cellsArray) {
|
|||
if (onborderCell === undefined) continue;
|
||||
|
||||
const feature = pack.features[cells.f[onborderCell]];
|
||||
if (feature.type === "lake") {
|
||||
if (!feature.shoreline) Lakes.getShoreline(feature);
|
||||
if (feature.type === "lake" && feature.shoreline) {
|
||||
if (feature.shoreline.every(ofSameType)) continue; // inner lake
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue