Ticket #3764: add_hm_lib-2016_5_8_circular.patch
File add_hm_lib-2016_5_8_circular.patch, 37.4 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 * To use this library use TILE_CENTERED_HEIGHT_MAP = false (default) 10 * Otherwise TILE_CENTERED_HEIGHT_MAP has nothing to do with any tile centered map in this library 11 * @todo - TILE_CENTERED_HEIGHT_MAP should be removed and g_Map.height should never be tile centered 12 */ 13 14 /** 15 * Get the height range of a heightmap 16 * @param {array} [heightmap=g_Map.height] - The reliefmap the minimum and maximum height should be determined for 17 * @return {object} [height] - Height range with 2 floats in properties "min" and "max" 18 */ 19 function getMinAndMaxHeight(heightmap = g_Map.height) 20 { 21 let height = {}; 22 height.min = Infinity; 23 height.max = - Infinity; 24 for (let x = 0; x < heightmap.length; ++x) 25 { 26 for (let y = 0; y < heightmap[x].length; ++y) 27 { 28 if (heightmap[x][y] < height.min) 29 height.min = heightmap[x][y]; 30 else if (heightmap[x][y] > height.max) 31 height.max = heightmap[x][y]; 32 } 33 } 34 return height; 35 } 36 37 /** 38 * Rescales a heightmap so its minimum and maximum height is as the arguments told preserving it's global shape 39 * @param {float} [minHeight=MIN_HEIGHT] - Minimum height that should be used for the resulting heightmap 40 * @param {float} [maxHeight=MAX_HEIGHT] - Maximum height that should be used for the resulting heightmap 41 * @param {array} [heightmap=g_Map.height] - A reliefmap 42 * @todo Add preserveCostline to leave a certain height untoucht and scale below and above that seperately 43 */ 44 function rescaleHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height) 45 { 46 let oldHeightRange = getMinAndMaxHeight(heightmap); 47 let max_x = heightmap.length; 48 let max_y = heightmap[0].length; 49 for (let x = 0; x < max_x; ++x) 50 for (let y = 0; y < max_y; ++y) 51 heightmap[x][y] = minHeight + (heightmap[x][y] - oldHeightRange.min) / (oldHeightRange.max - oldHeightRange.min) * (maxHeight - minHeight); 52 } 53 54 /** 55 * Get start location with the largest minimum distance between players 56 * @param {array} [hightRange] - The height range start locations are allowed 57 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared 58 * @param {float} [minDistToBorder=20] - How far start locations have to be away from the map border 59 * @param {integer} [numberOfPlayers=g_MapSettings.PlayerData.length] - How many start locations should be placed 60 * @param {array} [heightmap=g_Map.height] - The reliefmap for the start locations to be placed on 61 * @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular 62 * @return {array} [finalStartLoc] - Array of 2D points in the format { "x": float, "y": float} 63 */ 64 function getStartLocationsByHeightmap(hightRange, maxTries = 1000, minDistToBorder = 20, numberOfPlayers = g_MapSettings.PlayerData.length - 1, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap) 65 { 66 let validStartLoc = []; 67 let r = heightmap.length / 2; 68 for (let x = minDistToBorder; x < heightmap.length - minDistToBorder; ++x) 69 for (let y = minDistToBorder; y < heightmap[0].length - minDistToBorder; ++y) 70 if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Is in height range 71 if (!isCircular || r - Math.sqrt((r - x) * (r - x) + (r - y) * (r - y)) >= minDistToBorder) // Is far enough away from map border 72 validStartLoc.push({ "x": x, "y": y }); 73 74 let maxMinDist = 0; 75 let finalStartLoc; 76 for (let tries = 0; tries < maxTries; ++tries) 77 { 78 let startLoc = []; 79 let minDist = Infinity; 80 for (let p = 0; p < numberOfPlayers; ++p) 81 startLoc.push(validStartLoc[randInt(validStartLoc.length)]); 82 for (let p1 = 0; p1 < numberOfPlayers - 1; ++p1) 83 { 84 for (let p2 = p1 + 1; p2 < numberOfPlayers; ++p2) 85 { 86 let dist = getDistance(startLoc[p1].x, startLoc[p1].y, startLoc[p2].x, startLoc[p2].y); 87 if (dist < minDist) 88 minDist = dist; 89 } 90 } 91 if (minDist > maxMinDist) 92 { 93 maxMinDist = minDist; 94 finalStartLoc = startLoc; 95 } 96 } 97 98 return finalStartLoc; 99 } 100 101 /** 102 * Meant to place e.g. resource spots within a hight range 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 * @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular 110 */ 111 function distributeEntitiesByHeight(hightRange, avoidPoints, minDistance = 30, entityList = [g_Gaia.stoneLarge, g_Gaia.metalLarge], maxTries = 1000, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap) 112 { 113 let placements = deepcopy(avoidPoints); 114 let validTiles = []; 115 let r = heightmap.length / 2; 116 for (let x = minDistance; x < heightmap.length - minDistance; ++x) 117 for (let 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 if (!isCircular || r - Math.sqrt((r - x) * (r - x) + (r - y) * (r - y)) >= minDistance) // Is far enough away from map border 120 validTiles.push({ "x": x, "y": y }); 121 122 for (let tries = 0; tries < maxTries; ++tries) 123 { 124 let tile = validTiles[randInt(validTiles.length)]; 125 let isValid = true; 126 for (let p = 0; p < placements.length; ++p) 127 { 128 if (getDistance(placements[p].x, placements[p].y, tile.x, tile.y) < minDistance) 129 { 130 isValid = false; 131 break; 132 } 133 } 134 if (isValid) 135 { 136 placeObject(tile.x, tile.y, entityList[randInt(entityList.length)], 0, randFloat(0, 2*PI)); 137 placements.push(tile); 138 } 139 } 140 } 141 142 /** 143 * Sets a given heightmap to entirely random values within a given range 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 (let x = 0; x < heightmap.length; ++x) 151 for (let 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 * The function doubles the size of the initial heightmap (if given, else a random 2x2 one) until it's big enough, then the extend is cut off 158 * @note min/maxHeight will not necessarily be present in the heightmap 159 * @note On circular maps the edges (given by initialHeightmap) may not be in the playable map area 160 * @note The impact of the initial heightmap depends on its size and 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 {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 {float} [smoothness=0.5] - Float between 0 (rough, more local structures) to 1 (smoother, only larger scale structures) 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 = undefined, 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 let heightRange = maxHeight - minHeight; 171 if (heightRange <= 0) 172 warn("setBaseTerrainDiamondSquare: heightRange <= 0"); 173 174 let offset = heightRange / 2; 175 176 // Double initialHeightmap width until target width is reached (diamond square method) 177 let newHeightmap = []; 178 while (initialHeightmap.length < heightmap.length) 179 { 180 newHeightmap = []; 181 let oldWidth = initialHeightmap.length; 182 // Square 183 for (let x = 0; x < 2 * oldWidth - 1; ++x) 184 { 185 newHeightmap.push([]); 186 for (let y = 0; y < 2 * oldWidth - 1; ++y) 187 { 188 if (x % 2 == 0 && y % 2 == 0) // Old tile 189 newHeightmap[x].push(initialHeightmap[x/2][y/2]); 190 else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors 191 { 192 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); 193 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset); 194 } 195 else // New tile with straight old tile neighbors 196 newHeightmap[x].push(undefined); // Define later 197 } 198 } 199 // Diamond 200 for (let x = 0; x < 2 * oldWidth - 1; ++x) 201 { 202 for (let y = 0; y < 2 * oldWidth - 1; ++y) 203 { 204 if (newHeightmap[x][y] !== undefined) 205 continue; 206 207 if (x > 0 && x + 1 < newHeightmap.length - 1 && y > 0 && y + 1 < newHeightmap.length - 1) // Not a border tile 208 { 209 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 4; 210 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset); 211 } 212 else if (x < newHeightmap.length - 1 && y > 0 && y < newHeightmap.length - 1) // Left border 213 { 214 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x][y-1]) / 3; 215 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset); 216 } 217 else if (x > 0 && y > 0 && y < newHeightmap.length - 1) // Right border 218 { 219 newHeightmap[x][y] = (newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3; 220 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset); 221 } 222 else if (x > 0 && x < newHeightmap.length - 1 && y < newHeightmap.length - 1) // Bottom border 223 { 224 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y]) / 3; 225 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset); 226 } 227 else if (x > 0 && x < newHeightmap.length - 1 && y > 0) // Top border 228 { 229 newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3; 230 newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset); 231 } 232 } 233 } 234 initialHeightmap = deepcopy(newHeightmap); 235 offset /= Math.pow(2, smoothness); 236 } 237 238 // Cut initialHeightmap to fit target width 239 let shift = [floor((newHeightmap.length - heightmap.length) / 2), floor((newHeightmap[0].length - heightmap[0].length) / 2)]; 240 for (let x = 0; x < heightmap.length; ++x) 241 for (let y = 0; y < heightmap[0].length; ++y) 242 heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]]; 243 } 244 245 /** 246 * Smoothens the entire map 247 * @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 248 * @param {array} [heightmap=g_Map.height] - The heightmap to be smoothed 249 * @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 250 */ 251 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]]) 252 { 253 let referenceHeightmap = deepcopy(heightmap); 254 let max_x = heightmap.length; 255 let max_y = heightmap[0].length; 256 for (let x = 0; x < max_x; ++x) 257 { 258 for (let y = 0; y < max_y; ++y) 259 { 260 for (let i = 0; i < smoothMap.length; ++i) 261 { 262 let mapX = x + smoothMap[i][0]; 263 let mapY = y + smoothMap[i][1]; 264 if (mapX >= 0 && mapX < max_x && mapY >= 0 && mapY < max_y) 265 heightmap[x][y] += strength / smoothMap.length * (referenceHeightmap[mapX][mapY] - referenceHeightmap[x][y]); 266 } 267 } 268 } 269 } 270 271 /** 272 * Pushes a rectangular area towards a given height smoothing it into the original terrain 273 * @note The window function to determine the smooth is not exactly a gaussian to ensure smooth edges 274 * @param {object} [center] - The x and y coordinates of the center point (rounded in this function) 275 * @param {float} [dx] - Distance from the center in x direction the rectangle ends (half width, rounded in this function) 276 * @param {float} [dy] - Distance from the center in y direction the rectangle ends (half depth, rounded in this function) 277 * @param {float} [targetHeight] - Height the center of the rectangle will be pushed to 278 * @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 279 * @param {array} [heightmap=g_Map.height] - The heightmap to be manipulated 280 * @todo Make the window function an argument and maybe add some 281 */ 282 function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength = 0.8, heightmap = g_Map.height) 283 { 284 let x = round(center.x); 285 let y = round(center.y); 286 dx = round(dx); 287 dy = round(dy); 288 289 let heightmapWin = []; 290 for (let wx = 0; wx < 2 * dx + 1; ++wx) 291 { 292 heightmapWin.push([]); 293 for (let wy = 0; wy < 2 * dy + 1; ++wy) 294 { 295 let actualX = x - dx + wx; 296 let actualY = y - dy + wy; 297 if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map 298 heightmapWin[wx].push(heightmap[actualX][actualY]); 299 else 300 heightmapWin[wx].push(targetHeight); 301 } 302 } 303 for (let wx = 0; wx < 2 * dx + 1; ++wx) 304 { 305 for (let wy = 0; wy < 2 * dy + 1; ++wy) 306 { 307 let actualX = x - dx + wx; 308 let actualY = y - dy + wy; 309 if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map 310 { 311 // Window function polynomial 2nd degree 312 let scaleX = 1 - (wx / dx - 1) * (wx / dx - 1); 313 let scaleY = 1 - (wy / dy - 1) * (wy / dy - 1); 314 315 heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]); 316 } 317 } 318 } 319 } -
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