Other Posts in Image Editing

  1. Perlin Noise
  2. Fault Formation
  3. Cellular Textures
  4. Resizing an Image in C#
  5. Box Blur and Gaussian Blur... Sort of...
  6. Thermal Erosion
  7. Using Mid Point Displacement to Create Cracks
  8. Fluvial Erosion
  9. Creating Marble Like Textures Procedurally
  10. Procedural Textures and Dilation
  11. Converting Image to Black and White in C#
  12. Getting an HTML Based Color Palette from an Image in C#
  13. Adding Noise/Jitter to an Image in C#
  14. Creating Pixelated Images in C#
  15. Edge detection in C#
  16. Using Sin to Get What You Want... In C#...
  17. Noise Reduction of an Image in C# using Median Filters
  18. Image Dilation in C#
  19. Sepia Tone in C#
  20. Kuwahara Filter in C#
  21. Matrix Convolution Filters in C#
  22. Symmetric Nearest Neighbor in C#
  23. Bump Map Creation Using C#
  24. Normal Map Creation Using C#
  25. Creating Negative Images using C#
  26. Red, Blue, and Green Filters in C#
  27. Converting an Image to ASCII Art in C#
  28. Adjusting Brightness of an Image in C#
  29. Adding Noise to an Image in C#
  30. Adjusting the Gamma of an Image Using C#
  31. Adjusting Contrast of an Image in C#
  32. Drawing a Box With Rounded Corners in C#
  33. Anding Two Images Together Using C#
  34. Motion Detection in C#
  35. Creating Thermometer Chart in C#
  36. Colorizing a Black and White Image in C#
  37. Extracting an Icon From a File
  38. Setting the Pixel Format and Image Format of an Image in .Net
  39. Using Unsafe Code for Faster Image Manipulation
  40. Sobel Edge Detection and Laplace Edge Detection in C#

Fluvial Erosion

4/18/2008

It's been a while since I posted anything about game programming. It's not because I've been lazy, but because most of what I've been doing wouldn't really interest the public at large. However, I did implement something rather interesting: Fluvial Erosion. If you're curious what that is, it's erosion caused by moving water (think rushing rivers, etc.). The version that I created is based off of the one presented by Ken Musgrave in his PHD Dissertation (page 58).

Now his version doesn't take into account pressure, evaporation, force of the water striking something, etc. In fact, it's about as basic as you can get. The basic premise is, you drop some water somewhere, it picks up some stuff, and it moves downhill. As it moves downhill, it either drops some of the stuff it picked up or picks up some more stuff... That's it. To be honest, there's not that much of a difference between this and the thermal erosion technique unless you carve out the rivers, gullies, etc. before using this approach. And like the thermal erosion technique, you really wont see much, if any difference between the images that you generate. In face with anything less than 10,000 iterations, you will most likely end up with no difference at all. On the bright side, each iteration is extremely fast. So if you were to say, run this in game for a river, it might get noticeable in a year or so (definitely something to think about when you create that next gen MMO). Anyway, the basic code for this is here:

   1: /// <summary>
   2: /// Does fluvial erosion on black and white images
   3: /// </summary>
   4: /// <param name="Image">Image to manipulate</param>
   5: /// <param name="X">X position to drop water</param>
   6: /// <param name="Y">Y position to drop water</param>
   7: /// <param name="WaterPercent">Percent that the space is filled with water (1.0=100%, 0.0=0%)</param>
   8: /// <param name="DepositRate">When the water moves, this is the amount that is deposited in the new spot (1.0=all, 0.0=none)</param>
   9: /// <param name="PickupRate">The amount of "land" that the water picks up when it gets in a new spot</param>
  10: /// <param name="Iterations">Number of steps that the water should take before stopping</param>
  11: /// <returns>Returns the resulting Bitmap</returns>
  12: public static Bitmap FluvialErosion(Bitmap Image, int X, int Y,
  13:     float WaterPercent, float DepositRate, float PickupRate, int Iterations)
  14: {
  15:     Bitmap ReturnValue=new Bitmap(Image.Width,Image.Height);
  16:     BitmapData ImageData = LockImage(Image);
  17:     int ImagePixelSize = GetPixelSize(ImageData);
  18:     BitmapData ReturnData = LockImage(ReturnValue);
  19:     int ReturnPixelSize = GetPixelSize(ReturnData);
  20:     float[,] SedimentValues = new float[Image.Width, Image.Height];
  21:     float[,] WaterValues = new float[Image.Width, Image.Height];
  22:     float[,] Data = new float[Image.Width, Image.Height];
  23:     for (int x = 0; x < Image.Width; ++x)
  24:     {
  25:         for (int y = 0; y < Image.Height; ++y)
  26:         {
  27:             Data[x, y] = GetHeight(GetPixel(ImageData, x, y, ImagePixelSize));
  28:         }
  29:     }
  30:     WaterValues[X, Y] = WaterPercent;
  31:     if (Data[X, Y] > PickupRate)
  32:     {
  33:         SedimentValues[X, Y] = PickupRate;
  34:         Data[X, Y] -= PickupRate;
  35:         Data[X, Y] = Math.MathHelper.Clamp(Data[X, Y], 1.0f, 0.0f);
  36:     }
  37:     else
  38:     {
  39:         SedimentValues[X, Y] = Data[X, Y];
  40:         Data[X, Y] -= PickupRate;
  41:         Data[X, Y] = Math.MathHelper.Clamp(Data[X, Y], 1.0f, 0.0f);
  42:     }
  43:     for (int i = 0; i < Iterations; ++i)
  44:     {
  45:         for (int x = 0; x < Image.Width; ++x)
  46:         {
  47:             for (int y = 0; y < Image.Height; ++y)
  48:             {
  49:                 if (WaterValues[x, y] > 0.0f)
  50:                 {
  51:                     List<int> MinX = new List<int>();
  52:                     List<int> MinY = new List<int>();
  53:                     float MinValue = Data[x, y];
  54:                     for (int BoxX = Math.MathHelper.Clamp(x - 1, Image.Width - 1, 0); BoxX <= Math.MathHelper.Clamp(x + 1, Image.Width - 1, 0); ++BoxX)
  55:                     {
  56:                         for (int BoxY = Math.MathHelper.Clamp(y - 1, Image.Height - 1, 0); BoxY <= Math.MathHelper.Clamp(y + 1, Image.Height - 1, 0); ++BoxY)
  57:                         {
  58:                             if (BoxX != x || BoxY != y)
  59:                             {
  60:                                 if (Data[BoxX, BoxY] < MinValue)
  61:                                 {
  62:                                     MinValue = Data[BoxX, BoxY];
  63:                                     MinX.Clear();
  64:                                     MinX.Add(BoxX);
  65:                                     MinY.Clear();
  66:                                     MinY.Add(BoxY);
  67:                                 }
  68:                                 else if (Data[BoxX, BoxY] == MinValue)
  69:                                 {
  70:                                     MinX.Add(BoxX);
  71:                                     MinY.Add(BoxY);
  72:                                 }
  73:                             }
  74:                         }
  75:                     }
  76:                     float WaterChange = WaterValues[x, y];
  77:                     for (int z = 0; z < MinX.Count; ++z)
  78:                     {
  79:                         if (WaterChange > ((WaterValues[x, y] + Data[x, y]) - (WaterValues[MinX[z], MinY[z]] + Data[MinX[z], MinY[z]])))
  80:                         {
  81:                             WaterChange = ((WaterValues[x, y] + Data[x, y]) - (WaterValues[MinX[z], MinY[z]] + Data[MinX[z], MinY[z]]));
  82:                         }
  83:                     }
  84:                     if (WaterChange <= 0.0f)
  85:                     {
  86:                         Data[x, y] += (DepositRate * SedimentValues[x, y]);
  87:                         SedimentValues[x, y] *= (1.0f - DepositRate);
  88:                     }
  89:                     else
  90:                     {
  91:                         float SedimentCapacity = WaterValues[x, y] * PickupRate;
  92:                         for (int z = 0; z < MinX.Count; ++z)
  93:                         {
  94:                             WaterValues[MinX[z], MinY[z]] += (WaterChange / (float)MinX.Count);
  95:                             if (SedimentValues[x, y] >= SedimentCapacity)
  96:                             {
  97:                                 SedimentValues[MinX[z], MinY[z]] += (SedimentCapacity / (float)MinX.Count);
  98:                             }
  99:                             else
 100:                             {
 101:                                 SedimentValues[MinX[z], MinY[z]] += SedimentValues[x, y] + (PickupRate * ((SedimentCapacity - SedimentValues[x, y]) / (float)MinX.Count));
 102:                             }
 103:                         }
 104:                         if (SedimentValues[x, y] >= SedimentCapacity)
 105:                         {
 106:                             Data[x, y] += DepositRate * (SedimentValues[x, y] - SedimentCapacity);
 107:                             SedimentValues[x, y] = (1.0f - DepositRate) * (SedimentValues[x, y] - SedimentCapacity);
 108:                         }
 109:                         else
 110:                         {
 111:                             Data[x, y] += DepositRate * (SedimentCapacity - SedimentValues[x, y]);
 112:                             SedimentValues[x, y] = 0.0f;
 113:                         }
 114:                         WaterValues[x, y] -= WaterChange;
 115:                     }
 116:                 }
 117:             }
 118:         }
 119:     }
 120:     for (int x = 0; x < Image.Width; ++x)
 121:     {
 122:         for (int y = 0; y < Image.Height; ++y)
 123:         {
 124:             Data[x, y] += SedimentValues[x, y];
 125:             Data[x, y] = Math.MathHelper.Clamp(Data[x, y], 1.0f, 0.0f);
 126:             Data[x,y]*=255.0f;
 127:             SetPixel(ReturnData, x, y, Color.FromArgb((int)Data[x, y], (int)Data[x, y], (int)Data[x, y]), ReturnPixelSize);
 128:         }
 129:     }
 130:     UnlockImage(ReturnValue, ReturnData);
 131:     UnlockImage(Image, ImageData);
 132:     return ReturnValue;
 133: }

Not the best implementation in the world (as I'm sure there are a couple of bugs) but it does the job. So try it out, leave feedback, and happy coding.



Comments