1
0
forked from mirrors/0ad

Move landscape generation functions from misc.js to gaia_terrain.js, refs #4804.

Rename passageMaker to createShallowsPassage to avoid confusion with
straightPassageMaker aka PassMaker from Corsica & Sardinia and Pyrenean
Sierra.
Remove TILE_CENTERED_HEIGHT_MAP operations in that function from
3c6bce057e that don't do anything.
Add infinite loop protection to getTIPIADBON.
Remove unneeded parentheses, braces, redundancy, whitespace issues, use
early return and merge consecutive if-statements with a logical or in
these functions.

This was SVN commit r20410.
This commit is contained in:
elexis
2017-11-05 13:56:21 +00:00
parent 86b9159609
commit cfdd37f6d9
8 changed files with 499 additions and 629 deletions
@@ -218,7 +218,7 @@ for (let i = 0; i <= randIntInclusive(8, scaleByMapSize(12, 20)); ++i)
log("Creating shallows in tributaries...");
for (let z of [0.25, 0.75])
passageMaker(
createShallowsPassage(
Math.round(fractionToTiles(0.2)),
Math.round(fractionToTiles(z)),
Math.round(fractionToTiles(0.8)),
@@ -162,7 +162,7 @@ log("Creating the shallows of the main river...");
for (let i = 0; i <= randIntInclusive(3, scaleByMapSize(4, 6)); ++i)
{
let cLocation = Math.floor(fractionToTiles(randFloat(0.15, 0.85)));
passageMaker(
createShallowsPassage(
cLocation,
Math.floor(fractionToTiles(0.35)),
cLocation,
@@ -230,7 +230,7 @@ for (let i = 0; i <= randIntInclusive(8, scaleByMapSize(12, 20)); ++i)
log("Creating shallows to make tributaries passable...");
for (let coord of [0.25, 0.75])
passageMaker(
createShallowsPassage(
Math.floor(fractionToTiles(0.2)),
Math.floor(fractionToTiles(coord)),
Math.floor(fractionToTiles(0.8)),
@@ -62,6 +62,226 @@ function createMountains(terrain, constraint, tileClass, count, maxHeight, minRa
14);
}
/**
* Create a mountain using a technique very similar to ChainPlacer.
*/
function createMountain(maxHeight, minRadius, maxRadius, numCircles, constraint, x, z, terrain, tileClass, fcc = 0, q = [])
{
if (constraint instanceof Array)
constraint = new AndConstraint(constraint);
if (!g_Map.inMapBounds(x, z) || !constraint.allows(x, z))
return;
let mapSize = getMapSize();
let queueEmpty = !q.length;
let gotRet = [];
for (let i = 0; i < mapSize; ++i)
{
gotRet[i] = [];
for (let j = 0; j < mapSize; ++j)
gotRet[i][j] = -1;
}
--mapSize;
minRadius = Math.max(1, Math.min(minRadius, maxRadius));
let edges = [[x, z]];
let circles = [];
for (let i = 0; i < numCircles; ++i)
{
let badPoint = false;
let [cx, cz] = pickRandom(edges);
let radius;
if (queueEmpty)
radius = randIntInclusive(minRadius, maxRadius);
else
{
radius = q.pop();
queueEmpty = !q.length;
}
let sx = Math.max(0, cx - radius);
let sz = Math.max(0, cz - radius);
let lx = Math.min(cx + radius, mapSize);
let lz = Math.min(cz + radius, mapSize);
let radius2 = Math.square(radius);
for (let ix = sx; ix <= lx; ++ix)
{
for (let iz = sz; iz <= lz; ++iz)
{
if (Math.euclidDistance2D(ix, iz, cx, cz) > radius2 || !g_Map.inMapBounds(ix, iz))
continue;
if (!constraint.allows(ix, iz))
{
badPoint = true;
break;
}
let state = gotRet[ix][iz];
if (state == -1)
{
gotRet[ix][iz] = -2;
}
else if (state >= 0)
{
let s = edges.splice(state, 1);
gotRet[ix][iz] = -2;
let edgesLength = edges.length;
for (let k = state; k < edges.length; ++k)
--gotRet[edges[k][0]][edges[k][1]];
}
}
if (badPoint)
break;
}
if (badPoint)
continue;
circles.push([cx, cz, radius]);
for (let ix = sx; ix <= lx; ++ix)
for (let iz = sz; iz <= lz; ++iz)
{
if (gotRet[ix][iz] != -2 ||
fcc && (x - ix > fcc || ix - x > fcc || z - iz > fcc || iz - z > fcc) ||
ix > 0 && gotRet[ix-1][iz] == -1 ||
iz > 0 && gotRet[ix][iz-1] == -1 ||
ix < mapSize && gotRet[ix+1][iz] == -1 ||
iz < mapSize && gotRet[ix][iz+1] == -1)
continue;
edges.push([ix, iz]);
gotRet[ix][iz] = edges.length - 1;
}
}
for (let [cx, cz, radius] of circles)
{
let sx = Math.max(0, cx - radius);
let sz = Math.max(0, cz - radius);
let lx = Math.min(cx + radius, mapSize);
let lz = Math.min(cz + radius, mapSize);
let clumpHeight = radius / maxRadius * maxHeight * randFloat(0.8, 1.2);
for (let ix = sx; ix <= lx; ++ix)
for (let iz = sz; iz <= lz; ++iz)
{
let distance = Math.euclidDistance2D(ix, iz, cx, cz);
let newHeight =
randIntInclusive(0, 2) +
Math.round(2/3 * clumpHeight * (Math.sin(Math.PI * 2/3 * (3/4 - distance / radius)) + 0.5));
if (distance > radius)
continue;
if (getHeight(ix, iz) < newHeight)
setHeight(ix, iz, newHeight);
else if (getHeight(ix, iz) >= newHeight && getHeight(ix, iz) < newHeight + 4)
setHeight(ix, iz, newHeight + 4);
if (terrain !== undefined)
placeTerrain(ix, iz, terrain);
if (tileClass !== undefined)
addToClass(ix, iz, tileClass);
}
}
}
/**
* Generates a volcano mountain. Smoke and lava are optional.
*
* @param {number} fx - Horizontal coordinate of the center.
* @param {number} fz - Horizontal coordinate of the center.
* @param {number} tileClass - Painted onto every tile that is occupied by the volcano.
* @param {string} terrainTexture - The texture painted onto the volcano hill.
* @param {array} lavaTextures - Three different textures for the interior, from the outside to the inside.
* @param {boolean} smoke - Whether to place smoke particles.
* @param {number} elevationType - Elevation painter type, ELEVATION_SET = absolute or ELEVATION_MODIFY = relative.
*/
function createVolcano(fx, fz, tileClass, terrainTexture, lavaTextures, smoke, elevationType)
{
log("Creating volcano");
let ix = Math.round(fractionToTiles(fx));
let iz = Math.round(fractionToTiles(fz));
let baseSize = getMapArea() / scaleByMapSize(1, 8);
let coherence = 0.7;
let smoothness = 0.05;
let failFraction = 100;
let steepness = 3;
let clLava = createTileClass();
let layers = [
{
"clumps": 0.067,
"elevation": 15,
"tileClass": tileClass
},
{
"clumps": 0.05,
"elevation": 25,
"tileClass": createTileClass()
},
{
"clumps": 0.02,
"elevation": 45,
"tileClass": createTileClass()
},
{
"clumps": 0.011,
"elevation": 62,
"tileClass": createTileClass()
},
{
"clumps": 0.003,
"elevation": 42,
"tileClass": clLava,
"painter": lavaTextures && new LayeredPainter([terrainTexture, ...lavaTextures], [1, 1, 1]),
"steepness": 1
}
];
for (let i = 0; i < layers.length; ++i)
createArea(
new ClumpPlacer(baseSize * layers[i].clumps, coherence, smoothness, failFraction, ix, iz),
[
layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]),
new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness || steepness),
paintClass(layers[i].tileClass)
],
i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1));
if (smoke)
{
let num = Math.floor(baseSize * 0.002);
createObjectGroup(
new SimpleGroup(
[new SimpleObject("actor|particle/smoke.xml", num, num, 0, 7)],
false,
clLava,
ix,
iz),
0,
stayClasses(tileClass, 1));
}
}
/**
* Paint the given terrain texture in the given sizes at random places of the map to diversify monotone land texturing.
*/
@@ -93,3 +313,271 @@ function createLayeredPatches(sizes, terrains, terrainWidths, constraint, count,
constraint,
count);
}
/**
* Creates a meandering river at the given location and width.
* Optionally calls a function on the affected tiles.
*
* @property horizontal - Whether the river is horizontal or vertical
* @property parallel - Whether the shorelines should be parallel or meander separately.
* @property position - Location of the river. Number between 0 and 1.
* @property width - Size between the two shorelines. Number between 0 and 1.
* @property fadeDist - Size of the shoreline.
* @property deviation - Fuzz effect on the shoreline if greater than 0.
* @property waterHeight - Ground height of the riverbed.
* @proeprty landHeight - Ground height of the end of the shoreline.
* @property meanderShort - Strength of frequent meanders.
* @property meanderLong - Strength of less frequent meanders.
* @property waterFunc - Optional function called on water tiles, providing ix, iz, height.
* @property landFunc - Optional function called on land tiles, providing ix, iz, shoreDist1, shoreDist2.
*/
function paintRiver(args)
{
log("Creating the river");
let theta1 = randFloat(0, 1);
let theta2 = randFloat(0, 1);
let seed1 = randFloat(2, 3);
let seed2 = randFloat(2, 3);
let meanderShort = args.meanderShort / scaleByMapSize(35, 160);
let meanderLong = args.meanderLong / scaleByMapSize(35, 100);
let mapSize = getMapSize();
for (let ix = 0; ix < mapSize; ++ix)
for (let iz = 0; iz < mapSize; ++iz)
{
if (args.constraint && !args.constraint.allows(ix, iz))
continue;
let x = ix / (mapSize + 1);
let z = iz / (mapSize + 1);
let coord1 = args.horizontal ? z : x;
let coord2 = args.horizontal ? x : z;
// River curve at this place
let curve1 =
meanderShort * rndRiver(theta1 + coord2 * mapSize / 128, seed1) +
meanderLong * rndRiver(theta2 + coord2 * mapSize / 256, seed2);
let curve2 = args.parallel ? curve1 :
meanderShort * rndRiver(theta2 + coord2 * mapSize / 128, seed2) +
meanderLong * rndRiver(theta2 + coord2 * mapSize / 256, seed2);
// Fuzz the river border
let devCoord1 = coord1 * randFloat(1 - args.deviation, 1 + args.deviation);
let devCoord2 = coord2 * randFloat(1 - args.deviation, 1 + args.deviation);
let shoreDist1 = -devCoord1 + curve1 + args.position - args.width / 2;
let shoreDist2 = -devCoord1 + curve2 + args.position + args.width / 2;
if (shoreDist1 < 0 && shoreDist2 > 0)
{
let height = args.waterHeight;
if (shoreDist1 > -args.fadeDist)
height += (args.landHeight - args.waterHeight) * (1 + shoreDist1 / args.fadeDist);
else if (shoreDist2 < args.fadeDist)
height += (args.landHeight - args.waterHeight) * (1 - shoreDist2 / args.fadeDist);
setHeight(ix, iz, height);
if (args.waterFunc)
args.waterFunc(ix, iz, height);
}
else if (args.landFunc)
args.landFunc(ix, iz, shoreDist1, shoreDist2);
}
}
/**
* Helper function to create a meandering river.
* It works the same as sin or cos function with the difference that it's period is 1 instead of 2 pi.
*/
function rndRiver(f, seed)
{
let rndRw = seed;
for (let i = 0; i <= f; ++i)
rndRw = 10 * (rndRw % 1);
let rndRr = f % 1;
let retVal = (i % 2 ? 1 : -1) * rndRr * (rndRr - 1);
let rndRe = Math.floor(rndRw) % 5;
if (rndRe == 0)
retVal *= 2.3 * (rndRr - 0.5) * (rndRr - 0.5);
else if (rndRe == 1)
retVal *= 2.6 * (rndRr - 0.3) * (rndRr - 0.7);
else if (rndRe == 2)
retVal *= 22 * (rndRr - 0.2) * (rndRr - 0.3) * (rndRr - 0.3) * (rndRr - 0.8);
else if (rndRe == 3)
retVal *= 180 * (rndRr - 0.2) * (rndRr - 0.2) * (rndRr - 0.4) * (rndRr - 0.6) * (rndRr - 0.6) * (rndRr - 0.8);
else if (rndRe == 4)
retVal *= 2.6 * (rndRr - 0.5) * (rndRr - 0.7);
return retVal;
}
/**
* Create shallow water between (x1, z1) and (x2, z2) of tiles below maxHeight.
*/
function createShallowsPassage(x1, z1, x2, z2, width, maxHeight, shallowHeight, smooth, tileClass, terrain, riverHeight)
{
let a = z1 - z2;
let b = x2 - x1;
let distance = Math.euclidDistance2D(x1, z1, x2, z2);
let mapSize = getMapSize();
for (let ix = 0; ix < mapSize; ++ix)
for (let iz = 0; iz < mapSize; ++iz)
{
let c = a * (ix - x1) + b * (iz - z1);
let my = iz - b * c / Math.square(distance);
let inline = 0;
let dis;
if (b == 0)
{
dis = Math.abs(ix - x1);
if (iz >= Math.min(z1, z2) && iz <= Math.max(z1, z2))
inline = 1;
}
else if (my >= Math.min(z1, z2) && my <= Math.max(z1, z2))
{
dis = Math.abs(c) / distance;
inline = 1;
}
if (dis > width || !inline || getHeight(ix, iz) > maxHeight)
continue;
if (dis > width - smooth)
setHeight(ix, iz, ((width - dis) * shallowHeight + riverHeight * (smooth - width + dis)) / smooth);
else if (dis <= width - smooth)
setHeight(ix, iz, shallowHeight);
if (tileClass !== undefined)
addToClass(ix, iz, tileClass);
if (terrain !== undefined)
placeTerrain(ix, iz, terrain);
}
}
/**
* Creates a ramp from (x1, y1) to (x2, y2).
*/
function createRamp(x1, y1, x2, y2, minHeight, maxHeight, width, smoothLevel, mainTerrain, edgeTerrain, tileClass)
{
let halfWidth = width / 2;
let x3;
let y3;
if (y1 == y2)
{
x3 = x2;
y3 = y2 + halfWidth;
}
else
{
x3 = x2 + halfWidth;
y3 = (x1 - x2) / (y1 - y2) * (x2 - x3) + y2;
}
let minBoundX = Math.max(Math.min(x1, x2) - halfWidth, 0);
let minBoundY = Math.max(Math.min(y1, y2) - halfWidth, 0);
let maxBoundX = Math.min(Math.max(x1, x2) + halfWidth, getMapSize());
let maxBoundY = Math.min(Math.max(y1, y2) + halfWidth, getMapSize());
for (let x = minBoundX; x < maxBoundX; ++x)
for (let y = minBoundY; y < maxBoundY; ++y)
{
let lDist = distanceOfPointFromLine(x3, y3, x2, y2, x, y);
let sDist = distanceOfPointFromLine(x1, y1, x2, y2, x, y);
let rampLength = Math.euclidDistance2D(x1, y1, x2, y2);
if (lDist > rampLength || sDist > halfWidth)
continue;
let height = ((rampLength - lDist) * maxHeight + lDist * minHeight) / rampLength;
if (sDist >= halfWidth - smoothLevel)
{
height = (height - minHeight) * (halfWidth - sDist) / smoothLevel + minHeight;
if (edgeTerrain)
placeTerrain(x, y, edgeTerrain);
}
else if (mainTerrain)
placeTerrain(x, y, mainTerrain);
if (tileClass !== undefined)
addToClass(x, y, tileClass);
if (getHeight(Math.floor(x), Math.floor(y)) < height && height <= maxHeight)
setHeight(x, y, height);
}
}
/**
* Get The Intended Point In A Direction Based On Height.
* Retrieves the N'th point with a specific height in a line and returns it as a [x, y] array.
*
* @param startPoint - [x, y] array defining the start point
* @param endPoint - [x, y] array defining the ending point
* @param heightRange - [min, max] array defining the range which the height of the intended point can be. includes both "min" and "max"
* @param step - how much tile units per turn should the search go. more value means faster but less accurate
* @param n - how many points to skip before ending the search. skips """n-1 points""".
*/
function getTIPIADBON(startPoint, endPoint, heightRange, step, n)
{
let X = endPoint[0] - startPoint[0];
let Y = endPoint[1] - startPoint[1];
if (!X && !Y)
{
error("getTIPIADBON startPoint and endPoint are identical! " + new Error().stack);
return undefined;
}
let M = Math.sqrt(Math.square(X) + step * Math.square(Y));
let stepX = step * X / M;
let stepY = step * Y / M;
let y = startPoint[1];
let checked = 0;
let mapSize = getMapSize();
for (let x = startPoint[0]; true; x += stepX)
{
let ix = Math.floor(x);
let iy = Math.floor(y);
if (ix < mapSize || iy < mapSize)
{
if (getHeight(ix, iy) <= heightRange[1] &&
getHeight(ix, iy) >= heightRange[0])
++checked;
if (checked >= n)
return [x, y];
}
y += stepY;
if (y > endPoint[1] && stepY > 0 ||
y < endPoint[1] && stepY < 0 ||
x > endPoint[1] && stepX > 0 ||
x < endPoint[1] && stepX < 0)
return undefined;
}
return undefined;
}
@@ -1,212 +1,3 @@
/////////////////////////////////////////////////////////////////////////////////////////
// passageMaker
//
// Function for creating shallow water between two given points by changing the height of all tiles in
// the path with height less than or equal to "maxheight" to "height"
//
// x1,z1: Starting point of path
// x2,z2: Ending point of path
// width: Width of the shallow
// maxheight: Maximum height that it changes
// height: Height of the shallow
// smooth: smooth elevation in borders
// tileclass: (Optianal) - Adds those tiles to the class given
// terrain: (Optional) - Changes the texture of the elevated land
//
/////////////////////////////////////////////////////////////////////////////////////////
function passageMaker(x1, z1, x2, z2, width, maxheight, height, smooth, tileclass, terrain, riverheight)
{
var tchm = TILE_CENTERED_HEIGHT_MAP;
TILE_CENTERED_HEIGHT_MAP = true;
var mapSize = g_Map.size;
for (var ix = 0; ix < mapSize; ix++)
{
for (var iz = 0; iz < mapSize; iz++)
{
var a = z1-z2;
var b = x2-x1;
var c = (z1*(x1-x2))-(x1*(z1-z2));
var dis = abs(a*ix + b*iz + c)/sqrt(a*a + b*b);
var k = (a*ix + b*iz + c)/(a*a + b*b);
var my = iz-(b*k);
var inline = 0;
if (b == 0)
{
dis = abs(ix-x1);
if ((iz <= Math.max(z1,z2))&&(iz >= Math.min(z1,z2)))
{
inline = 1;
}
}
else
{
if ((my <= Math.max(z1,z2))&&(my >= Math.min(z1,z2)))
{
inline = 1;
}
}
if ((dis <= width)&&(inline))
{
if(g_Map.getHeight(ix, iz) <= maxheight)
{
if (dis > width - smooth)
{
g_Map.setHeight(ix, iz, ((width - dis)*(height)+(riverheight)*(smooth - width + dis))/(smooth));
}
else if (dis <= width - smooth)
{
g_Map.setHeight(ix, iz, height);
}
if (tileclass !== undefined)
{
addToClass(ix, iz, tileclass);
}
if (terrain !== undefined)
{
placeTerrain(ix, iz, terrain);
}
}
}
}
}
TILE_CENTERED_HEIGHT_MAP = tchm;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//rndRiver is a fuction that creates random values useful for making a jagged river.
//
//it works the same as sin or cos function. the only difference is that it's period is 1 instead of 2*pi
//it needs the "seed" parameter to use it to make random curves that don't get broken.
//seed must be created using randFloat(). or else it won't work
//
// f: Input: Same as angle in a sine function
// seed: Random Seed: Best to implement is to use randFloat()
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function rndRiver(f, seed)
{
var rndRq = seed;
var rndRw = rndRq;
var rndRe = 0;
var rndRr = f-floor(f);
var rndRa = 0;
for (var rndRx=0; rndRx<=floor(f); rndRx++)
{
rndRw = 10*(rndRw-floor(rndRw));
}
if (rndRx%2==0)
{
var rndRs = -1;
}
else
{
var rndRs = 1;
}
rndRe = (floor(rndRw))%5;
if (rndRe==0)
{
rndRa = (rndRs)*2.3*(rndRr)*(rndRr-1)*(rndRr-0.5)*(rndRr-0.5);
}
else if (rndRe==1)
{
rndRa = (rndRs)*2.6*(rndRr)*(rndRr-1)*(rndRr-0.3)*(rndRr-0.7);
}
else if (rndRe==2)
{
rndRa = (rndRs)*22*(rndRr)*(rndRr-1)*(rndRr-0.2)*(rndRr-0.3)*(rndRr-0.3)*(rndRr-0.8);
}
else if (rndRe==3)
{
rndRa = (rndRs)*180*(rndRr)*(rndRr-1)*(rndRr-0.2)*(rndRr-0.2)*(rndRr-0.4)*(rndRr-0.6)*(rndRr-0.6)*(rndRr-0.8);
}
else if (rndRe==4)
{
rndRa = (rndRs)*2.6*(rndRr)*(rndRr-1)*(rndRr-0.5)*(rndRr-0.7);
}
return rndRa;
}
/**
* Creates a meandering river at the given location and width.
* Optionally calls a function on the affected tiles.
*
* @property horizontal - Whether the river is horizontal or vertical
* @property parallel - Whether the shorelines should be parallel or meander separately.
* @property position - Location of the river. Number between 0 and 1.
* @property width - Size between the two shorelines. Number between 0 and 1.
* @property fadeDist - Size of the shoreline.
* @property deviation - Fuzz effect on the shoreline if greater than 0.
* @property waterHeight - Ground height of the riverbed.
* @proeprty landHeight - Ground height of the end of the shoreline.
* @property meanderShort - Strength of frequent meanders.
* @property meanderLong - Strength of less frequent meanders.
* @property waterFunc - Optional function called on water tiles, providing ix, iz, height.
* @property landFunc - Optional function called on land tiles, providing ix, iz, shoreDist1, shoreDist2.
*/
function paintRiver(args)
{
log("Creating the river");
let theta1 = randFloat(0, 1);
let theta2 = randFloat(0, 1);
let seed1 = randFloat(2, 3);
let seed2 = randFloat(2, 3);
let meanderShort = args.meanderShort / scaleByMapSize(35, 160);
let meanderLong = args.meanderLong / scaleByMapSize(35, 100);
let mapSize = g_Map.size;
for (let ix = 0; ix < mapSize; ++ix)
for (let iz = 0; iz < mapSize; ++iz)
{
if (args.constraint && !args.constraint.allows(ix, iz))
continue;
let x = ix / (mapSize + 1.0);
let z = iz / (mapSize + 1.0);
let coord1 = args.horizontal ? z : x;
let coord2 = args.horizontal ? x : z;
// River curve at this place
let cu1 = meanderShort * rndRiver(theta1 + coord2 * mapSize / 128, seed1);
let cu2 = meanderShort * rndRiver(theta2 + coord2 * mapSize / 128, seed2);
cu1 += meanderLong * rndRiver(theta2 + coord2 * mapSize / 256, seed2);
cu2 += meanderLong * rndRiver(theta2 + coord2 * mapSize / 256, seed2);
if (args.parallel)
cu2 = cu1;
// Fuzz the river border
let devCoord1 = coord1 * randFloat(1 - args.deviation, 1 + args.deviation);
let devCoord2 = coord2 * randFloat(1 - args.deviation, 1 + args.deviation);
let shoreDist1 = -devCoord1 + cu1 + args.position - args.width / 2;
let shoreDist2 = -devCoord1 + cu2 + args.position + args.width / 2;
if (shoreDist1 < 0 && shoreDist2 > 0)
{
let height = args.waterHeight;
if (shoreDist1 > -args.fadeDist)
height += (args.landHeight - args.waterHeight) * (1 + shoreDist1 / args.fadeDist);
else if (shoreDist2 < args.fadeDist)
height += (args.landHeight - args.waterHeight) * (1 - shoreDist2 / args.fadeDist);
setHeight(ix, iz, height);
if (args.waterFunc)
args.waterFunc(ix, iz, height);
}
else if (args.landFunc)
args.landFunc(ix, iz, shoreDist1, shoreDist2);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// createStartingPlayerEntities
//
@@ -362,412 +153,3 @@ function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileclass)
removeFromClass(qx, qz, tileclass);
});
}
/////////////////////////////////////////////////////////////////////////////////////////
// getTIPIADBON
//
// "get The Intended Point In A Direction Based On Height"
// gets the N'th point with a specific height in a line and returns it as a [x, y] array
// startPoint: [x, y] array defining the start point
// endPoint: [x, y] array defining the ending point
// heightRange: [min, max] array defining the range which the height of the intended point can be. includes both "min" and "max"
// step: how much tile units per turn should the search go. more value means faster but less accurate
// n: how many points to skip before ending the search. skips """n-1 points""".
//
///////////////////////////////////////////////////////////////////////////////////////////
function getTIPIADBON(startPoint, endPoint, heightRange, step, n)
{
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])));
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])));
var y = startPoint[1];
var checked = 0;
for (var x = startPoint[0]; true; x += stepX)
{
if ((floor(x) < g_Map.size)||(floor(y) < g_Map.size))
{
if ((g_Map.getHeight(floor(x), floor(y)) <= heightRange[1])&&(g_Map.getHeight(floor(x), floor(y)) >= heightRange[0]))
{
++checked;
}
if (checked >= n)
{
return [x, y];
}
}
y += stepY;
if ((y > endPoint[1])&&(stepY>0))
break;
if ((y < endPoint[1])&&(stepY<0))
break;
if ((x > endPoint[1])&&(stepX>0))
break;
if ((x < endPoint[1])&&(stepX<0))
break;
}
return undefined;
}
/////////////////////////////////////////////////////////////////////////////////////////
// createRamp
//
// creates a ramp from point (x1, y1) to (x2, y2).
// x1, y1, x2, y2: determine the position of the start and end of the ramp
// minHeight, maxHeight: determine the height levels of the start and end point
// width: determines the width of the ramp
// smoothLevel: determines the smooth level around the edges of the ramp
// mainTerrain: (Optional) determines the terrain texture for the ramp
// edgeTerrain: (Optional) determines the terrain texture for the edges
// tileclass: (Optional) adds the ramp to this tile class
//
///////////////////////////////////////////////////////////////////////////////////////////
function createRamp (x1, y1, x2, y2, minHeight, maxHeight, width, smoothLevel, mainTerrain, edgeTerrain, tileclass)
{
var halfWidth = width / 2;
var mapSize = g_Map.size;
if (y1 == y2)
{
var x3 = x2;
var y3 = y2 + halfWidth;
}
else
{
var m = (x1 - x2) / (y1 - y2);
var b = y2 + m * x2;
var x3 = x2 + halfWidth;
var y3 = - m * x3 + b;
}
var minBoundX = (x1 <= x2 ? (x1 > halfWidth ? x1 - halfWidth : 0) : (x2 > halfWidth ? x2 - halfWidth : 0));
var maxBoundX = (x1 >= x2 ? (x1 < mapSize - halfWidth ? x1 + halfWidth : mapSize) : (x2 < mapSize - halfWidth ? x2 + halfWidth : mapSize));
var minBoundY = (y1 <= y2 ? (y1 > halfWidth ? y1 - halfWidth : 0) : (y2 > halfWidth ? y2 - halfWidth : 0));
var maxBoundY = (y1 >= y2 ? (x1 < mapSize - halfWidth ? y1 + halfWidth : mapSize) : (y2 < mapSize - halfWidth ? y2 + halfWidth : mapSize));
for (var x = minBoundX; x < maxBoundX; ++x)
{
for (var y = minBoundY; y < maxBoundY; ++y)
{
var lDist = distanceOfPointFromLine(x3, y3, x2, y2, x, y);
var sDist = distanceOfPointFromLine(x1, y1, x2, y2, x, y);
var rampLength = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
if (lDist <= rampLength && sDist <= halfWidth)
{
var h = ((rampLength - lDist) * maxHeight + lDist * minHeight) / rampLength;
if (sDist >= halfWidth - smoothLevel)
{
h = (h - minHeight) * (halfWidth - sDist) / smoothLevel + minHeight;
if (edgeTerrain !== undefined)
placeTerrain(x, y, edgeTerrain);
}
else
{
if (mainTerrain !== undefined)
placeTerrain(x, y, mainTerrain);
}
if (tileclass !== undefined)
addToClass(x, y, tileclass);
if((g_Map.getHeight(floor(x), floor(y)) < h) && (h <= maxHeight))
g_Map.setHeight(x, y, h);
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// createMountain
//
// creates a mountain using a tecnique very similar to chain placer.
//
///////////////////////////////////////////////////////////////////////////////////////////
function createMountain(maxHeight, minRadius, maxRadius, numCircles, constraint, x, z, terrain, tileclass, fcc, q)
{
fcc = (fcc !== undefined ? fcc : 0);
q = (q !== undefined ? q : []);
// checking for an array of constraints
if (constraint instanceof Array)
{
var constraintArray = constraint;
constraint = new AndConstraint(constraintArray);
}
// Preliminary bounds check
if (!g_Map.inMapBounds(x, z) || !constraint.allows(x, z))
{
return;
}
var size = getMapSize();
var queueEmpty = (q.length ? false : true);
var gotRet = [];
for (var i = 0; i < size; ++i)
{
gotRet[i] = [];
for (var j = 0; j < size; ++j)
gotRet[i][j] = -1;
}
--size;
if (minRadius < 1) minRadius = 1;
if (minRadius > maxRadius) minRadius = maxRadius;
var edges = [[x, z]];
var circles = [];
for (var i = 0; i < numCircles; ++i)
{
var badPoint = false;
var [cx, cz] = pickRandom(edges);
if (queueEmpty)
var radius = randIntInclusive(minRadius, maxRadius);
else
{
var radius = q.pop();
queueEmpty = (q.length ? false : true);
}
var sx = cx - radius, lx = cx + radius;
var sz = cz - radius, lz = cz + radius;
sx = (sx < 0 ? 0 : sx);
sz = (sz < 0 ? 0 : sz);
lx = (lx > size ? size : lx);
lz = (lz > size ? size : lz);
var radius2 = radius * radius;
var dx, dz, distance2;
//log (uneval([sx, sz, lx, lz]));
for (var ix = sx; ix <= lx; ++ix)
{
for (var iz = sz; iz <= lz; ++ iz)
{
dx = ix - cx;
dz = iz - cz;
distance2 = dx * dx + dz * dz;
if (dx * dx + dz * dz <= radius2)
{
if (g_Map.inMapBounds(ix, iz))
{
if (!constraint.allows(ix, iz))
{
badPoint = true;
break;
}
var state = gotRet[ix][iz];
if (state == -1)
{
gotRet[ix][iz] = -2;
}
else if (state >= 0)
{
var s = edges.splice(state, 1);
gotRet[ix][iz] = -2;
var edgesLength = edges.length;
for (var k = state; k < edges.length; ++k)
{
--gotRet[edges[k][0]][edges[k][1]];
}
}
}
}
}
if (badPoint)
break;
}
if (badPoint)
continue;
else
circles.push([cx, cz, radius]);
for (var ix = sx; ix <= lx; ++ix)
{
for (var iz = sz; iz <= lz; ++ iz)
{
if (fcc)
if ((x - ix) > fcc || (ix - x) > fcc || (z - iz) > fcc || (iz - z) > fcc)
continue;
if (gotRet[ix][iz] == -2)
{
if (ix > 0)
{
if (gotRet[ix-1][iz] == -1)
{
edges.push([ix, iz]);
gotRet[ix][iz] = edges.length - 1;
continue;
}
}
if (iz > 0)
{
if (gotRet[ix][iz-1] == -1)
{
edges.push([ix, iz]);
gotRet[ix][iz] = edges.length - 1;
continue;
}
}
if (ix < size)
{
if (gotRet[ix+1][iz] == -1)
{
edges.push([ix, iz]);
gotRet[ix][iz] = edges.length - 1;
continue;
}
}
if (iz < size)
{
if (gotRet[ix][iz+1] == -1)
{
edges.push([ix, iz]);
gotRet[ix][iz] = edges.length - 1;
continue;
}
}
}
}
}
}
var numFinalCircles = circles.length;
for (var i = 0; i < numFinalCircles; ++i)
{
var point = circles[i];
var cx = point[0], cz = point[1], radius = point[2];
var sx = cx - radius, lx = cx + radius;
var sz = cz - radius, lz = cz + radius;
sx = (sx < 0 ? 0 : sx);
sz = (sz < 0 ? 0 : sz);
lx = (lx > size ? size : lx);
lz = (lz > size ? size : lz);
var radius2 = radius * radius;
var dx, dz, distance2;
var clumpHeight = radius / maxRadius * maxHeight * randFloat(0.8, 1.2);
for (var ix = sx; ix <= lx; ++ix)
{
for (var iz = sz; iz <= lz; ++ iz)
{
dx = ix - cx;
dz = iz - cz;
distance2 = dx * dx + dz * dz;
var newHeight = Math.round((Math.sin(PI * (2 * ((radius - Math.sqrt(distance2)) / radius) / 3 - 1/6)) + 0.5) * 2/3 * clumpHeight) + randIntInclusive(0, 2);
if (dx * dx + dz * dz <= radius2)
{
if (g_Map.getHeight(ix, iz) < newHeight)
g_Map.setHeight(ix, iz, newHeight);
else if (g_Map.getHeight(ix, iz) >= newHeight && g_Map.getHeight(ix, iz) < newHeight + 4)
g_Map.setHeight(ix, iz, newHeight + 4);
if (terrain !== undefined)
placeTerrain(ix, iz, terrain);
if (tileclass !== undefined)
addToClass(ix, iz, tileclass);
}
}
}
}
}
/**
* Generates a volcano mountain. Smoke and lava are optional.
*
* @param {number} fx - Horizontal coordinate of the center.
* @param {number} fz - Horizontal coordinate of the center.
* @param {number} tileClass - Painted onto every tile that is occupied by the volcano.
* @param {string} terrainTexture - The texture painted onto the volcano hill.
* @param {array} lavaTextures - Three different textures for the interior, from the outside to the inside.
* @param {boolean} smoke - Whether to place smoke particles.
* @param {number} elevationType - Elevation painter type, ELEVATION_SET = absolute or ELEVATION_MODIFY = relative.
*/
function createVolcano(fx, fz, tileClass, terrainTexture, lavaTextures, smoke, elevationType)
{
log("Creating volcano");
let ix = Math.round(fractionToTiles(fx));
let iz = Math.round(fractionToTiles(fz));
let baseSize = getMapArea() / scaleByMapSize(1, 8);
let coherence = 0.7;
let smoothness = 0.05;
let failFraction = 100;
let steepness = 3;
let clLava = createTileClass();
let layers = [
{
"clumps": 0.067,
"elevation": 15,
"tileClass": tileClass
},
{
"clumps": 0.05,
"elevation": 25,
"tileClass": createTileClass()
},
{
"clumps": 0.02,
"elevation": 45,
"tileClass": createTileClass()
},
{
"clumps": 0.011,
"elevation": 62,
"tileClass": createTileClass()
},
{
"clumps": 0.003,
"elevation": 42,
"tileClass": clLava,
"painter": lavaTextures && new LayeredPainter([terrainTexture, ...lavaTextures], [1, 1, 1]),
"steepness": 1
}
];
for (let i = 0; i < layers.length; ++i)
createArea(
new ClumpPlacer(baseSize * layers[i].clumps, coherence, smoothness, failFraction, ix, iz),
[
layers[i].painter || new LayeredPainter([terrainTexture, terrainTexture], [3]),
new SmoothElevationPainter(elevationType, layers[i].elevation, layers[i].steepness || steepness),
paintClass(layers[i].tileClass)
],
i == 0 ? null : stayClasses(layers[i - 1].tileClass, 1));
if (smoke)
{
let num = Math.floor(baseSize * 0.002);
createObjectGroup(
new SimpleGroup(
[new SimpleObject("actor|particle/smoke.xml", num, num, 0, 7)],
false,
clLava,
ix,
iz),
0,
stayClasses(tileClass, 1));
}
}
@@ -187,7 +187,7 @@ for (var i = 0; i < numPlayers; i++)
{
if (i+1 == numPlayers)
{
passageMaker(
createShallowsPassage(
round(fractionToTiles(playerX[i])),
round(fractionToTiles(playerZ[i])),
round(fractionToTiles(playerX[0])),
@@ -209,7 +209,7 @@ for (var i = 0; i < numPlayers; i++)
}
else
{
passageMaker(
createShallowsPassage(
fractionToTiles(playerX[i]),
fractionToTiles(playerZ[i]),
fractionToTiles(playerX[i+1]),
@@ -458,9 +458,9 @@ else if (md == 4) //central river
{
var cLocation = randFloat(0.15,0.85);
if (mdd1 == 1)
passageMaker(floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.35)), floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.65)), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
createShallowsPassage(floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.35)), floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.65)), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
else
passageMaker(floor(fractionToTiles(0.35)), floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.65)), floor(fractionToTiles(cLocation)), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
createShallowsPassage(floor(fractionToTiles(0.35)), floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.65)), floor(fractionToTiles(cLocation)), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
}
}
@@ -318,9 +318,9 @@ else if (md == 4) //central river
{
var cLocation = randFloat(0.15,0.85);
if (mdd1 == 1)
passageMaker(floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.35)), floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.65)), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
createShallowsPassage(floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.35)), floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.65)), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
else
passageMaker(floor(fractionToTiles(0.35)), floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.65)), floor(fractionToTiles(cLocation)), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
createShallowsPassage(floor(fractionToTiles(0.35)), floor(fractionToTiles(cLocation)), floor(fractionToTiles(0.65)), floor(fractionToTiles(cLocation)), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
}
if (randBool())
@@ -354,9 +354,9 @@ else if (md == 4) //central river
{
var cLocation = randFloat(0.15,0.85);
if (mdd1 == 1)
passageMaker(fractionToTiles(cLocation), fractionToTiles(0.35), fractionToTiles(cLocation), fractionToTiles(0.65), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
createShallowsPassage(fractionToTiles(cLocation), fractionToTiles(0.35), fractionToTiles(cLocation), fractionToTiles(0.65), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
else
passageMaker(fractionToTiles(0.35), fractionToTiles(cLocation), fractionToTiles(0.65), fractionToTiles(cLocation), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
createShallowsPassage(fractionToTiles(0.35), fractionToTiles(cLocation), fractionToTiles(0.65), fractionToTiles(cLocation), scaleByMapSize(4,8), -2, -2, 2, clShallow, undefined, -4);
}
}