Remove some more recursive duplication in the rmgen library (painting terrain based...
[0ad.git] / binaries / data / mods / public / maps / random / rmgen / misc.js
blob6843d9514a832022252327644320f1c86701f8b9
1 /////////////////////////////////////////////////////////////////////////////////////////
2 //      passageMaker
3 //
4 //      Function for creating shallow water between two given points by changing the height of all tiles in
5 //      the path with height less than or equal to "maxheight" to "height"
6 //
7 //      x1,z1:  Starting point of path
8 //      x2,z2:  Ending point of path
9 //      width:  Width of the shallow
10 //      maxheight:              Maximum height that it changes
11 //      height:         Height of the shallow
12 //      smooth:         smooth elevation in borders
13 //      tileclass:              (Optianal) - Adds those tiles to the class given
14 //      terrain:                (Optional) - Changes the texture of the elevated land
16 /////////////////////////////////////////////////////////////////////////////////////////
18 function passageMaker(x1, z1, x2, z2, width, maxheight, height, smooth, tileclass, terrain, riverheight)
20         var tchm = TILE_CENTERED_HEIGHT_MAP;
21         TILE_CENTERED_HEIGHT_MAP = true;
22         var mapSize = g_Map.size;
23         for (var ix = 0; ix < mapSize; ix++)
24         {
25                 for (var iz = 0; iz < mapSize; iz++)
26                 {
27                         var a = z1-z2;
28                         var b = x2-x1;
29                         var c = (z1*(x1-x2))-(x1*(z1-z2));
30                         var dis = abs(a*ix + b*iz + c)/sqrt(a*a + b*b);
31                         var k = (a*ix + b*iz + c)/(a*a + b*b);
32                         var my = iz-(b*k);
33                         var inline = 0;
34                         if (b == 0)
35                         {
36                                 dis = abs(ix-x1);
37                                 if ((iz <= Math.max(z1,z2))&&(iz >= Math.min(z1,z2)))
38                                 {
39                                         inline = 1;
40                                 }
41                         }
42                         else
43                         {
44                                 if ((my <= Math.max(z1,z2))&&(my >= Math.min(z1,z2)))
45                                 {
46                                         inline = 1;
47                                 }
48                         }
49                         if ((dis <= width)&&(inline))
50                         {
51                                 if(g_Map.getHeight(ix, iz) <= maxheight)
52                                 {
53                                         if (dis > width - smooth)
54                                         {
55                                                 g_Map.setHeight(ix, iz, ((width - dis)*(height)+(riverheight)*(smooth - width + dis))/(smooth));
56                                         }
57                                         else if (dis <= width - smooth)
58                                         {
59                                                 g_Map.setHeight(ix, iz, height);
60                                         }
61                                         if (tileclass !== undefined)
62                                         {
63                                                 addToClass(ix, iz, tileclass);
64                                         }
65                                         if (terrain !== undefined)
66                                         {
67                                                 placeTerrain(ix, iz, terrain);
68                                         }
69                                 }
70                         }
71                 }
72         }
73         TILE_CENTERED_HEIGHT_MAP = tchm;
76 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
77 //rndRiver is a fuction that creates random values useful for making a jagged river.
79 //it works the same as sin or cos function. the only difference is that it's period is 1 instead of 2*pi
80 //it needs the "seed" parameter to use it to make random curves that don't get broken.
81 //seed must be created using randFloat(). or else it won't work
83 //      f:      Input: Same as angle in a sine function
84 //      seed:   Random Seed: Best to implement is to use randFloat()
86 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
88 function rndRiver(f, seed)
90         var rndRq = seed;
91         var rndRw = rndRq;
92         var rndRe = 0;
93         var rndRr = f-floor(f);
94         var rndRa = 0;
95         for (var rndRx=0; rndRx<=floor(f); rndRx++)
96         {
97                 rndRw = 10*(rndRw-floor(rndRw));
98         }
99         if (rndRx%2==0)
100         {
101                 var rndRs = -1;
102         }
103         else
104         {
105                 var rndRs = 1;
106         }
107         rndRe = (floor(rndRw))%5;
108         if (rndRe==0)
109         {
110                 rndRa = (rndRs)*2.3*(rndRr)*(rndRr-1)*(rndRr-0.5)*(rndRr-0.5);
111         }
112         else if (rndRe==1)
113         {
114                 rndRa = (rndRs)*2.6*(rndRr)*(rndRr-1)*(rndRr-0.3)*(rndRr-0.7);
115         }
116         else if (rndRe==2)
117         {
118                 rndRa = (rndRs)*22*(rndRr)*(rndRr-1)*(rndRr-0.2)*(rndRr-0.3)*(rndRr-0.3)*(rndRr-0.8);
119         }
120         else if (rndRe==3)
121         {
122                 rndRa = (rndRs)*180*(rndRr)*(rndRr-1)*(rndRr-0.2)*(rndRr-0.2)*(rndRr-0.4)*(rndRr-0.6)*(rndRr-0.6)*(rndRr-0.8);
123         }
124         else if (rndRe==4)
125         {
126                 rndRa = (rndRs)*2.6*(rndRr)*(rndRr-1)*(rndRr-0.5)*(rndRr-0.7);
127         }
128         return rndRa;
132  * Creates a meandering river at the given location and width.
133  * Optionally calls a function on the affected tiles.
135  * @property horizontal - Whether the river is horizontal or vertical
136  * @property parallel - Whether the shorelines should be parallel or meander separately.
137  * @property position - Location of the river. Number between 0 and 1.
138  * @property width - Size between the two shorelines. Number between 0 and 1.
139  * @property fadeDist - Size of the shoreline.
140  * @property deviation - Fuzz effect on the shoreline if greater than 0.
141  * @property waterHeight - Ground height of the riverbed.
142  * @proeprty landHeight - Ground height of the end of the shoreline.
143  * @property meanderShort - Strength of frequent meanders.
144  * @property meanderLong - Strength of less frequent meanders.
145  * @property waterFunc - Optional function called on water tiles, providing ix, iz, height.
146  * @property landFunc - Optional function called on land tiles, providing ix, iz, shoreDist1, shoreDist2.
147  */
148 function paintRiver(args)
150         log("Creating the river");
152         let theta1 = randFloat(0, 1);
153         let theta2 = randFloat(0, 1);
155         let seed1 = randFloat(2, 3);
156         let seed2 = randFloat(2, 3);
158         let meanderShort = args.meanderShort / scaleByMapSize(35, 160);
159         let meanderLong = args.meanderLong / scaleByMapSize(35, 100);
161         let mapSize = g_Map.size;
163         for (let ix = 0; ix < mapSize; ++ix)
164                 for (let iz = 0; iz < mapSize; ++iz)
165                 {
166                         if (args.constraint && !args.constraint.allows(ix, iz))
167                                 continue;
169                         let x = ix / (mapSize + 1.0);
170                         let z = iz / (mapSize + 1.0);
172                         let coord1 = args.horizontal ? z : x;
173                         let coord2 = args.horizontal ? x : z;
175                         // River curve at this place
176                         let cu1 = meanderShort * rndRiver(theta1 + coord2 * mapSize / 128, seed1);
177                         let cu2 = meanderShort * rndRiver(theta2 + coord2 * mapSize / 128, seed2);
179                         cu1 += meanderLong * rndRiver(theta2 + coord2 * mapSize / 256, seed2);
180                         cu2 += meanderLong * rndRiver(theta2 + coord2 * mapSize / 256, seed2);
181                         if (args.parallel)
182                                 cu2 = cu1;
184                         // Fuzz the river border
185                         let devCoord1 = coord1 * randFloat(1 - args.deviation, 1 + args.deviation);
186                         let devCoord2 = coord2 * randFloat(1 - args.deviation, 1 + args.deviation);
188                         let shoreDist1 = -devCoord1 + cu1 + args.position - args.width / 2;
189                         let shoreDist2 = -devCoord1 + cu2 + args.position + args.width / 2;
191                         if (shoreDist1 < 0 && shoreDist2 > 0)
192                         {
193                                 let height = args.waterHeight;
195                                 if (shoreDist1 > -args.fadeDist)
196                                         height += (args.landHeight - args.waterHeight) * (1 + shoreDist1 / args.fadeDist);
197                                 else if (shoreDist2 < args.fadeDist)
198                                         height += (args.landHeight - args.waterHeight) * (1 - shoreDist2 / args.fadeDist);
200                                 setHeight(ix, iz, height);
202                                 if (args.waterFunc)
203                                         args.waterFunc(ix, iz, height);
204                         }
205                         else if (args.landFunc)
206                                 args.landFunc(ix, iz, shoreDist1, shoreDist2);
207                 }
210 /////////////////////////////////////////////////////////////////////////////////////////
211 // createStartingPlayerEntities
213 //      Creates the starting player entities
214 //      fx&fz: position of player base
215 //      playerid: id of player
216 //      civEntities: use getStartingEntities(id-1) fo this one
217 //      orientation: orientation of the main base building, default is BUILDING_ORIENTATION
219 ///////////////////////////////////////////////////////////////////////////////////////////
220 function createStartingPlayerEntities(fx, fz, playerid, civEntities, orientation = BUILDING_ORIENTATION)
222         var uDist = 6;
223         var uSpace = 2;
224         placeObject(fx, fz, civEntities[0].Template, playerid, orientation);
225         for (var j = 1; j < civEntities.length; ++j)
226         {
227                 var uAngle = orientation - PI * (2-j) / 2;
228                 var count = (civEntities[j].Count !== undefined ? civEntities[j].Count : 1);
229                 for (var numberofentities = 0; numberofentities < count; numberofentities++)
230                 {
231                         var ux = fx + uDist * cos(uAngle) + numberofentities * uSpace * cos(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * cos(uAngle + PI/2));
232                         var uz = fz + uDist * sin(uAngle) + numberofentities * uSpace * sin(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * sin(uAngle + PI/2));
233                         placeObject(ux, uz, civEntities[j].Template, playerid, uAngle);
234                 }
235         }
238 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
239 // placeCivDefaultEntities
241 //      Creates the default starting player entities depending on the players civ
242 //      fx&fy: position of player base
243 //      playerid: id of player
244 //      kwargs: Takes some optional keyword arguments to tweek things
245 //              'iberWall': may be false, 'walls' (default) or 'towers'. Determines the defensive structures Iberians get as civ bonus
246 //              'orientation': angle of the main base building, default is BUILDING_ORIENTATION
248 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
249 function placeCivDefaultEntities(fx, fz, playerid, kwargs = {})
251         // Unpack kwargs
252         var iberWall = 'walls';
253         if (getMapSize() <= 128)
254                 iberWall = false;
255         if ('iberWall' in kwargs)
256                 iberWall = kwargs['iberWall'];
257         var orientation = BUILDING_ORIENTATION;
258         if ('orientation' in kwargs)
259                 orientation = kwargs['orientation'];
260         // Place default civ starting entities
261         var civ = getCivCode(playerid-1);
262         var civEntities = getStartingEntities(playerid-1);
263         var uDist = 6;
264         var uSpace = 2;
265         placeObject(fx, fz, civEntities[0].Template, playerid, orientation);
266         for (var j = 1; j < civEntities.length; ++j)
267         {
268                 var uAngle = orientation - PI * (2-j) / 2;
269                 var count = (civEntities[j].Count !== undefined ? civEntities[j].Count : 1);
270                 for (var numberofentities = 0; numberofentities < count; numberofentities++)
271                 {
272                         var ux = fx + uDist * cos(uAngle) + numberofentities * uSpace * cos(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * cos(uAngle + PI/2));
273                         var uz = fz + uDist * sin(uAngle) + numberofentities * uSpace * sin(uAngle + PI/2) - (0.75 * uSpace * floor(count / 2) * sin(uAngle + PI/2));
274                         placeObject(ux, uz, civEntities[j].Template, playerid, uAngle);
275                 }
276         }
277         // Add defensive structiures for Iberians as their civ bonus
278         if (civ == 'iber' && iberWall != false)
279         {
280                 if (iberWall == 'towers')
281                         placePolygonalWall(fx, fz, 15, ['entry'], 'tower', civ, playerid, orientation, 7);
282                 else
283                         placeGenericFortress(fx, fz, 20/*radius*/, playerid);
284         }
287 function placeDefaultChicken(playerX, playerZ, tileClass, constraint = undefined, template = "gaia/fauna_chicken")
289         for (let j = 0; j < 2; ++j)
290                 for (var tries = 0; tries < 10; ++tries)
291                 {
292                         let aAngle = randFloat(0, TWO_PI);
294                         // Roman and ptolemian civic centers have a big footprint!
295                         let aDist = 9;
297                         let aX = round(playerX + aDist * cos(aAngle));
298                         let aZ = round(playerZ + aDist * sin(aAngle));
300                         let group = new SimpleGroup(
301                                 [new SimpleObject(template, 5,5, 0,2)],
302                                 true, tileClass, aX, aZ
303                         );
305                         if (createObjectGroup(group, 0, constraint))
306                                 break;
307                 }
311  * Typically used for placing grass tufts around the civic centers.
312  */
313 function placeDefaultDecoratives(playerX, playerZ, template, tileclass, radius, constraint = undefined)
315         for (let i = 0; i < PI * radius * radius / 250; ++i)
316         {
317                 let angle = randFloat(0, 2 * PI);
318                 let dist = radius - randIntInclusive(5, 11);
320                 createObjectGroup(
321                         new SimpleGroup(
322                                 [new SimpleObject(template, 2, 5, 0, 1, -PI/8, PI/8)],
323                                 false,
324                                 tileclass,
325                                 Math.round(playerX + dist * Math.cos(angle)),
326                                 Math.round(playerZ + dist * Math.sin(angle))
327                         ), 0, constraint);
328         }
331 function modifyTilesBasedOnHeight(minHeight, maxHeight, mode, func)
333         for (let qx = 0; qx < g_Map.size; ++qx)
334                 for (let qz = 0; qz < g_Map.size; ++qz)
335                 {
336                         let height = g_Map.getHeight(qx, qz);
337                         if (mode == 0 && height >  minHeight && height < maxHeight ||
338                             mode == 1 && height >= minHeight && height < maxHeight ||
339                             mode == 2 && height >  minHeight && height <= maxHeight ||
340                             mode == 3 && height >= minHeight && height <= maxHeight)
341                         func(qx, qz);
342                 }
345 function paintTerrainBasedOnHeight(minHeight, maxHeight, mode, terrain)
347         modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => {
348                 placeTerrain(qx, qz, terrain);
349         });
352 function paintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileclass)
354         modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => {
355                 addToClass(qx, qz, tileclass);
356         });
359 function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileclass)
361         modifyTilesBasedOnHeight(minHeight, maxHeight, mode, (qx, qz) => {
362                 removeFromClass(qx, qz, tileclass);
363         });
366 /////////////////////////////////////////////////////////////////////////////////////////
367 // getTIPIADBON
369 //      "get The Intended Point In A Direction Based On Height"
370 //      gets the N'th point with a specific height in a line and returns it as a [x, y] array
371 //      startPoint: [x, y] array defining the start point
372 //      endPoint: [x, y] array defining the ending point
373 //      heightRange: [min, max] array defining the range which the height of the intended point can be. includes both "min" and "max"
374 //  step: how much tile units per turn should the search go. more value means faster but less accurate
375 //  n: how many points to skip before ending the search. skips """n-1 points""".
377 ///////////////////////////////////////////////////////////////////////////////////////////
379 function getTIPIADBON(startPoint, endPoint, heightRange, step, n)
381         var stepX = step*(endPoint[0]-startPoint[0])/(sqrt((endPoint[0]-startPoint[0])*(endPoint[0]-startPoint[0]) + step*(endPoint[1]-startPoint[1])*(endPoint[1]-startPoint[1])));
382         var stepY = step*(endPoint[1]-startPoint[1])/(sqrt((endPoint[0]-startPoint[0])*(endPoint[0]-startPoint[0]) + step*(endPoint[1]-startPoint[1])*(endPoint[1]-startPoint[1])));
383         var y = startPoint[1];
384         var checked = 0;
385         for (var x = startPoint[0]; true; x += stepX)
386         {
387                 if ((floor(x) < g_Map.size)||(floor(y) < g_Map.size))
388                 {
389                         if ((g_Map.getHeight(floor(x), floor(y)) <= heightRange[1])&&(g_Map.getHeight(floor(x), floor(y)) >= heightRange[0]))
390                         {
391                                 ++checked;
392                         }
393                         if (checked >= n)
394                         {
395                                 return [x, y];
396                         }
397                 }
398                 y += stepY;
399                 if ((y > endPoint[1])&&(stepY>0))
400                         break;
401                 if ((y < endPoint[1])&&(stepY<0))
402                         break;
403                 if ((x > endPoint[1])&&(stepX>0))
404                         break;
405                 if ((x < endPoint[1])&&(stepX<0))
406                         break;
407         }
408         return undefined;
411 /////////////////////////////////////////////////////////////////////////////////////////
412 // doIntersect
414 //      determines if two lines with the width "width" intersect or collide with each other
415 //      x1, y1, x2, y2: determine the position of the first line
416 //      x3, y3, x4, y4: determine the position of the second line
417 //      width: determines the width of the lines
419 ///////////////////////////////////////////////////////////////////////////////////////////
421 function checkIfIntersect (x1, y1, x2, y2, x3, y3, x4, y4, width)
423         if (x1 == x2)
424         {
425                 if (((x3 - x1) < width) || ((x4 - x2) < width))
426                         return true;
427         }
428         else
429         {
430                 var m = (y1 - y2) / (x1 - x2);
431                 var b = y1 - m * x1;
432                 var m2 = sqrt(m * m + 1);
433                 if ((Math.abs((y3 - x3 * m - b)/m2) < width) || (Math.abs((y4 - x4 * m - b)/m2) < width))
434                         return true;
435                 //neccessary for some situations.
436                 if (x3 == x4)
437                 {
438                         if (((x1 - x3) < width) || ((x2 - x4) < width))
439                                 return true;
440                 }
441                 else
442                 {
443                         var m = (y3 - y4) / (x3 - x4);
444                         var b = y3 - m * x3;
445                         var m2 = sqrt(m * m + 1);
446                         if ((Math.abs((y1 - x1 * m - b)/m2) < width) || (Math.abs((y2 - x2 * m - b)/m2) < width))
447                                 return true;
448                 }
449         }
451         var s = ((x1 - x2) * (y3 - y1) - (y1 - y2) * (x3 - x1)), p = ((x1 - x2) * (y4 - y1) - (y1 - y2) * (x4 - x1));
452         if ((s * p) <= 0)
453         {
454                 s = ((x3 - x4) * (y1 - y3) - (y3 - y4) * (x1 - x3));
455                 p = ((x3 - x4) * (y2 - y3) - (y3 - y4) * (x2 - x3));
456                 if ((s * p) <= 0)
457                         return true;
458         }
459         return false;
462 /////////////////////////////////////////////////////////////////////////////////////////
463 // distanceOfPointFromLine
465 //      returns the distance of a point from a line
466 //      x1, y1, x2, y2: determine the position of the line
467 //      x3, y3: determine the position of the point
469 ///////////////////////////////////////////////////////////////////////////////////////////
471 function distanceOfPointFromLine (x1, y1, x2, y2, x3, y3)
473         if (x1 == x2)
474         {
475                 return Math.abs(x3 - x1);
476         }
477         else if (y1 == y2)
478         {
479                 return Math.abs(y3 - y1);
480         }
481         else
482         {
483                 var m = (y1 - y2) / (x1 - x2);
484                 var b = y1 - m * x1;
485                 var m2 = sqrt(m * m + 1);
486                 return Math.abs((y3 - x3 * m - b)/m2);
487         }
490 /////////////////////////////////////////////////////////////////////////////////////////
491 // createRamp
493 //      creates a ramp from point (x1, y1) to (x2, y2).
494 //      x1, y1, x2, y2: determine the position of the start and end of the ramp
495 //      minHeight, maxHeight: determine the height levels of the start and end point
496 //      width: determines the width of the ramp
497 //      smoothLevel: determines the smooth level around the edges of the ramp
498 //      mainTerrain: (Optional) determines the terrain texture for the ramp
499 //      edgeTerrain: (Optional) determines the terrain texture for the edges
500 //      tileclass: (Optional) adds the ramp to this tile class
502 ///////////////////////////////////////////////////////////////////////////////////////////
504 function createRamp (x1, y1, x2, y2, minHeight, maxHeight, width, smoothLevel, mainTerrain, edgeTerrain, tileclass)
506         var halfWidth = width / 2;
507         var mapSize = g_Map.size;
509         if (y1 == y2)
510         {
511                 var x3 = x2;
512                 var y3 = y2 + halfWidth;
513         }
514         else
515         {
516                 var m = (x1 - x2) / (y1 - y2);
517                 var b = y2 + m * x2;
518                 var x3 = x2 + halfWidth;
519                 var y3 = - m * x3 + b;
520         }
522         var minBoundX = (x1 <= x2 ? (x1 > halfWidth ? x1 - halfWidth : 0) : (x2 > halfWidth ? x2 - halfWidth : 0));
523         var maxBoundX = (x1 >= x2 ? (x1 < mapSize - halfWidth ? x1 + halfWidth : mapSize) : (x2 < mapSize - halfWidth ? x2 + halfWidth : mapSize));
524         var minBoundY = (y1 <= y2 ? (y1 > halfWidth ? y1 - halfWidth : 0) : (y2 > halfWidth ? y2 - halfWidth : 0));
525         var maxBoundY = (y1 >= y2 ? (x1 < mapSize - halfWidth ? y1 + halfWidth : mapSize) : (y2 < mapSize - halfWidth ? y2 + halfWidth : mapSize));
527         for (var x = minBoundX; x < maxBoundX; ++x)
528         {
529                 for (var y = minBoundY; y < maxBoundY; ++y)
530                 {
531                         var lDist = distanceOfPointFromLine(x3, y3, x2, y2, x, y);
532                         var sDist = distanceOfPointFromLine(x1, y1, x2, y2, x, y);
533                         var rampLength = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
534                         if (lDist <= rampLength && sDist <= halfWidth)
535                         {
536                                 var h = ((rampLength - lDist) * maxHeight + lDist * minHeight) / rampLength;
537                                 if (sDist >= halfWidth - smoothLevel)
538                                 {
539                                         h = (h - minHeight) * (halfWidth - sDist) / smoothLevel + minHeight;
540                                         if (edgeTerrain !== undefined)
541                                                 placeTerrain(x, y, edgeTerrain);
542                                 }
543                                 else
544                                 {
545                                         if (mainTerrain !== undefined)
546                                                 placeTerrain(x, y, mainTerrain);
547                                 }
548                                 if (tileclass !== undefined)
549                                         addToClass(x, y, tileclass);
550                                 if((g_Map.getHeight(floor(x), floor(y)) < h) && (h <= maxHeight))
551                                         g_Map.setHeight(x, y, h);
552                         }
553                 }
554         }
557 /////////////////////////////////////////////////////////////////////////////////////////
558 // createMountain
560 //      creates a mountain using a tecnique very similar to chain placer.
562 ///////////////////////////////////////////////////////////////////////////////////////////
564 function createMountain(maxHeight, minRadius, maxRadius, numCircles, constraint, x, z, terrain, tileclass, fcc, q)
566         fcc = (fcc !== undefined ? fcc : 0);
567         q = (q !== undefined ? q : []);
569         // checking for an array of constraints
570         if (constraint instanceof Array)
571         {
572                 var constraintArray = constraint;
573                 constraint = new AndConstraint(constraintArray);
574         }
576         // Preliminary bounds check
577         if (!g_Map.inMapBounds(x, z) || !constraint.allows(x, z))
578         {
579                 return;
580         }
582         var size = getMapSize();
583         var queueEmpty = (q.length ? false : true);
585         var gotRet = [];
586         for (var i = 0; i < size; ++i)
587         {
588                 gotRet[i] = [];
589                 for (var j = 0; j < size; ++j)
590                         gotRet[i][j] = -1;
591         }
593         --size;
595         if (minRadius < 1) minRadius = 1;
596         if (minRadius > maxRadius) minRadius = maxRadius;
598         var edges = [[x, z]];
599         var circles = [];
601         for (var i = 0; i < numCircles; ++i)
602         {
603                 var badPoint = false;
604                 var [cx, cz] = pickRandom(edges);
606                 if (queueEmpty)
607                         var radius = randIntInclusive(minRadius, maxRadius);
608                 else
609                 {
610                         var radius = q.pop();
611                         queueEmpty = (q.length ? false : true);
612                 }
614                 var sx = cx - radius, lx = cx + radius;
615                 var sz = cz - radius, lz = cz + radius;
617                 sx = (sx < 0 ? 0 : sx);
618                 sz = (sz < 0 ? 0 : sz);
619                 lx = (lx > size ? size : lx);
620                 lz = (lz > size ? size : lz);
622                 var radius2 = radius * radius;
623                 var dx, dz, distance2;
625                 //log (uneval([sx, sz, lx, lz]));
627                 for (var ix = sx; ix <= lx; ++ix)
628                 {
629                         for (var iz = sz; iz <= lz; ++ iz)
630                         {
631                                 dx = ix - cx;
632                                 dz = iz - cz;
633                                 distance2 = dx * dx + dz * dz;
634                                 if (dx * dx + dz * dz <= radius2)
635                                 {
636                                         if (g_Map.inMapBounds(ix, iz))
637                                         {
638                                                 if (!constraint.allows(ix, iz))
639                                                 {
640                                                         badPoint = true;
641                                                         break;
642                                                 }
644                                                 var state = gotRet[ix][iz];
646                                                 if (state == -1)
647                                                 {
648                                                         gotRet[ix][iz] = -2;
649                                                 }
650                                                 else if (state >= 0)
651                                                 {
653                                                         var s = edges.splice(state, 1);
655                                                         gotRet[ix][iz] = -2;
657                                                         var edgesLength = edges.length;
658                                                         for (var k = state; k < edges.length; ++k)
659                                                         {
660                                                                 --gotRet[edges[k][0]][edges[k][1]];
661                                                         }
662                                                 }
663                                         }
664                                 }
665                         }
666                         if (badPoint)
667                                 break;
668                 }
670                 if (badPoint)
671                         continue;
672                 else
673                         circles.push([cx, cz, radius]);
675                 for (var ix = sx; ix <= lx; ++ix)
676                 {
677                         for (var iz = sz; iz <= lz; ++ iz)
678                         {
679                                 if (fcc)
680                                         if ((x - ix) > fcc || (ix - x) > fcc || (z - iz) > fcc || (iz - z) > fcc)
681                                                 continue;
683                                 if (gotRet[ix][iz] == -2)
684                                 {
685                                         if (ix > 0)
686                                         {
687                                                 if (gotRet[ix-1][iz] == -1)
688                                                 {
689                                                         edges.push([ix, iz]);
690                                                         gotRet[ix][iz] = edges.length - 1;
691                                                         continue;
692                                                 }
693                                         }
694                                         if (iz > 0)
695                                         {
696                                                 if (gotRet[ix][iz-1] == -1)
697                                                 {
698                                                         edges.push([ix, iz]);
699                                                         gotRet[ix][iz] = edges.length - 1;
700                                                         continue;
701                                                 }
702                                         }
703                                         if (ix < size)
704                                         {
705                                                 if (gotRet[ix+1][iz] == -1)
706                                                 {
707                                                         edges.push([ix, iz]);
708                                                         gotRet[ix][iz] = edges.length - 1;
709                                                         continue;
710                                                 }
711                                         }
712                                         if (iz < size)
713                                         {
714                                                 if (gotRet[ix][iz+1] == -1)
715                                                 {
716                                                         edges.push([ix, iz]);
717                                                         gotRet[ix][iz] = edges.length - 1;
718                                                         continue;
719                                                 }
720                                         }
721                                 }
722                         }
723                 }
724         }
726         var numFinalCircles = circles.length;
728         for (var i = 0; i < numFinalCircles; ++i)
729         {
730                 var point = circles[i];
731                 var cx = point[0], cz = point[1], radius = point[2];
733                 var sx = cx - radius, lx = cx + radius;
734                 var sz = cz - radius, lz = cz + radius;
736                 sx = (sx < 0 ? 0 : sx);
737                 sz = (sz < 0 ? 0 : sz);
738                 lx = (lx > size ? size : lx);
739                 lz = (lz > size ? size : lz);
741                 var radius2 = radius * radius;
742                 var dx, dz, distance2;
744                 var clumpHeight = radius / maxRadius * maxHeight * randFloat(0.8, 1.2);
746                 for (var ix = sx; ix <= lx; ++ix)
747                 {
748                         for (var iz = sz; iz <= lz; ++ iz)
749                         {
750                                 dx = ix - cx;
751                                 dz = iz - cz;
752                                 distance2 = dx * dx + dz * dz;
754                                 var newHeight = Math.round((Math.sin(PI * (2 * ((radius - Math.sqrt(distance2)) / radius) / 3 - 1/6)) + 0.5) * 2/3 * clumpHeight) + randIntInclusive(0, 2);
756                                 if (dx * dx + dz * dz <= radius2)
757                                 {
758                                         if (g_Map.getHeight(ix, iz) < newHeight)
759                                                 g_Map.setHeight(ix, iz, newHeight);
760                                         else if (g_Map.getHeight(ix, iz) >= newHeight && g_Map.getHeight(ix, iz) < newHeight + 4)
761                                                 g_Map.setHeight(ix, iz, newHeight + 4);
762                                         if (terrain !== undefined)
763                                                 placeTerrain(ix, iz, terrain);
764                                         if (tileclass !== undefined)
765                                                 addToClass(ix, iz, tileclass);
766                                 }
767                         }
768                 }
769         }
773  * Generates a volcano mountain. Smoke and lava are optional.
775  * @param {number} fx - Horizontal coordinate of the center.
776  * @param {number} fz - Horizontal coordinate of the center.
777  * @param {number} tileClass - Painted onto every tile that is occupied by the volcano.
778  * @param {string} terrainTexture - The texture painted onto the volcano hill.
779  * @param {array} lavaTextures - Three different textures for the interior, from the outside to the inside.
780  * @param {boolean} smoke - Whether to place smoke particles.
781  * @param {number} elevationType - Elevation painter type, ELEVATION_SET = absolute or ELEVATION_MODIFY = relative.
782  */
783 function createVolcano(fx, fz, tileClass, terrainTexture, lavaTextures, smoke, elevationType)
785         log("Creating volcano");
787         let ix = Math.round(fractionToTiles(fx));
788         let iz = Math.round(fractionToTiles(fz));
790         let baseSize = mapArea / scaleByMapSize(1, 8);
792         let coherence = 0.7;
793         let smoothness = 0.05;
794         let failFraction = 100;
795         let steepness = 3;
797         let clLava = createTileClass();
799         let layers = [
800                 {
801                         "clumps": 0.067,
802                         "elevation": 15,
803                         "tileClass": tileClass
804                 },
805                 {
806                         "clumps": 0.05,
807                         "elevation": 25,
808                         "tileClass": createTileClass()
809                 },
810                 {
811                         "clumps": 0.02,
812                         "elevation": 45,
813                         "tileClass": createTileClass()
814                 },
815                 {
816                         "clumps": 0.011,
817                         "elevation": 62,
818                         "tileClass": createTileClass()
819                 },
820                 {
821                         "clumps": 0.003,
822                         "elevation": 42,
823                         "tileClass": clLava,
824                         "painter": lavaTextures && new LayeredPainter([terrainTexture, ...lavaTextures], [1, 1, 1]),
825                         "steepness": 1
826                 }
827         ];
829         for (let i = 0; i < layers.length; ++i)
830                 createArea(
831                         new ClumpPlacer(baseSize * layers[i].clumps, coherence, smoothness, failFraction, ix, iz),
832                         [
833                                 layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]),
834                                 new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness || steepness),
835                                 paintClass(layers[i].tileClass)
836                         ],
837                         i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1));
839         if (smoke)
840         {
841                 let num = Math.floor(baseSize * 0.002);
842                 createObjectGroup(
843                         new SimpleGroup(
844                                 [new SimpleObject("actor|particle/smoke.xml", num, num, 0, 7)],
845                                 false,
846                                 clLava,
847                                 ix,
848                                 iz),
849                         0,
850                 stayClasses(tileClass, 1));
851         }