Ticket #3764: add_hm_lib-2016_5_1e.patch

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

Added @return doxygen comments to heightmap.js, removed some more comments

  • 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
     
    1718var mapSize = getMapSize();
    1819
    1920
    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
    5122function setReliefmap(reliefmap)
    5223{
    5324    // g_Map.height = reliefmap;
     
    5627            setHeight(x, y, reliefmap[x][y]);
    5728}
    5829
    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;
    6530
    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 
    120 //////////
    121 // Prepare for hightmap munipulation
    122 //////////
    123 
    12431// Set target min and max height depending on map size to make average stepness the same on all map sizes
    12532var heightRange = {"min": MIN_HEIGHT * mapSize / 8192, "max": MAX_HEIGHT * mapSize / 8192};
    12633
     
    215122    log("Starting giant while loop try " + tries);
    216123
    217124    // Generate reliefmap
    218     var myReliefmap = getRandomReliefmap(heightRange.min, heightRange.max);
     125    var myReliefmap = deepcopy(g_Map.height);
     126    setRandomHeightmap(heightRange.min, heightRange.max, myReliefmap);
    219127    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);
    221129
    222     myReliefmap = getRescaledReliefmap(myReliefmap, heightRange.min, heightRange.max);
     130    rescaleHeightmap(heightRange.min, heightRange.max, myReliefmap);
    223131    setReliefmap(myReliefmap);
    224132   
    225133    // Find good start position tiles
  • binaries/data/mods/public/maps/random/heightmap/heightmap.js

     
     1/**
     2 * Heightmap manipulation functionality
     3 *
     4 * A heightmapt is an array of width arrays of height floats
     5 * Width and height is normally mapSize+1 (Number of vertices is one bigger than number of tiles in each direction)
     6 * The default heightmap is g_Map.height (See the Map object)
     7 *
     8 * WARNING: Ambiguous naming and potential confusion:
     9 * For using this heightmap functionalities it is VITAL that TILE_CENTERED_HEIGHT_MAP = false (default)!
     10 * Otherwise TILE_CENTERED_HEIGHT_MAP has nothing to do with any tile centered heightmap in this library!
     11 * All tile centered heightmaps in this module are temporary objects only and should never replace g_Map.height!
     12 * (Tile centered heightmaps are one less in width and hight than heightmaps used by the engine)
     13 * (Multiple conversions will lead to strange smoothing effects at best and potentially break the map entirely!)
     14 * In the long run TILE_CENTERED_HEIGHT_MAP should be removed and g_Map.height should never be tile centered...
     15 */
     16
     17/**
     18 * Returns the minimum and maximum heights present in this heightmap (an associative array of the form {"min": minHeightValue, "max": maxHeightValue})
     19 * @param {array} [heightmap=g_Map.height] - The reliefmap the minimum and maximum height should be determined for
     20 * @return {object} [height] - Height range with 2 floats in properties "min" and "max"
     21 */
     22function getMinAndMaxHeight(heightmap = g_Map.height)
     23{
     24    var height = {};
     25    height.min = Infinity;
     26    height.max = - Infinity;
     27    for (var x = 0; x < heightmap.length; ++x)
     28    {
     29        for (var y = 0; y < heightmap[x].length; ++y)
     30        {
     31            if (heightmap[x][y] < height.min)
     32                height.min = heightmap[x][y];
     33            else if (heightmap[x][y] > height.max)
     34                height.max = heightmap[x][y];
     35        }
     36    }
     37    return height;
     38}
     39
     40/**
     41 * Rescales the heightmap so its minimum and maximum height is as the arguments told (preserving it's global shape)
     42 * @param {float} [minHeight=MIN_HEIGHT] - Minimum height that should be used for the resulting heightmap
     43 * @param {float} [maxHeight=MAX_HEIGHT] - Maximum height that should be used for the resulting heightmap
     44 * @param {array} [heightmap=g_Map.height] - A reliefmap
     45 * ToDo:    Add preserveRatios to not change relative heights within the given height range
     46 * ToDo:    Add preserveCostline to leave a certain height untoucht and scale below and above that seperately
     47 */
     48function rescaleHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height)
     49{
     50    var oldHeightRange = getMinAndMaxHeight(heightmap);
     51    var max_x = heightmap.length;
     52    var max_y = heightmap[0].length;
     53    for (var x = 0; x < max_x; ++x)
     54        for (var y = 0; y < max_y; ++y)
     55            heightmap[x][y] = minHeight + (heightmap[x][y] - oldHeightRange.min) / (oldHeightRange.max - oldHeightRange.min) * (maxHeight - minHeight);
     56}
     57
     58/**
     59 * Get start location with the largest minimum distance between players
     60 * Returns an array of 2D points (arrays of length 2)
     61 * @param {array} [hightRange] - The height range start locations are allowed
     62 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared
     63 * @param {float} [minDistToBorder=20.] - How far start locations have to be away from the map border
     64 * @param {integer} [numberOfPlayers=g_MapSettings.PlayerData.length] - How many start locations should be placed
     65 * @param {array} [heightmap=g_Map.height] - The reliefmap for the start locations to be placed on
     66 * @return {array} [finalStartLoc] - Array of 2D points in the format { "x": float, "y": float}
     67 */
     68function getStartLocationsByHeightmap(hightRange, maxTries = 1000, minDistToBorder = 20., numberOfPlayers = g_MapSettings.PlayerData.length - 1, heightmap = g_Map.height)
     69{
     70    var validStartLoc = [];
     71    for (var x = minDistToBorder; x < heightmap.length - minDistToBorder; ++x)
     72        for (var y = minDistToBorder; y < heightmap[0].length - minDistToBorder; ++y)
     73            if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Is in height range
     74                validStartLoc.push({ "x": x, "y": y });
     75   
     76    var maxMinDist = 0;
     77    for (var tries = 0; tries < maxTries; ++tries)
     78    {
     79        var startLoc = [];
     80        var minDist = heightmap.length;
     81        for (var p = 0; p < numberOfPlayers; ++p)
     82            startLoc.push(validStartLoc[randInt(validStartLoc.length)]);
     83        for (var p1 = 0; p1 < numberOfPlayers - 1; ++p1)
     84        {
     85            for (var p2 = p1 + 1; p2 < numberOfPlayers; ++p2)
     86            {
     87                var dist = getDistance(startLoc[p1].x, startLoc[p1].y, startLoc[p2].x, startLoc[p2].y);
     88                if (dist < minDist)
     89                    minDist = dist;
     90            }
     91        }
     92        if (minDist > maxMinDist)
     93        {
     94            maxMinDist = minDist;
     95            var finalStartLoc = startLoc;
     96        }
     97    }
     98   
     99    return finalStartLoc;
     100}
     101
     102/**
     103 * Meant to place e.g. resource spots within a hight range
     104 * Returns an array of 2D points (arrays of length 2)
     105 * @param {array} [hightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float)
     106 * @param {array} [avoidPoints] - An array of 2D points (arrays of length 2), points that will be avoided in the given minDistance e.g. start locations
     107 * @param {integer} [minDistance=30] - How many tile widths the entities to place have to be away from each other, start locations and the map border
     108 * @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on
     109 * @param {array} [entityList=[g_Gaia.stoneLarge, g_Gaia.metalLarge]] - Entity/actor strings to be placed with placeObject()
     110 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared
     111 */
     112function distributeEntitiesByHeight(hightRange, avoidPoints, minDistance = 30, entityList = [g_Gaia.stoneLarge, g_Gaia.metalLarge], maxTries = 1000, heightmap = g_Map.height)
     113{
     114    var placements = deepcopy(avoidPoints);
     115    var validTiles = [];
     116    for (var x = minDistance; x < heightmap.length - minDistance; ++x)
     117        for (var y = minDistance; y < heightmap[0].length - minDistance; ++y)
     118            if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Has the right hight
     119                validTiles.push({ "x": x, "y": y });
     120   
     121    for (var tries = 0; tries < maxTries; ++tries)
     122    {
     123        var tile = validTiles[randInt(validTiles.length)];
     124        var isValid = true;
     125        for (var p = 0; p < placements.length; ++p)
     126        {
     127            if (getDistance(placements[p].x, placements[p].y, tile.x, tile.y) < minDistance)
     128            {
     129                isValid = false;
     130                break;
     131            }
     132        }
     133        if (isValid)
     134        {
     135            placeObject(tile.x, tile.y, entityList[randInt(entityList.length)], 0, randFloat(0, 2*PI));
     136            placements.push(tile);
     137        }
     138    }
     139}
     140
     141/**
     142 * Sets a given heightmap to entirely random values within a given range
     143 * Returns nothing, directly sets the given heightmap
     144 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled
     145 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled
     146 * @param {array} [heightmap=g_Map.height] - The reliefmap that should be randomized
     147 */
     148function setRandomHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height)
     149{
     150    for (var x = 0; x < heightmap.length; ++x)
     151        for (var y = 0; y < heightmap[0].length; ++y)
     152            heightmap[x][y] = randFloat(minHeight, maxHeight);
     153}
     154
     155/**
     156 * Sets the heightmap to a relatively realistic shape
     157 * min/maxHeight will not necessarily be present in the heightmap
     158 * On circular maps the edges (given by initialHeightmap) may not be in the playable map area
     159 * The function doubles the size of the initial heightmap (if given, else a random 2x2 one) until it's big enough.
     160 * Then the extend is cut off. So the impact and scale of the initial heightmap depends on its size and the target map size!
     161 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled
     162 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled
     163 * @param {float} [smoothness=0.5] - Float between 0 (rough, more local structures) to 1 (smoother, only larger scale structures)
     164 * @param {array} [initialHeightmap] - Optional, Small (e.g. 3x3) heightmap describing the global shape of the map e.g. an island [[MIN_HEIGHT, MIN_HEIGHT, MIN_HEIGHT], [MIN_HEIGHT, MAX_HEIGHT, MIN_HEIGHT], [MIN_HEIGHT, MIN_HEIGHT, MIN_HEIGHT]]
     165 * @param {array} [heightmap=g_Map.height] - The reliefmap that will be set by this function
     166 */
     167function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, initialHeightmap, smoothness = 0.5, heightmap = g_Map.height)
     168{
     169    initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]);
     170    var heightRange = maxHeight - minHeight;
     171    if (heightRange <= 0)
     172        warn("setBaseTerrainDiamondSquare: heightRange <= 0");
     173   
     174    var offset = heightRange / 2;
     175   
     176    // Double initialHeightmap width until target width is reached (diamond square method)
     177    while (initialHeightmap.length < heightmap.length)
     178    {
     179        var newHeightmap = [];
     180        var oldWidth = initialHeightmap.length;
     181        // Square
     182        for (var x = 0; x < 2 * oldWidth - 1; ++x)
     183        {
     184            newHeightmap.push([]);
     185            for (var y = 0; y < 2 * oldWidth - 1; ++y)
     186            {
     187                if (x % 2 == 0 && y % 2 == 0) // Old tile
     188                    newHeightmap[x].push(initialHeightmap[x/2][y/2]);
     189                else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors
     190                {
     191                    newHeightmap[x].push((initialHeightmap[(x-1)/2][(y-1)/2] + initialHeightmap[(x+1)/2][(y-1)/2] + initialHeightmap[(x-1)/2][(y+1)/2] + initialHeightmap[(x+1)/2][(y+1)/2]) / 4);
     192                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     193                }
     194                else // New tile with straight old tile neighbors
     195                    newHeightmap[x].push(undefined); // Define later
     196            }
     197        }
     198        // Diamond
     199        for (var x = 0; x < 2 * oldWidth - 1; ++x)
     200        {
     201            for (var y = 0; y < 2 * oldWidth - 1; ++y)
     202            {
     203                if (newHeightmap[x][y] !=== undefined)
     204                    continue;
     205               
     206                if (x > 0 && x + 1 < newHeightmap.length - 1 && y > 0 && y + 1 < newHeightmap.length - 1) // Not a border tile
     207                {
     208                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 4;
     209                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     210                }
     211                else if (x < newHeightmap.length - 1 && y > 0 && y < newHeightmap.length - 1) // Left border
     212                {
     213                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x][y-1]) / 3;
     214                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     215                }
     216                else if (x > 0 && y > 0 && y < newHeightmap.length - 1) // Right border
     217                {
     218                    newHeightmap[x][y] = (newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;
     219                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     220                }
     221                else if (x > 0 && x < newHeightmap.length - 1 && y < newHeightmap.length - 1) // Bottom border
     222                {
     223                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y]) / 3;
     224                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     225                }
     226                else if (x > 0 && x < newHeightmap.length - 1 && y > 0) // Top border
     227                {
     228                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;
     229                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset)
     230                }
     231            }
     232        }
     233        initialHeightmap = deepcopy(newHeightmap);
     234        offset /= Math.pow(2, smoothness);
     235    }
     236   
     237    // Cut initialHeightmap to fit target width
     238    var shift = [floor((newHeightmap.length - heightmap.length) / 2.), floor((newHeightmap[0].length - heightmap[0].length) / 2.)];
     239    for (var x = 0; x < heightmap.length; ++x)
     240        for (var y = 0; y < heightmap[0].length; ++y)
     241            heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]];
     242}
     243
     244/**
     245 * Smoothens the entire map
     246 * @param {float} [strength=0.8] - How strong the smooth effect should be: 0 means no effect at all, 1 means quite strong, higher values might cause interferences, better apply it multiple times
     247 * @param {array} [heightmap=g_Map.height] - The heightmap to be smoothed
     248 * @param {array} [smoothMap=[[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]] - Array of offsets discribing the neighborhood tiles to smooth the height of a tile to
     249 */
     250function globalSmoothHeightmap(strength = 0.8, heightmap = g_Map.height, smoothMap = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]])
     251{
     252    var referenceHeightmap = deepcopy(heightmap);
     253    var max_x = heightmap.length;
     254    var max_y = heightmap[0].length;
     255    for (var x = 0; x < max_x; ++x)
     256    {
     257        for (var y = 0; y < max_y; ++y)
     258        {
     259            for (var i = 0; i < smoothMap.length; ++i)
     260            {
     261                var mapX = x + smoothMap[i][0];
     262                var mapY = y + smoothMap[i][1];
     263                if (mapX >= 0 && mapX < max_x && mapY >= 0 && mapY < max_y)
     264                    heightmap[x][y] += strength / smoothMap.length * (referenceHeightmap[mapX][mapY] - referenceHeightmap[x][y]);
     265            }
     266        }
     267    }
     268}
     269
     270/**
     271 * Pushes a rectangular area towards a given height smoothing it into the original terrain
     272 * The window function to determine the smooth is not exactly a gaussian to ensure smooth edges
     273 * @param {object} [center] - The x and y coordinates of the center point (rounded in this function)
     274 * @param {float} [dx] - Distance from the center in x direction the rectangle ends (half width, rounded in this function)
     275 * @param {float} [dy] - Distance from the center in y direction the rectangle ends (half depth, rounded in this function)
     276 * @param {float} [targetHeight] - Height the center of the rectangle will be pushed to
     277 * @param {float} [strength=1] - How strong the height is pushed: 0 means not at all, 1 means the center will be pushed to the target height
     278 * @param {array} [heightmap=g_Map.height] - The heightmap to be manipulated
     279 * ToDo:    Make the window function an argument and maybe add some
     280 */
     281function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength = 0.8, heightmap = g_Map.height)
     282{
     283    var x = round(center.x);
     284    var y = round(center.y);
     285    dx = round(dx);
     286    dy = round(dy);
     287   
     288    var heightmapWin = [];
     289    for (var wx = 0; wx < 2 * dx + 1; ++wx)
     290    {
     291        heightmapWin.push([]);
     292        for (var wy = 0; wy < 2 * dy + 1; ++wy)
     293        {
     294            var actualX = x - dx + wx;
     295            var actualY = y - dy + wy;
     296            if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
     297                heightmapWin[wx].push(heightmap[actualX][actualY]);
     298            else
     299                heightmapWin[wx].push(targetHeight);
     300        }
     301    }
     302    for (var wx = 0; wx < 2 * dx + 1; ++wx)
     303    {
     304        for (var wy = 0; wy < 2 * dy + 1; ++wy)
     305        {
     306            var actualX = x - dx + wx;
     307            var actualY = y - dy + wy;
     308            if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
     309            {
     310                // Window function polynomial 2nd degree
     311                var scaleX = 1 - (wx / dx - 1) * (wx / dx - 1);
     312                var scaleY = 1 - (wy / dy - 1) * (wy / dy - 1);
     313               
     314                heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]);
     315            }
     316        }
     317    }
     318}
  • binaries/data/mods/public/maps/random/island_stronghold.js

     
    1 function decayErrodeHeightmap(strength, heightmap)
    2 {
    3     strength = strength || 0.9; // 0 to 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

     
    1 // Created by Niek ten Brinke (aka niektb)
    2 // Based on FeXoR's Daimond Square Algorithm for heightmap generation and several official random maps
    3 
    4 'use strict';
    5 
    61RMS.LoadLibrary('rmgen');
     2RMS.LoadLibrary("heightmap");
    73
    8 // initialize map
    9 
    104log('Initializing map...');
    115
    126InitMap();
     
    178172    return ret;
    179173};
    180174
    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 = [];
    189175
    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 
    242176////////////////
    243 //
    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 //
    550 //  Actually do stuff
    551 //
    552 ////////////////
    553 
    554 ////////////////
    555177// Set height limits and water level by map size
    556178////////////////
    557179
     
    571193// Setting a 3x3 Grid as initial heightmap
    572194var initialReliefmap = [[heightRange.max, heightRange.max, heightRange.max], [heightRange.max, heightRange.min, heightRange.max], [heightRange.max, heightRange.max, heightRange.max]];
    573195
    574 setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, 0.5, initialReliefmap, g_Map.height);
     196setBaseTerrainDiamondSquare(heightRange.min, heightRange.max, initialReliefmap);
    575197// Apply simple erosion
    576198for (var i = 0; i < 5; i++)
    577     decayErrodeHeightmap(0.5);
     199    globalSmoothHeightmap();
    578200rescaleHeightmap(heightRange.min, heightRange.max);
    579201
    580202RMS.SetProgress(50);
     
    642264}
    643265
    644266// 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);
     267distributeEntitiesByHeight({ 'min': heighLimits[3], 'max': ((heighLimits[4] + heighLimits[3]) / 2) }, startLocations, 40);
     268distributeEntitiesByHeight({ 'min': ((heighLimits[5] + heighLimits[6]) / 2), 'max': heighLimits[7] }, startLocations, 40);
    647269
    648270RMS.SetProgress(50);
    649271