Ticket #3764: add_hm_lib-2016_5_1f.patch

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

Added @todo and @warning, made a parameter optional, removed some dots "20."->"20", fixed an "use out of scope"

  • 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 * Get the height range of a heightmap
     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 (let x = 0; x < heightmap.length; ++x)
     28    {
     29        for (let 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 a 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 (let x = 0; x < max_x; ++x)
     54        for (let 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 * @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 * @return {array} [finalStartLoc] - Array of 2D points in the format { "x": float, "y": float}
     66 */
     67function getStartLocationsByHeightmap(hightRange, maxTries = 1000, minDistToBorder = 20, numberOfPlayers = g_MapSettings.PlayerData.length - 1, heightmap = g_Map.height)
     68{
     69    var validStartLoc = [];
     70    for (let x = minDistToBorder; x < heightmap.length - minDistToBorder; ++x)
     71        for (let y = minDistToBorder; y < heightmap[0].length - minDistToBorder; ++y)
     72            if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Is in height range
     73                validStartLoc.push({ "x": x, "y": y });
     74   
     75    var maxMinDist = 0;
     76    var finalStartLoc;
     77    for (let tries = 0; tries < maxTries; ++tries)
     78    {
     79        var startLoc = [];
     80        var minDist = heightmap.length;
     81        for (let p = 0; p < numberOfPlayers; ++p)
     82            startLoc.push(validStartLoc[randInt(validStartLoc.length)]);
     83        for (let p1 = 0; p1 < numberOfPlayers - 1; ++p1)
     84        {
     85            for (let 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            finalStartLoc = startLoc;
     96        }
     97    }
     98   
     99    return finalStartLoc;
     100}
     101
     102/**
     103 * Meant to place e.g. resource spots within a hight range
     104 * @param {array} [hightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float)
     105 * @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
     106 * @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
     107 * @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on
     108 * @param {array} [entityList=[g_Gaia.stoneLarge, g_Gaia.metalLarge]] - Entity/actor strings to be placed with placeObject()
     109 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared
     110 */
     111function distributeEntitiesByHeight(hightRange, avoidPoints, minDistance = 30, entityList = [g_Gaia.stoneLarge, g_Gaia.metalLarge], maxTries = 1000, heightmap = g_Map.height)
     112{
     113    var placements = deepcopy(avoidPoints);
     114    var validTiles = [];
     115    for (let x = minDistance; x < heightmap.length - minDistance; ++x)
     116        for (let y = minDistance; y < heightmap[0].length - minDistance; ++y)
     117            if (heightmap[x][y] > hightRange.min && heightmap[x][y] < hightRange.max) // Has the right hight
     118                validTiles.push({ "x": x, "y": y });
     119   
     120    for (let tries = 0; tries < maxTries; ++tries)
     121    {
     122        var tile = validTiles[randInt(validTiles.length)];
     123        var isValid = true;
     124        for (let p = 0; p < placements.length; ++p)
     125        {
     126            if (getDistance(placements[p].x, placements[p].y, tile.x, tile.y) < minDistance)
     127            {
     128                isValid = false;
     129                break;
     130            }
     131        }
     132        if (isValid)
     133        {
     134            placeObject(tile.x, tile.y, entityList[randInt(entityList.length)], 0, randFloat(0, 2*PI));
     135            placements.push(tile);
     136        }
     137    }
     138}
     139
     140/**
     141 * Sets a given heightmap to entirely random values within a given range
     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 (let x = 0; x < heightmap.length; ++x)
     149        for (let 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 * @note min/maxHeight will not necessarily be present in the heightmap
     156 * @note 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 {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]]
     162 * @param {float} [smoothness=0.5] - Float between 0 (rough, more local structures) to 1 (smoother, only larger scale structures)
     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 = undefined, 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 (let x = 0; x < 2 * oldWidth - 1; ++x)
     181        {
     182            newHeightmap.push([]);
     183            for (let 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 (let x = 0; x < 2 * oldWidth - 1; ++x)
     198        {
     199            for (let 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 (let x = 0; x < heightmap.length; ++x)
     238        for (let 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 (let x = 0; x < max_x; ++x)
     254    {
     255        for (let y = 0; y < max_y; ++y)
     256        {
     257            for (let 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 * @note 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 (let wx = 0; wx < 2 * dx + 1; ++wx)
     288    {
     289        heightmapWin.push([]);
     290        for (let 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 (let wx = 0; wx < 2 * dx + 1; ++wx)
     301    {
     302        for (let 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

     
    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