Implement ElevationBlendingPainter which interpolates the height of the given area with the desired height.

Primary use case, as represented on Caledonian Meadows, is creating a
path through impassable area, mountains or water, refs #4952.
Supersedes placeRandomPathToHeight from the heightmap library, refs
#3764.
The painter has the advantage that it can be applied to arbitrary areas
with arbitrary forms of smoothing (or no smoothing).
Replace the rectangularSmoothToHeight calls that only smooth an area
with a SmoothElevationPainter call.
Replace placeRandomPathToHeight which is a copy of the the
RandomPathPlacer from cbcbd19e0b, refs #4805.

This was SVN commit r21182.
This commit is contained in:
elexis
2018-02-12 01:22:10 +00:00
parent c9a7817752
commit c1904a725a
5 changed files with 60 additions and 97 deletions
@@ -9,30 +9,6 @@ var oGroveEntities = ["structures/gaul_outpost", "gaia/flora_tree_oak_new"];
var g_Map = new RandomMap(0, "whiteness");
/**
* Drags a path to a target height smoothing it at the edges and return some points along the path.
*/
function placeRandomPathToHeight(start, target, targetHeight, tileClass, texture, width, distance, strength, heightmap)
{
let painters = [new TerrainPainter(texture)];
if (tileClass)
painters.push(new TileClassPainter(tileClass));
let position = start.clone();
while (position.distanceTo(target) >= distance / 2)
{
rectangularSmoothToHeight(position, width * 3, width * 3, targetHeight, strength, heightmap);
createArea(
new ClumpPlacer(diskArea(width), 1, 1, Infinity, position),
painters);
position.add(new Vector2D(distance, 0).rotate(
-getAngle(position.x, position.y, target.x, target.y) - randFloat(-1, 1) * Math.PI / 2));
}
}
/**
* Design resource spots
*/
@@ -318,15 +294,29 @@ myBiome.push({ // 10 Hilltop
let [playerIDs, playerPosition] = sortPlayersByLocation(getStartLocationsByHeightmap({ "min": heighLimits[4], "max": heighLimits[5] }, 1000, 30));
Engine.SetProgress(30);
g_Map.log("Smooth player locations");
for (let p = 0; p < playerIDs.length; ++p)
rectangularSmoothToHeight(playerPosition[p], 35, 35, playerHeight, 0.7);
g_Map.log("Smoothing player locations");
for (let position of playerPosition)
createArea(
new ClumpPlacer(diskArea(35), 1, 1, Infinity, position),
new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 35));
g_Map.log("Creating paths");
let tchm = getTileCenteredHeightmap();
g_Map.log("Creating paths between players");
let clPath = g_Map.createTileClass();
for (let i = 0; i < playerPosition.length; ++i)
placeRandomPathToHeight(playerPosition[i], playerPosition[(i + 1) % playerPosition.length], playerHeight, clPath, tPath, 4, 4, 0.08, g_Map.height);
createArea(
new RandomPathPlacer(playerPosition[i], playerPosition[(i + 1) % playerPosition.length], 4, 2, false),
[
new TerrainPainter(tPath),
new ElevationBlendingPainter(playerHeight, 0.4),
new TileClassPainter(clPath)
]);
g_Map.log("Smoothing paths");
createArea(
new MapBoundsPlacer(),
new SmoothingPainter(5, 1, 1),
new NearTileClassConstraint(clPath, 5));
Engine.SetProgress(45);
g_Map.log("Determining resource locations");
@@ -339,6 +329,7 @@ Engine.SetProgress(55);
/**
* Divide tiles in areas by height and avoid paths
*/
let tchm = getTileCenteredHeightmap();
let areas = heighLimits.map(heightLimit => []);
for (let x = 0; x < tchm.length; ++x)
for (let y = 0; y < tchm[0].length; ++y)
@@ -120,6 +120,8 @@ function getStartLocationsByHeightmap(heightRange, maxTries = 1000, minDistToBor
*/
function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, initialHeightmap = undefined, smoothness = 0.5, heightmap = g_Map.height)
{
g_Map.log("Generating map using the diamond-square algorithm");
initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]);
let heightRange = maxHeight - minHeight;
if (heightRange <= 0)
@@ -196,56 +198,6 @@ function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEI
heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]];
}
/**
* Pushes a rectangular area towards a given height smoothing it into the original terrain
* @note The window function to determine the smooth is not exactly a gaussian to ensure smooth edges
* @param {Vector2D} center - The x and y coordinates of the center point (rounded in this function)
* @param {float} [dx] - Distance from the center in x direction the rectangle ends (half width, rounded in this function)
* @param {float} [dy] - Distance from the center in y direction the rectangle ends (half depth, rounded in this function)
* @param {float} [targetHeight] - Height the center of the rectangle will be pushed to
* @param {float} [strength=1] - How strong the height is pushed: 0 means not at all, 1 means the center will be pushed to the target height
* @param {array} [heightmap=g_Map.height] - The heightmap to be manipulated
* @todo Make the window function an argument and maybe add some
*/
function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength = 0.8, heightmap = g_Map.height)
{
let x = Math.round(center.x);
let y = Math.round(center.y);
dx = Math.round(dx);
dy = Math.round(dy);
let heightmapWin = [];
for (let wx = 0; wx < 2 * dx + 1; ++wx)
{
heightmapWin.push([]);
for (let wy = 0; wy < 2 * dy + 1; ++wy)
{
let actualX = x - dx + wx;
let actualY = y - dy + wy;
if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
heightmapWin[wx].push(heightmap[actualX][actualY]);
else
heightmapWin[wx].push(targetHeight);
}
}
for (let wx = 0; wx < 2 * dx + 1; ++wx)
{
for (let wy = 0; wy < 2 * dy + 1; ++wy)
{
let actualX = x - dx + wx;
let actualY = y - dy + wy;
if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
{
// Window function polynomial 2nd degree
let scaleX = 1 - (wx / dx - 1) * (wx / dx - 1);
let scaleY = 1 - (wy / dy - 1) * (wy / dy - 1);
heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]);
}
}
}
}
/**
* Meant to place e.g. resource spots within a height range
* @param {array} [heightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float)
@@ -211,6 +211,21 @@ RandomElevationPainter.prototype.paint = function(area)
}
};
/**
* The ElevationBlendingPainter sets the elevation of each point of the given area to the weighted targetHeight.
*/
function ElevationBlendingPainter(targetHeight, strength)
{
this.targetHeight = targetHeight;
this.strength = strength;
}
ElevationBlendingPainter.prototype.paint = function(area)
{
for (let point of area.points)
g_Map.setHeight(point, this.strength * this.targetHeight + (1 - this.strength) * g_Map.getHeight(point));
};
/**
* Absolute height change.
*/
@@ -136,8 +136,7 @@ var heighLimits = [
heightSeaGroundAdjusted + 7/8 * (heightRange.max - heightSeaGroundAdjusted), // 9 Upper forest border
heightSeaGroundAdjusted + (heightRange.max - heightSeaGroundAdjusted)]; // 10 Hilltop
var playerHeight = (heighLimits[4] + heighLimits[5]) / 2;
g_Map.log("Locating and smoothing playerbases");
for (let i = 0; i < numPlayers; ++i)
{
playerPosition[i] = Vector2D.add(
@@ -145,7 +144,9 @@ for (let i = 0; i < numPlayers; ++i)
new Vector2D(randFloat(minPlayerRadius, maxPlayerRadius), 0).rotate(
-((playerAngleStart + i * playerAngleAddAvrg + randFloat(0, playerAngleMaxOff)) % (2 * Math.PI)))).round();
rectangularSmoothToHeight(playerPosition[i], 20, 20, playerHeight, 0.8);
createArea(
new ClumpPlacer(diskArea(20), 0.8, 0.8, Infinity, playerPosition[i]),
new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(playerPosition[i]), 20));
}
placePlayerBases({
@@ -466,15 +466,16 @@ if (g_Map.size >= 384)
setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, 0.8);
// Apply simple erosion
g_Map.log("Eroding map");
for (let i = 0; i < 5; ++i)
splashErodeMap(0.1);
g_Map.log("Smoothing map");
createArea(
new MapBoundsPlacer(),
new SmoothingPainter(1, 0.8, 1));
// Final rescale
g_Map.log("Rescaling map");
rescaleHeightmap(heightRange.min, heightRange.max);
Engine.SetProgress(25);
@@ -499,16 +500,13 @@ let playerHeight = (playerHeightRange.min + playerHeightRange.max) / 2; // Avera
g_Map.log("Chosing starting locations");
let [playerIDs, playerPosition] = sortPlayersByLocation(getStartLocationsByHeightmap(playerHeightRange, 1000, 30));
Engine.SetProgress(30);
g_Map.log("Smoothing starting locations before height calculation");
for (let position of playerPosition)
createArea(
new ClumpPlacer(diskArea(20), 0.8, 0.8, Infinity, position),
new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(position), 20));
/**
* Smooth Start Locations before height region calculation
*/
let playerBaseRadius = 35;
if (g_Map.size < 256)
playerBaseRadius = 25;
for (let p = 0; p < playerIDs.length; ++p)
rectangularSmoothToHeight(playerPosition[p], playerBaseRadius, playerBaseRadius, playerHeight, 0.7);
Engine.SetProgress(30);
/**
* Calculate tile centered height map after start position smoothing but before placing paths
@@ -607,6 +605,7 @@ let mercenaryCamps = isNomad() ? 0 : Math.ceil(g_Map.size / 256);
g_Map.log("Placing at most " + mercenaryCamps + " mercenary camps");
for (let i = 0; i < resourceSpots.length; ++i)
{
let radius;
let choice = i % 5;
if (choice == 0)
placeMine(resourceSpots[i], g_Gaia.stoneLarge);
@@ -617,22 +616,27 @@ for (let i = 0; i < resourceSpots.length; ++i)
if (choice == 3)
{
placeCamp(resourceSpots[i]);
rectangularSmoothToHeight(resourceSpots[i], 5, 5, g_Map.getHeight(resourceSpots[i]) - 10, 0.5);
radius = 5;
}
if (choice == 4)
{
if (mercenaryCamps)
{
placeStartingEntities(resourceSpots[i], 0, mercenaryCampGuards[currentBiome()]);
rectangularSmoothToHeight(resourceSpots[i], 15, 15, g_Map.getHeight(resourceSpots[i]), 0.5);
radius = 15;
--mercenaryCamps;
}
else
{
placeCustomFortress(resourceSpots[i], pickRandom(fences), "other", 0, randomAngle());
rectangularSmoothToHeight(resourceSpots[i], 10, 10, g_Map.getHeight(resourceSpots[i]), 0.5);
radius = 10;
}
}
if (radius)
createArea(
new ClumpPlacer(diskArea(radius), 1, 1, Infinity, resourceSpots[i]),
new SmoothElevationPainter(ELEVATION_SET, g_Map.getHeight(resourceSpots[i]), radius));
}
g_Map.ExportMap();