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]; } } }