Ticket #3764: add_hm_lib-2016_5_1c.patch

File add_hm_lib-2016_5_1c.patch, 36.8 KB (added by FeXoR, 8 years ago)

Replaced tabs in comments with spaces, added some spaces in schwarzwald

  • binaries/data/mods/public/maps/random/belgian_uplands.js

     
    44
    55// Importing rmgen libraries
    66RMS.LoadLibrary("rmgen");
     7RMS.LoadLibrary("heightmap");
    78
    89const BUILDING_ANGlE = -PI/4;
    910
     
    2122// Heightmap functionality
    2223//////////
    2324
    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 
    5025// Apply a heightmap
    5126function setReliefmap(reliefmap)
    5227{
     
    5631            setHeight(x, y, reliefmap[x][y]);
    5732}
    5833
    59 // Get minimum and maxumum height used in a heightmap
    60 function getMinAndMaxHeight(reliefmap)
    61 {
    62     var height = {};
    63     height.min = Infinity;
    64     height.max = -Infinity;
    6534
    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 newReliefmap
    98 }
    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 interference
    105 
    106     var map = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]; // Default
    107 
    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 is
    114         }
    115 
    116     return newReliefmap;
    117 }
    118 
    119 
    12035//////////
    12136// Prepare for hightmap munipulation
    12237//////////
     
    215130    log("Starting giant while loop try " + tries);
    216131
    217132    // Generate reliefmap
    218     var myReliefmap = getRandomReliefmap(heightRange.min, heightRange.max);
     133    var myReliefmap = deepcopy(g_Map.height);
     134    setRandomHeightmap(heightRange.min, heightRange.max, myReliefmap);
    219135    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);
    221137
    222     myReliefmap = getRescaledReliefmap(myReliefmap, heightRange.min, heightRange.max);
     138    rescaleHeightmap(heightRange.min, heightRange.max, myReliefmap);
    223139    setReliefmap(myReliefmap);
    224140   
    225141    // 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 */
     21function 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 */
     47function 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 */
     66function 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 */
     110function 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 */
     146function 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 * On circular maps the edges (given by initialHeightmap) may not be in the playable map area
     157 * The function doubles the size of the initial heightmap (if given, else a random 2x2 one) until it's big enough.
     158 * Then the extend is cut off. So the impact and scale of the initial heightmap depends on its size and the target map size!
     159 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled
     160 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled
     161 * @param {float} [smoothness=0.5] - Float between 0 (rough, more local structures) to 1 (smoother, only larger scale structures)
     162 * @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]]
     163 * @param {array} [heightmap=g_Map.height] - The reliefmap that will be set by this function
     164 */
     165function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, initialHeightmap, smoothness = 0.5, heightmap = g_Map.height)
     166{
     167    initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]);
     168    var heightRange = maxHeight - minHeight;
     169    if (heightRange <= 0)
     170        warn("setBaseTerrainDiamondSquare: heightRange <= 0");
     171   
     172    var offset = heightRange / 2;
     173   
     174    // Double initialHeightmap width until target width is reached (diamond square method)
     175    while (initialHeightmap.length < heightmap.length)
     176    {
     177        var newHeightmap = [];
     178        var oldWidth = initialHeightmap.length;
     179        // Square
     180        for (var x = 0; x < 2 * oldWidth - 1; ++x)
     181        {
     182            newHeightmap.push([]);
     183            for (var y = 0; y < 2 * oldWidth - 1; ++y)
     184            {
     185                if (x % 2 == 0 && y % 2 == 0) // Old tile
     186                    newHeightmap[x].push(initialHeightmap[x/2][y/2]);
     187                else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors
     188                {
     189                    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);
     190                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     191                }
     192                else // New tile with straight old tile neighbors
     193                    newHeightmap[x].push(undefined); // Define later
     194            }
     195        }
     196        // Diamond
     197        for (var x = 0; x < 2 * oldWidth - 1; ++x)
     198        {
     199            for (var y = 0; y < 2 * oldWidth - 1; ++y)
     200            {
     201                if (newHeightmap[x][y] !=== undefined)
     202                    continue;
     203               
     204                if (x > 0 && x + 1 < newHeightmap.length - 1 && y > 0 && y + 1 < newHeightmap.length - 1) // Not a border tile
     205                {
     206                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 4;
     207                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     208                }
     209                else if (x < newHeightmap.length - 1 && y > 0 && y < newHeightmap.length - 1) // Left border
     210                {
     211                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x][y-1]) / 3;
     212                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     213                }
     214                else if (x > 0 && y > 0 && y < newHeightmap.length - 1) // Right border
     215                {
     216                    newHeightmap[x][y] = (newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;
     217                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     218                }
     219                else if (x > 0 && x < newHeightmap.length - 1 && y < newHeightmap.length - 1) // Bottom border
     220                {
     221                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y]) / 3;
     222                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     223                }
     224                else if (x > 0 && x < newHeightmap.length - 1 && y > 0) // Top border
     225                {
     226                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;
     227                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     228                }
     229            }
     230        }
     231        initialHeightmap = deepcopy(newHeightmap);
     232        offset /= Math.pow(2, smoothness);
     233    }
     234   
     235    // Cut initialHeightmap to fit target width
     236    var shift = [floor((newHeightmap.length - heightmap.length) / 2.), floor((newHeightmap[0].length - heightmap[0].length) / 2.)];
     237    for (var x = 0; x < heightmap.length; ++x)
     238        for (var y = 0; y < heightmap[0].length; ++y)
     239            heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]];
     240}
     241
     242/**
     243 * Smoothens the entire map
     244 * @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
     245 * @param {array} [heightmap=g_Map.height] - The heightmap to be smoothed
     246 * @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
     247 */
     248function 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]])
     249{
     250    var referenceHeightmap = deepcopy(heightmap);
     251    var max_x = heightmap.length;
     252    var max_y = heightmap[0].length;
     253    for (var x = 0; x < max_x; ++x)
     254    {
     255        for (var y = 0; y < max_y; ++y)
     256        {
     257            for (var i = 0; i < smoothMap.length; ++i)
     258            {
     259                var mapX = x + smoothMap[i][0];
     260                var mapY = y + smoothMap[i][1];
     261                if (mapX >= 0 && mapX < max_x && mapY >= 0 && mapY < max_y)
     262                    heightmap[x][y] += strength / smoothMap.length * (referenceHeightmap[mapX][mapY] - referenceHeightmap[x][y]);
     263            }
     264        }
     265    }
     266}
     267
     268/**
     269 * Pushes a rectangular area towards a given height smoothing it into the original terrain
     270 * The window function to determine the smooth is not exactly a gaussian to ensure smooth edges
     271 * @param {object} [center] - The x and y coordinates of the center point (rounded in this function)
     272 * @param {float} [dx] - Distance from the center in x direction the rectangle ends (half width, rounded in this function)
     273 * @param {float} [dy] - Distance from the center in y direction the rectangle ends (half depth, rounded in this function)
     274 * @param {float} [targetHeight] - Height the center of the rectangle will be pushed to
     275 * @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
     276 * @param {array} [heightmap=g_Map.height] - The heightmap to be manipulated
     277 * ToDo:    Make the window function an argument and maybe add some
     278 */
     279function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength = 0.8, heightmap = g_Map.height)
     280{
     281    var x = round(center.x);
     282    var y = round(center.y);
     283    dx = round(dx);
     284    dy = round(dy);
     285   
     286    var heightmapWin = [];
     287    for (var wx = 0; wx < 2 * dx + 1; ++wx)
     288    {
     289        heightmapWin.push([]);
     290        for (var wy = 0; wy < 2 * dy + 1; ++wy)
     291        {
     292            var actualX = x - dx + wx;
     293            var actualY = y - dy + wy;
     294            if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
     295                heightmapWin[wx].push(heightmap[actualX][actualY]);
     296            else
     297                heightmapWin[wx].push(targetHeight);
     298        }
     299    }
     300    for (var wx = 0; wx < 2 * dx + 1; ++wx)
     301    {
     302        for (var wy = 0; wy < 2 * dy + 1; ++wy)
     303        {
     304            var actualX = x - dx + wx;
     305            var actualY = y - dy + wy;
     306            if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
     307            {
     308                // Window function polynomial 2nd degree
     309                var scaleX = 1 - (wx / dx - 1) * (wx / dx - 1);
     310                var scaleY = 1 - (wy / dy - 1) * (wy / dy - 1);
     311               
     312                heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]);
     313            }
     314        }
     315    }
     316}
  • binaries/data/mods/public/maps/random/island_stronghold.js

     
    1 function decayErrodeHeightmap(strength, heightmap)
    2 {
    3     strength = strength || 0.9; // 0 to 1
    4     heightmap = heightmap || g_Map.height;
    5 
    6     let referenceHeightmap = deepcopy(heightmap);
    7     // let map = [[1, 0], [0, 1], [-1, 0], [0, -1]]; // faster
    8     let map = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]; // smoother
    9     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 is
    15 }
    16 
    171/**
    182 * Returns starting position in tile coordinates for the given player.
    193 */
     
    2812}
    2913
    3014RMS.LoadLibrary("rmgen");
     15RMS.LoadLibrary("heightmap");
    3116
    3217const g_InitialMines = 1;
    3318const g_InitialMineDistance = 14;
     
    372357
    373358log("Smoothing heightmap...");
    374359for (let i = 0; i < 5; ++i)
    375     decayErrodeHeightmap(0.5);
     360    globalSmoothHeightmap();
    376361
    377362// repaint clLand to compensate for smoothing
    378363unPaintTileClassBasedOnHeight(-10, 10, 3, clLand);
     
    420405    scaleByMapSize(4, 13)
    421406);
    422407for (let i = 0; i < 3; ++i)
    423     decayErrodeHeightmap(0.2);
     408    globalSmoothHeightmap();
    424409
    425410createStragglerTrees(
    426411        [oTree1, oTree2, oTree4, oTree3],
  • binaries/data/mods/public/maps/random/rmgen/library.js

     
    77const MIN_MAP_SIZE = 128;
    88const MAX_MAP_SIZE = 512;
    99const FALLBACK_CIV = "athen";
     10/**
     11 * Constants needed for heightmap_manipulation.js
     12 */
     13const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE // Engine limit, Roughly 700 meters
     14const MIN_HEIGHT = - SEA_LEVEL;
     15const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL;
    1016
    1117function fractionToTiles(f)
    1218{
  • binaries/data/mods/public/maps/random/schwarzwald.js

     
    44'use strict';
    55
    66RMS.LoadLibrary('rmgen');
     7RMS.LoadLibrary("heightmap");
    78
    89// initialize map
    910
     
    178179    return ret;
    179180};
    180181
    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 points
    199     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 least
    210     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 
    242182////////////////
    243183//
    244 //  Heightmap functionality
    245 //
    246 ////////////////
    247 
    248 // Some heightmap constants
    249 const MIN_HEIGHT = - SEA_LEVEL; // -20
    250 const MAX_HEIGHT = 0xFFFF/HEIGHT_UNITS_PER_METRE - SEA_LEVEL; // A bit smaller than 90
    251 
    252 // Get the diferrence between minimum and maxumum height
    253 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 getStartLocationsByHeightmap
    288 Takes
    289     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) floats
    291     maxTries        Optional, default is 1000, an integer, how often random player distributions are rolled to be compared
    292     minDistToBorder Optional, default is 20, an integer, how far start locations have to be
    293     numberOfPlayers Optional, default is getNumPlayers, an integer, how many start locations should be placed
    294 Returns
    295     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 hight
    310                 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 derivateEntitiesByHeight
    341 Takes
    342     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) floats
    345     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 compared
    347     minDistance     Optional, default is 30, an integer, how far start locations have to be away from start locations and the map border
    348 Returns
    349     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 hight
    364                 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 functionality
    394 //
    395 ////////////////
    396 
    397 function setBaseTerrainDiamondSquare(minHeight, maxHeight, smoothness, initialHeightmap, heightmap)
    398 {
    399     // Make some arguments optional
    400     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         // Square
    419         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 tile
    425                     newHeightmap[x].push(initialHeightmap[x/2][y/2]);
    426                 else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors
    427                 {
    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 neighbors
    432                     newHeightmap[x].push(undefined); // Define later
    433             }
    434         }
    435 
    436         // Diamond
    437         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 tile
    443                     {
    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 border
    448                     {
    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 border
    453                     {
    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 border
    458                     {
    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 border
    463                     {
    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 width
    475     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 functionality
    485 //
    486 ////////////////
    487 
    488 function decayErrodeHeightmap(strength, heightmap)
    489 {
    490     strength = (strength || 0.9); // 0 to 1
    491     heightmap = (heightmap || g_Map.height);
    492    
    493     var referenceHeightmap = deepcopy(heightmap);
    494     // var map = [[1, 0], [0, 1], [-1, 0], [0, -1]]; // faster
    495     var map = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]; // smoother
    496     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 is
    503 }
    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 map
    524                 heightmapWin[wx].push(heightmap[actualX][actualY]);
    525             else
    526                 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 map
    537             {
    538                 // Window function polynomial 2nd degree
    539                 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 //
    550184//  Actually do stuff
    551185//
    552186////////////////
     
    571205// Setting a 3x3 Grid as initial heightmap
    572206var initialReliefmap = [[heightRange.max, heightRange.max, heightRange.max], [heightRange.max, heightRange.min, heightRange.max], [heightRange.max, heightRange.max, heightRange.max]];
    573207
    574 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, 0.5, initialReliefmap, g_Map.height);
     208setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialReliefmap);
    575209// Apply simple erosion
    576210for (var i = 0; i < 5; i++)
    577     decayErrodeHeightmap(0.5);
     211    globalSmoothHeightmap();
    578212rescaleHeightmap(heightRange.min, heightRange.max);
    579213
    580214RMS.SetProgress(50);
     
    642276}
    643277
    644278// Add further stone and metal mines
    645 derivateEntitiesByHeight({'min': heighLimits[3], 'max': ((heighLimits[4]+heighLimits[3])/2)}, startLocations);
    646 derivateEntitiesByHeight({'min': ((heighLimits[5]+heighLimits[6])/2), 'max': heighLimits[7]}, startLocations);
     279distributeEntitiesByHeight( {'min': heighLimits[3], 'max': ( (heighLimits[4]+heighLimits[3]) / 2) }, startLocations, 40);
     280distributeEntitiesByHeight( {'min': ( (heighLimits[5]+heighLimits[6]) / 2), 'max': heighLimits[7]}, startLocations, 40);
    647281
    648282RMS.SetProgress(50);
    649283