remove small lakes, use largest body of water as ocean

This commit is contained in:
Evan
2025-03-03 20:00:12 -08:00
parent 0a482e2e6e
commit 84f067d66b
19 changed files with 757 additions and 26304 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+67 -67
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+116 -20588
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+204 -204
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+104 -70
View File
@@ -57,7 +57,6 @@ async function loadTerrainMap(mapName: string): Promise<void> {
.fill(null)
.map(() => Array(img.height).fill(null));
// Iterate through each pixel
for (let x = 0; x < img.width; x++) {
for (let y = 0; y < img.height; y++) {
const color = img.getPixelRGBA(x, y);
@@ -208,6 +207,83 @@ function neighbors(x: number, y: number, map: Terrain[][]): Terrain[] {
return ns;
}
// Improved processOcean function that identifies the largest body of water
function processOcean(map: Terrain[][]) {
const visited = new Set<string>();
const waterBodies: { coords: Coord[]; size: number }[] = [];
// Find all distinct water bodies
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
if (map[x][y].type === TerrainType.Water) {
const key = `${x},${y}`;
if (visited.has(key)) continue;
// Find all connected water tiles
const waterBody: Coord[] = [];
const queue: Coord[] = [{ x, y }];
while (queue.length > 0) {
const coord = queue.shift()!;
const currentKey = `${coord.x},${coord.y}`;
if (visited.has(currentKey)) continue;
visited.add(currentKey);
if (map[coord.x][coord.y].type === TerrainType.Water) {
waterBody.push(coord);
// Check all four directions
for (const [dx, dy] of [
[-1, 0],
[1, 0],
[0, -1],
[0, 1],
]) {
const newX = coord.x + dx;
const newY = coord.y + dy;
if (
newX >= 0 &&
newX < map.length &&
newY >= 0 &&
newY < map[0].length
) {
queue.push({ x: newX, y: newY });
}
}
}
}
// Store this water body if it has any tiles
if (waterBody.length > 0) {
waterBodies.push({
coords: waterBody,
size: waterBody.length,
});
}
}
}
}
// Sort water bodies by size (largest first)
waterBodies.sort((a, b) => b.size - a.size);
// Mark the largest water body as ocean
if (waterBodies.length > 0) {
const largestWaterBody = waterBodies[0];
// Mark all tiles in the largest water body as ocean
for (const coord of largestWaterBody.coords) {
map[coord.x][coord.y].ocean = true;
}
console.log(`Identified ocean with ${largestWaterBody.size} water tiles`);
} else {
console.log("No water bodies found in the map");
}
}
function packTerrain(mapName: string, map: Terrain[][]): Uint8Array {
const width = map.length;
const height = map[0].length;
@@ -249,58 +325,14 @@ function packTerrain(mapName: string, map: Terrain[][]): Uint8Array {
return packedData;
}
function processOcean(map: Terrain[][]) {
const queue: Coord[] = [];
if (map[0][0].type == TerrainType.Water) {
queue.push({ x: 0, y: 0 });
} else if (map[map.length - 1][map[0].length - 1].type == TerrainType.Water) {
queue.push({ x: map.length - 1, y: map[0].length - 1 });
} else {
queue.push({ x: 0, y: map[0].length - 1 });
}
const visited = new Set<string>();
while (queue.length > 0) {
const coord = queue.shift()!;
const key = `${coord.x},${coord.y}`;
if (visited.has(key)) continue;
visited.add(key);
const terrain = map[coord.x][coord.y];
if (terrain.type === TerrainType.Water) {
terrain.ocean = true;
// Check neighbors
for (const [dx, dy] of [
[-1, 0],
[1, 0],
[0, -1],
[0, 1],
]) {
const newX = coord.x + dx;
const newY = coord.y + dy;
if (
newX >= 0 &&
newX < map.length &&
newY >= 0 &&
newY < map[0].length
) {
queue.push({ x: newX, y: newY });
}
}
}
}
}
function getIsland(
function getArea(
map: Terrain[][],
x: number,
y: number,
visited: Set<string>,
targetType: TerrainType,
) {
let island = [];
let area = [];
let next = [[x, y]];
while (next.length) {
const [x, y] = next.pop();
@@ -310,24 +342,22 @@ function getIsland(
x >= map.length ||
y < 0 ||
y >= map[0].length ||
x < 0 ||
x >= map.length ||
visited.has(key)
)
continue;
if (map[x][y].type == TerrainType.Land) {
if (map[x][y].type === targetType) {
next.push([x + 1, y]);
next.push([x - 1, y]);
next.push([x, y + 1]);
next.push([x, y - 1]);
}
island.push([x, y]);
area.push([x, y]);
visited.add(key);
}
return island;
return area;
}
function removeSmallIslands(map: Terrain[][]) {
@@ -335,14 +365,11 @@ function removeSmallIslands(map: Terrain[][]) {
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
if (map[x][y].type == TerrainType.Land) {
if (map[x][y].type === TerrainType.Land) {
const key = `${x},${y}`;
// PERF: If getIsland already visited that coordinates then it's
// useless to go over it again.
if (visited.has(key)) continue;
const island = getIsland(map, x, y, visited);
const island = getArea(map, x, y, visited, TerrainType.Land);
if (island.length < min_island_size) {
island.forEach((pos) => {
const x = pos[0];
@@ -357,23 +384,30 @@ function removeSmallIslands(map: Terrain[][]) {
}
function removeSmallLakes(mapName: string, map: Terrain[][]) {
console.log(`${mapName}: removing lakes ${map.length}, ${map[0].length}`);
const visited = new Set<string>();
const min_lake_size = 30; // Using same size threshold as islands
console.log(
`${mapName}: removing small lakes ${map.length}, ${map[0].length}`,
);
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
if (map[x][y].type != TerrainType.Water) {
continue;
}
let allLand = true;
for (const neighbor of neighbors(x, y, map)) {
if (neighbor.type != TerrainType.Land) {
allLand = false;
if (map[x][y].type === TerrainType.Water && !map[x][y].ocean) {
const key = `${x},${y}`;
if (visited.has(key)) continue;
const lake = getArea(map, x, y, visited, TerrainType.Water);
if (lake.length < min_lake_size) {
lake.forEach((pos) => {
const x = pos[0];
const y = pos[1];
map[x][y].type = TerrainType.Land;
map[x][y].magnitude = 0;
map[x][y].ocean = false;
});
}
}
if (allLand) {
map[x][y].type = TerrainType.Land;
map[x][y].magnitude = 0;
}
}
}
}