From f08832b60db9041d3a1e6bd3f116f755cdd2b013 Mon Sep 17 00:00:00 2001 From: Fabio Pedretti Date: Tue, 13 Oct 2026 08:47:15 +0200 Subject: [PATCH] Search the closest water point among boundary points only Area.getClosestPointTo scans every point of the water search area, and it was called up to count * retryFactor times in placeDocks. The nearest point of a set to a point outside of it is always a boundary point, so compute the boundary once and scan only that. The logic lives in two small helpers, getBoundaryPoints (Uint8Array mask based) and getClosestPointInList, so it can be reused and read apart from placeDocks. getBoundaryPoints returns the original point objects, while getClosestPointInList returns a clone of the point it finds, so callers cannot mutate the points stored in the list. --- .../maps/random/rmgen-common/gaia_entities.js | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js b/binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js index 8dc4520c94..733ce14af3 100644 --- a/binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js +++ b/binaries/data/mods/public/maps/random/rmgen-common/gaia_entities.js @@ -262,6 +262,53 @@ function createDecoration(objects, counts, constraint) 5); } +/** + * Returns the points of the given list that have at least one 4-neighbour outside of the list. + * The closest point of a set to a point outside of it always lies on the set's boundary, + * since a point with all four neighbours in the set always has a strictly closer neighbour. + * + * @param {Vector2D[]} points - Integer tile positions within the map. + * @param {number} mapSize - The side length of the map in tiles. + * @returns {Vector2D[]} + */ +function getBoundaryPoints(points, mapSize) +{ + const inSet = new Uint8Array(mapSize * mapSize); + for (const p of points) + inSet[p.x * mapSize + p.y] = 1; + + const contains = (x, y) => x >= 0 && y >= 0 && x < mapSize && y < mapSize && inSet[x * mapSize + y] === 1; + + return points.filter(p => + !contains(p.x - 1, p.y) || !contains(p.x + 1, p.y) || + !contains(p.x, p.y - 1) || !contains(p.x, p.y + 1)); +} + +/** + * Returns a copy of the point of the given list that is closest to the given position. + * + * @param {Vector2D[]} points - Must not be empty. + * @param {Vector2D} position + * @returns {Vector2D} + */ +function getClosestPointInList(points, position) +{ + let best; + let bestDistSquared = Infinity; + for (const p of points) + { + const dx = p.x - position.x; + const dy = p.y - position.y; + const distSquared = dx * dx + dy * dy; + if (distSquared < bestDistSquared) + { + bestDistSquared = distSquared; + best = p; + } + } + return best.clone(); +} + /** * Places docks in situations where the location of land and water is not known in advance. * Do determine the position, it picks a random point on the land, find the closest, significantly large body of water, @@ -288,7 +335,8 @@ function placeDocks(template, playerID, count, tileClassWater, tileClassDock, he undefined, stayClasses(tileClassWater, 20)); - if (!areaSearchEnd || !areaSearchEnd.getPoints().length) + const endPoints = areaSearchEnd?.getPoints(); + if (!endPoints?.length) return; g_Map.log("Marking dock search start area"); @@ -301,6 +349,8 @@ function placeDocks(template, playerID, count, tileClassWater, tileClassDock, he if (!startPoints?.length) return; + const boundaryPoints = getBoundaryPoints(endPoints, g_Map.getSize()); + // TODO: computing the exact intersection with the waterplane would both not require us to pass reasonable heights and be more precise const constraint = new AndConstraint(constraints); g_Map.log("Placing docks"); @@ -308,7 +358,7 @@ function placeDocks(template, playerID, count, tileClassWater, tileClassDock, he for (let tries = 0; tries < retryFactor; ++tries) { const positionLand = pickRandom(startPoints); - const positionWaterLarge = areaSearchEnd.getClosestPointTo(positionLand); + const positionWaterLarge = getClosestPointInList(boundaryPoints, positionLand); const positionDock = findLocationInDirectionBasedOnHeight(positionWaterLarge, positionLand, heightMin, heightMax, offset); if (!positionDock) continue;