Advertisement
JontePonte

funky erosion

Oct 26th, 2024
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 8.57 KB | None | 0 0
  1. using UnityEngine;
  2. using System;
  3.  
  4. public class Erosion : MonoBehaviour
  5. {
  6.     public int seed;
  7.     [Range(2, 8)]
  8.     public int erosionRadius = 3;
  9.     [Range(0, 1)]
  10.     public float inertia = .05f; // At zero, water will instantly change direction to flow downhill. At 1, water will never change direction.
  11.     public float sedimentCapacityFactor = 4; // Multiplier for how much sediment a droplet can carry
  12.     public float minSedimentCapacity = .01f; // Used to prevent carry capacity getting too close to zero on flatter terrain
  13.     [Range(0, 1)]
  14.     public float erodeSpeed = .3f;
  15.     [Range(0, 1)]
  16.     public float depositSpeed = .3f;
  17.     [Range(0, 1)]
  18.     public float evaporateSpeed = .01f;
  19.     public float gravity = 4;
  20.     public int maxDropletLifetime = 30;
  21.  
  22.     public float initialWaterVolume = 1;
  23.     public float initialSpeed = 1;
  24.  
  25.     // Indices and weights of erosion brush precomputed for every node
  26.     int[][] erosionBrushIndices;
  27.     float[][] erosionBrushWeights;
  28.     System.Random prng;
  29.  
  30.     int currentSeed;
  31.     int currentErosionRadius;
  32.     int currentMapSize;
  33.  
  34.     // Initialization creates a System.Random object and precomputes indices and weights of erosion brush
  35.     void Initialize(int mapSize, bool resetSeed)
  36.     {
  37.         if (resetSeed || prng == null || currentSeed != seed)
  38.         {
  39.             prng = new System.Random(seed);
  40.             currentSeed = seed;
  41.         }
  42.  
  43.         if (erosionBrushIndices == null || currentErosionRadius != erosionRadius || currentMapSize != mapSize)
  44.         {
  45.             InitializeBrushIndices(mapSize, erosionRadius);
  46.             currentErosionRadius = erosionRadius;
  47.             currentMapSize = mapSize;
  48.         }
  49.     }
  50.  
  51.     public void Erode(float[] map, int mapSize, int numIterations = 1, bool resetSeed = false)
  52.     {
  53.         Initialize(mapSize, resetSeed);
  54.  
  55.         float[] mapCopy = new float[map.Length];
  56.         Array.Copy(map, mapCopy, map.Length);
  57.  
  58.         for (int iteration = 0; iteration < numIterations; iteration++)
  59.         {
  60.             // Create water droplet at random point on map
  61.             float posX = prng.Next(0, mapSize - 1);
  62.             float posY = prng.Next(0, mapSize - 1);
  63.             float dirX = 0;
  64.             float dirY = 0;
  65.             float speed = initialSpeed;
  66.             float water = initialWaterVolume;
  67.             float sediment = 0;
  68.  
  69.             for (int lifetime = 0; lifetime < maxDropletLifetime; lifetime++)
  70.             {
  71.                 int nodeX = (int)posX;
  72.                 int nodeY = (int)posY;
  73.                 int dropletIndex = nodeY * mapSize + nodeX;
  74.                 // Calculate droplet's offset inside the cell (0,0) = at NW node, (1,1) = at SE node
  75.                 float cellOffsetX = posX - nodeX;
  76.                 float cellOffsetY = posY - nodeY;
  77.  
  78.                 // Calculate droplet's height and direction of flow with bilinear interpolation of surrounding heights
  79.                 HeightAndGradient heightAndGradient = CalculateHeightAndGradient(mapCopy, mapSize, posX, posY);
  80.  
  81.                 // Update the droplet's direction and position (move position 1 unit regardless of speed)
  82.                 dirX = (dirX * inertia - heightAndGradient.gradientX * (1 - inertia));
  83.                 dirY = (dirY * inertia - heightAndGradient.gradientY * (1 - inertia));
  84.                 // Normalize direction
  85.                 float len = Mathf.Sqrt(dirX * dirX + dirY * dirY);
  86.                 if (len != 0)
  87.                 {
  88.                     dirX /= len;
  89.                     dirY /= len;
  90.                 }
  91.                 posX += dirX;
  92.                 posY += dirY;
  93.  
  94.                 // Stop simulating droplet if it's not moving or has flowed over edge of map
  95.                 if ((dirX == 0 && dirY == 0) || posX < 0 || posX >= mapSize - 1 || posY < 0 || posY >= mapSize - 1)
  96.                 {
  97.                     break;
  98.                 }
  99.  
  100.                 // Find the droplet's new height and calculate the deltaHeight
  101.                 float newHeight = CalculateHeightAndGradient(mapCopy, mapSize, posX, posY).height;
  102.                 float deltaHeight = newHeight - heightAndGradient.height;
  103.  
  104.                 // Calculate the droplet's sediment capacity (higher when moving fast down a slope and contains lots of water)
  105.                 float sedimentCapacity = Mathf.Max(-deltaHeight * speed * water * sedimentCapacityFactor, minSedimentCapacity);
  106.  
  107.                 // If carrying more sediment than capacity, or if flowing uphill:
  108.                 if (sediment > sedimentCapacity || deltaHeight > 0)
  109.                 {
  110.                     // If moving uphill (deltaHeight > 0) try fill up to the current height, otherwise deposit a fraction of the excess sediment
  111.                     float amountToDeposit = (deltaHeight > 0) ? Mathf.Min(deltaHeight, sediment) : (sediment - sedimentCapacity) * depositSpeed;
  112.                     sediment -= amountToDeposit;
  113.  
  114.                     // Add the sediment to the four nodes of the current cell using bilinear interpolation
  115.                     // Deposition is not distributed over a radius (like erosion) so that it can fill small pits
  116.                     map[dropletIndex] += amountToDeposit * (1 - cellOffsetX) * (1 - cellOffsetY);
  117.                     map[dropletIndex + 1] += amountToDeposit * cellOffsetX * (1 - cellOffsetY);
  118.                     map[dropletIndex + mapSize] += amountToDeposit * (1 - cellOffsetX) * cellOffsetY;
  119.                     map[dropletIndex + mapSize + 1] += amountToDeposit * cellOffsetX * cellOffsetY;
  120.  
  121.                 }
  122.                 else
  123.                 {
  124.                     // Erode a fraction of the droplet's current carry capacity.
  125.                     // Clamp the erosion to the change in height so that it doesn't dig a hole in the terrain behind the droplet
  126.                     float amountToErode = Mathf.Min((sedimentCapacity - sediment) * erodeSpeed, -deltaHeight);
  127.  
  128.                     // Use erosion brush to erode from all nodes inside the droplet's erosion radius
  129.                     for (int brushPointIndex = 0; brushPointIndex < erosionBrushIndices[dropletIndex].Length; brushPointIndex++)
  130.                     {
  131.                         int nodeIndex = erosionBrushIndices[dropletIndex][brushPointIndex];
  132.                         float weighedErodeAmount = amountToErode * erosionBrushWeights[dropletIndex][brushPointIndex];
  133.                         float deltaSediment = (mapCopy[nodeIndex] < weighedErodeAmount) ? mapCopy[nodeIndex] : weighedErodeAmount;
  134.                         map[nodeIndex] -= deltaSediment;
  135.                         sediment += deltaSediment;
  136.                     }
  137.                 }
  138.  
  139.                 // Update droplet's speed and water content
  140.                 speed = Mathf.Sqrt(speed * speed + deltaHeight * gravity);
  141.                 water *= (1 - evaporateSpeed);
  142.             }
  143.         }
  144.     }
  145.  
  146.     HeightAndGradient CalculateHeightAndGradient(float[] nodes, int mapSize, float posX, float posY)
  147.     {
  148.         int coordX = (int)posX;
  149.         int coordY = (int)posY;
  150.  
  151.         // Calculate droplet's offset inside the cell (0,0) = at NW node, (1,1) = at SE node
  152.         float x = posX - coordX;
  153.         float y = posY - coordY;
  154.  
  155.         // Calculate heights of the four nodes of the droplet's cell
  156.         int nodeIndexNW = coordY * mapSize + coordX;
  157.         float heightNW = nodes[nodeIndexNW];
  158.         float heightNE = nodes[nodeIndexNW + 1];
  159.         float heightSW = nodes[nodeIndexNW + mapSize];
  160.         float heightSE = nodes[nodeIndexNW + mapSize + 1];
  161.  
  162.         // Calculate droplet's direction of flow with bilinear interpolation of height difference along the edges
  163.         float gradientX = (heightNE - heightNW) * (1 - y) + (heightSE - heightSW) * y;
  164.         float gradientY = (heightSW - heightNW) * (1 - x) + (heightSE - heightNE) * x;
  165.  
  166.         // Calculate height with bilinear interpolation of the heights of the nodes of the cell
  167.         float height = heightNW * (1 - x) * (1 - y) + heightNE * x * (1 - y) + heightSW * (1 - x) * y + heightSE * x * y;
  168.  
  169.         return new HeightAndGradient() { height = height, gradientX = gradientX, gradientY = gradientY };
  170.     }
  171.  
  172.     void InitializeBrushIndices(int mapSize, int radius)
  173.     {
  174.         erosionBrushIndices = new int[mapSize * mapSize][];
  175.         erosionBrushWeights = new float[mapSize * mapSize][];
  176.  
  177.         int[] xOffsets = new int[radius * radius * 4];
  178.         int[] yOffsets = new int[radius * radius * 4];
  179.         float[] weights = new float[radius * radius * 4];
  180.         float weightSum = 0;
  181.         int addIndex = 0;
  182.  
  183.         for (int i = 0; i < erosionBrushIndices.GetLength(0); i++)
  184.         {
  185.             int centreX = i % mapSize;
  186.             int centreY = i / mapSize;
  187.  
  188.             if (centreY <= radius || centreY >= mapSize - radius || centreX <= radius + 1 || centreX >= mapSize - radius)
  189.             {
  190.                 weightSum = 0;
  191.                 addIndex = 0;
  192.                 for (int y = -radius; y <= radius; y++)
  193.                 {
  194.                     for (int x = -radius; x <= radius; x++)
  195.                     {
  196.                         float sqrDst = x * x + y * y;
  197.                         if (sqrDst < radius * radius)
  198.                         {
  199.                             int coordX = centreX + x;
  200.                             int coordY = centreY + y;
  201.  
  202.                             if (coordX >= 0 && coordX < mapSize && coordY >= 0 && coordY < mapSize)
  203.                             {
  204.                                 float weight = 1 - Mathf.Sqrt(sqrDst) / radius;
  205.                                 weightSum += weight;
  206.                                 weights[addIndex] = weight;
  207.                                 xOffsets[addIndex] = x;
  208.                                 yOffsets[addIndex] = y;
  209.                                 addIndex++;
  210.                             }
  211.                         }
  212.                     }
  213.                 }
  214.             }
  215.  
  216.             int numEntries = addIndex;
  217.             erosionBrushIndices[i] = new int[numEntries];
  218.             erosionBrushWeights[i] = new float[numEntries];
  219.  
  220.             for (int j = 0; j < numEntries; j++)
  221.             {
  222.                 erosionBrushIndices[i][j] = (yOffsets[j] + centreY) * mapSize + xOffsets[j] + centreX;
  223.                 erosionBrushWeights[i][j] = weights[j] / weightSum;
  224.             }
  225.         }
  226.     }
  227.  
  228.     struct HeightAndGradient
  229.     {
  230.         public float height;
  231.         public float gradientX;
  232.         public float gradientY;
  233.     }
  234. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement