From c1904a725a68f4c02222fcdc4e54fadd6ba27afb Mon Sep 17 00:00:00 2001 From: elexis Date: Mon, 12 Feb 2018 01:22:10 +0000 Subject: [PATCH] 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. --- .../public/maps/random/caledonian_meadows.js | 51 ++++++++---------- .../public/maps/random/heightmap/heightmap.js | 52 +------------------ .../mods/public/maps/random/rmgen/painter.js | 15 ++++++ .../mods/public/maps/random/schwarzwald.js | 7 +-- .../data/mods/public/maps/random/wild_lake.js | 32 +++++++----- 5 files changed, 60 insertions(+), 97 deletions(-) diff --git a/binaries/data/mods/public/maps/random/caledonian_meadows.js b/binaries/data/mods/public/maps/random/caledonian_meadows.js index d306ff72a0..2803aeccbe 100644 --- a/binaries/data/mods/public/maps/random/caledonian_meadows.js +++ b/binaries/data/mods/public/maps/random/caledonian_meadows.js @@ -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) diff --git a/binaries/data/mods/public/maps/random/heightmap/heightmap.js b/binaries/data/mods/public/maps/random/heightmap/heightmap.js index 5d17e50583..f51cba12a9 100644 --- a/binaries/data/mods/public/maps/random/heightmap/heightmap.js +++ b/binaries/data/mods/public/maps/random/heightmap/heightmap.js @@ -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) diff --git a/binaries/data/mods/public/maps/random/rmgen/painter.js b/binaries/data/mods/public/maps/random/rmgen/painter.js index 50e14b3d09..7f0407c57d 100644 --- a/binaries/data/mods/public/maps/random/rmgen/painter.js +++ b/binaries/data/mods/public/maps/random/rmgen/painter.js @@ -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. */ diff --git a/binaries/data/mods/public/maps/random/schwarzwald.js b/binaries/data/mods/public/maps/random/schwarzwald.js index 94c235875c..09c17dbf98 100644 --- a/binaries/data/mods/public/maps/random/schwarzwald.js +++ b/binaries/data/mods/public/maps/random/schwarzwald.js @@ -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({ diff --git a/binaries/data/mods/public/maps/random/wild_lake.js b/binaries/data/mods/public/maps/random/wild_lake.js index 8417c42f78..9d4faee81d 100644 --- a/binaries/data/mods/public/maps/random/wild_lake.js +++ b/binaries/data/mods/public/maps/random/wild_lake.js @@ -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();