private static void DropSedimentFromKernel(Field2d <float> field, IField2d <float> kernel, int centerX, int centerY, float targetSediment) { for (int y = 0; y < kernel.Height; y++) { for (int x = 0; x < kernel.Width; x++) { int cX = centerX + x - kernel.Width / 2; int cY = centerY + y - kernel.Height / 2; if (cX >= 0 && cY >= 0 && cX < field.Width && cY < field.Height) { field[cY, cX] += targetSediment * kernel[y, x]; } } } }
public IField2d <T> RenderToField() { Field2d <T> output = new Field2d <T>(new Common.ConstantField <T>(this.Width, this.Height, default(T))); foreach (var chunk in this.chunks) { for (int y = 0; y < chunk.Field.Height; y++) { for (int x = 0; x < chunk.Field.Width; x++) { output[y + chunk.MinPoint.Y, x + chunk.MinPoint.X] = chunk.Field[y, x]; } } } return(output); }
// TODO: Don't allow the field to erode below zero. // TODO: Here, at last, is the source of the insane bug. The way this bug works is easiest to understand // if one envisions a kernel of small radius and a "trough" consisting of two elevated walls and a lower // middle. The nature of a trough is that a particle can become "trapped" in one, rolling back and forth // as each wall turns the particle back. If a trough is narrow enough that a particle dropping sediment in // the middle will also drop sediment on both walls by virtue of its kernel, then oscillation in the trough // can cause the particle to "build towers." This happens because when the particle is picking up sediment-- // i.e., when it's beginning to go down-hill--its kernel covers part of the trough and a little bit outside; // however, when the particle is depositing sediment--i.e., when it's beginning to go uphill--its kernel is // entirely over the trough. By repeated action, this allows the particle to "dig" sediment from just outside // the walls of its trough and bring that sediment back into the trough, creating the extremely // characteristic "bars" of high and low elevation in extremely close proximity. The solution to this, // presumably, is to prevent this "digging" behavior, presumably by taking sediment in a more cautious // manner that won't allow erosion computed from a high place to induce the removal of sediment from a low // place. private static void PickUpSedimentFromKernel(Field2d <float> field, IField2d <float> kernel, int centerX, int centerY, float targetSediment) { if (targetSediment == 0) { return; } float targetMin = field[centerY, centerX] - kernel[kernel.Height / 2, kernel.Width / 2] * targetSediment; float collected = 0f; for (int y = 0; y < kernel.Height; y++) { for (int x = 0; x < kernel.Width; x++) { int cX = centerX + x - kernel.Width / 2; int cY = centerY + y - kernel.Height / 2; if (cX >= 0 && cY >= 0 && cX < field.Width && cY < field.Height && field[cY, cX] >= targetMin) { collected += targetSediment * kernel[y, x]; } } } float scalar = targetSediment / collected; for (int y = 0; y < kernel.Height; y++) { for (int x = 0; x < kernel.Width; x++) { int cX = centerX + x - kernel.Width / 2; int cY = centerY + y - kernel.Height / 2; if (cX >= 0 && cY >= 0 && cX < field.Width && cY < field.Height && field[cY, cX] >= targetMin) { field[cY, cX] -= targetSediment * kernel[y, x] * scalar; } } } }
// Based on the approach by Hans Theobald Beyer, "Implementation of a method for hydraulic erosion," 2015 public static Field2d <float> DropletHydraulic(IField2d <float> inputHeightmap, int numDroplets, int iterationsPerDrop, float minSlope = 0f, float maxHeight = 1f, int radius = 0) { Random random = new Random(); float pFriction = 0.3f; float pCapacity = 1f; float pErode = 0.3f; float pDeposit = 0.3f; float pGravity = 0.8f / maxHeight; float pEvaporate = 0.01f; const int STARTING_DIRECTION_GRANULARITY = 32; var kernel = GetKernel(radius); Field2d <float> heightmap = new Field2d <float>(inputHeightmap); for (int idx = 0; idx < numDroplets; idx++) { Droplet droplet = new Droplet() { Position = new vFloat(random.Next(heightmap.Width), random.Next(heightmap.Height)), Direction = new vFloat(random.Next(STARTING_DIRECTION_GRANULARITY) - STARTING_DIRECTION_GRANULARITY / 2, random.Next(STARTING_DIRECTION_GRANULARITY) - STARTING_DIRECTION_GRANULARITY / 2).norm(), Speed = 0, Water = 1, Sediment = 0, }; for (int iteration = 0; iteration < iterationsPerDrop; iteration++) { (int x, int y)oldPos = ((int)droplet.Position[0], (int)droplet.Position[1]); float oldHeight = heightmap[oldPos.y, oldPos.x]; var gradient = heightmap.GradientAtPoint(droplet.Position); droplet.Direction = (droplet.Direction + gradient) * (1 - pFriction); if (droplet.Direction.magSq() > 0) { droplet.Position += droplet.Direction.norm(); } if (droplet.Position[0] < 0f || droplet.Position[1] < 0f || droplet.Position[0] >= heightmap.Width || droplet.Position[1] >= heightmap.Height) { break; } (int x, int y)newPos = ((int)droplet.Position[0], (int)droplet.Position[1]); float newHeight = heightmap[newPos.y, newPos.x]; if (newHeight > oldHeight) { float droppedSediment = Math.Min(newHeight - oldHeight, droplet.Sediment); DropSedimentFromKernel(heightmap, kernel, oldPos.x, oldPos.y, droppedSediment); droplet.Sediment -= droppedSediment; } else if (newHeight < oldHeight) { float capacity = Math.Max(oldHeight - newHeight, minSlope) * droplet.Speed * droplet.Water * pCapacity; if (droplet.Sediment > capacity) { float droppedSediment = (droplet.Sediment - capacity) * pDeposit; DropSedimentFromKernel(heightmap, kernel, oldPos.x, oldPos.y, droppedSediment); droplet.Sediment -= droppedSediment; } else { float pickedUpSediment = Math.Min((capacity - droplet.Sediment) * pErode, oldHeight - newHeight); PickUpSedimentFromKernel(heightmap, kernel, oldPos.x, oldPos.y, pickedUpSediment); droplet.Sediment += pickedUpSediment; } } // This is from the paper, but it's super weird. So, the drops will pick up speed even if they go uphill? // I think speed is the wrong term for this variable. In fact, this whole concept is very magical. Speed should // be determined by the magnitude of the velocity, not by some random accumulator. On the other hand, I tried // that, and this works way better. So... droplet.Speed = (float)Math.Sqrt(droplet.Speed * droplet.Speed + Math.Abs(newHeight - oldHeight) * pGravity); droplet.Water = droplet.Water * (1 - pEvaporate); } } return(heightmap); }
public static List <Point2d> FindPath(Rectangle area, Point2d from, Point2d to, Func <Point2d, Point2d, float> getCostOfStep = null, Func <Point2d, Point2d, float> estimateCostOfStep = null) { getCostOfStep = getCostOfStep ?? Point2d.Distance; estimateCostOfStep = estimateCostOfStep ?? Point2d.Distance; Field2d <float> visited = new Field2d <float>(new ConstantField <float>(area.Width, area.Height, float.NaN)); visited[from.y, from.x] = 0f; BinaryHeap <CostedPoint> toVisit = new BinaryHeap <CostedPoint>(); toVisit.Push(new CostedPoint() { point = from, cost = visited[from.y, from.x] + estimateCostOfStep(from, to) }); Field2d <Point2d> cameFrom = new Field2d <Point2d>(new ConstantField <Point2d>(area.Width, area.Height, new Point2d(-1, -1))); cameFrom[from.y, from.x] = from; while (toVisit.Count > 0) { var curr = toVisit.Pop().point; for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { if (i == 0 && j == 0) { continue; } Point2d next = new Point2d(curr.x + i, curr.y + j); if (next.x <= area.Left || next.x >= area.Right || next.y <= area.Top || next.y >= area.Bottom) { continue; } if (next == to) { List <Point2d> path = new List <Point2d>(); path.Add(next); path.Add(curr); Point2d it = curr; while (cameFrom[it.y, it.x] != it) { it = cameFrom[it.y, it.x]; path.Add(it); } path.Reverse(); return(path); } float cost = visited[curr.y, curr.x] + getCostOfStep(curr, next); if (float.IsNaN(visited[next.y, next.x]) || visited[next.y, next.x] > cost) { visited[next.y, next.x] = cost; cameFrom[next.y, next.x] = curr; toVisit.Push(new CostedPoint() { point = next, cost = cost + estimateCostOfStep(next, to) }); } } } } return(null); }