using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TransportGame.Business;
using TransportGame.Model;
namespace TransportGame.Generator
{
///
/// Simulates water erosion
///
public class TerrainEroder
{
#region Constants
/// Direction vector (X)
///
/// In order: Center, North, North-East, East, South-East, South, South-West, West, North-West
///
readonly static private int[] DirectionsX = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
/// Direction vector (Y)
///
/// In order: Center, North, North-East, East, South-East, South, South-West, West, North-West
///
readonly static private int[] DirectionsY = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
#endregion
#region Public properties
///
/// Gets the terrain to erode
///
public Map Terrain { get; private set; }
#endregion
protected int SpringCount { get { return ConfigurationManager.TerrGenConfig.ErodePoints; } }
protected int IterationCount { get { return ConfigurationManager.TerrGenConfig.ErodeIterations; } }
protected float ErosionAmount { get { return ConfigurationManager.TerrGenConfig.ErodeAmountPercent; } }
#region Private variables
private Random random = new Random();
private float[,] water;
private int[] springsX;
private int[] springsY;
#endregion
public TerrainEroder(Map terrain)
{
this.Terrain = terrain;
}
public void Erode()
{
Initialize();
GenerateSprings();
for (int i = 0; i < IterationCount; i++)
NextIteration();
}
private void Initialize()
{
// Initialize matrices
water = new float[Terrain.Width, Terrain.Height];
}
private void GenerateSprings()
{
// Step 1: Generate random points, which will be the water springs
springsX = new int[SpringCount];
springsY = new int[SpringCount];
for (int i = 0; i < SpringCount; i++)
{
springsX[i] = random.Next(0, Terrain.Width);
springsY[i] = random.Next(0, Terrain.Height);
}
// Step 2: Find local maximums, where the springs are placed
bool changed;
int iterations = 100;
do
{
changed = false;
for (int i = 0; i < SpringCount; i++)
{
// Find best neighbour
float maxHeight = Terrain[springsX[i], springsY[i]];
int maxDir = 0;
ForEachDirection(springsX[i], springsY[i], (dx, dy, dir) =>
{
if (Terrain[dx, dy] >= maxHeight)
{
maxHeight = Terrain[dx, dy];
maxDir = dir;
}
});
if (maxDir > 0)
{
springsX[i] += DirectionsX[maxDir];
springsY[i] += DirectionsY[maxDir];
changed = true;
}
}
--iterations;
} while (changed && iterations > 0);
}
private void NextIteration()
{
Step1_RainAndErode();
Step2_MoveWater();
Step3_Evaporate();
}
private void Step1_RainAndErode()
{
// Springs generate a lot of water
for (int i = 0; i < SpringCount; i++)
water[springsX[i], springsY[i]] += 100f;
// Rain in the rest of the terrain
for (int x = 0; x < Terrain.Width; x++)
for (int y = 0; y < Terrain.Height; y++)
{
water[x, y] += 1f;
// Erode some terrain
Terrain[x, y] -= ErosionAmount * water[x, y];
}
}
private void Step2_MoveWater()
{
for (int x = 0; x < Terrain.Width; x++)
for (int y = 0; y < Terrain.Height; y++)
{
List directions = new List();
// Find relevant directions
ForEachDirection(x, y, (dx, dy, dir) =>
{
if (Level(x, y) > Level(dx, dy))
directions.Add(dir);
});
// Order directions by level
directions.Sort((dir1, dir2) =>
{
float level1 = Level(x + DirectionsX[dir1], y + DirectionsY[dir1]);
float level2 = Level(x + DirectionsX[dir2], y + DirectionsY[dir2]);
return Convert.ToInt32(100 * (level1 - level2));
});
// Distribute water
for (int dirIndex = 0; dirIndex < directions.Count; dirIndex++)
{
int dir = directions[dirIndex];
// We distribute all the water equally to cells from
// directions 0..dir.
float distributeAmount = water[x, y] / (dirIndex + 1);
// If the resulting level is higher than the level of the next direction,
// we only add the level difference
if (dirIndex < directions.Count - 1)
{
int dx = x + DirectionsX[dir], dy = y + DirectionsY[dir];
int dx1 = x + DirectionsX[directions[dirIndex + 1]], dy1 = y + DirectionsY[directions[dirIndex + 1]];
distributeAmount = Math.Min(distributeAmount, Level(dx1, dy1) - Level(dx, dy));
}
// Distribute water & sediment
for (int dirDistribIndex = 0; dirDistribIndex < dirIndex; dirDistribIndex++)
{
int dirDistrib = directions[dirDistribIndex];
water[x + DirectionsX[dirDistrib], y + DirectionsY[dirDistrib]] += distributeAmount;
}
// Subtract from current cell
water[x, y] -= distributeAmount * (dirIndex + 1);
}
}
}
private void Step3_Evaporate()
{
for (int x = 0; x < Terrain.Width; x++)
for (int y = 0; y < Terrain.Height; y++)
{
// Some sediment deposits
Terrain[x, y] += water[x, y] * ErosionAmount;
// Water evaporates
water[x, y] *= .5f;
}
}
private void ForEachDirection(int x, int y, Action action)
{
for (int dir = 1; dir < DirectionsX.Length; dir++)
{
int dx = x + DirectionsX[dir];
int dy = y + DirectionsY[dir];
if (Terrain.IsInside(dx, dy))
action(dx, dy, dir);
}
}
///
/// Gets the soil + water level
///
/// x coordinate
/// y coordinate
/// level
private float Level(int x, int y)
{
return Terrain[x, y] + water[x, y];
}
}
}