From 02fe3ef3e32484fa68fa51d5ef47968b29cd8eb2 Mon Sep 17 00:00:00 2001 From: elexis Date: Sun, 11 Feb 2018 14:56:55 +0000 Subject: [PATCH] Implement SmoothingPainter for random maps, fixes #5027. This allows only specific regions of the map to be smoothened, especially important on imported digital elevation models. It uses the Inverse Distance Weighting / Shepard's method as mentioned by Imarok and formerly implemented in the Pyrenean Sierra map by wraitii in a796800bb1. Supersedes the globalSmoothHeightmap function in FeXoRs heightmap library, refs #3764. Drop the heightmap argument to be consistent with the other painters. If painting on arbitrary heightmaps is wished, the createArea mechanism, all Placers, Painters, Constraints and Areas can and should support that. Update the HeightmapPainter from 6319647795 to not break if TILE_CENTERED_HEIGHT_MAP is enabled (i.e. numVertices = numTiles), refs #5018. Use that mode on Mediterranean and Red Sea. Drop the disabling of bicubic interpolation in the HeightmapPainter instead of extending it to this feature. Inevitable smoothing performance improvement for Belgian Uplands (from 45 to 15 seconds per call), even if it implies a somewhat different outcome, refs #5011. This was SVN commit r21175. --- .../data/mods/public/maps/random/bahrain.js | 5 +- .../public/maps/random/belgian_uplands.js | 15 +-- .../public/maps/random/heightmap/heightmap.js | 26 ----- .../public/maps/random/island_stronghold.js | 12 +- .../data/mods/public/maps/random/marmara.js | 5 +- .../mods/public/maps/random/mediterranean.js | 7 +- .../mods/public/maps/random/ngorongoro.js | 5 +- .../data/mods/public/maps/random/pompeii.js | 5 +- .../public/maps/random/pyrenean_sierra.js | 56 ++-------- .../data/mods/public/maps/random/ratumacos.js | 5 +- .../data/mods/public/maps/random/red_sea.js | 9 +- .../mods/public/maps/random/rmgen/library.js | 4 + .../mods/public/maps/random/rmgen/math.js | 18 ++- .../mods/public/maps/random/rmgen/painter.js | 103 +++++++++++++----- .../public/maps/random/rmgen/random_map.js | 18 ++- .../mods/public/maps/random/schwarzwald.js | 6 +- .../public/maps/random/scythian_rivulet.js | 1 - .../data/mods/public/maps/random/wild_lake.js | 5 +- 18 files changed, 160 insertions(+), 145 deletions(-) diff --git a/binaries/data/mods/public/maps/random/bahrain.js b/binaries/data/mods/public/maps/random/bahrain.js index ca350a09ad..a6c4e41817 100644 --- a/binaries/data/mods/public/maps/random/bahrain.js +++ b/binaries/data/mods/public/maps/random/bahrain.js @@ -19,7 +19,6 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); -Engine.LoadLibrary("heightmap"); setBiome("generic/desert"); setLandBiome(); @@ -93,7 +92,9 @@ createArea( Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); -globalSmoothHeightmap(scaleByMapSize(0.1, 0.5)); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, scaleByMapSize(0.1, 0.5), 1)); Engine.SetProgress(25); g_Map.log("Marking water"); diff --git a/binaries/data/mods/public/maps/random/belgian_uplands.js b/binaries/data/mods/public/maps/random/belgian_uplands.js index 9d99c08617..0834226d3f 100644 --- a/binaries/data/mods/public/maps/random/belgian_uplands.js +++ b/binaries/data/mods/public/maps/random/belgian_uplands.js @@ -6,27 +6,21 @@ const tPrimary = ["temp_grass", "temp_grass_b", "temp_grass_c", "temp_grass_d", "temp_grass_long_b", "temp_grass_clovers_2", "temp_grass_mossy", "temp_grass_plants"]; const heightLand = 0; - var g_Map = new RandomMap(heightLand, tPrimary); - var numPlayers = getNumPlayers(); - var mapSize = g_Map.getSize(); var mapCenter = g_Map.getCenter(); // Set target min and max height depending on map size to make average stepness the same on all map sizes var heightRange = {"min": MIN_HEIGHT * mapSize / 8192, "max": MAX_HEIGHT * mapSize / 8192}; -// Set average water coverage -var averageWaterCoverage = 1/3; // NOTE: Since errosion is not predictable actual water coverage might differ much with the same value -if (mapSize < 200) // Sink the waterlevel on tiny maps to ensure enough space - averageWaterCoverage = 2/3 * averageWaterCoverage; +// Since erosion is not predictable, actual water coverage can differ much with the same value +var averageWaterCoverage = scaleByMapSize(1/5, 1/3); var heightSeaGround = -MIN_HEIGHT + heightRange.min + averageWaterCoverage * (heightRange.max - heightRange.min); var heightSeaGroundAdjusted = heightSeaGround + MIN_HEIGHT; setWaterHeight(heightSeaGround); -// Prepare terrain texture by height placement var textueByHeight = []; // Deep water @@ -94,8 +88,9 @@ while (true) // More cycles yield bigger structures g_Map.log("Smoothing map"); - for (let i = 0; i < 50 + mapSize/4; ++i) - globalSmoothHeightmap(); + createArea( + new MapBoundsPlacer(), + new SmoothingPainter(2, 1, 20)); g_Map.log("Rescaling map"); rescaleHeightmap(heightRange.min, heightRange.max, g_Map.height); diff --git a/binaries/data/mods/public/maps/random/heightmap/heightmap.js b/binaries/data/mods/public/maps/random/heightmap/heightmap.js index bf79c5521b..e810777e5f 100644 --- a/binaries/data/mods/public/maps/random/heightmap/heightmap.js +++ b/binaries/data/mods/public/maps/random/heightmap/heightmap.js @@ -209,32 +209,6 @@ function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEI heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]]; } -/** - * Smoothens the entire map - * @param {float} [strength=0.8] - How strong the smooth effect should be: 0 means no effect at all, 1 means quite strong, higher values might cause interferences, better apply it multiple times - * @param {array} [heightmap=g_Map.height] - The heightmap to be smoothed - * @param {array} [smoothMap=[[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]] - Array of offsets discribing the neighborhood tiles to smooth the height of a tile to - */ -function globalSmoothHeightmap(strength = 0.8, heightmap = g_Map.height, smoothMap = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]) -{ - let referenceHeightmap = clone(heightmap); - let max_x = heightmap.length; - let max_y = heightmap[0].length; - for (let x = 0; x < max_x; ++x) - { - for (let y = 0; y < max_y; ++y) - { - for (let i = 0; i < smoothMap.length; ++i) - { - let mapX = x + smoothMap[i][0]; - let mapY = y + smoothMap[i][1]; - if (mapX >= 0 && mapX < max_x && mapY >= 0 && mapY < max_y) - heightmap[x][y] += strength / smoothMap.length * (referenceHeightmap[mapX][mapY] - referenceHeightmap[x][y]); - } - } - } -} - /** * 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 diff --git a/binaries/data/mods/public/maps/random/island_stronghold.js b/binaries/data/mods/public/maps/random/island_stronghold.js index 1cfaef1407..affaf241f3 100644 --- a/binaries/data/mods/public/maps/random/island_stronghold.js +++ b/binaries/data/mods/public/maps/random/island_stronghold.js @@ -1,7 +1,6 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); -Engine.LoadLibrary("heightmap"); const g_InitialMineDistance = 14; const g_InitialTrees = 50; @@ -203,8 +202,9 @@ createAreas( Engine.SetProgress(70); g_Map.log("Smoothing heightmap"); -for (let i = 0; i < 5; ++i) - globalSmoothHeightmap(); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, 0.8, 5)); // repaint clLand to compensate for smoothing unPaintTileClassBasedOnHeight(-10, 10, 3, clLand); @@ -249,8 +249,10 @@ createAreas( scaleByMapSize(4, 13) ); -for (let i = 0; i < 3; ++i) - globalSmoothHeightmap(); +g_Map.log("Smoothing heightmap"); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(0.8, 1, 3)); createStragglerTrees( [oTree1, oTree2, oTree4, oTree3], diff --git a/binaries/data/mods/public/maps/random/marmara.js b/binaries/data/mods/public/maps/random/marmara.js index 3e8457e541..759b319eee 100644 --- a/binaries/data/mods/public/maps/random/marmara.js +++ b/binaries/data/mods/public/maps/random/marmara.js @@ -18,7 +18,6 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); -Engine.LoadLibrary("heightmap"); setBiome("generic/mediterranean"); @@ -76,7 +75,9 @@ createArea( Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); -globalSmoothHeightmap(scaleByMapSize(0.1, 0.2)); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, scaleByMapSize(0.1, 0.2), 1)); Engine.SetProgress(25); g_Map.log("Marking water"); diff --git a/binaries/data/mods/public/maps/random/mediterranean.js b/binaries/data/mods/public/maps/random/mediterranean.js index 06e57a69ee..8d4658b882 100644 --- a/binaries/data/mods/public/maps/random/mediterranean.js +++ b/binaries/data/mods/public/maps/random/mediterranean.js @@ -19,7 +19,8 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); -Engine.LoadLibrary("heightmap"); + +TILE_CENTERED_HEIGHT_MAP = true; const tWater = "medit_sand_wet"; const tSnowedRocks = ["alpine_cliff_b", "alpine_cliff_snow"]; @@ -92,7 +93,9 @@ createArea( Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); -globalSmoothHeightmap(scaleByMapSize(0.3, 0.8)); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, scaleByMapSize(0.3, 0.8), 1)); Engine.SetProgress(25); g_Map.log("Marking water"); diff --git a/binaries/data/mods/public/maps/random/ngorongoro.js b/binaries/data/mods/public/maps/random/ngorongoro.js index 33ddfa845d..fbd67b60f6 100644 --- a/binaries/data/mods/public/maps/random/ngorongoro.js +++ b/binaries/data/mods/public/maps/random/ngorongoro.js @@ -19,7 +19,6 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); -Engine.LoadLibrary("heightmap"); setBiome("generic/savanna"); @@ -94,7 +93,9 @@ g_Map.LoadHeightmapImage("ngorongoro.png", 0, heightMax); Engine.SetProgress(15); g_Map.log("Smoothing heightmap"); -globalSmoothHeightmap(scaleByMapSize(0.1, 0.5)); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, scaleByMapSize(0.1, 0.5), 1)); Engine.SetProgress(25); g_Map.log("Marking land"); diff --git a/binaries/data/mods/public/maps/random/pompeii.js b/binaries/data/mods/public/maps/random/pompeii.js index 3452627d8f..aa513e8824 100644 --- a/binaries/data/mods/public/maps/random/pompeii.js +++ b/binaries/data/mods/public/maps/random/pompeii.js @@ -19,7 +19,6 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); -Engine.LoadLibrary("heightmap"); setBiome("generic/mediterranean"); @@ -97,7 +96,9 @@ createArea( Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); -globalSmoothHeightmap(0.8); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, 0.8, 1)); Engine.SetProgress(25); g_Map.log("Marking water"); diff --git a/binaries/data/mods/public/maps/random/pyrenean_sierra.js b/binaries/data/mods/public/maps/random/pyrenean_sierra.js index 3ec5c1a230..0def77cef1 100644 --- a/binaries/data/mods/public/maps/random/pyrenean_sierra.js +++ b/binaries/data/mods/public/maps/random/pyrenean_sierra.js @@ -242,20 +242,6 @@ function createPyreneans() } } -g_Map.log("Smoothing pyreneans"); -for (let ix = 1; ix < mapSize - 1; ++ix) - for (let iz = 1; iz < mapSize - 1; ++iz) - { - let position = new Vector2D(ix, iz); - if (g_Map.validHeight(position) && clPyrenneans.countMembersInRadius(position, 1)) - { - let height = g_Map.getHeight(position); - let index = 1 / (1 + Math.max(0, height / 7)); - g_Map.setHeight(position, height * (1 - index) + g_Map.getAverageHeight(position) * index); - } - } -Engine.SetProgress(48); - g_Map.log("Creating passages"); var passageLocation = 0.35; var passageVec = mountainDirection.perpendicular().mult(passageLength); @@ -278,18 +264,11 @@ for (let passLoc of [passageLocation, 1 - passageLocation]) } Engine.SetProgress(50); -g_Map.log("Smoothing the mountains"); -for (let ix = 1; ix < mapSize - 1; ++ix) - for (let iz = 1; iz < mapSize - 1; ++iz) - { - let position = new Vector2D(ix, iz); - if (g_Map.inMapBounds(position) && clPyrenneans.countMembersInRadius(position, 1)) - { - let heightNeighbor = g_Map.getAverageHeight(position); - let index = 1 / (1 + Math.max(0, (g_Map.getHeight(position) - 10) / 7)); - g_Map.setHeight(position, g_Map.getHeight(position) * (1 - index) + heightNeighbor * index); - } - } +g_Map.log("Smoothing the pyreneans"); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, 0.3, 1), + new NearTileClassConstraint(clPyrenneans, 1)); g_Map.log("Creating oceans"); for (let ocean of distributePointsOnCircle(2, oceanAngle, fractionToTiles(0.48), mapCenter)[0]) @@ -301,27 +280,10 @@ for (let ocean of distributePointsOnCircle(2, oceanAngle, fractionToTiles(0.48), ]); g_Map.log("Smoothing around the water"); -var smoothDist = 5; -for (let ix = 1; ix < mapSize - 1; ++ix) - for (let iz = 1; iz < mapSize - 1; ++iz) - { - let position = new Vector2D(ix, iz); - if (!g_Map.inMapBounds(position) || !clWater.countMembersInRadius(position, smoothDist)) - continue; - let averageHeight = 0; - let todivide = 0; - for (let xx = -smoothDist; xx <= smoothDist; ++xx) - for (let yy = -smoothDist; yy <= smoothDist; ++yy) - { - let smoothPos = Vector2D.add(position, new Vector2D(xx, yy)); - if (g_Map.inMapBounds(smoothPos) && (xx != 0 || yy != 0)) - { - averageHeight += g_Map.getHeight(smoothPos) / (Math.abs(xx) + Math.abs(yy)); - todivide += 1 / (Math.abs(xx) + Math.abs(yy)); - } - } - g_Map.setHeight(position, (averageHeight + 2 * g_Map.getHeight(position)) / (todivide + 2)); - } +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(5, 0.9, 1), + new NearTileClassConstraint(clWater, 5)); Engine.SetProgress(55); g_Map.log("Creating hills"); diff --git a/binaries/data/mods/public/maps/random/ratumacos.js b/binaries/data/mods/public/maps/random/ratumacos.js index b67a4ebc2b..dbc48fc97b 100644 --- a/binaries/data/mods/public/maps/random/ratumacos.js +++ b/binaries/data/mods/public/maps/random/ratumacos.js @@ -19,7 +19,6 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); -Engine.LoadLibrary("heightmap"); setBiome("generic/alpine"); @@ -69,7 +68,9 @@ g_Map.LoadHeightmapImage("ratumacos.png", -3, 20); Engine.SetProgress(15); g_Map.log("Smoothing heightmap"); -globalSmoothHeightmap(0.1); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, 0.1, 1)); Engine.SetProgress(25); g_Map.log("Creating shallows..."); diff --git a/binaries/data/mods/public/maps/random/red_sea.js b/binaries/data/mods/public/maps/random/red_sea.js index 9662f52c0a..73de9a3d0f 100644 --- a/binaries/data/mods/public/maps/random/red_sea.js +++ b/binaries/data/mods/public/maps/random/red_sea.js @@ -18,7 +18,8 @@ Engine.LoadLibrary("rmgen"); Engine.LoadLibrary("rmgen2"); Engine.LoadLibrary("rmbiome"); -Engine.LoadLibrary("heightmap"); + +TILE_CENTERED_HEIGHT_MAP = true; setBiome("generic/desert"); @@ -73,7 +74,9 @@ createArea( Engine.SetProgress(20); g_Map.log("Smoothing heightmap"); -globalSmoothHeightmap(scaleByMapSize(0.1, 0.5)); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, scaleByMapSize(0.1, 0.5), 1)); Engine.SetProgress(25); g_Map.log("Marking water"); @@ -119,7 +122,7 @@ if (!isNomad()) let playerBases = placeRandom( sortAllPlayers(), [ - avoidClasses(g_TileClasses.mountain, 5), + avoidClasses(g_TileClasses.mountain, scaleByMapSize(5, 10)), stayClasses(g_TileClasses.land, defaultPlayerBaseRadius()) ]); diff --git a/binaries/data/mods/public/maps/random/rmgen/library.js b/binaries/data/mods/public/maps/random/rmgen/library.js index 4db2604fdd..943dc748b3 100644 --- a/binaries/data/mods/public/maps/random/rmgen/library.js +++ b/binaries/data/mods/public/maps/random/rmgen/library.js @@ -1,6 +1,10 @@ const TERRAIN_SEPARATOR = "|"; const SEA_LEVEL = 20.0; const HEIGHT_UNITS_PER_METRE = 92; + +/** + * Number of impassable, unexplorable tiles at the map border. + */ const MAP_BORDER_WIDTH = 3; const g_DamageTypes = new DamageTypes(); diff --git a/binaries/data/mods/public/maps/random/rmgen/math.js b/binaries/data/mods/public/maps/random/rmgen/math.js index 8f1346af3a..b1a0e9e2ae 100644 --- a/binaries/data/mods/public/maps/random/rmgen/math.js +++ b/binaries/data/mods/public/maps/random/rmgen/math.js @@ -1,4 +1,20 @@ -const g_TileVertices = deepfreeze([new Vector2D(0, 0), new Vector2D(1, 0), new Vector2D(0, 1), new Vector2D(1, 1)]); +const g_TileVertices = deepfreeze([ + new Vector2D(0, 0), + new Vector2D(0, 1), + new Vector2D(1, 0), + new Vector2D(1, 1) +]); + +const g_AdjacentCoordinates = deepfreeze([ + new Vector2D(1, 0), + new Vector2D(1, 1), + new Vector2D(0, 1), + new Vector2D(-1, 1), + new Vector2D(-1, 0), + new Vector2D(-1, -1), + new Vector2D(0, -1), + new Vector2D(1, -1) +]); function diskArea(radius) { diff --git a/binaries/data/mods/public/maps/random/rmgen/painter.js b/binaries/data/mods/public/maps/random/rmgen/painter.js index cd3132e2e1..35e6a9a5a9 100644 --- a/binaries/data/mods/public/maps/random/rmgen/painter.js +++ b/binaries/data/mods/public/maps/random/rmgen/painter.js @@ -106,6 +106,72 @@ LayeredPainter.prototype.paint = function(area) }); }; +/** + * Applies smoothing to the given area using Inverse-Distance-Weighting / Shepard's method. + * + * @param {Number} size - Determines the number of neighboring heights to interpolate. The area is a square with the length twice this size. + * @param {Number} strength - Between 0 (no effect) and 1 (only neighbor heights count). This parameter has the lowest performance impact. + * @param {Number} iterations - How often the process should be repeated. Typically 1. Can be used to gain even more smoothing. + */ +function SmoothingPainter(size, strength, iterations) +{ + if (size <= 0) + throw new Error("Invalid size: " + size); + + if (strength <= 0 || strength > 1) + throw new Error("Invalid strength: " + strength); + + if (iterations <= 0) + throw new Error("Invalid iterations: " + iterations); + + this.size = size; + this.strength = strength; + this.iterations = iterations; +} + +SmoothingPainter.prototype.paint = function(area) +{ + let brushPoints = getPointsInBoundingBox(getBoundingBox( + new Array(2).fill(0).map((zero, i) => new Vector2D(1, 1).mult(this.size).floor().mult(i ? 1 : -1)))); + + for (let i = 0; i < this.iterations; ++i) + { + let heightmap = clone(g_Map.height); + + // Additional complexity to process all 4 vertices of each tile, i.e the last row too + let seen = new Array(heightmap.length).fill(0).map(zero => new Uint8Array(heightmap.length).fill(0)); + + for (let point of area.points) + for (let tileVertex of g_TileVertices) + { + let vertex = Vector2D.add(point, tileVertex); + if (!g_Map.validHeight(vertex) || seen[vertex.x][vertex.y]) + continue; + + seen[vertex.x][vertex.y] = 1; + + let sumWeightedHeights = 0; + let sumWeights = 0; + + for (let brushPoint of brushPoints) + { + let position = Vector2D.add(vertex, brushPoint); + let distance = Math.abs(brushPoint.x) + Math.abs(brushPoint.y); + if (!distance || !g_Map.validHeight(position)) + continue; + + sumWeightedHeights += g_Map.getHeight(position) / distance; + sumWeights += 1 / distance; + } + + g_Map.setHeight( + vertex, + this.strength * sumWeightedHeights / sumWeights + + (1 - this.strength) * g_Map.getHeight(vertex)); + } + } +}; + /** * Sets the given height in the given Area. */ @@ -120,7 +186,7 @@ ElevationPainter.prototype.paint = function(area) for (let vertex of g_TileVertices) { let position = Vector2D.add(point, vertex); - if (g_Map.inMapBounds(position)) + if (g_Map.validHeight(position)) g_Map.setHeight(position, this.elevation); } }; @@ -337,7 +403,7 @@ function TerrainTextureArrayPainter(textureIDs, textureNames) { this.textureIDs = textureIDs; this.textureNames = textureNames; -}; +} TerrainTextureArrayPainter.prototype.paint = function(area) { @@ -353,29 +419,21 @@ TerrainTextureArrayPainter.prototype.paint = function(area) /** * Copies the given heightmap to the given area. - * Scales the horizontal plane proportionally and optionally uses bicubic interpolation. + * Scales the horizontal plane proportionally and applies bicubic interpolation. * The heightrange is either scaled proportionally or mapped to the given heightrange. * * @param {Uint16Array} heightmap - One dimensional array of vertex heights. * @param {Number} [normalMinHeight] - The minimum height the elevation grid of 320 tiles would have. * @param {Number} [normalMaxHeight] - The maximum height the elevation grid of 320 tiles would have. */ -function HeightmapPainter(heightmap, bicubicInterpolation, normalMinHeight = undefined, normalMaxHeight = undefined) +function HeightmapPainter(heightmap, normalMinHeight = undefined, normalMaxHeight = undefined) { this.heightmap = heightmap; this.bicubicInterpolation = bicubicInterpolation; this.verticesPerSide = Math.sqrt(heightmap.length); this.normalMinHeight = normalMinHeight; this.normalMaxHeight = normalMaxHeight; -}; - -HeightmapPainter.prototype.paint = function(area) -{ - if (this.bicubicInterpolation) - this.paintBicubic(area); - else - this.paintNearest(area); -}; +} HeightmapPainter.prototype.getScale = function() { @@ -393,17 +451,7 @@ HeightmapPainter.prototype.scaleHeight = function(height) return minHeight + (maxHeight - minHeight) * height / 0xFFFF; }; -HeightmapPainter.prototype.paintNearest = function(area) -{ - let scale = this.getScale(); - for (let point of area.points) - { - let sourcePos = Vector2D.mult(point, scale).floor(); - g_Map.setHeight(point, scaleHeight(this.heightmap[sourcePos.y * this.verticesPerSide + sourcePos.x])); - } -}; - -HeightmapPainter.prototype.paintBicubic = function(area) +HeightmapPainter.prototype.paint = function(area) { let scale = this.getScale(); let leftBottom = new Vector2D(0, 0); @@ -412,16 +460,17 @@ HeightmapPainter.prototype.paintBicubic = function(area) let brushCenter = new Vector2D(1, 1); // Additional complexity to process all 4 vertices of each tile, i.e the last row too - let seen = new Array(g_Map.getSize() + 1).fill(0).map(zero => new Uint8Array(g_Map.getSize() + 1).fill(false)); + let seen = new Array(g_Map.height.length).fill(0).map(zero => new Uint8Array(g_Map.height.length).fill(0)); for (let point of area.points) for (let vertex of g_TileVertices) { let vertexPos = Vector2D.add(point, vertex); - if (seen[vertexPos.x][vertexPos.y]) + + if (!g_Map.validHeight(vertexPos) || seen[vertexPos.x][vertexPos.y]) continue; - seen[vertexPos.x][vertexPos.y] = true; + seen[vertexPos.x][vertexPos.y] = 1; let sourcePos = Vector2D.mult(vertexPos, scale); let sourceTilePos = sourcePos.clone().floor(); diff --git a/binaries/data/mods/public/maps/random/rmgen/random_map.js b/binaries/data/mods/public/maps/random/rmgen/random_map.js index da3fc3230a..414a2f9f46 100644 --- a/binaries/data/mods/public/maps/random/rmgen/random_map.js +++ b/binaries/data/mods/public/maps/random/rmgen/random_map.js @@ -78,7 +78,7 @@ RandomMap.prototype.LoadMapTerrain = function(filename) g_Map.log("Loading terrain file " + filename); let mapTerrain = Engine.LoadMapTerrain("maps/random/" + filename + ".pmp"); - let heightmapPainter = new HeightmapPainter(mapTerrain.height, true); + let heightmapPainter = new HeightmapPainter(mapTerrain.height); createArea( new MapBoundsPlacer(), @@ -99,7 +99,7 @@ RandomMap.prototype.LoadHeightmapImage = function(filename, normalMinHeight, nor { g_Map.log("Loading heightmap " + filename); - let heightmapPainter = new HeightmapPainter(Engine.LoadHeightmapImage("maps/random/" + filename), true, normalMinHeight, normalMaxHeight); + let heightmapPainter = new HeightmapPainter(Engine.LoadHeightmapImage("maps/random/" + filename), normalMinHeight, normalMaxHeight); createArea( new MapBoundsPlacer(), @@ -374,14 +374,12 @@ RandomMap.prototype.getAdjacentPoints = function(position) { let adjacentPositions = []; - for (let x = -1; x <= 1; ++x) - for (let z = -1; z <= 1; ++z) - if (x || z ) - { - let adjacentPos = Vector2D.add(position, new Vector2D(x, z)).round(); - if (this.inMapBounds(adjacentPos)) - adjacentPositions.push(adjacentPos); - } + for (let adjacentCoordinate of g_AdjacentCoordinates) + { + let adjacentPos = Vector2D.add(position, adjacentCoordinate).round(); + if (this.inMapBounds(adjacentPos)) + adjacentPositions.push(adjacentPos); + } return adjacentPositions; }; diff --git a/binaries/data/mods/public/maps/random/schwarzwald.js b/binaries/data/mods/public/maps/random/schwarzwald.js index d2df9a9452..a2debf9458 100644 --- a/binaries/data/mods/public/maps/random/schwarzwald.js +++ b/binaries/data/mods/public/maps/random/schwarzwald.js @@ -116,8 +116,10 @@ var initialReliefmap = [[heightRange.max, heightRange.max, heightRange.max], [he setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialReliefmap); -for (var i = 0; i < 5; i++) - globalSmoothHeightmap(); +g_Map.log("Smoothing map"); +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, 0.8, 5)); rescaleHeightmap(heightRange.min, heightRange.max); diff --git a/binaries/data/mods/public/maps/random/scythian_rivulet.js b/binaries/data/mods/public/maps/random/scythian_rivulet.js index 9285dcfd03..9d359a5f37 100644 --- a/binaries/data/mods/public/maps/random/scythian_rivulet.js +++ b/binaries/data/mods/public/maps/random/scythian_rivulet.js @@ -1,5 +1,4 @@ Engine.LoadLibrary("rmgen"); -Engine.LoadLibrary("heightmap"); const tMainTerrain = "alpine_snow_a"; const tTier1Terrain = "snow rough"; diff --git a/binaries/data/mods/public/maps/random/wild_lake.js b/binaries/data/mods/public/maps/random/wild_lake.js index 387c35b979..8417c42f78 100644 --- a/binaries/data/mods/public/maps/random/wild_lake.js +++ b/binaries/data/mods/public/maps/random/wild_lake.js @@ -469,7 +469,10 @@ setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialHeightmap, // Apply simple erosion for (let i = 0; i < 5; ++i) splashErodeMap(0.1); -globalSmoothHeightmap(); + +createArea( + new MapBoundsPlacer(), + new SmoothingPainter(1, 0.8, 1)); // Final rescale rescaleHeightmap(heightRange.min, heightRange.max);