Ticket #3764: add_hm_lib-2016_5_1.patch
File add_hm_lib-2016_5_1.patch, 36.8 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 … … 21 22 // Heightmap functionality 22 23 ////////// 23 24 24 // Some general heightmap settings25 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 functionality29 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 25 // Apply a heightmap 51 26 function setReliefmap(reliefmap) 52 27 { … … 56 31 setHeight(x, y, reliefmap[x][y]); 57 32 } 58 33 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 34 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 35 ////////// 121 36 // Prepare for hightmap munipulation 122 37 ////////// … … 215 130 log("Starting giant while loop try " + tries); 216 131 217 132 // Generate reliefmap 218 var myReliefmap = getRandomReliefmap(heightRange.min, heightRange.max); 133 var myReliefmap = deepcopy(g_Map.height); 134 setRandomHeightmap(heightRange.min, heightRange.max, myReliefmap); 219 135 for (var i = 0; i < 50 + mapSize/4; i++) // Cycles depend on mapsize (more cycles -> bigger structures) 220 myReliefmap = getHeightErrosionedReliefmap(myReliefmap, 1);136 globalSmoothHeightmap(0.8, myReliefmap); 221 137 222 myReliefmap = getRescaledReliefmap(myReliefmap, heightRange.min, heightRange.max);138 rescaleHeightmap(heightRange.min, heightRange.max, myReliefmap); 223 139 setReliefmap(myReliefmap); 224 140 225 141 // 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 * NOTE (ambiguous naming/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 */ 21 function getMinAndMaxHeight(heightmap = g_Map.height) 22 { 23 var height = {}; 24 height.min = Infinity; 25 height.max = - Infinity; 26 for (var x = 0; x < heightmap.length; ++x) 27 { 28 for (var y = 0; y < heightmap[x].length; ++y) 29 { 30 if (heightmap[x][y] < height.min) 31 height.min = heightmap[x][y]; 32 else if (heightmap[x][y] > height.max) 33 height.max = heightmap[x][y]; 34 } 35 } 36 return height; 37 } 38 39 /** 40 * Rescales the heightmap so its minimum and maximum height is as the arguments told (preserving it's global shape) 41 * @param {float} [minHeight=MIN_HEIGHT] - Minimum height that should be used for the resulting heightmap 42 * @param {float} [maxHeight=MAX_HEIGHT] - Maximum height that should be used for the resulting heightmap 43 * @param {array} [heightmap=g_Map.height] - A reliefmap 44 * ToDo: Add preserveRatios to not change relative heights within the given height range 45 * ToDo: Add preserveCostline to leave a certain height untoucht and scale below and above that seperately 46 */ 47 function rescaleHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height) 48 { 49 var oldHeightRange = getMinAndMaxHeight(heightmap); 50 var max_x = heightmap.length; 51 var max_y = heightmap[0].length; 52 for (var x = 0; x < max_x; ++x) 53 for (var y = 0; y < max_y; ++y) 54 heightmap[x][y] = minHeight + (heightmap[x][y] - oldHeightRange.min) / (oldHeightRange.max - oldHeightRange.min) * (maxHeight - minHeight); 55 } 56 57 /** 58 * Get start location with the largest minimum distance between players 59 * Returns an array of 2D points (arrays of length 2) 60 * @param {array} [hightRange] - The height range start locations are allowed 61 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared 62 * @param {float} [minDistToBorder=20.] - How far start locations have to be away from the map border 63 * @param {integer} [numberOfPlayers=g_MapSettings.PlayerData.length] - How many start locations should be placed 64 * @param {array} [heightmap=g_Map.height] - The reliefmap for the start locations to be placed on 65 */ 66 function getStartLocationsByHeightmap(hightRange, maxTries = 1000, minDistToBorder = 20., numberOfPlayers = g_MapSettings.PlayerData.length - 1, heightmap = g_Map.height) 67 { 68 var validStartLoc = []; 69 for (var x = minDistToBorder; x < heightmap.length - minDistToBorder; ++x) 70 for (var y = minDistToBorder; y < heightmap[0].length - minDistToBorder; ++y) 71 if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Is in height range 72 validStartLoc.push({ "x": x, "y": y }); 73 74 var maxMinDist = 0; 75 for (var tries = 0; tries < maxTries; ++tries) 76 { 77 var startLoc = []; 78 var minDist = heightmap.length; 79 for (var p = 0; p < numberOfPlayers; ++p) 80 startLoc.push(validStartLoc[randInt(validStartLoc.length)]); 81 for (var p1 = 0; p1 < numberOfPlayers - 1; ++p1) 82 { 83 for (var p2 = p1 + 1; p2 < numberOfPlayers; ++p2) 84 { 85 var dist = getDistance(startLoc[p1].x, startLoc[p1].y, startLoc[p2].x, startLoc[p2].y); 86 if (dist < minDist) 87 minDist = dist; 88 } 89 } 90 if (minDist > maxMinDist) 91 { 92 maxMinDist = minDist; 93 var finalStartLoc = startLoc; 94 } 95 } 96 97 return finalStartLoc; 98 } 99 100 /** 101 * Meant to place e.g. resource spots within a hight range 102 * Returns an array of 2D points (arrays of length 2) 103 * @param {array} [hightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float) 104 * @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 105 * @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 106 * @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on 107 * @param {array} [entityList=[g_Gaia.stoneLarge, g_Gaia.metalLarge]] - Entity/actor strings to be placed with placeObject() 108 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared 109 */ 110 function distributeEntitiesByHeight(hightRange, avoidPoints, minDistance = 30, entityList = [g_Gaia.stoneLarge, g_Gaia.metalLarge], maxTries = 1000, heightmap = g_Map.height) 111 { 112 var placements = deepcopy(avoidPoints); 113 var validTiles = []; 114 for (var x = minDistance; x < heightmap.length - minDistance; ++x) 115 for (var y = minDistance; y < heightmap[0].length - minDistance; ++y) 116 if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Has the right hight 117 validTiles.push({ "x": x, "y": y }); 118 119 for (var tries = 0; tries < maxTries; ++tries) 120 { 121 var tile = validTiles[randInt(validTiles.length)]; 122 var isValid = true; 123 for (var p = 0; p < placements.length; ++p) 124 { 125 if (getDistance(placements[p].x, placements[p].y, tile.x, tile.y) < minDistance) 126 { 127 isValid = false; 128 break; 129 } 130 } 131 if (isValid) 132 { 133 placeObject(tile.x, tile.y, entityList[randInt(entityList.length)], 0, randFloat(0, 2*PI)); 134 placements.push(tile); 135 } 136 } 137 } 138 139 /** 140 * Sets a given heightmap to entirely random values within a given range 141 * Returns nothing, directly sets the given heightmap 142 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled 143 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled 144 * @param {array} [heightmap=g_Map.height] - The reliefmap that should be randomized 145 */ 146 function setRandomHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height) 147 { 148 for (var x = 0; x < heightmap.length; ++x) 149 for (var y = 0; y < heightmap[0].length; ++y) 150 heightmap[x][y] = randFloat(minHeight, maxHeight); 151 } 152 153 /** 154 * Sets the heightmap to a relatively realistic shape 155 * min/maxHeight will not necessarily be present in the heightmap 156 * This function only supports rectangular heightmaps. 157 * This means that e.g. the edges (given by initialHeightmap) will not be in the playable map area in circular maps. 158 * The function doubles the size of the initial heightmap (if given, else a random 2x2 one) until it's big enough. 159 * Then the extend is cut. So the impact and scale of the initial heightmap depends on its size and the target map size! 160 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled 161 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled 162 * @param {float} [smoothness=0.5] - Float between 0 (rough, more local structures) to 1 (smoother, only larger scale structures) 163 * @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]] 164 * @param {array} [heightmap=g_Map.height] - The reliefmap that will be set by this function 165 */ 166 function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, initialHeightmap, smoothness = 0.5, heightmap = g_Map.height) 167 { 168 initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]); 169 var heightRange = maxHeight - minHeight; 170 if (heightRange <= 0) 171 warn("setBaseTerrainDiamondSquare: heightRange <= 0"); 172 173 var offset = heightRange / 2; 174 175 // Double initialHeightmap width until target width is reached (diamond square method) 176 while (initialHeightmap.length < heightmap.length) 177 { 178 var newHeightmap = []; 179 var oldWidth = initialHeightmap.length; 180 // Square 181 for (var x = 0; x < 2 * oldWidth - 1; ++x) 182 { 183 newHeightmap.push([]); 184 for (var y = 0; y < 2 * oldWidth - 1; ++y) 185 { 186 if (x % 2 == 0 && y % 2 == 0) // Old tile 187 newHeightmap[x].push(initialHeightmap[x/2][y/2]); 188 else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors 189 { 190 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); 191 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 192 } 193 else // New tile with straight old tile neighbors 194 newHeightmap[x].push(undefined); // Define later 195 } 196 } 197 // Diamond 198 for (var x = 0; x < 2 * oldWidth - 1; ++x) 199 { 200 for (var y = 0; y < 2 * oldWidth - 1; ++y) 201 { 202 if (newHeightmap[x][y] !=== undefined) 203 continue; 204 205 if (x > 0 && x + 1 < newHeightmap.length - 1 && y > 0 && y + 1 < newHeightmap.length - 1) // Not a border tile 206 { 207 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 4; 208 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 209 } 210 else if (x < newHeightmap.length - 1 && y > 0 && y < newHeightmap.length - 1) // Left border 211 { 212 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x][y-1]) / 3; 213 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 214 } 215 else if (x > 0 && y > 0 && y < newHeightmap.length - 1) // Right border 216 { 217 newHeightmap[x][y] = (newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3; 218 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 219 } 220 else if (x > 0 && x < newHeightmap.length - 1 && y < newHeightmap.length - 1) // Bottom border 221 { 222 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y]) / 3; 223 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 224 } 225 else if (x > 0 && x < newHeightmap.length - 1 && y > 0) // Top border 226 { 227 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3; 228 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset) 229 } 230 } 231 } 232 initialHeightmap = deepcopy(newHeightmap); 233 offset /= Math.pow(2, smoothness); 234 } 235 236 // Cut initialHeightmap to fit target width 237 var shift = [floor((newHeightmap.length - heightmap.length) / 2.), floor((newHeightmap[0].length - heightmap[0].length) / 2.)]; 238 for (var x = 0; x < heightmap.length; ++x) 239 for (var y = 0; y < heightmap[0].length; ++y) 240 heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]]; 241 } 242 243 /** 244 * Smoothens the entire map 245 * @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 246 * @param {array} [heightmap=g_Map.height] - The heightmap to be smoothed 247 * @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 248 */ 249 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]]) 250 { 251 var referenceHeightmap = deepcopy(heightmap); 252 var max_x = heightmap.length; 253 var max_y = heightmap[0].length; 254 for (var x = 0; x < max_x; ++x) 255 { 256 for (var y = 0; y < max_y; ++y) 257 { 258 for (var i = 0; i < smoothMap.length; ++i) 259 { 260 var mapX = x + smoothMap[i][0]; 261 var mapY = y + smoothMap[i][1]; 262 if (mapX >= 0 && mapX < max_x && mapY >= 0 && mapY < max_y) 263 heightmap[x][y] += strength / smoothMap.length * (referenceHeightmap[mapX][mapY] - referenceHeightmap[x][y]); 264 } 265 } 266 } 267 } 268 269 /** 270 * Pushes a rectangular area towards a given height smoothing it into the original terrain 271 * The window function to determine the smooth is not exactly a gaussian to ensure smooth edges 272 * @param {object} [center] - The x and y coordinates of the center point (rounded in this function) 273 * @param {float} [dx] - Distance from the center in x direction the rectangle ends (half width, rounded in this function) 274 * @param {float} [dy] - Distance from the center in y direction the rectangle ends (half depth, rounded in this function) 275 * @param {float} [targetHeight] - Height the center of the rectangle will be pushed to 276 * @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 277 * @param {array} [heightmap=g_Map.height] - The heightmap to be manipulated 278 * ToDo: Make the window function an argument and maybe add some 279 */ 280 function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength = 0.8, heightmap = g_Map.height) 281 { 282 var x = round(center.x); 283 var y = round(center.y); 284 dx = round(dx); 285 dy = round(dy); 286 287 var heightmapWin = []; 288 for (var wx = 0; wx < 2 * dx + 1; ++wx) 289 { 290 heightmapWin.push([]); 291 for (var wy = 0; wy < 2 * dy + 1; ++wy) 292 { 293 var actualX = x - dx + wx; 294 var actualY = y - dy + wy; 295 if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map 296 heightmapWin[wx].push(heightmap[actualX][actualY]); 297 else 298 heightmapWin[wx].push(targetHeight); 299 } 300 } 301 for (var wx = 0; wx < 2 * dx + 1; ++wx) 302 { 303 for (var wy = 0; wy < 2 * dy + 1; ++wy) 304 { 305 var actualX = x - dx + wx; 306 var actualY = y - dy + wy; 307 if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map 308 { 309 // Window function polynomial 2nd degree 310 var scaleX = 1 - (wx / dx - 1) * (wx / dx - 1); 311 var scaleY = 1 - (wy / dy - 1) * (wy / dy - 1); 312 313 heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]); 314 } 315 } 316 } 317 } -
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
4 4 'use strict'; 5 5 6 6 RMS.LoadLibrary('rmgen'); 7 RMS.LoadLibrary("heightmap"); 7 8 8 9 // initialize map 9 10 … … 178 179 return ret; 179 180 }; 180 181 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 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 182 //////////////// 243 183 // 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 184 // Actually do stuff 551 185 // 552 186 //////////////// … … 571 205 // Setting a 3x3 Grid as initial heightmap 572 206 var initialReliefmap = [[heightRange.max, heightRange.max, heightRange.max], [heightRange.max, heightRange.min, heightRange.max], [heightRange.max, heightRange.max, heightRange.max]]; 573 207 574 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, 0.5, initialReliefmap, g_Map.height);208 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialReliefmap); 575 209 // Apply simple erosion 576 210 for (var i = 0; i < 5; i++) 577 decayErrodeHeightmap(0.5);211 globalSmoothHeightmap(); 578 212 rescaleHeightmap(heightRange.min, heightRange.max); 579 213 580 214 RMS.SetProgress(50); … … 642 276 } 643 277 644 278 // 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);279 distributeEntitiesByHeight({'min': heighLimits[3], 'max': ((heighLimits[4]+heighLimits[3])/2)}, startLocations, 40); 280 distributeEntitiesByHeight({'min': ((heighLimits[5]+heighLimits[6])/2), 'max': heighLimits[7]}, startLocations, 40); 647 281 648 282 RMS.SetProgress(50); 649 283