Exemple #1
0
        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];
                    }
                }
            }
        }
Exemple #2
0
        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);
        }
Exemple #3
0
        // 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;
                    }
                }
            }
        }
Exemple #4
0
        // 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);
        }
Exemple #5
0
        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);
        }