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 : }
|