LCOV - code coverage report
Current view: top level - maps/random/rmgen-common - gaia_terrain.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 221 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 14 0.0 %

          Line data    Source code
       1             : /**
       2             :  * @file These functions are often used to create a landscape, for instance shaping mountains, hills, rivers or grass and dirt patches.
       3             :  */
       4             : 
       5             : /**
       6             :  * Bumps add slight, diverse elevation differences to otherwise completely level terrain.
       7             :  */
       8             : function createBumps(constraints, count, minSize, maxSize, spread, failFraction = 0, elevation = 2)
       9             : {
      10           0 :         g_Map.log("Creating bumps");
      11           0 :         createAreas(
      12             :                 new ChainPlacer(
      13             :                         minSize || 1,
      14             :                         maxSize || Math.floor(scaleByMapSize(4, 6)),
      15             :                         spread || Math.floor(scaleByMapSize(2, 5)),
      16             :                         failFraction),
      17             :                 new SmoothElevationPainter(ELEVATION_MODIFY, elevation, 2),
      18             :                 constraints,
      19             :                 count || scaleByMapSize(100, 200));
      20             : }
      21             : 
      22             : /**
      23             :  * Hills are elevated, planar, impassable terrain areas.
      24             :  */
      25             : function createHills(terrainset, constraints, tileClass, count, minSize, maxSize, spread, failFraction = 0.5, elevation = 18, elevationSmoothing = 2)
      26             : {
      27           0 :         g_Map.log("Creating hills");
      28           0 :         createAreas(
      29             :                 new ChainPlacer(
      30             :                         minSize || 1,
      31             :                         maxSize || Math.floor(scaleByMapSize(4, 6)),
      32             :                         spread || Math.floor(scaleByMapSize(16, 40)),
      33             :                         failFraction),
      34             :                 [
      35             :                         new LayeredPainter(terrainset, [1, elevationSmoothing]),
      36             :                         new SmoothElevationPainter(ELEVATION_SET, elevation, elevationSmoothing),
      37             :                         new TileClassPainter(tileClass)
      38             :                 ],
      39             :                 constraints,
      40             :                 count || scaleByMapSize(1, 4) * getNumPlayers());
      41             : }
      42             : 
      43             : /**
      44             :  * Mountains are impassable smoothened cones.
      45             :  */
      46             : function createMountains(terrain, constraints, tileClass, count, maxHeight, minRadius, maxRadius, numCircles)
      47             : {
      48           0 :         g_Map.log("Creating mountains");
      49           0 :         let mapSize = g_Map.getSize();
      50             : 
      51           0 :         for (let i = 0; i < (count || scaleByMapSize(1, 4) * getNumPlayers()); ++i)
      52           0 :                 createMountain(
      53             :                         maxHeight !== undefined ? maxHeight : Math.floor(scaleByMapSize(30, 50)),
      54             :                         minRadius || Math.floor(scaleByMapSize(3, 4)),
      55             :                         maxRadius || Math.floor(scaleByMapSize(6, 12)),
      56             :                         numCircles || Math.floor(scaleByMapSize(4, 10)),
      57             :                         constraints,
      58             :                         randIntExclusive(0, mapSize),
      59             :                         randIntExclusive(0, mapSize),
      60             :                         terrain,
      61             :                         tileClass,
      62             :                         14);
      63             : }
      64             : 
      65             : /**
      66             :  * Create a mountain using a technique very similar to ChainPlacer.
      67             :  */
      68             : function createMountain(maxHeight, minRadius, maxRadius, numCircles, constraints, x, z, terrain, tileClass, fcc = 0, q = [])
      69             : {
      70           0 :         let position = new Vector2D(x, z);
      71           0 :         let constraint = new AndConstraint(constraints);
      72             : 
      73           0 :         if (!g_Map.inMapBounds(position) || !constraint.allows(position))
      74           0 :                 return;
      75             : 
      76           0 :         let mapSize = g_Map.getSize();
      77           0 :         let queueEmpty = !q.length;
      78             : 
      79           0 :         let gotRet = [];
      80           0 :         for (let i = 0; i < mapSize; ++i)
      81             :         {
      82           0 :                 gotRet[i] = [];
      83           0 :                 for (let j = 0; j < mapSize; ++j)
      84           0 :                         gotRet[i][j] = -1;
      85             :         }
      86             : 
      87           0 :         --mapSize;
      88             : 
      89           0 :         minRadius = Math.max(1, Math.min(minRadius, maxRadius));
      90             : 
      91           0 :         let edges = [[x, z]];
      92           0 :         let circles = [];
      93             : 
      94           0 :         for (let i = 0; i < numCircles; ++i)
      95             :         {
      96           0 :                 let badPoint = false;
      97           0 :                 let [cx, cz] = pickRandom(edges);
      98             : 
      99             :                 let radius;
     100           0 :                 if (queueEmpty)
     101           0 :                         radius = randIntInclusive(minRadius, maxRadius);
     102             :                 else
     103             :                 {
     104           0 :                         radius = q.pop();
     105           0 :                         queueEmpty = !q.length;
     106             :                 }
     107             : 
     108           0 :                 let sx = Math.max(0, cx - radius);
     109           0 :                 let sz = Math.max(0, cz - radius);
     110           0 :                 let lx = Math.min(cx + radius, mapSize);
     111           0 :                 let lz = Math.min(cz + radius, mapSize);
     112             : 
     113           0 :                 let radius2 = Math.square(radius);
     114             : 
     115           0 :                 for (let ix = sx; ix <= lx; ++ix)
     116             :                 {
     117           0 :                         for (let iz = sz; iz <= lz; ++iz)
     118             :                         {
     119           0 :                                 let pos = new Vector2D(ix, iz);
     120             : 
     121           0 :                                 if (Math.euclidDistance2D(ix, iz, cx, cz) > radius2 || !g_Map.inMapBounds(pos))
     122           0 :                                         continue;
     123             : 
     124           0 :                                 if (!constraint.allows(pos))
     125             :                                 {
     126           0 :                                         badPoint = true;
     127           0 :                                         break;
     128             :                                 }
     129             : 
     130           0 :                                 let state = gotRet[ix][iz];
     131           0 :                                 if (state == -1)
     132             :                                 {
     133           0 :                                         gotRet[ix][iz] = -2;
     134             :                                 }
     135           0 :                                 else if (state >= 0)
     136             :                                 {
     137           0 :                                         edges.splice(state, 1);
     138           0 :                                         gotRet[ix][iz] = -2;
     139             : 
     140           0 :                                         for (let k = state; k < edges.length; ++k)
     141           0 :                                                 --gotRet[edges[k][0]][edges[k][1]];
     142             :                                 }
     143             :                         }
     144             : 
     145           0 :                         if (badPoint)
     146           0 :                                 break;
     147             :                 }
     148             : 
     149           0 :                 if (badPoint)
     150           0 :                         continue;
     151             : 
     152           0 :                 circles.push([cx, cz, radius]);
     153             : 
     154           0 :                 for (let ix = sx; ix <= lx; ++ix)
     155           0 :                         for (let iz = sz; iz <= lz; ++iz)
     156             :                         {
     157           0 :                                 if (gotRet[ix][iz] != -2 ||
     158             :                                     fcc && (x - ix > fcc || ix - x > fcc || z - iz > fcc || iz - z > fcc) ||
     159             :                                     ix > 0 && gotRet[ix-1][iz] == -1 ||
     160             :                                     iz > 0 && gotRet[ix][iz-1] == -1 ||
     161             :                                     ix < mapSize && gotRet[ix+1][iz] == -1 ||
     162             :                                     iz < mapSize && gotRet[ix][iz+1] == -1)
     163           0 :                                         continue;
     164             : 
     165           0 :                                 edges.push([ix, iz]);
     166           0 :                                 gotRet[ix][iz] = edges.length - 1;
     167             :                         }
     168             :         }
     169             : 
     170           0 :         for (let [cx, cz, radius] of circles)
     171             :         {
     172           0 :                 let circlePosition = new Vector2D(cx, cz);
     173           0 :                 let sx = Math.max(0, cx - radius);
     174           0 :                 let sz = Math.max(0, cz - radius);
     175           0 :                 let lx = Math.min(cx + radius, mapSize);
     176           0 :                 let lz = Math.min(cz + radius, mapSize);
     177             : 
     178           0 :                 let clumpHeight = radius / maxRadius * maxHeight * randFloat(0.8, 1.2);
     179             : 
     180           0 :                 for (let ix = sx; ix <= lx; ++ix)
     181           0 :                         for (let iz = sz; iz <= lz; ++iz)
     182             :                         {
     183           0 :                                 let position = new Vector2D(ix, iz);
     184           0 :                                 let distance = position.distanceTo(circlePosition);
     185             : 
     186             :                                 let newHeight =
     187           0 :                                         randIntInclusive(0, 2) +
     188             :                                         Math.round(2/3 * clumpHeight * (Math.sin(Math.PI * 2/3 * (3/4 - distance / radius)) + 0.5));
     189             : 
     190           0 :                                 if (distance > radius)
     191           0 :                                         continue;
     192             : 
     193           0 :                                 if (g_Map.getHeight(position) < newHeight)
     194           0 :                                         g_Map.setHeight(position, newHeight);
     195           0 :                                 else if (g_Map.getHeight(position) >= newHeight && g_Map.getHeight(position) < newHeight + 4)
     196           0 :                                         g_Map.setHeight(position, newHeight + 4);
     197             : 
     198           0 :                                 if (terrain)
     199           0 :                                         createTerrain(terrain).place(position);
     200             : 
     201           0 :                                 if (tileClass)
     202           0 :                                         tileClass.add(position);
     203             :                         }
     204             :         }
     205             : }
     206             : 
     207             : /**
     208             :  * Generates a volcano mountain. Smoke and lava are optional.
     209             :  *
     210             :  * @param {number} center - Vector2D location on the tilemap.
     211             :  * @param {number} tileClass - Painted onto every tile that is occupied by the volcano.
     212             :  * @param {string} terrainTexture - The texture painted onto the volcano hill.
     213             :  * @param {array} lavaTextures - Three different textures for the interior, from the outside to the inside.
     214             :  * @param {boolean} smoke - Whether to place smoke particles.
     215             :  * @param {number} elevationType - Elevation painter type, ELEVATION_SET = absolute or ELEVATION_MODIFY = relative.
     216             :  */
     217             : function createVolcano(position, tileClass, terrainTexture, lavaTextures, smoke, elevationType)
     218             : {
     219           0 :         g_Map.log("Creating volcano");
     220             : 
     221           0 :         let clLava = g_Map.createTileClass();
     222           0 :         let layers = [
     223             :                 {
     224             :                         "clumps": diskArea(scaleByMapSize(18, 25)),
     225             :                         "elevation": 15,
     226             :                         "tileClass": tileClass,
     227             :                         "steepness": 3
     228             :                 },
     229             :                 {
     230             :                         "clumps": diskArea(scaleByMapSize(16, 23)),
     231             :                         "elevation": 25,
     232             :                         "tileClass": g_Map.createTileClass(),
     233             :                         "steepness": 3
     234             :                 },
     235             :                 {
     236             :                         "clumps": diskArea(scaleByMapSize(10, 15)),
     237             :                         "elevation": 45,
     238             :                         "tileClass": g_Map.createTileClass(),
     239             :                         "steepness": 3
     240             :                 },
     241             :                 {
     242             :                         "clumps": diskArea(scaleByMapSize(8, 11)),
     243             :                         "elevation": 62,
     244             :                         "tileClass": g_Map.createTileClass(),
     245             :                         "steepness": 3
     246             :                 },
     247             :                 {
     248             :                         "clumps": diskArea(scaleByMapSize(4, 6)),
     249             :                         "elevation": 42,
     250             :                         "tileClass": clLava,
     251             :                         "painter": lavaTextures && new LayeredPainter([terrainTexture, ...lavaTextures], [1, 1, 1]),
     252             :                         "steepness": 1
     253             :                 }
     254             :         ];
     255             : 
     256           0 :         for (let i = 0; i < layers.length; ++i)
     257           0 :                 createArea(
     258             :                         new ClumpPlacer(layers[i].clumps, 0.7, 0.05, Infinity, position),
     259             :                         [
     260             :                                 layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]),
     261             :                                 new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness),
     262             :                                 new TileClassPainter(layers[i].tileClass)
     263             :                         ],
     264             :                         i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1));
     265             : 
     266           0 :         if (smoke)
     267             :         {
     268           0 :                 let num = Math.floor(diskArea(scaleByMapSize(3, 5)));
     269           0 :                 createObjectGroup(
     270             :                         new SimpleGroup(
     271             :                                 [new SimpleObject("actor|particle/smoke.xml", num, num, 0, 7)],
     272             :                                 false,
     273             :                                 clLava,
     274             :                                 position),
     275             :                         0,
     276             :                 stayClasses(tileClass, 1));
     277             :         }
     278             : }
     279             : 
     280             : /**
     281             :  * Paint the given terrain texture in the given sizes at random places of the map to diversify monotone land texturing.
     282             :  */
     283             : function createPatches(sizes, terrain, constraints, count,  tileClass, failFraction =  0.5)
     284             : {
     285           0 :         for (let size of sizes)
     286           0 :                 createAreas(
     287             :                         new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, failFraction),
     288             :                         [
     289             :                                 new TerrainPainter(terrain),
     290             :                                 new TileClassPainter(tileClass)
     291             :                         ],
     292             :                         constraints,
     293             :                         count);
     294             : }
     295             : 
     296             : /**
     297             :  * Same as createPatches, but each patch consists of a set of textures drawn depending to the distance of the patch border.
     298             :  */
     299             : function createLayeredPatches(sizes, terrains, terrainWidths, constraints, count, tileClass, failFraction = 0.5)
     300             : {
     301           0 :         for (let size of sizes)
     302           0 :                 createAreas(
     303             :                         new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, failFraction),
     304             :                         [
     305             :                                 new LayeredPainter(terrains, terrainWidths),
     306             :                                 new TileClassPainter(tileClass)
     307             :                         ],
     308             :                         constraints,
     309             :                         count);
     310             : }
     311             : 
     312             : /**
     313             :  * Creates a meandering river at the given location and width.
     314             :  * Optionally calls a function on the affected tiles.
     315             :  *
     316             :  * @property start - A Vector2D in tile coordinates stating where the river starts.
     317             :  * @property end - A Vector2D in tile coordinates stating where the river ends.
     318             :  * @property parallel - Whether the shorelines should be parallel or meander separately.
     319             :  * @property width - Size between the two shorelines.
     320             :  * @property fadeDist - Size of the shoreline.
     321             :  * @property deviation - Fuzz effect on the shoreline if greater than 0.
     322             :  * @property heightRiverbed - Ground height of the riverbed.
     323             :  * @proeprty heightLand - Ground height of the end of the shoreline.
     324             :  * @property meanderShort - Strength of frequent meanders.
     325             :  * @property meanderLong - Strength of less frequent meanders.
     326             :  * @property [constraint] - If given, ignores any tiles that don't satisfy the given Constraint.
     327             :  * @property [waterFunc] - Optional function called on tiles within the river.
     328             :  *                         Provides location on the tilegrid, new elevation and
     329             :  *                         the location on the axis parallel to the river as a fraction of the river length.
     330             :  * @property [landFunc] - Optional function called on land tiles, providing ix, iz, shoreDist1, shoreDist2.
     331             :  * @property [minHeight] - If given, only changes the elevation below this height while still calling the given functions.
     332             :  */
     333             : function paintRiver(args)
     334             : {
     335           0 :         g_Map.log("Creating river");
     336             : 
     337             :         // Model the river meandering as the sum of two sine curves.
     338           0 :         let meanderShort = fractionToTiles(args.meanderShort / scaleByMapSize(35, 160));
     339           0 :         let meanderLong = fractionToTiles(args.meanderLong / scaleByMapSize(35, 100));
     340             : 
     341             :         // Unless the river is parallel, each riverside will receive an own random seed and starting angle.
     342           0 :         let seed1 = randFloat(2, 3);
     343           0 :         let seed2 = randFloat(2, 3);
     344             : 
     345           0 :         let startingAngle1 = randFloat(0, 1);
     346           0 :         let startingAngle2 = randFloat(0, 1);
     347             : 
     348             :         // Computes the deflection of the river at a given point.
     349           0 :         let riverCurve = (riverFraction, startAngle, seed) =>
     350           0 :                 meanderShort * rndRiver(startAngle + fractionToTiles(riverFraction) / 128, seed) +
     351             :                 meanderLong * rndRiver(startAngle + fractionToTiles(riverFraction) / 256, seed);
     352             : 
     353             :         // Describe river location in vectors.
     354           0 :         let riverLength = args.start.distanceTo(args.end);
     355           0 :         let unitVecRiver = Vector2D.sub(args.start, args.end).normalize();
     356             : 
     357             :         // Describe river boundaries.
     358           0 :         let riverMinX = Math.min(args.start.x, args.end.x);
     359           0 :         let riverMinZ = Math.min(args.start.y, args.end.y);
     360           0 :         let riverMaxX = Math.max(args.start.x, args.end.x);
     361           0 :         let riverMaxZ = Math.max(args.start.y, args.end.y);
     362             : 
     363           0 :         let mapSize = g_Map.getSize();
     364           0 :         for (let ix = 0; ix < mapSize; ++ix)
     365           0 :                 for (let iz = 0; iz < mapSize; ++iz)
     366             :                 {
     367           0 :                         let vecPoint = new Vector2D(ix, iz);
     368             : 
     369           0 :                         if (args.constraint && !args.constraint.allows(vecPoint))
     370           0 :                                 continue;
     371             : 
     372             :                         // Compute the shortest distance to the river.
     373           0 :                         let distanceToRiver = distanceOfPointFromLine(args.start, args.end, vecPoint);
     374             : 
     375             :                         // Closest point on the river (i.e the foot of the perpendicular).
     376           0 :                         let river = Vector2D.sub(vecPoint, unitVecRiver.perpendicular().mult(distanceToRiver));
     377             : 
     378             :                         // Only process points that actually are perpendicular with the river.
     379           0 :                         if (river.x < riverMinX || river.x > riverMaxX ||
     380             :                             river.y < riverMinZ || river.y > riverMaxZ)
     381           0 :                                 continue;
     382             : 
     383             :                         // Coordinate between 0 and 1 on the axis parallel to the river.
     384           0 :                         let riverFraction = river.distanceTo(args.start) / riverLength;
     385             : 
     386             :                         // Amplitude of the river at this location.
     387           0 :                         let riverCurve1 = riverCurve(riverFraction, startingAngle1, seed1);
     388           0 :                         let riverCurve2 = args.parallel ? riverCurve1 : riverCurve(riverFraction, startingAngle2, seed2);
     389             : 
     390             :                         // Add noise.
     391           0 :                         let deviation = args.deviation * randFloat(-1, 1);
     392             : 
     393             :                         // Compute the distance to the shoreline.
     394           0 :                         let shoreDist1 = riverCurve1 + distanceToRiver - deviation - args.width / 2;
     395           0 :                         let shoreDist2 = riverCurve2 + distanceToRiver - deviation + args.width / 2;
     396             : 
     397             :                         // Create the elevation for the water and the slopy shoreline and call the user functions.
     398           0 :                         if (shoreDist1 < 0 && shoreDist2 > 0)
     399             :                         {
     400           0 :                                 let height = args.heightRiverbed;
     401             : 
     402           0 :                                 if (shoreDist1 > -args.fadeDist)
     403           0 :                                         height += (args.heightLand - args.heightRiverbed) * (1 + shoreDist1 / args.fadeDist);
     404           0 :                                 else if (shoreDist2 < args.fadeDist)
     405           0 :                                         height += (args.heightLand - args.heightRiverbed) * (1 - shoreDist2 / args.fadeDist);
     406             : 
     407           0 :                                 if (args.minHeight === undefined || height < args.minHeight)
     408           0 :                                         g_Map.setHeight(vecPoint, height);
     409             : 
     410           0 :                                 if (args.waterFunc)
     411           0 :                                         args.waterFunc(vecPoint, height, riverFraction);
     412             :                         }
     413           0 :                         else if (args.landFunc)
     414           0 :                                 args.landFunc(vecPoint, shoreDist1, shoreDist2);
     415             :                 }
     416             : }
     417             : 
     418             : /**
     419             :  * Helper function to create a meandering river.
     420             :  * It works the same as sin or cos function with the difference that it's period is 1 instead of 2 pi.
     421             :  */
     422             : function rndRiver(f, seed)
     423             : {
     424           0 :         let rndRw = seed;
     425             : 
     426           0 :         for (let i = 0; i <= f; ++i)
     427           0 :                 rndRw = 10 * (rndRw % 1);
     428             : 
     429           0 :         let rndRr = f % 1;
     430           0 :         let retVal = (Math.floor(f) % 2 ? -1 : 1) * rndRr * (rndRr - 1);
     431             : 
     432           0 :         let rndRe = Math.floor(rndRw) % 5;
     433           0 :         if (rndRe == 0)
     434           0 :                 retVal *= 2.3 * (rndRr - 0.5) * (rndRr - 0.5);
     435           0 :         else if (rndRe == 1)
     436           0 :                 retVal *= 2.6 * (rndRr - 0.3) * (rndRr - 0.7);
     437           0 :         else if (rndRe == 2)
     438           0 :                 retVal *= 22 * (rndRr - 0.2) * (rndRr - 0.3) * (rndRr - 0.3) * (rndRr - 0.8);
     439           0 :         else if (rndRe == 3)
     440           0 :                 retVal *= 180 * (rndRr - 0.2) * (rndRr - 0.2) * (rndRr - 0.4) * (rndRr - 0.6) * (rndRr - 0.6) * (rndRr - 0.8);
     441           0 :         else if (rndRe == 4)
     442           0 :                 retVal *= 2.6 * (rndRr - 0.5) * (rndRr - 0.7);
     443             : 
     444           0 :         return retVal;
     445             : }
     446             : 
     447             : /**
     448             :  * Add small rivers with shallows starting at a central river ending at the map border, if the given Constraint is met.
     449             :  */
     450             : function createTributaryRivers(riverAngle, riverCount, riverWidth, heightRiverbed, heightRange, maxAngle, tributaryRiverTileClass, shallowTileClass, constraint)
     451             : {
     452           0 :         g_Map.log("Creating tributary rivers");
     453           0 :         let waviness = 0.4;
     454           0 :         let smoothness = scaleByMapSize(3, 12);
     455           0 :         let offset = 0.1;
     456           0 :         let tapering = 0.05;
     457           0 :         let heightShallow = -2;
     458             : 
     459           0 :         let mapSize = g_Map.getSize();
     460           0 :         let mapCenter = g_Map.getCenter();
     461           0 :         let mapBounds = g_Map.getBounds();
     462             : 
     463           0 :         let riverConstraint = avoidClasses(tributaryRiverTileClass, 3);
     464           0 :         if (shallowTileClass)
     465           0 :                 riverConstraint = new AndConstraint([riverConstraint, avoidClasses(shallowTileClass, 2)]);
     466             : 
     467           0 :         for (let i = 0; i < riverCount; ++i)
     468             :         {
     469             :                 // Determining tributary river location
     470           0 :                 let searchCenter = new Vector2D(fractionToTiles(randFloat(tapering, 1 - tapering)), mapCenter.y);
     471           0 :                 let sign = randBool() ? 1 : -1;
     472           0 :                 let distanceVec = new Vector2D(0, sign * tapering);
     473             : 
     474           0 :                 let searchStart = Vector2D.add(searchCenter, distanceVec).rotateAround(riverAngle, mapCenter);
     475           0 :                 let searchEnd = Vector2D.sub(searchCenter, distanceVec).rotateAround(riverAngle, mapCenter);
     476             : 
     477           0 :                 let start = findLocationInDirectionBasedOnHeight(searchStart, searchEnd, heightRange[0], heightRange[1], 4);
     478           0 :                 if (!start)
     479           0 :                         continue;
     480             : 
     481           0 :                 start.round();
     482           0 :                 let end = Vector2D.add(mapCenter, new Vector2D(mapSize, 0).rotate(riverAngle - sign * randFloat(maxAngle, 2 * Math.PI - maxAngle))).round();
     483             : 
     484             :                 // Create river
     485           0 :                 if (!createArea(
     486             :                         new PathPlacer(start, end, riverWidth, waviness, smoothness, offset, tapering),
     487             :                         [
     488             :                                 new SmoothElevationPainter(ELEVATION_SET, heightRiverbed, 4),
     489             :                                 new TileClassPainter(tributaryRiverTileClass)
     490             :                         ],
     491             :                         new AndConstraint([constraint, riverConstraint])))
     492           0 :                         continue;
     493             : 
     494             :                 // Create small puddles at the map border to ensure players being separated
     495           0 :                 createArea(
     496             :                         new ClumpPlacer(diskArea(riverWidth / 2), 0.95, 0.6, Infinity, end),
     497             :                         new SmoothElevationPainter(ELEVATION_SET, heightRiverbed, 3),
     498             :                         constraint);
     499             :         }
     500             : 
     501             :         // Create shallows
     502           0 :         if (shallowTileClass)
     503             :         {
     504           0 :                 g_Map.log("Creating shallows in the tributary rivers");
     505           0 :                 for (let z of [0.25, 0.75])
     506           0 :                         createPassage({
     507             :                                 "start": new Vector2D(mapBounds.left, fractionToTiles(z)).rotateAround(riverAngle, mapCenter),
     508             :                                 "end": new Vector2D(mapBounds.right, fractionToTiles(z)).rotateAround(riverAngle, mapCenter),
     509             :                                 "startWidth": scaleByMapSize(8, 12),
     510             :                                 "endWidth": scaleByMapSize(8, 12),
     511             :                                 "smoothWidth": 2,
     512             :                                 "constraints": new HeightConstraint(-Infinity, heightShallow),
     513             :                                 "startHeight": heightShallow,
     514             :                                 "endHeight": heightShallow,
     515             :                                 "tileClass": shallowTileClass
     516             :                         });
     517             :         }
     518             : }
     519             : 
     520             : /**
     521             :  * Creates a smooth, passable path between between start and end with the given startWidth and endWidth.
     522             :  * Paints the given tileclass and terrain.
     523             :  *
     524             :  * @property {Vector2D} start - Location of the passage.
     525             :  * @property {Vector2D} end
     526             :  * @property {Constraint|Array} [constraints] - Only tiles that meet these constraints are changed.
     527             :  * @property {number} startWidth - Size of the passage (perpendicular to the direction of the passage).
     528             :  * @property {number} endWidth
     529             :  * @property {number} [startHeight] - Fixed height to be used if the height at the location shouldn't be used.
     530             :  * @property {number} [endHeight]
     531             :  * @property {number} smoothWidth - Number of tiles at the passage border to apply height interpolation.
     532             :  * @property {number} [tileClass] - Marks the passage with this tile class.
     533             :  * @property {string} [terrain] - Texture to be painted on the passage area.
     534             :  * @property {string} [edgeTerrain] - Texture to be painted on the borders of the passage.
     535             :  * @returns {Area}
     536             :  */
     537             : function createPassage(args)
     538             : {
     539           0 :         let bound = x => Math.max(0, Math.min(Math.round(x), g_Map.height.length - 1));
     540             : 
     541           0 :         let startHeight = args.startHeight !== undefined ? args.startHeight : g_Map.getHeight(new Vector2D(bound(args.start.x), bound(args.start.y)));
     542           0 :         let endHeight = args.endHeight !== undefined ? args.endHeight : g_Map.getHeight(new Vector2D(bound(args.end.x), bound(args.end.y)));
     543             : 
     544           0 :         let passageVec = Vector2D.sub(args.end, args.start);
     545           0 :         let widthDirection = passageVec.perpendicular().normalize();
     546           0 :         let lengthStep = 1 / (2 * passageVec.length());
     547           0 :         let points = [];
     548             : 
     549           0 :         let constraint = args.constraints && new StaticConstraint(args.constraints);
     550             : 
     551           0 :         for (let lengthFraction = 0; lengthFraction <= 1; lengthFraction += lengthStep)
     552             :         {
     553           0 :                 let locationLength = Vector2D.add(args.start, Vector2D.mult(passageVec, lengthFraction));
     554           0 :                 let halfPassageWidth = (args.startWidth + (args.endWidth - args.startWidth) * lengthFraction) / 2;
     555           0 :                 let passageHeight = startHeight + (endHeight - startHeight) * lengthFraction;
     556             : 
     557           0 :                 for (let stepWidth = -halfPassageWidth; stepWidth <= halfPassageWidth; stepWidth += 0.5)
     558             :                 {
     559           0 :                         let location = Vector2D.add(locationLength, Vector2D.mult(widthDirection, stepWidth)).round();
     560             : 
     561           0 :                         if (!g_Map.inMapBounds(location) ||
     562             :                             constraint && !constraint.allows(location))
     563           0 :                                 continue;
     564             : 
     565           0 :                         points.push(location);
     566             : 
     567           0 :                         let smoothDistance = args.smoothWidth + Math.abs(stepWidth) - halfPassageWidth;
     568             : 
     569           0 :                         g_Map.setHeight(
     570             :                                 location,
     571             :                                 smoothDistance > 0 ?
     572             :                                         (g_Map.getHeight(location) * smoothDistance + passageHeight / smoothDistance) / (smoothDistance + 1 / smoothDistance) :
     573             :                                         passageHeight);
     574             : 
     575           0 :                         if (args.tileClass)
     576           0 :                                 args.tileClass.add(location);
     577             : 
     578           0 :                         if (args.edgeTerrain && smoothDistance > 0)
     579           0 :                                 createTerrain(args.edgeTerrain).place(location);
     580           0 :                         else if (args.terrain)
     581           0 :                                 createTerrain(args.terrain).place(location);
     582             :                 }
     583             :         }
     584             : 
     585           0 :         return new Area(points);
     586             : }
     587             : 
     588             : /**
     589             :  * Returns the first location between startPoint and endPoint that lies within the given heightrange.
     590             :  */
     591             : function findLocationInDirectionBasedOnHeight(startPoint, endPoint, minHeight, maxHeight, offset = 0)
     592             : {
     593           0 :         let stepVec = Vector2D.sub(endPoint, startPoint);
     594           0 :         let distance = Math.ceil(stepVec.length());
     595           0 :         stepVec.normalize();
     596             : 
     597           0 :         for (let i = 0; i < distance; ++i)
     598             :         {
     599           0 :                 let pos = Vector2D.add(startPoint, Vector2D.mult(stepVec, i));
     600           0 :                 let ipos = pos.clone().round();
     601             : 
     602           0 :                 if (g_Map.validHeight(ipos) &&
     603             :                     g_Map.getHeight(ipos) >= minHeight &&
     604             :                     g_Map.getHeight(ipos) <= maxHeight)
     605           0 :                         return pos.add(stepVec.mult(offset));
     606             :         }
     607             : 
     608           0 :         return undefined;
     609             : }

Generated by: LCOV version 1.14