Ticket #3764: add_hm_lib-2016_5_8_circular_b.patch

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

Fixed typo hight, fixed a fail by one, used getDistance

  • 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 * @param {array} [heightRange] - The height range start locations are allowed
     57 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared
     58 * @param {float} [minDistToBorder=20] - How far start locations have to be away from the map border
     59 * @param {integer} [numberOfPlayers=g_MapSettings.PlayerData.length] - How many start locations should be placed
     60 * @param {array} [heightmap=g_Map.height] - The reliefmap for the start locations to be placed on
     61 * @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular
     62 * @return {array} [finalStartLoc] - Array of 2D points in the format { "x": float, "y": float}
     63 */
     64function getStartLocationsByHeightmap(heightRange, maxTries = 1000, minDistToBorder = 20, numberOfPlayers = g_MapSettings.PlayerData.length - 1, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap)
     65{
     66    let validStartLoc = [];
     67    let r = 0.5 * (heightmap.length - 1); // Map center x/y as well as radius
     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] > heightRange.min && heightmap[x][y] < heightRange.max) // Is in height range
     71                if (!isCircular || r - getDistance(x, y, r, r) >= minDistToBorder) // Is far enough away from map border
     72                    validStartLoc.push({ "x": x, "y": y });
     73   
     74    let maxMinDist = 0;
     75    let finalStartLoc;
     76    for (let tries = 0; tries < maxTries; ++tries)
     77    {
     78        let startLoc = [];
     79        let minDist = Infinity;
     80        for (let p = 0; p < numberOfPlayers; ++p)
     81            startLoc.push(validStartLoc[randInt(validStartLoc.length)]);
     82        for (let p1 = 0; p1 < numberOfPlayers - 1; ++p1)
     83        {
     84            for (let p2 = p1 + 1; p2 < numberOfPlayers; ++p2)
     85            {
     86                let dist = getDistance(startLoc[p1].x, startLoc[p1].y, startLoc[p2].x, startLoc[p2].y);
     87                if (dist < minDist)
     88                    minDist = dist;
     89            }
     90        }
     91        if (minDist > maxMinDist)
     92        {
     93            maxMinDist = minDist;
     94            finalStartLoc = startLoc;
     95        }
     96    }
     97   
     98    return finalStartLoc;
     99}
     100
     101/**
     102 * Meant to place e.g. resource spots within a height range
     103 * @param {array} [heightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float)
     104 * @param {array} [avoidPoints] - An array of 2D points (arrays of length 2), points that will be avoided in the given minDistance e.g. start locations
     105 * @param {integer} [minDistance=30] - How many tile widths the entities to place have to be away from each other, start locations and the map border
     106 * @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on
     107 * @param {array} [entityList=[g_Gaia.stoneLarge, g_Gaia.metalLarge]] - Entity/actor strings to be placed with placeObject()
     108 * @param {integer} [maxTries=1000] - How often random player distributions are rolled to be compared
     109 * @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular
     110 */
     111function distributeEntitiesByHeight(heightRange, avoidPoints, minDistance = 30, entityList = [g_Gaia.stoneLarge, g_Gaia.metalLarge], maxTries = 1000, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap)
     112{
     113    let placements = deepcopy(avoidPoints);
     114    let validTiles = [];
     115    let r = 0.5 * (heightmap.length - 1); // Map center x/y as well as radius
     116    for (let x = minDistance; x < heightmap.length - minDistance; ++x)
     117        for (let y = minDistance; y < heightmap[0].length - minDistance; ++y)
     118            if (heightmap[x][y] > heightRange.min && heightmap[x][y] < heightRange.max) // Has the right height
     119                if (!isCircular || r - getDistance(x, y, r, r) >= minDistance) // Is far enough away from map border
     120                    validTiles.push({ "x": x, "y": y });
     121   
     122    for (let tries = 0; tries < maxTries; ++tries)
     123    {
     124        let tile = validTiles[randInt(validTiles.length)];
     125        let isValid = true;
     126        for (let p = 0; p < placements.length; ++p)
     127        {
     128            if (getDistance(placements[p].x, placements[p].y, tile.x, tile.y) < minDistance)
     129            {
     130                isValid = false;
     131                break;
     132            }
     133        }
     134        if (isValid)
     135        {
     136            placeObject(tile.x, tile.y, entityList[randInt(entityList.length)], 0, randFloat(0, 2*PI));
     137            placements.push(tile);
     138        }
     139    }
     140}
     141
     142/**
     143 * Sets a given heightmap to entirely random values within a given range
     144 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled
     145 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled
     146 * @param {array} [heightmap=g_Map.height] - The reliefmap that should be randomized
     147 */
     148function setRandomHeightmap(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, heightmap = g_Map.height)
     149{
     150    for (let x = 0; x < heightmap.length; ++x)
     151        for (let y = 0; y < heightmap[0].length; ++y)
     152            heightmap[x][y] = randFloat(minHeight, maxHeight);
     153}
     154
     155/**
     156 * Sets the heightmap to a relatively realistic shape
     157 * The function doubles the size of the initial heightmap (if given, else a random 2x2 one) until it's big enough, then the extend is cut off
     158 * @note min/maxHeight will not necessarily be present in the heightmap
     159 * @note On circular maps the edges (given by initialHeightmap) may not be in the playable map area
     160 * @note The impact of the initial heightmap depends on its size and target map size
     161 * @param {float} [minHeight=MIN_HEIGHT] - Lower limit of the random height to be rolled
     162 * @param {float} [maxHeight=MAX_HEIGHT] - Upper limit of the random height to be rolled
     163 * @param {array} [initialHeightmap] - Optional, Small (e.g. 3x3) heightmap describing the global shape of the map e.g. an island [[MIN_HEIGHT, MIN_HEIGHT, MIN_HEIGHT], [MIN_HEIGHT, MAX_HEIGHT, MIN_HEIGHT], [MIN_HEIGHT, MIN_HEIGHT, MIN_HEIGHT]]
     164 * @param {float} [smoothness=0.5] - Float between 0 (rough, more local structures) to 1 (smoother, only larger scale structures)
     165 * @param {array} [heightmap=g_Map.height] - The reliefmap that will be set by this function
     166 */
     167function setBaseTerrainDiamondSquare(minHeight = MIN_HEIGHT, maxHeight = MAX_HEIGHT, initialHeightmap = undefined, smoothness = 0.5, heightmap = g_Map.height)
     168{
     169    initialHeightmap = (initialHeightmap || [[randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)], [randFloat(minHeight / 2, maxHeight / 2), randFloat(minHeight / 2, maxHeight / 2)]]);
     170    let heightRange = maxHeight - minHeight;
     171    if (heightRange <= 0)
     172        warn("setBaseTerrainDiamondSquare: heightRange <= 0");
     173   
     174    let offset = heightRange / 2;
     175   
     176    // Double initialHeightmap width until target width is reached (diamond square method)
     177    let newHeightmap = [];
     178    while (initialHeightmap.length < heightmap.length)
     179    {
     180        newHeightmap = [];
     181        let oldWidth = initialHeightmap.length;
     182        // Square
     183        for (let x = 0; x < 2 * oldWidth - 1; ++x)
     184        {
     185            newHeightmap.push([]);
     186            for (let y = 0; y < 2 * oldWidth - 1; ++y)
     187            {
     188                if (x % 2 == 0 && y % 2 == 0) // Old tile
     189                    newHeightmap[x].push(initialHeightmap[x/2][y/2]);
     190                else if (x % 2 == 1 && y % 2 == 1) // New tile with diagonal old tile neighbors
     191                {
     192                    newHeightmap[x].push((initialHeightmap[(x-1)/2][(y-1)/2] + initialHeightmap[(x+1)/2][(y-1)/2] + initialHeightmap[(x-1)/2][(y+1)/2] + initialHeightmap[(x+1)/2][(y+1)/2]) / 4);
     193                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
     194                }
     195                else // New tile with straight old tile neighbors
     196                    newHeightmap[x].push(undefined); // Define later
     197            }
     198        }
     199        // Diamond
     200        for (let x = 0; x < 2 * oldWidth - 1; ++x)
     201        {
     202            for (let y = 0; y < 2 * oldWidth - 1; ++y)
     203            {
     204                if (newHeightmap[x][y] !== undefined)
     205                    continue;
     206               
     207                if (x > 0 && x + 1 < newHeightmap.length - 1 && y > 0 && y + 1 < newHeightmap.length - 1) // Not a border tile
     208                {
     209                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 4;
     210                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
     211                }
     212                else if (x < newHeightmap.length - 1 && y > 0 && y < newHeightmap.length - 1) // Left border
     213                {
     214                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x][y-1]) / 3;
     215                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
     216                }
     217                else if (x > 0 && y > 0 && y < newHeightmap.length - 1) // Right border
     218                {
     219                    newHeightmap[x][y] = (newHeightmap[x][y+1] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;
     220                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
     221                }
     222                else if (x > 0 && x < newHeightmap.length - 1 && y < newHeightmap.length - 1) // Bottom border
     223                {
     224                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x][y+1] + newHeightmap[x-1][y]) / 3;
     225                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
     226                }
     227                else if (x > 0 && x < newHeightmap.length - 1 && y > 0) // Top border
     228                {
     229                    newHeightmap[x][y] = (newHeightmap[x+1][y] + newHeightmap[x-1][y] + newHeightmap[x][y-1]) / 3;
     230                    newHeightmap[x][y] += (newHeightmap[x][y] - minHeight) / heightRange * randFloat(-offset, offset);
     231                }
     232            }
     233        }
     234        initialHeightmap = deepcopy(newHeightmap);
     235        offset /= Math.pow(2, smoothness);
     236    }
     237   
     238    // Cut initialHeightmap to fit target width
     239    let shift = [floor((newHeightmap.length - heightmap.length) / 2), floor((newHeightmap[0].length - heightmap[0].length) / 2)];
     240    for (let x = 0; x < heightmap.length; ++x)
     241        for (let y = 0; y < heightmap[0].length; ++y)
     242            heightmap[x][y] = newHeightmap[x + shift[0]][y + shift[1]];
     243}
     244
     245/**
     246 * Smoothens the entire map
     247 * @param {float} [strength=0.8] - How strong the smooth effect should be: 0 means no effect at all, 1 means quite strong, higher values might cause interferences, better apply it multiple times
     248 * @param {array} [heightmap=g_Map.height] - The heightmap to be smoothed
     249 * @param {array} [smoothMap=[[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]] - Array of offsets discribing the neighborhood tiles to smooth the height of a tile to
     250 */
     251function globalSmoothHeightmap(strength = 0.8, heightmap = g_Map.height, smoothMap = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]])
     252{
     253    let referenceHeightmap = deepcopy(heightmap);
     254    let max_x = heightmap.length;
     255    let max_y = heightmap[0].length;
     256    for (let x = 0; x < max_x; ++x)
     257    {
     258        for (let y = 0; y < max_y; ++y)
     259        {
     260            for (let i = 0; i < smoothMap.length; ++i)
     261            {
     262                let mapX = x + smoothMap[i][0];
     263                let mapY = y + smoothMap[i][1];
     264                if (mapX >= 0 && mapX < max_x && mapY >= 0 && mapY < max_y)
     265                    heightmap[x][y] += strength / smoothMap.length * (referenceHeightmap[mapX][mapY] - referenceHeightmap[x][y]);
     266            }
     267        }
     268    }
     269}
     270
     271/**
     272 * Pushes a rectangular area towards a given height smoothing it into the original terrain
     273 * @note The window function to determine the smooth is not exactly a gaussian to ensure smooth edges
     274 * @param {object} [center] - The x and y coordinates of the center point (rounded in this function)
     275 * @param {float} [dx] - Distance from the center in x direction the rectangle ends (half width, rounded in this function)
     276 * @param {float} [dy] - Distance from the center in y direction the rectangle ends (half depth, rounded in this function)
     277 * @param {float} [targetHeight] - Height the center of the rectangle will be pushed to
     278 * @param {float} [strength=1] - How strong the height is pushed: 0 means not at all, 1 means the center will be pushed to the target height
     279 * @param {array} [heightmap=g_Map.height] - The heightmap to be manipulated
     280 * @todo Make the window function an argument and maybe add some
     281 */
     282function rectangularSmoothToHeight(center, dx, dy, targetHeight, strength = 0.8, heightmap = g_Map.height)
     283{
     284    let x = round(center.x);
     285    let y = round(center.y);
     286    dx = round(dx);
     287    dy = round(dy);
     288   
     289    let heightmapWin = [];
     290    for (let wx = 0; wx < 2 * dx + 1; ++wx)
     291    {
     292        heightmapWin.push([]);
     293        for (let wy = 0; wy < 2 * dy + 1; ++wy)
     294        {
     295            let actualX = x - dx + wx;
     296            let actualY = y - dy + wy;
     297            if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
     298                heightmapWin[wx].push(heightmap[actualX][actualY]);
     299            else
     300                heightmapWin[wx].push(targetHeight);
     301        }
     302    }
     303    for (let wx = 0; wx < 2 * dx + 1; ++wx)
     304    {
     305        for (let wy = 0; wy < 2 * dy + 1; ++wy)
     306        {
     307            let actualX = x - dx + wx;
     308            let actualY = y - dy + wy;
     309            if (actualX >= 0 && actualX < heightmap.length - 1 && actualY >= 0 && actualY < heightmap[0].length - 1) // Is in map
     310            {
     311                // Window function polynomial 2nd degree
     312                let scaleX = 1 - (wx / dx - 1) * (wx / dx - 1);
     313                let scaleY = 1 - (wy / dy - 1) * (wy / dy - 1);
     314               
     315                heightmap[actualX][actualY] = heightmapWin[wx][wy] + strength * scaleX * scaleY * (targetHeight - heightmapWin[wx][wy]);
     316            }
     317        }
     318    }
     319}
  • binaries/data/mods/public/maps/random/island_stronghold.js

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