Ticket #3764: add_hm_lib-2016_5_8.patch

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

Fixed an out of scope, changed var to let, shortened lib description, added comments for circular maps

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

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