Ticket #3764: add_hm_lib-2016_5_1e.patch
File add_hm_lib-2016_5_1e.patch, 37.5 KB (added by , 8 years ago) |
---|
-
binaries/data/mods/public/maps/random/belgian_uplands.js
4 4 5 5 // Importing rmgen libraries 6 6 RMS.LoadLibrary("rmgen"); 7 RMS.LoadLibrary("heightmap"); 7 8 8 9 const BUILDING_ANGlE = -PI/4; 9 10 … … 17 18 var mapSize = getMapSize(); 18 19 19 20 20 ////////// 21 // Heightmap functionality 22 ////////// 23 24 // Some general heightmap settings 25 const MIN_HEIGHT = - SEA_LEVEL; // 20, should be set in the libs! 26 const MAX_HEIGHT = 0xFFFF/HEIGHT_UNITS_PER_METRE - SEA_LEVEL; // A bit smaler than 90, should be set in the libs! 27 28 // Add random heightmap generation functionality 29 function getRandomReliefmap(minHeight, maxHeight) 30 { 31 minHeight = (minHeight || MIN_HEIGHT); 32 maxHeight = (maxHeight || MAX_HEIGHT); 33 34 if (minHeight < MIN_HEIGHT) 35 warn("getRandomReliefmap: Argument minHeight is smaler then the supported minimum height of " + MIN_HEIGHT + " (const MIN_HEIGHT): " + minHeight) 36 37 if (maxHeight > MAX_HEIGHT) 38 warn("getRandomReliefmap: Argument maxHeight is smaler then the supported maximum height of " + MAX_HEIGHT + " (const MAX_HEIGHT): " + maxHeight) 39 40 var reliefmap = []; 41 for (var x = 0; x <= mapSize; x++) 42 { 43 reliefmap.push([]); 44 for (var y = 0; y <= mapSize; y++) 45 reliefmap[x].push(randFloat(minHeight, maxHeight)); 46 } 47 return reliefmap; 48 } 49 50 // Apply a heightmap 21 // Function to apply a heightmap 51 22 function setReliefmap(reliefmap) 52 23 { 53 24 // g_Map.height = reliefmap; … … 56 27 setHeight(x, y, reliefmap[x][y]); 57 28 } 58 29 59 // Get minimum and maxumum height used in a heightmap60 function getMinAndMaxHeight(reliefmap)61 {62 var height = {};63 height.min = Infinity;64 height.max = -Infinity;65 30 66 for (var x = 0; x <= mapSize; x++)67 for (var y = 0; y <= mapSize; y++)68 {69 if (reliefmap[x][y] < height.min)70 height.min = reliefmap[x][y];71 else if (reliefmap[x][y] > height.max)72 height.max = reliefmap[x][y];73 }74 75 return height;76 }77 78 // Rescale a heightmap (Waterlevel is not taken into consideration!)79 function getRescaledReliefmap(reliefmap, minHeight, maxHeight)80 {81 var newReliefmap = deepcopy(reliefmap);82 minHeight = (minHeight || MIN_HEIGHT);83 maxHeight = (maxHeight || MAX_HEIGHT);84 85 if (minHeight < MIN_HEIGHT)86 warn("getRescaledReliefmap: Argument minHeight is smaler then the supported minimum height of " + MIN_HEIGHT + " (const MIN_HEIGHT): " + minHeight)87 88 if (maxHeight > MAX_HEIGHT)89 warn("getRescaledReliefmap: Argument maxHeight is smaler then the supported maximum height of " + MAX_HEIGHT + " (const MAX_HEIGHT): " + maxHeight)90 91 var oldHeightRange = getMinAndMaxHeight(reliefmap);92 93 for (var x = 0; x <= mapSize; x++)94 for (var y = 0; y <= mapSize; y++)95 newReliefmap[x][y] = minHeight + (reliefmap[x][y] - oldHeightRange.min) / (oldHeightRange.max - oldHeightRange.min) * (maxHeight - minHeight);96 97 return newReliefmap98 }99 100 // Applying decay errosion (terrain independent)101 function getHeightErrosionedReliefmap(reliefmap, strength)102 {103 var newReliefmap = deepcopy(reliefmap);104 strength = (strength || 1.0); // Values much higher then 1 (1.32+ for an 8 tile map, 1.45+ for a 12 tile map, 1.62+ @ 20 tile map, 0.99 @ 4 tiles) will result in a resonance disaster/self interference105 106 var map = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]; // Default107 108 for (var x = 0; x <= mapSize; x++)109 for (var y = 0; y <= mapSize; y++)110 {111 var div = 0;112 for (var i = 0; i < map.length; i++)113 newReliefmap[x][y] += strength / map.length * (reliefmap[(x + map[i][0] + mapSize + 1) % (mapSize + 1)][(y + map[i][1] + mapSize + 1) % (mapSize + 1)] - reliefmap[x][y]); // Not entirely sure if scaling with map.length is perfect but tested values seam to indicate it is114 }115 116 return newReliefmap;117 }118 119 120 //////////121 // Prepare for hightmap munipulation122 //////////123 124 31 // Set target min and max height depending on map size to make average stepness the same on all map sizes 125 32 var heightRange = {"min": MIN_HEIGHT * mapSize / 8192, "max": MAX_HEIGHT * mapSize / 8192}; 126 33 … … 215 122 log("Starting giant while loop try " + tries); 216 123 217 124 // Generate reliefmap 218 var myReliefmap = getRandomReliefmap(heightRange.min, heightRange.max); 125 var myReliefmap = deepcopy(g_Map.height); 126 setRandomHeightmap(heightRange.min, heightRange.max, myReliefmap); 219 127 for (var i = 0; i < 50 + mapSize/4; i++) // Cycles depend on mapsize (more cycles -> bigger structures) 220 myReliefmap = getHeightErrosionedReliefmap(myReliefmap, 1);128 globalSmoothHeightmap(0.8, myReliefmap); 221 129 222 myReliefmap = getRescaledReliefmap(myReliefmap, heightRange.min, heightRange.max);130 rescaleHeightmap(heightRange.min, heightRange.max, myReliefmap); 223 131 setReliefmap(myReliefmap); 224 132 225 133 // Find good start position tiles -
binaries/data/mods/public/maps/random/heightmap/heightmap.js
1 /** 2 * Heightmap manipulation functionality 3 * 4 * A heightmapt is an array of width arrays of height floats 5 * Width and height is normally mapSize+1 (Number of vertices is one bigger than number of tiles in each direction) 6 * The default heightmap is g_Map.height (See the Map object) 7 * 8 * WARNING: Ambiguous naming and potential confusion: 9 * For using this heightmap functionalities it is VITAL that TILE_CENTERED_HEIGHT_MAP = false (default)! 10 * Otherwise TILE_CENTERED_HEIGHT_MAP has nothing to do with any tile centered heightmap in this library! 11 * All tile centered heightmaps in this module are temporary objects only and should never replace g_Map.height! 12 * (Tile centered heightmaps are one less in width and hight than heightmaps used by the engine) 13 * (Multiple conversions will lead to strange smoothing effects at best and potentially break the map entirely!) 14 * In the long run TILE_CENTERED_HEIGHT_MAP should be removed and g_Map.height should never be tile centered... 15 */ 16 17 /** 18 * Returns the minimum and maximum heights present in this heightmap (an associative array of the form {"min": minHeightValue, "max": maxHeightValue}) 19 * @param {array} [heightmap=g_Map.height] - The reliefmap the minimum and maximum height should be determined for 20 * @return {object} [height] - Height range with 2 floats in properties "min" and "max" 21 */ 22 function getMinAndMaxHeight(heightmap = g_Map.height) 23 { 24 var height = {}; 25 height.min = Infinity; 26 height.max = - Infinity; 27 for (var x = 0; x < heightmap.length; ++x) 28 { 29 for (var y = 0; y < heightmap[x].length; ++y) 30 { 31 if (heightmap[x][y] < height.min) 32 height.min = heightmap[x][y]; 33 else if (heightmap[x][y] > height.max) 34 height.max = heightmap[x][y]; 35 } 36 } 37 return height; 38 } 39 40 /** 41 * Rescales the heightmap so its minimum and maximum height is as the arguments told (preserving it's global shape) 42 * @param {float} [minHeight=MIN_HEIGHT] - Minimum height that should be used for the resulting heightmap 43 * @param {float} [maxHeight=MAX_HEIGHT] - Maximum height that should be used for the resulting heightmap 44 * @param {array} [heightmap=g_Map.height] - A reliefmap 45 * ToDo: Add preserveRatios to not change relative heights within the given height range 46 * ToDo: Add preserveCostline to leave a certain height untoucht and scale below and above that seperately 47 */ 48 function rescaleHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height) 49 { 50 var oldHeightRange = getMinAndMaxHeight(heightmap); 51 var max_x = heightmap.length; 52 var max_y = heightmap[0].length; 53 for (var x = 0; x < max_x; ++x) 54 for (var y = 0; y < max_y; ++y) 55 heightmap[x][y] = minHeight + (heightmap[x][y] - oldHeightRange.min) / (oldHeightRange.max - oldHeightRange.min) * (maxHeight - minHeight); 56 } 57 58 /** 59 * Get start location with the largest minimum distance between players 60 * Returns an array of 2D points (arrays of length 2) 61 * @param {array} [hightRange] - The height range start locations are allowed 62 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared 63 * @param {float} [minDistToBorder=20.] - How far start locations have to be away from the map border 64 * @param {integer} [numberOfPlayers=g_MapSettings.PlayerData.length] - How many start locations should be placed 65 * @param {array} [heightmap=g_Map.height] - The reliefmap for the start locations to be placed on 66 * @return {array} [finalStartLoc] - Array of 2D points in the format { "x": float, "y": float} 67 */ 68 function getStartLocationsByHeightmap(hightRange, maxTries = 1000, minDistToBorder = 20., numberOfPlayers = g_MapSettings.PlayerData.length - 1, heightmap = g_Map.height) 69 { 70 var validStartLoc = []; 71 for (var x = minDistToBorder; x < heightmap.length - minDistToBorder; ++x) 72 for (var y = minDistToBorder; y < heightmap[0].length - minDistToBorder; ++y) 73 if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Is in height range 74 validStartLoc.push({ "x": x, "y": y }); 75 76 var maxMinDist = 0; 77 for (var tries = 0; tries < maxTries; ++tries) 78 { 79 var startLoc = []; 80 var minDist = heightmap.length; 81 for (var p = 0; p < numberOfPlayers; ++p) 82 startLoc.push(validStartLoc[randInt(validStartLoc.length)]); 83 for (var p1 = 0; p1 < numberOfPlayers - 1; ++p1) 84 { 85 for (var p2 = p1 + 1; p2 < numberOfPlayers; ++p2) 86 { 87 var dist = getDistance(startLoc[p1].x, startLoc[p1].y, startLoc[p2].x, startLoc[p2].y); 88 if (dist < minDist) 89 minDist = dist; 90 } 91 } 92 if (minDist > maxMinDist) 93 { 94 maxMinDist = minDist; 95 var finalStartLoc = startLoc; 96 } 97 } 98 99 return finalStartLoc; 100 } 101 102 /** 103 * Meant to place e.g. resource spots within a hight range 104 * Returns an array of 2D points (arrays of length 2) 105 * @param {array} [hightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float) 106 * @param {array} [avoidPoints] - An array of 2D points (arrays of length 2), points that will be avoided in the given minDistance e.g. start locations 107 * @param {integer} [minDistance=30] - How many tile widths the entities to place have to be away from each other, start locations and the map border 108 * @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on 109 * @param {array} [entityList=[g_Gaia.stoneLarge, g_Gaia.metalLarge]] - Entity/actor strings to be placed with placeObject() 110 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared 111 */ 112 function distributeEntitiesByHeight(hightRange, avoidPoints, minDistance = 30, entityList = [g_Gaia.stoneLarge, g_Gaia.metalLarge], maxTries = 1000, heightmap = g_Map.height) 113 { 114 var placements = deepcopy(avoidPoints); 115 var validTiles = []; 116 for (var x = minDistance; x < heightmap.length - minDistance; ++x) 117 for (var y = minDistance; y < heightmap[0].length - minDistance; ++y) 118 if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Has the right hight 119 validTiles.push({ "x": x, "y": y }); 120 121 for (var tries = 0; tries < maxTries; ++tries) 122 { 123 var tile = validTiles[randInt(validTiles.length)]; 124 var isValid = true; 125 for (var p = 0; p < placements.length; ++p) 126 { 127 if (getDistance(placements[p].x, placements[p].y, tile.x, tile.y) < minDistance) 128 { 129 isValid = false; 130 break; 131 } 132 } 133 if (isValid) 134 { 135 placeObject(tile.x, tile.y, entityList[randInt(entityList.length)], 0, randFloat(0, 2*PI)); 136 placements.push(tile); 137 } 138 } 139 } 140 141 /** 142 * Sets a given heightmap to entirely random values within a given range 143 * Returns nothing, directly sets the given heightmap 144 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled 145 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled 146 * @param {array} [heightmap=g_Map.height] - The reliefmap that should be randomized 147 */ 148 function setRandomHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height) 149 { 150 for (var x = 0; x < heightmap.length; ++x) 151 for (var y = 0; y < heightmap[0].length; ++y) 152 heightmap[x][y] = randFloat(minHeight, maxHeight); 153 } 154 155 /** 156 * Sets the heightmap to a relatively realistic shape 157 * min/maxHeight will not necessarily be present in the heightmap 158 * On circular maps the edges (given by initialHeightmap) may not be in the playable map area 159 * The function doubles the size of the initial heightmap (if given, else a random 2x2 one) until it's big enough. 160 * Then the extend is cut off. So the impact and scale of the initial heightmap depends on its size and the target map size! 161 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled 162 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled 163 * @param {float} [smoothness=0.5] - Float between 0 (rough, more local structures) to 1 (smoother, only larger scale structures) 164 * @param {array} [initialHeightmap] - Optional, Small (e.g. 3x3) heightmap describing the global shape of the map e.g. an island [[MIN_HEIGHT, MIN_HEIGHT, MIN_HEIGHT], [MIN_HEIGHT, MAX_HEIGHT, MIN_HEIGHT], [MIN_HEIGHT, MIN_HEIGHT, MIN_HEIGHT]] 165 * @param {array} [heightmap=g_Map.height] - The reliefmap that will be set by this function 166 */ 167 function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, initialHeightmap, smoothness = 0.5, heightmap = g_Map.height) 168 { 169 initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]); 170 var heightRange = maxHeight - minHeight; 171 if (heightRange <= 0) 172 warn("setBaseTerrainDiamondSquare: heightRange <= 0"); 173 174 var offset = heightRange / 2; 175 176 // Double initialHeightmap width until target width is reached (diamond square method) 177 while (initialHeightmap.length < heightmap.length) 178 { 179 var newHeightmap = []; 180 var oldWidth = initialHeightmap.length; 181 // Square 182 for (var x = 0; x < 2 * oldWidth - 1; ++x) 183 { 184 newHeightmap.push([]); 185 for (var y = 0; y < 2 * oldWidth - 1; ++y) 186 { 187 if (x % 2 == 0 && y % 2 == 0) // Old tile 188 newHeightmap[x].push(initialHeightmap[x/2][y/2]); 189 else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors 190 { 191 newHeightmap[x].push((initialHeightmap[(x-1)/2][(y-1)/2] + initialHeightmap[(x+1)/2][(y-1)/2] + initialHeightmap[(x-1)/2][(y+1)/2] + initialHeightmap[(x+1)/2][(y+1)/2]) / 4); 192 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 193 } 194 else // New tile with straight old tile neighbors 195 newHeightmap[x].push(undefined); // Define later 196 } 197 } 198 // Diamond 199 for (var x = 0; x < 2 * oldWidth - 1; ++x) 200 { 201 for (var y = 0; y < 2 * oldWidth - 1; ++y) 202 { 203 if (newHeightmap[x][y] !=== undefined) 204 continue; 205 206 if (x > 0 && x + 1 < newHeightmap.length - 1 && y > 0 && y + 1 < newHeightmap.length - 1) // Not a border tile 207 { 208 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 4; 209 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 210 } 211 else if (x < newHeightmap.length - 1 && y > 0 && y < newHeightmap.length - 1) // Left border 212 { 213 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x][y-1]) / 3; 214 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 215 } 216 else if (x > 0 && y > 0 && y < newHeightmap.length - 1) // Right border 217 { 218 newHeightmap[x][y] = (newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3; 219 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 220 } 221 else if (x > 0 && x < newHeightmap.length - 1 && y < newHeightmap.length - 1) // Bottom border 222 { 223 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y]) / 3; 224 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 225 } 226 else if (x > 0 && x < newHeightmap.length - 1 && y > 0) // Top border 227 { 228 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3; 229 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 230 } 231 } 232 } 233 initialHeightmap = deepcopy(newHeightmap); 234 offset /= Math.pow(2, smoothness); 235 } 236 237 // Cut initialHeightmap to fit target width 238 var shift = [floor((newHeightmap.length - heightmap.length) / 2.), floor((newHeightmap[0].length - heightmap[0].length) / 2.)]; 239 for (var x = 0; x < heightmap.length; ++x) 240 for (var y = 0; y < heightmap[0].length; ++y) 241 heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]]; 242 } 243 244 /** 245 * Smoothens the entire map 246 * @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 247 * @param {array} [heightmap=g_Map.height] - The heightmap to be smoothed 248 * @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 249 */ 250 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]]) 251 { 252 var referenceHeightmap = deepcopy(heightmap); 253 var max_x = heightmap.length; 254 var max_y = heightmap[0].length; 255 for (var x = 0; x < max_x; ++x) 256 { 257 for (var y = 0; y < max_y; ++y) 258 { 259 for (var i = 0; i < smoothMap.length; ++i) 260 { 261 var mapX = x + smoothMap[i][0]; 262 var mapY = y + smoothMap[i][1]; 263 if (mapX >= 0 && mapX < max_x && mapY >= 0 && mapY < max_y) 264 heightmap[x][y] += strength / smoothMap.length * (referenceHeightmap[mapX][mapY] - referenceHeightmap[x][y]); 265 } 266 } 267 } 268 } 269 270 /** 271 * Pushes a rectangular area towards a given height smoothing it into the original terrain 272 * The window function to determine the smooth is not exactly a gaussian to ensure smooth edges 273 * @param {object} [center] - The x and y coordinates of the center point (rounded in this function) 274 * @param {float} [dx] - Distance from the center in x direction the rectangle ends (half width, rounded in this function) 275 * @param {float} [dy] - Distance from the center in y direction the rectangle ends (half depth, rounded in this function) 276 * @param {float} [targetHeight] - Height the center of the rectangle will be pushed to 277 * @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 278 * @param {array} [heightmap=g_Map.height] - The heightmap to be manipulated 279 * ToDo: Make the window function an argument and maybe add some 280 */ 281 function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength = 0.8, heightmap = g_Map.height) 282 { 283 var x = round(center.x); 284 var y = round(center.y); 285 dx = round(dx); 286 dy = round(dy); 287 288 var heightmapWin = []; 289 for (var wx = 0; wx < 2 * dx + 1; ++wx) 290 { 291 heightmapWin.push([]); 292 for (var wy = 0; wy < 2 * dy + 1; ++wy) 293 { 294 var actualX = x - dx + wx; 295 var actualY = y - dy + wy; 296 if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map 297 heightmapWin[wx].push(heightmap[actualX][actualY]); 298 else 299 heightmapWin[wx].push(targetHeight); 300 } 301 } 302 for (var wx = 0; wx < 2 * dx + 1; ++wx) 303 { 304 for (var wy = 0; wy < 2 * dy + 1; ++wy) 305 { 306 var actualX = x - dx + wx; 307 var actualY = y - dy + wy; 308 if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map 309 { 310 // Window function polynomial 2nd degree 311 var scaleX = 1 - (wx / dx - 1) * (wx / dx - 1); 312 var scaleY = 1 - (wy / dy - 1) * (wy / dy - 1); 313 314 heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]); 315 } 316 } 317 } 318 } -
binaries/data/mods/public/maps/random/island_stronghold.js
1 function decayErrodeHeightmap(strength, heightmap)2 {3 strength = strength || 0.9; // 0 to 14 heightmap = heightmap || g_Map.height;5 6 let referenceHeightmap = deepcopy(heightmap);7 // let map = [[1, 0], [0, 1], [-1, 0], [0, -1]]; // faster8 let map = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]; // smoother9 let max_x = heightmap.length;10 let max_y = heightmap[0].length;11 for (let x = 0; x < max_x; ++x)12 for (let y = 0; y < max_y; ++y)13 for (let i = 0; i < map.length; ++i)14 heightmap[x][y] += strength / map.length * (referenceHeightmap[(x + map[i][0] + max_x) % max_x][(y + map[i][1] + max_y) % max_y] - referenceHeightmap[x][y]); // Not entirely sure if scaling with map.length is perfect but tested values seam to indicate it is15 }16 17 1 /** 18 2 * Returns starting position in tile coordinates for the given player. 19 3 */ … … 28 12 } 29 13 30 14 RMS.LoadLibrary("rmgen"); 15 RMS.LoadLibrary("heightmap"); 31 16 32 17 const g_InitialMines = 1; 33 18 const g_InitialMineDistance = 14; … … 372 357 373 358 log("Smoothing heightmap..."); 374 359 for (let i = 0; i < 5; ++i) 375 decayErrodeHeightmap(0.5);360 globalSmoothHeightmap(); 376 361 377 362 // repaint clLand to compensate for smoothing 378 363 unPaintTileClassBasedOnHeight(-10, 10, 3, clLand); … … 420 405 scaleByMapSize(4, 13) 421 406 ); 422 407 for (let i = 0; i < 3; ++i) 423 decayErrodeHeightmap(0.2);408 globalSmoothHeightmap(); 424 409 425 410 createStragglerTrees( 426 411 [oTree1, oTree2, oTree4, oTree3], -
binaries/data/mods/public/maps/random/rmgen/library.js
7 7 const MIN_MAP_SIZE = 128; 8 8 const MAX_MAP_SIZE = 512; 9 9 const FALLBACK_CIV = "athen"; 10 /** 11 * Constants needed for heightmap_manipulation.js 12 */ 13 const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE // Engine limit, Roughly 700 meters 14 const MIN_HEIGHT = - SEA_LEVEL; 15 const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL; 10 16 11 17 function fractionToTiles(f) 12 18 { -
binaries/data/mods/public/maps/random/schwarzwald.js
1 // Created by Niek ten Brinke (aka niektb)2 // Based on FeXoR's Daimond Square Algorithm for heightmap generation and several official random maps3 4 'use strict';5 6 1 RMS.LoadLibrary('rmgen'); 2 RMS.LoadLibrary("heightmap"); 7 3 8 // initialize map9 10 4 log('Initializing map...'); 11 5 12 6 InitMap(); … … 178 172 return ret; 179 173 }; 180 174 181 /*182 Takes an array of 2D points (arrays of length 2)183 Returns the order to go through the points for the shortest closed path (array of indices)184 */185 function getOrderOfPointsForShortestClosePath(points)186 {187 var order = [];188 var distances = [];189 175 190 if (points.length <= 3)191 {192 for (var i = 0; i < points.length; i++)193 order.push(i);194 195 return order;196 }197 198 // Just add the first 3 points199 var pointsToAdd = deepcopy(points);200 for (var i = 0; i < min(points.length, 3); i++)201 {202 order.push(i);203 pointsToAdd.shift(i);204 if (i)205 distances.push(getDistance(points[order[i]][0], points[order[i]][1], points[order[i - 1]][0], points[order[i - 1]][1]));206 }207 distances.push(getDistance(points[order[0]][0], points[order[0]][1], points[order[order.length - 1]][0], points[order[order.length - 1]][1]));208 209 // Add remaining points so the path lengthens the least210 var numPointsToAdd = pointsToAdd.length;211 for (var i = 0; i < numPointsToAdd; i++)212 {213 var indexToAddTo = undefined;214 var minEnlengthen = Infinity;215 var minDist1 = 0;216 var minDist2 = 0;217 218 for (var k = 0; k < order.length; k++)219 {220 var dist1 = getDistance(pointsToAdd[0][0], pointsToAdd[0][1], points[order[k]][0], points[order[k]][1]);221 var dist2 = getDistance(pointsToAdd[0][0], pointsToAdd[0][1], points[order[(k + 1) % order.length]][0], points[order[(k + 1) % order.length]][1]);222 var enlengthen = dist1 + dist2 - distances[k];223 224 if (enlengthen < minEnlengthen)225 {226 indexToAddTo = k;227 minEnlengthen = enlengthen;228 minDist1 = dist1;229 minDist2 = dist2;230 }231 }232 233 order.splice(indexToAddTo + 1, 0, i + 3);234 distances.splice(indexToAddTo, 1, minDist1, minDist2);235 pointsToAdd.shift();236 }237 238 return order;239 }240 241 242 176 //////////////// 243 //244 // Heightmap functionality245 //246 ////////////////247 248 // Some heightmap constants249 const MIN_HEIGHT = - SEA_LEVEL; // -20250 const MAX_HEIGHT = 0xFFFF/HEIGHT_UNITS_PER_METRE - SEA_LEVEL; // A bit smaller than 90251 252 // Get the diferrence between minimum and maxumum height253 function getMinAndMaxHeight(reliefmap)254 {255 var height = {};256 height.min = Infinity;257 height.max = - Infinity;258 259 for (var x = 0; x < reliefmap.length; x++)260 for (var y = 0; y < reliefmap[x].length; y++)261 {262 if (reliefmap[x][y] < height.min)263 height.min = reliefmap[x][y];264 else if (reliefmap[x][y] > height.max)265 height.max = reliefmap[x][y];266 }267 268 return height;269 }270 271 function rescaleHeightmap(minHeight, maxHeight, heightmap)272 {273 minHeight = (minHeight || - SEA_LEVEL);274 maxHeight = (maxHeight || 0xFFFF / HEIGHT_UNITS_PER_METRE - SEA_LEVEL);275 heightmap = (heightmap || g_Map.height);276 277 var oldHeightRange = getMinAndMaxHeight(heightmap);278 var max_x = heightmap.length;279 var max_y = heightmap[0].length;280 281 for (var x = 0; x < max_x; x++)282 for (var y = 0; y < max_y; y++)283 heightmap[x][y] = minHeight + (heightmap[x][y] - oldHeightRange.min) / (oldHeightRange.max - oldHeightRange.min) * (maxHeight - minHeight);284 }285 286 /*287 getStartLocationsByHeightmap288 Takes289 hightRange An associative array with keys 'min' and 'max' each containing a float (the height range start locations are allowed)290 heightmap Optional, default is g_Map.height, an array of (map width) arrays of (map depth) floats291 maxTries Optional, default is 1000, an integer, how often random player distributions are rolled to be compared292 minDistToBorder Optional, default is 20, an integer, how far start locations have to be293 numberOfPlayers Optional, default is getNumPlayers, an integer, how many start locations should be placed294 Returns295 An array of 2D points (arrays of length 2)296 */297 function getStartLocationsByHeightmap(hightRange, maxTries, minDistToBorder, numberOfPlayers, heightmap)298 {299 maxTries = (maxTries || 1000);300 minDistToBorder = (minDistToBorder || 20);301 numberOfPlayers = (numberOfPlayers || getNumPlayers());302 heightmap = (heightmap || g_Map.height);303 304 var validStartLocTiles = [];305 306 for (var x = minDistToBorder; x < heightmap.length - minDistToBorder; x++)307 for (var y = minDistToBorder; y < heightmap[0].length - minDistToBorder; y++)308 309 if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Has the right hight310 validStartLocTiles.push([x, y]);311 312 var maxMinDist = 0;313 for (var tries = 0; tries < maxTries; tries++)314 {315 var startLoc = [];316 var minDist = heightmap.length;317 318 for (var p = 0; p < numberOfPlayers; p++)319 startLoc.push(validStartLocTiles[randInt(validStartLocTiles.length)]);320 321 for (var p1 = 0; p1 < numberOfPlayers - 1; p1++)322 for (var p2 = p1 + 1; p2 < numberOfPlayers; p2++)323 {324 var dist = getDistance(startLoc[p1][0], startLoc[p1][1], startLoc[p2][0], startLoc[p2][1]);325 if (dist < minDist)326 minDist = dist;327 }328 329 if (minDist > maxMinDist)330 {331 maxMinDist = minDist;332 var finalStartLoc = startLoc;333 }334 }335 336 return finalStartLoc;337 }338 339 /*340 derivateEntitiesByHeight341 Takes342 hightRange An associative array with keys 'min' and 'max' each containing a float (the height range start locations are allowed)343 startLoc An array of 2D points (arrays of length 2)344 heightmap Optional, default is g_Map.height, an array of (map width) arrays of (map depth) floats345 entityList Array of entities/actors (strings to be placed with placeObject())346 maxTries Optional, default is 1000, an integer, how often random player distributions are rolled to be compared347 minDistance Optional, default is 30, an integer, how far start locations have to be away from start locations and the map border348 Returns349 An array of 2D points (arrays of length 2)350 */351 function derivateEntitiesByHeight(hightRange, startLoc, entityList, maxTries, minDistance, heightmap)352 {353 entityList = (entityList || [templateMetalMine, templateStoneMine]);354 maxTries = (maxTries || 1000);355 minDistance = (minDistance || 40);356 heightmap = (heightmap || g_Map.height);357 358 var placements = deepcopy(startLoc);359 var validTiles = [];360 361 for (var x = minDistance; x < heightmap.length - minDistance; x++)362 for (var y = minDistance; y < heightmap[0].length - minDistance; y++)363 if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Has the right hight364 validTiles.push([x, y]);365 366 if (!validTiles.length)367 return;368 369 for (var tries = 0; tries < maxTries; tries++)370 {371 var tile = validTiles[randInt(validTiles.length)];372 var isValid = true;373 374 for (var p = 0; p < placements.length; p++)375 if (getDistance(placements[p][0], placements[p][1], tile[0], tile[1]) < minDistance)376 {377 isValid = false;378 break;379 }380 381 if (isValid)382 {383 placeObject(tile[0], tile[1], entityList[randInt(entityList.length)], 0, randFloat(0, 2*PI));384 // placeObject(tile[0], tile[1], 'actor|geology/decal_stone_medit_b.xml', 0, randFloat(0, 2*PI));385 placements.push(tile);386 }387 }388 }389 390 391 ////////////////392 //393 // Base terrain generation functionality394 //395 ////////////////396 397 function setBaseTerrainDiamondSquare(minHeight, maxHeight, smoothness, initialHeightmap, heightmap)398 {399 // Make some arguments optional400 minHeight = (minHeight || 0);401 maxHeight = (maxHeight || 1);402 403 var heightRange = maxHeight - minHeight;404 if (heightRange <= 0)405 warn('setBaseTerrainDiamondSquare: heightRange < 0');406 407 smoothness = (smoothness || 1);408 409 var offset = heightRange / 2;410 initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]);411 412 // Double initialHeightmap width untill target width is reached (diamond square method)413 while (initialHeightmap.length < heightmap.length)414 {415 var newHeightmap = [];416 var oldWidth = initialHeightmap.length;417 418 // Square419 for (var x = 0; x < 2 * oldWidth - 1; x++)420 {421 newHeightmap.push([]);422 for (var y = 0; y < 2 * oldWidth - 1; y++)423 {424 if (x % 2 === 0 && y % 2 === 0) // Old tile425 newHeightmap[x].push(initialHeightmap[x/2][y/2]);426 else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors427 {428 newHeightmap[x].push((initialHeightmap[(x-1)/2][(y-1)/2] + initialHeightmap[(x+1)/2][(y-1)/2] + initialHeightmap[(x-1)/2][(y+1)/2] + initialHeightmap[(x+1)/2][(y+1)/2]) / 4);429 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);430 }431 else // New tile with straight old tile neighbors432 newHeightmap[x].push(undefined); // Define later433 }434 }435 436 // Diamond437 for (var x = 0; x < 2 * oldWidth - 1; x++)438 for (var y = 0; y < 2 * oldWidth - 1; y++)439 {440 if (newHeightmap[x][y] === undefined)441 {442 if (x > 0 && x + 1 < newHeightmap.length - 1 && y > 0 && y + 1 < newHeightmap.length - 1) // Not a border tile443 {444 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 4;445 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);446 }447 else if (x < newHeightmap.length - 1 && y > 0 && y < newHeightmap.length - 1) // Left border448 {449 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x][y-1]) / 3;450 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);451 }452 else if (x > 0 && y > 0 && y < newHeightmap.length - 1) // Right border453 {454 newHeightmap[x][y] = (newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;455 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);456 }457 else if (x > 0 && x < newHeightmap.length - 1 && y < newHeightmap.length - 1) // Bottom border458 {459 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y]) / 3;460 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);461 }462 else if (x > 0 && x < newHeightmap.length - 1 && y > 0) // Top border463 {464 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;465 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);466 }467 }468 }469 470 initialHeightmap = deepcopy(newHeightmap);471 offset /= Math.pow(2, smoothness);472 }473 474 // Cut initialHeightmap to fit target width475 var shift = [floor((newHeightmap.length - heightmap.length) / 2), floor((newHeightmap[0].length - heightmap[0].length) / 2)];476 for (var x = 0; x < heightmap.length; x++)477 for (var y = 0; y < heightmap[0].length; y++)478 heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]];479 }480 481 482 ////////////////483 //484 // Terrain erosion functionality485 //486 ////////////////487 488 function decayErrodeHeightmap(strength, heightmap)489 {490 strength = (strength || 0.9); // 0 to 1491 heightmap = (heightmap || g_Map.height);492 493 var referenceHeightmap = deepcopy(heightmap);494 // var map = [[1, 0], [0, 1], [-1, 0], [0, -1]]; // faster495 var map = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]; // smoother496 var max_x = heightmap.length;497 var max_y = heightmap[0].length;498 499 for (var x = 0; x < max_x; x++)500 for (var y = 0; y < max_y; y++)501 for (var i = 0; i < map.length; i++)502 heightmap[x][y] += strength / map.length * (referenceHeightmap[(x + map[i][0] + max_x) % max_x][(y + map[i][1] + max_y) % max_y] - referenceHeightmap[x][y]); // Not entirely sure if scaling with map.length is perfect but tested values seam to indicate it is503 }504 505 function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength, heightmap)506 {507 var x = round(center[0]);508 var y = round(center[1]);509 dx = round(dx);510 dy = round(dy);511 strength = (strength || 1);512 heightmap = (heightmap || g_Map.height);513 514 var heightmapWin = [];515 for (var wx = 0; wx < 2 * dx + 1; wx++)516 {517 heightmapWin.push([]);518 for (var wy = 0; wy < 2 * dy + 1; wy++)519 {520 var actualX = x - dx + wx;521 var actualY = y - dy + wy;522 523 if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map524 heightmapWin[wx].push(heightmap[actualX][actualY]);525 else526 heightmapWin[wx].push(targetHeight);527 }528 }529 530 for (var wx = 0; wx < 2 * dx + 1; wx++)531 for (var wy = 0; wy < 2 * dy + 1; wy++)532 {533 var actualX = x - dx + wx;534 var actualY = y - dy + wy;535 536 if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map537 {538 // Window function polynomial 2nd degree539 var scaleX = 1 - (wx / dx - 1) * (wx / dx - 1);540 var scaleY = 1 - (wy / dy - 1) * (wy / dy - 1);541 542 heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]);543 }544 }545 }546 547 548 ////////////////549 //550 // Actually do stuff551 //552 ////////////////553 554 ////////////////555 177 // Set height limits and water level by map size 556 178 //////////////// 557 179 … … 571 193 // Setting a 3x3 Grid as initial heightmap 572 194 var initialReliefmap = [[heightRange.max, heightRange.max, heightRange.max], [heightRange.max, heightRange.min, heightRange.max], [heightRange.max, heightRange.max, heightRange.max]]; 573 195 574 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, 0.5, initialReliefmap, g_Map.height);196 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialReliefmap); 575 197 // Apply simple erosion 576 198 for (var i = 0; i < 5; i++) 577 decayErrodeHeightmap(0.5);199 globalSmoothHeightmap(); 578 200 rescaleHeightmap(heightRange.min, heightRange.max); 579 201 580 202 RMS.SetProgress(50); … … 642 264 } 643 265 644 266 // Add further stone and metal mines 645 d erivateEntitiesByHeight({'min': heighLimits[3], 'max': ((heighLimits[4]+heighLimits[3])/2)}, startLocations);646 d erivateEntitiesByHeight({'min': ((heighLimits[5]+heighLimits[6])/2), 'max': heighLimits[7]}, startLocations);267 distributeEntitiesByHeight({ 'min': heighLimits[3], 'max': ((heighLimits[4] + heighLimits[3]) / 2) }, startLocations, 40); 268 distributeEntitiesByHeight({ 'min': ((heighLimits[5] + heighLimits[6]) / 2), 'max': heighLimits[7] }, startLocations, 40); 647 269 648 270 RMS.SetProgress(50); 649 271