// Manages direction and actions of droplet such as direction, lifetime and sediment private void RunDropletCycle(Droplet newDrop) { for (int lifetime = 0; lifetime < dropletLifetime; lifetime++) { int nodeX = (int)newDrop.posX; int nodeY = (int)newDrop.posY; int dropletIndex = nodeY * currentMapSize + nodeX; float cellOffsetX = newDrop.posX - nodeX; float cellOffsetY = newDrop.posY - nodeY; // Gets Droplet's height and direction HeightAndGradient heightAndGradient = CalculateHeightAndGradient(newDrop.posX, newDrop.posY); // Update droplet newDrop.directionX = (newDrop.directionX * inertia - heightAndGradient.gradientX * (1 - inertia)); newDrop.directionY = (newDrop.directionY * inertia - heightAndGradient.gradientY * (1 - inertia)); float len = Mathf.Sqrt(newDrop.directionX * newDrop.directionX + newDrop.directionY * newDrop.directionY); if (len != 0) { newDrop.directionX /= len; newDrop.directionY /= len; } newDrop.posX += newDrop.directionX; newDrop.posY += newDrop.directionY; // Check if droplet is on map if ((newDrop.directionX == 0 && newDrop.directionY == 0) || newDrop.posX < 0 || newDrop.posX >= currentMapSize - 1 || newDrop.posY < 0 || newDrop.posY >= currentMapSize - 1) { break; } CalculateSediment(newDrop, heightAndGradient, dropletIndex, cellOffsetX, cellOffsetY); } }
// Calculate sediment capacity based on amount of water and height private void CalculateSediment(Droplet newDrop, HeightAndGradient heightAndGradient, int dropletIndex, float cellOffsetX, float cellOffsetY) { float newHeight = CalculateHeightAndGradient(newDrop.posX, newDrop.posY).height; float deltaHeight = newHeight - heightAndGradient.height; float sedimentCapacity = Mathf.Max(-deltaHeight * newDrop.newSpeed * newDrop.currentWater * sedimentMultiplier, minSediment); //Combined with minSediment to stop terrain spikes if (newDrop.sediment > sedimentCapacity || deltaHeight > 0) { DropSediment(deltaHeight, sedimentCapacity, dropletIndex, cellOffsetX, cellOffsetY, newDrop); } else { float amountToErode = Mathf.Min((sedimentCapacity - newDrop.sediment) * erosionSpeed, -deltaHeight); for (int brushPointIndex = 0; brushPointIndex < erosionBrushIndices[dropletIndex].Length; brushPointIndex++) { ErodeArea(dropletIndex, brushPointIndex, amountToErode, newDrop); } } newDrop.newSpeed = Mathf.Sqrt(newDrop.newSpeed * newDrop.newSpeed + deltaHeight * gravity); newDrop.currentWater *= (1 - evaporateSpeed); }
public void Erode(float[] map, int mapSize, int numIterations = 1, bool resetSeed = false) { Initialize(mapSize, resetSeed); for (int iteration = 0; iteration < numIterations; iteration++) { // Create water droplet at random point on map float posX = prng.Next(0, mapSize - 1); float posY = prng.Next(0, mapSize - 1); float dirX = 0; float dirY = 0; float speed = initialSpeed; float water = initialWaterVolume; float sediment = 0; for (int lifetime = 0; lifetime < maxDropletLifetime; lifetime++) { int nodeX = (int)posX; int nodeY = (int)posY; int dropletIndex = nodeY * mapSize + nodeX; // Calculate droplet's offset inside the cell (0,0) = at NW node, (1,1) = at SE node float cellOffsetX = posX - nodeX; float cellOffsetY = posY - nodeY; // Calculate droplet's height and direction of flow with bilinear interpolation of surrounding heights HeightAndGradient heightAndGradient = CalculateHeightAndGradient(map, mapSize, posX, posY); // Update the droplet's direction and position (move position 1 unit regardless of speed) dirX = (dirX * inertia - heightAndGradient.gradientX * (1 - inertia)); dirY = (dirY * inertia - heightAndGradient.gradientY * (1 - inertia)); // Normalize direction float len = Mathf.Sqrt(dirX * dirX + dirY * dirY); if (len != 0) { dirX /= len; dirY /= len; } posX += dirX; posY += dirY; // Stop simulating droplet if it's not moving or has flowed over edge of map if ((dirX == 0 && dirY == 0) || posX < 0 || posX >= mapSize - 1 || posY < 0 || posY >= mapSize - 1) { break; } // Find the droplet's new height and calculate the deltaHeight float newHeight = CalculateHeightAndGradient(map, mapSize, posX, posY).height; float deltaHeight = newHeight - heightAndGradient.height; // Calculate the droplet's sediment capacity (higher when moving fast down a slope and contains lots of water) float sedimentCapacity = Mathf.Max(-deltaHeight * speed * water * sedimentCapacityFactor, minSedimentCapacity); // If carrying more sediment than capacity, or if flowing uphill: if (sediment > sedimentCapacity || deltaHeight > 0) { // If moving uphill (deltaHeight > 0) try fill up to the current height, otherwise deposit a fraction of the excess sediment float amountToDeposit = (deltaHeight > 0) ? Mathf.Min(deltaHeight, sediment) : (sediment - sedimentCapacity) * depositSpeed; sediment -= amountToDeposit; // Add the sediment to the four nodes of the current cell using bilinear interpolation // Deposition is not distributed over a radius (like erosion) so that it can fill small pits map[dropletIndex] += amountToDeposit * (1 - cellOffsetX) * (1 - cellOffsetY); map[dropletIndex + 1] += amountToDeposit * cellOffsetX * (1 - cellOffsetY); map[dropletIndex + mapSize] += amountToDeposit * (1 - cellOffsetX) * cellOffsetY; map[dropletIndex + mapSize + 1] += amountToDeposit * cellOffsetX * cellOffsetY; } else { // Erode a fraction of the droplet's current carry capacity. // Clamp the erosion to the change in height so that it doesn't dig a hole in the terrain behind the droplet float amountToErode = Mathf.Min((sedimentCapacity - sediment) * erodeSpeed, -deltaHeight); // Use erosion brush to erode from all nodes inside the droplet's erosion radius for (int brushPointIndex = 0; brushPointIndex < erosionBrushIndices[dropletIndex].Length; brushPointIndex++) { int nodeIndex = erosionBrushIndices[dropletIndex][brushPointIndex]; float weighedErodeAmount = amountToErode * erosionBrushWeights[dropletIndex][brushPointIndex]; float deltaSediment = (map[nodeIndex] < weighedErodeAmount) ? map[nodeIndex] : weighedErodeAmount; map[nodeIndex] -= deltaSediment; sediment += deltaSediment; } } // Update droplet's speed and water content speed = Mathf.Sqrt(speed * speed + deltaHeight * gravity); water *= (1 - evaporateSpeed); } } }
public float[] Erode(float[] map, int mapSize, int numIterations = 1, bool resetSeed = false) { Initialize(mapSize, resetSeed); for (int iteration = 0; iteration < numIterations; iteration++) { float posX = prng.Next(0, mapSize - 1); float posY = prng.Next(0, mapSize - 1); float dirX = 0; float dirY = 0; float speed = initialSpeed; float water = initialWaterVolume; float sediment = 0; for (int lifetime = 0; lifetime < maxDropletLifetime; lifetime++) { int nodeX = (int)posX; int nodeY = (int)posY; int dropletIndex = nodeY * mapSize + nodeX; float cellOffsetX = posX - nodeX; float cellOffsetY = posY - nodeY; HeightAndGradient heightAndGradient = CalculateHeightAndGradient(map, mapSize, posX, posY); dirX = (dirX * inertia - heightAndGradient.gradientX * (1 - inertia)); dirY = (dirY * inertia - heightAndGradient.gradientY * (1 - inertia)); float len = Mathf.Sqrt(dirX * dirX + dirY * dirY); if (len != 0) { dirX /= len; dirY /= len; } posX += dirX; posY += dirY; if ((dirX == 0 && dirY == 0) || posX < 0 || posX >= mapSize - 1 || posY < 0 || posY >= mapSize - 1) { break; } float newHeight = CalculateHeightAndGradient(map, mapSize, posX, posY).height; float deltaHeight = newHeight - heightAndGradient.height; float sedimentCapacity = Mathf.Max(-deltaHeight * speed * water * sedimentCapacityFactor, minSedimentCapacity); if (sediment > sedimentCapacity || deltaHeight > 0) { float amountToDeposit = (deltaHeight > 0) ? Mathf.Min(deltaHeight, sediment) : (sediment - sedimentCapacity) * depositSpeed; sediment -= amountToDeposit; map[dropletIndex] += amountToDeposit * (1 - cellOffsetX) * (1 - cellOffsetY); map[dropletIndex + 1] += amountToDeposit * cellOffsetX * (1 - cellOffsetY); map[dropletIndex + mapSize] += amountToDeposit * (1 - cellOffsetX) * cellOffsetY; map[dropletIndex + mapSize + 1] += amountToDeposit * cellOffsetX * cellOffsetY; } else { float amountToErode = Mathf.Min((sedimentCapacity - sediment) * erodeSpeed, -deltaHeight); for (int brushPointIndex = 0; brushPointIndex < erosionBrushIndices[dropletIndex].Length; brushPointIndex++) { int nodeIndex = erosionBrushIndices[dropletIndex][brushPointIndex]; float weighedErodeAmount = amountToErode * erosionBrushWeights[dropletIndex][brushPointIndex]; float deltaSediment = (map[nodeIndex] < weighedErodeAmount) ? map[nodeIndex] : weighedErodeAmount; map[nodeIndex] -= deltaSediment; sediment += deltaSediment; } } speed = Mathf.Sqrt(speed * speed + deltaHeight * gravity); water *= (1 - evaporateSpeed); } } return(map); }
public void Erode(float[] map, int mapSize, int numIterations = 1, bool resetSeed = false) { Initialize(mapSize, resetSeed); for (int iteration = 0; iteration < numIterations; iteration++) { // Create water droplet at random point on map // Создание параметров капли воды, которая появится в случайной точке карты float posX = prng.Next(0, mapSize - 1); // случайная точка по X float posY = prng.Next(0, mapSize - 1); // случайная точка по Y float dirX = 0; // Направление по X float dirY = 0; // Направление по Y float speed = initialSpeed; // Инициализация скорости капли float water = initialWaterVolume; // Инициализация количества воды в капле float sediment = 0; // количество переносимого каплей осадка for (int lifetime = 0; lifetime < maxDropletLifetime; lifetime++) { int nodeX = (int)posX; // случайная позиция X int nodeY = (int)posY; // случайная позиция Y int dropletIndex = nodeY * mapSize + nodeX; // индекс капли // Calculate droplet's offset inside the cell (0,0) = at NW node, (1,1) = at SE node // Вычисление положения капли в одной клетке float cellOffsetX = posX - nodeX; float cellOffsetY = posY - nodeY; // Calculate droplet's height and direction of flow with bilinear interpolation of surrounding heights // Вычисление высоты капель и направления потока с билинейной интерполяцией окружающих высот HeightAndGradient heightAndGradient = CalculateHeightAndGradient(map, mapSize, posX, posY); // Update the droplet's direction and position (move position 1 unit regardless of speed) // Обновление направления и положения капель (перемещение позиции на 1 независимо от скорости) dirX = (dirX * inertia - heightAndGradient.gradientX * (1 - inertia)); dirY = (dirY * inertia - heightAndGradient.gradientY * (1 - inertia)); // Normalize direction float len = Mathf.Sqrt(dirX * dirX + dirY * dirY); if (len != 0) { dirX /= len; dirY /= len; } posX += dirX; posY += dirY; // Stop simulating droplet if it's not moving or has flowed over edge of map // Остановить моделирование капли, если она не движется или перетекла через край карты if ((dirX == 0 && dirY == 0) || posX < 0 || posX >= mapSize - 1 || posY < 0 || posY >= mapSize - 1) { break; } // Find the droplet's new height and calculate the deltaHeight // Нахождение новой высоты капель и вычисление Дельта-высоты float newHeight = CalculateHeightAndGradient(map, mapSize, posX, posY).height; float deltaHeight = newHeight - heightAndGradient.height; // Calculate the droplet's sediment capacity (higher when moving fast down a slope and contains lots of water) // Расчет емкости осадка капель (выше при быстром движении вниз по склону и содержит много воды) float sedimentCapacity = Mathf.Max(-deltaHeight * speed * water * sedimentCapacityFactor, minSedimentCapacity); // If carrying more sediment than capacity, or if flowing uphill: // Если несете больше наносов, чем емкость, или если течет в гору: if (sediment > sedimentCapacity || deltaHeight > 0) { // If moving uphill (deltaHeight > 0) try fill up to the current height, otherwise deposit a fraction of the excess sediment // При движении в гору (deltaHeight > 0) попробуйте заполнить до текущей высоты, в противном случае отложите часть избыточного осадка float amountToDeposit = (deltaHeight > 0) ? Mathf.Min(deltaHeight, sediment) : (sediment - sedimentCapacity) * depositSpeed; sediment -= amountToDeposit; // Add the sediment to the four nodes of the current cell using bilinear interpolation // Deposition is not distributed over a radius (like erosion) so that it can fill small pits // Добавьте осадок в четыре узла текущей ячейки с помощью билинейной интерполяции // Осаждение не распределяется по радиусу (как эрозия), так что он может заполнить небольшие ямы map[dropletIndex] += amountToDeposit * (1 - cellOffsetX) * (1 - cellOffsetY); map[dropletIndex + 1] += amountToDeposit * cellOffsetX * (1 - cellOffsetY); map[dropletIndex + mapSize] += amountToDeposit * (1 - cellOffsetX) * cellOffsetY; map[dropletIndex + mapSize + 1] += amountToDeposit * cellOffsetX * cellOffsetY; } else { // Erode a fraction of the droplet's current carry capacity. // Clamp the erosion to the change in height so that it doesn't dig a hole in the terrain behind the droplet // Выветривается часть капель тока, пропускная способность. // Зажмите размывание к изменению в высоте так, что она не выкопает отверстие в местности за каплей float amountToErode = Mathf.Min((sedimentCapacity - sediment) * erodeSpeed, -deltaHeight); // Use erosion brush to erode from all nodes inside the droplet's erosion radius // Используйте щетку размывания для того чтобы выветриться от всех узлов внутри радиуса размывания капелек for (int brushPointIndex = 0; brushPointIndex < erosionBrushIndices[dropletIndex].Length; brushPointIndex++) { int nodeIndex = erosionBrushIndices[dropletIndex][brushPointIndex]; float weighedErodeAmount = amountToErode * erosionBrushWeights[dropletIndex][brushPointIndex]; float deltaSediment = (map[nodeIndex] < weighedErodeAmount) ? map[nodeIndex] : weighedErodeAmount; map[nodeIndex] -= deltaSediment; sediment += deltaSediment; } } // Update droplet's speed and water content // Обновление скорости капель и содержания воды speed = Mathf.Sqrt(speed * speed + deltaHeight * gravity); water *= (1 - evaporateSpeed); } } }