Further clarify geometric meaning of the river painting in rP11137 and r20429.
[0ad.git] / binaries / data / mods / public / maps / random / rmgen / gaia_terrain.js
blob94c2c517894066cf27d6affb46847effb17efb80
1 /**
2  * @file These functions are often used to create a landscape, for instance shaping mountains, hills, rivers or grass and dirt patches.
3  */
5 /**
6  * Bumps add slight, diverse elevation differences to otherwise completely level terrain.
7  */
8 function createBumps(constraint, count, minSize, maxSize, spread, failFraction = 0, elevation = 2)
10         log("Creating bumps...");
11         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                 constraint,
19                 count || scaleByMapSize(100, 200));
22 /**
23  * Hills are elevated, planar, impassable terrain areas.
24  */
25 function createHills(terrainset, constraint, tileClass, count, minSize, maxSize, spread, failFraction = 0.5, elevation = 18, elevationSmoothing = 2)
27         log("Creating hills...");
28         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                         paintClass(tileClass)
38                 ],
39                 constraint,
40                 count || scaleByMapSize(1, 4) * getNumPlayers());
43 /**
44  * Mountains are impassable smoothened cones.
45  */
46 function createMountains(terrain, constraint, tileClass, count, maxHeight, minRadius, maxRadius, numCircles)
48         log("Creating mountains...");
49         let mapSize = getMapSize();
51         for (let i = 0; i < (count || scaleByMapSize(1, 4) * getNumPlayers()); ++i)
52                 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                         constraint,
58                         randIntExclusive(0, mapSize),
59                         randIntExclusive(0, mapSize),
60                         terrain,
61                         tileClass,
62                         14);
65 /**
66  * Create a mountain using a technique very similar to ChainPlacer.
67  */
68 function createMountain(maxHeight, minRadius, maxRadius, numCircles, constraint, x, z, terrain, tileClass, fcc = 0, q = [])
70         if (constraint instanceof Array)
71                 constraint = new AndConstraint(constraint);
73         if (!g_Map.inMapBounds(x, z) || !constraint.allows(x, z))
74                 return;
76         let mapSize = getMapSize();
77         let queueEmpty = !q.length;
79         let gotRet = [];
80         for (let i = 0; i < mapSize; ++i)
81         {
82                 gotRet[i] = [];
83                 for (let j = 0; j < mapSize; ++j)
84                         gotRet[i][j] = -1;
85         }
87         --mapSize;
89         minRadius = Math.max(1, Math.min(minRadius, maxRadius));
91         let edges = [[x, z]];
92         let circles = [];
94         for (let i = 0; i < numCircles; ++i)
95         {
96                 let badPoint = false;
97                 let [cx, cz] = pickRandom(edges);
99                 let radius;
100                 if (queueEmpty)
101                         radius = randIntInclusive(minRadius, maxRadius);
102                 else
103                 {
104                         radius = q.pop();
105                         queueEmpty = !q.length;
106                 }
108                 let sx = Math.max(0, cx - radius);
109                 let sz = Math.max(0, cz - radius);
110                 let lx = Math.min(cx + radius, mapSize);
111                 let lz = Math.min(cz + radius, mapSize);
113                 let radius2 = Math.square(radius);
115                 for (let ix = sx; ix <= lx; ++ix)
116                 {
117                         for (let iz = sz; iz <= lz; ++iz)
118                         {
119                                 if (Math.euclidDistance2D(ix, iz, cx, cz) > radius2 || !g_Map.inMapBounds(ix, iz))
120                                         continue;
122                                 if (!constraint.allows(ix, iz))
123                                 {
124                                         badPoint = true;
125                                         break;
126                                 }
128                                 let state = gotRet[ix][iz];
129                                 if (state == -1)
130                                 {
131                                         gotRet[ix][iz] = -2;
132                                 }
133                                 else if (state >= 0)
134                                 {
135                                         edges.splice(state, 1);
136                                         gotRet[ix][iz] = -2;
138                                         for (let k = state; k < edges.length; ++k)
139                                                 --gotRet[edges[k][0]][edges[k][1]];
140                                 }
141                         }
143                         if (badPoint)
144                                 break;
145                 }
147                 if (badPoint)
148                         continue;
150                 circles.push([cx, cz, radius]);
152                 for (let ix = sx; ix <= lx; ++ix)
153                         for (let iz = sz; iz <= lz; ++iz)
154                         {
155                                 if (gotRet[ix][iz] != -2 ||
156                                     fcc && (x - ix > fcc || ix - x > fcc || z - iz > fcc || iz - z > fcc) ||
157                                     ix > 0 && gotRet[ix-1][iz] == -1 ||
158                                     iz > 0 && gotRet[ix][iz-1] == -1 ||
159                                     ix < mapSize && gotRet[ix+1][iz] == -1 ||
160                                     iz < mapSize && gotRet[ix][iz+1] == -1)
161                                         continue;
163                                 edges.push([ix, iz]);
164                                 gotRet[ix][iz] = edges.length - 1;
165                         }
166         }
168         for (let [cx, cz, radius] of circles)
169         {
170                 let sx = Math.max(0, cx - radius);
171                 let sz = Math.max(0, cz - radius);
172                 let lx = Math.min(cx + radius, mapSize);
173                 let lz = Math.min(cz + radius, mapSize);
175                 let clumpHeight = radius / maxRadius * maxHeight * randFloat(0.8, 1.2);
177                 for (let ix = sx; ix <= lx; ++ix)
178                         for (let iz = sz; iz <= lz; ++iz)
179                         {
180                                 let distance = Math.euclidDistance2D(ix, iz, cx, cz);
182                                 let newHeight =
183                                         randIntInclusive(0, 2) +
184                                         Math.round(2/3 * clumpHeight * (Math.sin(Math.PI * 2/3 * (3/4 - distance / radius)) + 0.5));
186                                 if (distance > radius)
187                                         continue;
189                                 if (getHeight(ix, iz) < newHeight)
190                                         setHeight(ix, iz, newHeight);
191                                 else if (getHeight(ix, iz) >= newHeight && getHeight(ix, iz) < newHeight + 4)
192                                         setHeight(ix, iz, newHeight + 4);
194                                 if (terrain !== undefined)
195                                         placeTerrain(ix, iz, terrain);
197                                 if (tileClass !== undefined)
198                                         addToClass(ix, iz, tileClass);
199                         }
200         }
204  * Generates a volcano mountain. Smoke and lava are optional.
206  * @param {number} fx - Horizontal coordinate of the center.
207  * @param {number} fz - Horizontal coordinate of the center.
208  * @param {number} tileClass - Painted onto every tile that is occupied by the volcano.
209  * @param {string} terrainTexture - The texture painted onto the volcano hill.
210  * @param {array} lavaTextures - Three different textures for the interior, from the outside to the inside.
211  * @param {boolean} smoke - Whether to place smoke particles.
212  * @param {number} elevationType - Elevation painter type, ELEVATION_SET = absolute or ELEVATION_MODIFY = relative.
213  */
214 function createVolcano(fx, fz, tileClass, terrainTexture, lavaTextures, smoke, elevationType)
216         log("Creating volcano");
218         let ix = Math.round(fractionToTiles(fx));
219         let iz = Math.round(fractionToTiles(fz));
221         let baseSize = getMapArea() / scaleByMapSize(1, 8);
222         let coherence = 0.7;
223         let smoothness = 0.05;
224         let failFraction = 100;
225         let steepness = 3;
227         let clLava = createTileClass();
229         let layers = [
230                 {
231                         "clumps": 0.067,
232                         "elevation": 15,
233                         "tileClass": tileClass
234                 },
235                 {
236                         "clumps": 0.05,
237                         "elevation": 25,
238                         "tileClass": createTileClass()
239                 },
240                 {
241                         "clumps": 0.02,
242                         "elevation": 45,
243                         "tileClass": createTileClass()
244                 },
245                 {
246                         "clumps": 0.011,
247                         "elevation": 62,
248                         "tileClass": createTileClass()
249                 },
250                 {
251                         "clumps": 0.003,
252                         "elevation": 42,
253                         "tileClass": clLava,
254                         "painter": lavaTextures && new LayeredPainter([terrainTexture, ...lavaTextures], [1, 1, 1]),
255                         "steepness": 1
256                 }
257         ];
259         for (let i = 0; i < layers.length; ++i)
260                 createArea(
261                         new ClumpPlacer(baseSize * layers[i].clumps, coherence, smoothness, failFraction, ix, iz),
262                         [
263                                 layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]),
264                                 new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness || steepness),
265                                 paintClass(layers[i].tileClass)
266                         ],
267                         i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1));
269         if (smoke)
270         {
271                 let num = Math.floor(baseSize * 0.002);
272                 createObjectGroup(
273                         new SimpleGroup(
274                                 [new SimpleObject("actor|particle/smoke.xml", num, num, 0, 7)],
275                                 false,
276                                 clLava,
277                                 ix,
278                                 iz),
279                         0,
280                 stayClasses(tileClass, 1));
281         }
285  * Paint the given terrain texture in the given sizes at random places of the map to diversify monotone land texturing.
286  */
287 function createPatches(sizes, terrain, constraint, count,  tileClass, failFraction =  0.5)
289         for (let size of sizes)
290                 createAreas(
291                         new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, failFraction),
292                         [
293                                 new TerrainPainter(terrain),
294                                 paintClass(tileClass)
295                         ],
296                         constraint,
297                         count);
301  * Same as createPatches, but each patch consists of a set of textures drawn depending to the distance of the patch border.
302  */
303 function createLayeredPatches(sizes, terrains, terrainWidths, constraint, count, tileClass, failFraction = 0.5)
305         for (let size of sizes)
306                 createAreas(
307                         new ChainPlacer(1, Math.floor(scaleByMapSize(3, 5)), size, failFraction),
308                         [
309                                 new LayeredPainter(terrains, terrainWidths),
310                                 paintClass(tileClass)
311                         ],
312                         constraint,
313                         count);
317  * Creates a meandering river at the given location and width.
318  * Optionally calls a function on the affected tiles.
319  * Horizontal locations and widths (including fadeDist, meandering) are fractions of the mapsize.
321  * @property horizontal - Whether the river is horizontal or vertical
322  * @property parallel - Whether the shorelines should be parallel or meander separately.
323  * @property position - Location of the river.
324  * @property width - Size between the two shorelines.
325  * @property fadeDist - Size of the shoreline.
326  * @property deviation - Fuzz effect on the shoreline if greater than 0.
327  * @property waterHeight - Ground height of the riverbed.
328  * @proeprty landHeight - Ground height of the end of the shoreline.
329  * @property meanderShort - Strength of frequent meanders.
330  * @property meanderLong - Strength of less frequent meanders.
331  * @property [constraint] - If given, ignores any tiles that don't satisfy the given Constraint.
332  * @property [waterFunc] - Optional function called on tiles within the river.
333  *                         Provides location on the tilegrid, new elevation and
334  *                         the location on the axis parallel to the river as a fraction of the river length.
335  * @property [landFunc] - Optional function called on land tiles, providing ix, iz, shoreDist1, shoreDist2.
336  * @property [minHeight] - If given, only changes the elevation below this height while still calling the given functions.
337  */
338 function paintRiver(args)
340         log("Creating the river");
342         // Model the river meandering as the sum of two sine curves.
343         let meanderShort = fractionToTiles(args.meanderShort / scaleByMapSize(35, 160));
344         let meanderLong = fractionToTiles(args.meanderLong / scaleByMapSize(35, 100));
346         // Unless the river is parallel, each riverside will receive an own random seed and starting angle.
347         let seed1 = randFloat(2, 3);
348         let seed2 = randFloat(2, 3);
350         let startingAngle1 = randFloat(0, 1);
351         let startingAngle2 = randFloat(0, 1);
353         // Computes the deflection of the river at a given point.
354         let riverCurve = (riverFraction, startAngle, seed) =>
355                 meanderShort * rndRiver(startAngle + fractionToTiles(riverFraction) / 128, seed) +
356                 meanderLong * rndRiver(startAngle + fractionToTiles(riverFraction) / 256, seed);
358         // Describe river width and length of the shoreline.
359         let halfWidth = fractionToTiles(args.width / 2);
360         let fadeDist = fractionToTiles(args.fadeDist);
362         // Describe river location in vectors.
363         let mapSize = getMapSize();
364         let vecStart = new Vector2D(args.startX, args.startZ).mult(mapSize);
365         let vecEnd = new Vector2D(args.endX, args.endZ).mult(mapSize);
366         let riverLength = vecStart.distanceTo(vecEnd);
367         let unitVecRiver = Vector2D.sub(vecStart, vecEnd).normalize();
369         // Describe river boundaries.
370         let riverMinX = Math.min(vecStart.x, vecEnd.x);
371         let riverMinZ = Math.min(vecStart.y, vecEnd.y);
372         let riverMaxX = Math.max(vecStart.x, vecEnd.x);
373         let riverMaxZ = Math.max(vecStart.y, vecEnd.y);
375         for (let ix = 0; ix < mapSize; ++ix)
376                 for (let iz = 0; iz < mapSize; ++iz)
377                 {
378                         if (args.constraint && !args.constraint.allows(ix, iz))
379                                 continue;
381                         let vecPoint = new Vector2D(ix, iz);
383                         // Compute the shortest distance to the river.
384                         let distanceToRiver = unitVecRiver.cross(Vector2D.sub(vecPoint, vecEnd));
386                         // Closest point on the river (i.e the foot of the perpendicular).
387                         let river = Vector2D.sub(vecPoint, unitVecRiver.perpendicular().mult(distanceToRiver));
389                         // Only process points that actually are perpendicular with the river.
390                         if (river.x < riverMinX || river.x > riverMaxX ||
391                             river.y < riverMinZ || river.y > riverMaxZ)
392                                 continue;
394                         // Coordinate between 0 and 1 on the axis parallel to the river.
395                         let riverFraction = river.distanceTo(vecStart) / riverLength;
397                         // Amplitude of the river at this location.
398                         let riverCurve1 = riverCurve(riverFraction, startingAngle1, seed1);
399                         let riverCurve2 = args.parallel ? riverCurve1 : riverCurve(riverFraction, startingAngle2, seed2);
401                         // Add noise.
402                         let deviation = fractionToTiles(args.deviation) * randFloat(-1, 1);
404                         // Compute the distance to the shoreline.
405                         let sign = Math.sign(distanceToRiver || 1);
406                         let shoreDist1 = sign * riverCurve1 + Math.abs(distanceToRiver) - deviation - halfWidth;
407                         let shoreDist2 = sign * riverCurve2 + Math.abs(distanceToRiver) - deviation + halfWidth;
409                         // Create the elevation for the water and the slopy shoreline and call the user functions.
410                         if (shoreDist1 < 0 && shoreDist2 > 0)
411                         {
412                                 let height = args.waterHeight;
414                                 if (shoreDist1 > -fadeDist)
415                                         height += (args.landHeight - args.waterHeight) * (1 + shoreDist1 / fadeDist);
416                                 else if (shoreDist2 < fadeDist)
417                                         height += (args.landHeight - args.waterHeight) * (1 - shoreDist2 / fadeDist);
419                                 if (args.minHeight === undefined || height < args.minHeight)
420                                         setHeight(ix, iz, height);
422                                 if (args.waterFunc)
423                                         args.waterFunc(ix, iz, height, riverFraction);
424                         }
425                         else if (args.landFunc)
426                                 args.landFunc(ix, iz, shoreDist1, shoreDist2);
427                 }
431  * Helper function to create a meandering river.
432  * It works the same as sin or cos function with the difference that it's period is 1 instead of 2 pi.
433  */
434 function rndRiver(f, seed)
436         let rndRw = seed;
438         for (let i = 0; i <= f; ++i)
439                 rndRw = 10 * (rndRw % 1);
441         let rndRr = f % 1;
442         let retVal = (Math.floor(f) % 2 ? -1 : 1) * rndRr * (rndRr - 1);
444         let rndRe = Math.floor(rndRw) % 5;
445         if (rndRe == 0)
446                 retVal *= 2.3 * (rndRr - 0.5) * (rndRr - 0.5);
447         else if (rndRe == 1)
448                 retVal *= 2.6 * (rndRr - 0.3) * (rndRr - 0.7);
449         else if (rndRe == 2)
450                 retVal *= 22 * (rndRr - 0.2) * (rndRr - 0.3) * (rndRr - 0.3) * (rndRr - 0.8);
451         else if (rndRe == 3)
452                 retVal *= 180 * (rndRr - 0.2) * (rndRr - 0.2) * (rndRr - 0.4) * (rndRr - 0.6) * (rndRr - 0.6) * (rndRr - 0.8);
453         else if (rndRe == 4)
454                 retVal *= 2.6 * (rndRr - 0.5) * (rndRr - 0.7);
456         return retVal;
460  * Add small rivers with shallows starting at a central river ending at the map border, if the given Constraint is met.
461  */
462 function createTributaryRivers(horizontal, riverCount, riverWidth, waterHeight, heightRange, maxAngle, tributaryRiverTileClass, shallowTileClass, constraint)
464         log("Creating tributary rivers...");
465         let waviness = 0.4;
466         let smoothness = scaleByMapSize(3, 12);
467         let offset = 0.1;
468         let tapering = 0.05;
470         let riverConstraint = avoidClasses(tributaryRiverTileClass, 3);
471         if (shallowTileClass)
472                 riverConstraint = new AndConstraint([riverConstraint, avoidClasses(shallowTileClass, 2)]);
474         for (let i = 0; i < riverCount; ++i)
475         {
476                 // Determining tributary start point
477                 let location = randFloat(tapering, 1 - tapering);
478                 let sign = randBool() ? 1 : -1;
479                 let angle = sign * randFloat(maxAngle, 2 * Math.PI - maxAngle);
480                 let distance = sign * tapering;
482                 let searchStart = [fractionToTiles(location), fractionToTiles(0.5 + distance)];
483                 let searchEnd = [fractionToTiles(location), fractionToTiles(0.5 - distance)];
485                 if (!horizontal)
486                 {
487                         searchStart.reverse();
488                         searchEnd.reverse();
489                 }
491                 let start = getTIPIADBON(searchStart, searchEnd, heightRange, 0.5, 4);
492                 if (!start)
493                         continue;
495                 let endX = fractionToTiles(0.5 + 0.5 * Math.cos(angle));
496                 let endZ = fractionToTiles(0.5 + 0.5 * Math.sin(angle));
498                 // Create river
499                 if (!createArea(
500                         new PathPlacer(
501                                 Math.floor(start[0]),
502                                 Math.floor(start[1]),
503                                 Math.floor(endX),
504                                 Math.floor(endZ),
505                                 riverWidth,
506                                 waviness,
507                                 smoothness,
508                                 offset,
509                                 tapering),
510                         [
511                                 new SmoothElevationPainter(ELEVATION_SET, waterHeight, 4),
512                                 paintClass(tributaryRiverTileClass)
513                         ],
514                         new AndConstraint([constraint, riverConstraint])))
515                         continue;
517                 // Create small puddles at the map border to ensure players being separated
518                 createArea(
519                         new ClumpPlacer(Math.floor(diskArea(riverWidth / 2)), 0.95, 0.6, 10, endX, endZ),
520                         new SmoothElevationPainter(ELEVATION_SET, waterHeight, 3),
521                         constraint);
522         }
524         // Create shallows
525         if (shallowTileClass)
526                 for (let z of [0.25, 0.75])
527                 {
528                         let m1 = [Math.round(fractionToTiles(0.2)), Math.round(fractionToTiles(z))];
529                         let m2 = [Math.round(fractionToTiles(0.8)), Math.round(fractionToTiles(z))];
531                         if (!horizontal)
532                         {
533                                 m1.reverse();
534                                 m2.reverse();
535                         }
537                         createShallowsPassage(...m1, ...m2, scaleByMapSize(4, 8), -2, -2, 2, shallowTileClass, undefined, waterHeight);
538                 }
542  * Create shallow water between (x1, z1) and (x2, z2) of tiles below maxHeight.
543  */
544 function createShallowsPassage(x1, z1, x2, z2, width, maxHeight, shallowHeight, smooth, tileClass, terrain, riverHeight)
546         let a = z1 - z2;
547         let b = x2 - x1;
549         let distance = Math.euclidDistance2D(x1, z1, x2, z2);
550         let mapSize = getMapSize();
552         for (let ix = 0; ix < mapSize; ++ix)
553                 for (let iz = 0; iz < mapSize; ++iz)
554                 {
555                         let c = a * (ix - x1) + b * (iz - z1);
556                         let my = iz - b * c / Math.square(distance);
557                         let inline = 0;
559                         let dis;
560                         if (b == 0)
561                         {
562                                 dis = Math.abs(ix - x1);
563                                 if (iz >= Math.min(z1, z2) && iz <= Math.max(z1, z2))
564                                         inline = 1;
565                         }
566                         else if (my >= Math.min(z1, z2) && my <= Math.max(z1, z2))
567                         {
568                                 dis = Math.abs(c) / distance;
569                                 inline = 1;
570                         }
572                         if (dis > width || !inline || getHeight(ix, iz) > maxHeight)
573                                 continue;
575                         if (dis > width - smooth)
576                                 setHeight(ix, iz, ((width - dis) * shallowHeight + riverHeight * (smooth - width + dis)) / smooth);
577                         else if (dis <= width - smooth)
578                                 setHeight(ix, iz, shallowHeight);
580                         if (tileClass !== undefined)
581                                 addToClass(ix, iz, tileClass);
583                         if (terrain !== undefined)
584                                 placeTerrain(ix, iz, terrain);
585                 }
589  * Creates a ramp from (x1, y1) to (x2, y2).
590  */
591 function createRamp(x1, y1, x2, y2, minHeight, maxHeight, width, smoothLevel, mainTerrain, edgeTerrain, tileClass)
593         let halfWidth = width / 2;
595         let x3;
596         let y3;
598         if (y1 == y2)
599         {
600                 x3 = x2;
601                 y3 = y2 + halfWidth;
602         }
603         else
604         {
605                 x3 = x2 + halfWidth;
606                 y3 = (x1 - x2) / (y1 - y2) * (x2 - x3) + y2;
607         }
609         let minBoundX = Math.max(Math.min(x1, x2) - halfWidth, 0);
610         let minBoundY = Math.max(Math.min(y1, y2) - halfWidth, 0);
611         let maxBoundX = Math.min(Math.max(x1, x2) + halfWidth, getMapSize());
612         let maxBoundY = Math.min(Math.max(y1, y2) + halfWidth, getMapSize());
614         for (let x = minBoundX; x < maxBoundX; ++x)
615                 for (let y = minBoundY; y < maxBoundY; ++y)
616                 {
617                         let lDist = distanceOfPointFromLine(x3, y3, x2, y2, x, y);
618                         let sDist = distanceOfPointFromLine(x1, y1, x2, y2, x, y);
619                         let rampLength = Math.euclidDistance2D(x1, y1, x2, y2);
621                         if (lDist > rampLength || sDist > halfWidth)
622                                 continue;
624                         let height = ((rampLength - lDist) * maxHeight + lDist * minHeight) / rampLength;
626                         if (sDist >= halfWidth - smoothLevel)
627                         {
628                                 height = (height - minHeight) * (halfWidth - sDist) / smoothLevel + minHeight;
630                                 if (edgeTerrain)
631                                         placeTerrain(x, y, edgeTerrain);
632                         }
633                         else if (mainTerrain)
634                                 placeTerrain(x, y, mainTerrain);
636                         if (tileClass !== undefined)
637                                 addToClass(x, y, tileClass);
639                         if (getHeight(Math.floor(x), Math.floor(y)) < height && height <= maxHeight)
640                                 setHeight(x, y, height);
641                 }
645  * Get The Intended Point In A Direction Based On Height.
646  * Retrieves the N'th point with a specific height in a line and returns it as a [x, y] array.
648  * @param startPoint - [x, y] array defining the start point
649  * @param endPoint - [x, y] array defining the ending point
650  * @param heightRange - [min, max] array defining the range which the height of the intended point can be. includes both "min" and "max"
651  * @param step - how much tile units per turn should the search go. more value means faster but less accurate
652  * @param n - how many points to skip before ending the search. skips """n-1 points""".
653  */
654 function getTIPIADBON(startPoint, endPoint, heightRange, step, n)
656         let X = endPoint[0] - startPoint[0];
657         let Y = endPoint[1] - startPoint[1];
659         if (!X && !Y)
660         {
661                 error("getTIPIADBON startPoint and endPoint are identical! " + new Error().stack);
662                 return undefined;
663         }
665         let M = Math.sqrt(Math.square(X) + step * Math.square(Y));
666         let stepX = step * X / M;
667         let stepY = step * Y / M;
669         let y = startPoint[1];
670         let checked = 0;
672         let mapSize = getMapSize();
674         for (let x = startPoint[0]; true; x += stepX)
675         {
676                 let ix = Math.floor(x);
677                 let iy = Math.floor(y);
679                 if (ix < mapSize || iy < mapSize)
680                 {
681                         if (getHeight(ix, iy) <= heightRange[1] &&
682                             getHeight(ix, iy) >= heightRange[0])
683                                 ++checked;
685                         if (checked >= n)
686                                 return [x, y];
687                 }
689                 y += stepY;
691                 if (y > endPoint[1] && stepY > 0 ||
692                     y < endPoint[1] && stepY < 0 ||
693                     x > endPoint[1] && stepX > 0 ||
694                     x < endPoint[1] && stepX < 0)
695                         return undefined;
696         }
698         return undefined;