简体   繁体   中英

How can I efficiently generate a random X and Y value INSIDE of a polygon in C++?

So I'm creating a virtual mapping software that essentially breaks coordinates into Areas. An Area is comprised of a defined list of boundary coordinates (coordinates that make the outer rim of the area, which connect to one another).

With this software, I need to randomly select points in EACH area that reside INSIDE of the areas boundary coordinates. Each area is different from the other and can have many more or even less sides, but with a minimum of 3 sides and no maximum sides.

I currently have a solution in which I simply generate random numbers until the numbers are within the area. However, due to the quantity of Areas (have vastly different Boundary coordinates ranging in small to HUGE values) & the quantity of points (could be 1-100+) this tactic proves to be highly inefficient (takes a long time to finish running). I would like to hear peoples ideas or even experiences/work on how to optimize this so it isn't so sluggish.

I've created a small demo application to explain the situation better...

#include "stdafx.h"
#include <vector>
#include <random>

const int GenerateRandomNumberBetween(
   const int start,
   const int end)
{
   const int stable_end = ((end < start) ? start : end);
   std::random_device rd;
   std::mt19937 generator(rd());
   std::uniform_int_distribution<int> distribution(start, stable_end);

   return distribution(generator); // generates number in the range the distribution value 
}

class Area
{
public:
   Area()
   {
      // Define a primitive area for this example, but please note that this is a very basic area, and most areas are acctually much larger and have many more sides...
      // This sample area creates a triangle.

      //(-2, 2);
      boundaries_x_coordinates.push_back(-2);
      boundaries_y_coordinates.push_back(2);

      //(2, 2);
      boundaries_x_coordinates.push_back(2);
      boundaries_y_coordinates.push_back(2);

      //(-2, 2);
      boundaries_x_coordinates.push_back(-2);
      boundaries_y_coordinates.push_back(-2);
   }

   const bool InArea(
      const int x,
      const int y)
   {
      // This function works just fine, and can be ignored... I just included it to show that we check if the new coordinates are indeed within the given Area.
      int minX = 0;
      int maxX = 0;
      int minY = 0;
      int maxY = 0;
      for (int i = 0; i < boundaries_x_coordinates.size(); i++)
      {
         if (boundaries_x_coordinates[0] < minX)
         {
            minX = boundaries_x_coordinates[0];
         }

         if (boundaries_x_coordinates[0] > maxX)
         {
            maxX = boundaries_x_coordinates[0];
         }

         if (boundaries_y_coordinates[1] < minY)
         {
            minY = boundaries_y_coordinates[1];
         }

         if (boundaries_y_coordinates[1] > maxY)
         {
            maxY = boundaries_y_coordinates[1];
         }
      }

      if (boundaries_x_coordinates.size() < 3)
      {
         return false;
      }
      else if (x < minX || x > maxX || y < minY || y > maxY)
      {
         return false;
      }
      else
      {
         size_t i, j, c = 0;
         for (i = 0, j = boundaries_x_coordinates.size() - 1; i < boundaries_x_coordinates.size(); j = i++)
         {
            if (((boundaries_y_coordinates[i] > y) != (boundaries_y_coordinates[j] > y)) &&
               (x < (boundaries_x_coordinates[j] - boundaries_x_coordinates[i]) * (y - boundaries_y_coordinates[i]) /
               (boundaries_y_coordinates[j] - boundaries_y_coordinates[i]) + boundaries_x_coordinates[i]))
            {
               c = !c;
            }
         }
         return (c == 0) ? false : true;
      }
   }

   std::vector<int> GenerateRandomPointInsideArea()
   {
      int minX = 0, maxX = 0, minY = 0, maxY = 0;

      for (int i = 0; i < boundaries_x_coordinates.size(); i++)
      {
         if (boundaries_x_coordinates[i] < minX)
         {
            minX = boundaries_x_coordinates[i];
         }

         if (boundaries_x_coordinates[i] > maxX)
         {
            maxX = boundaries_x_coordinates[i];
         }

         if (boundaries_y_coordinates[i] < minY)
         {
            minY = boundaries_y_coordinates[i];
         }

         if (boundaries_y_coordinates[i] > maxY)
         {
            maxY = boundaries_y_coordinates[i];
         }
      }

      // The problem is here, this do while statement takes a tremendous of time to execute in realistic Areas simply because it takes a 
      // long time to generate all the random coordinates inside the area (sometimes could be as little as 1 coordinate set, sometimes could be 100).
      int random_x = 0;
      int random_y = 0;
      do
      {
         random_x = GenerateRandomNumberBetween(minX, maxX);
         random_y = GenerateRandomNumberBetween(minY, maxY);
      } while (!InArea(random_x, random_y));

      std::vector<int> random_coordinates;
      random_coordinates.push_back(random_x);
      random_coordinates.push_back(random_y);

      return random_coordinates;
   }

private:
   std::vector<int> boundaries_x_coordinates;
   std::vector<int> boundaries_y_coordinates;
};

int main()
{
   Area* sample_area = new Area();

   std::vector<int> random_coordinates = sample_area->GenerateRandomPointInsideArea();

   printf("Random Coordinate: (%i, %i)\n", random_coordinates[0], random_coordinates[1]);

   // Pause to see results.
   system("pause");
   return 0;
}

The sample output would output a coordinate set inside the Area... In this specific example my first run it output:

Random Coordinate: (-1, 1)

I've read that dividing the Area into triangles,then picking a random triangle, and generating a random coordinate within that triangle is the best solution... But I've no idea how to generate triangles out of an Areas coordinate set, and if I could do that... Why wouldn't I just use that technique to choose a random coordinate...?

--------Edit--------

Thanks to Matt Timmermans I was able to solve this issue by further researching the subject and applying most of what Matt explained below.

If anyone else is having difficulty with the subject, here's what I came up with (mostly what Matt mentioned, with some changes)

1) Triangulate the polygon into multiple triangles, in my case I needed a simple and lightweight C++ solution with 0 graphical interfaces. I managed to find a working class online called Triangulate here http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml .

2) Randomly choose a triangle using weighted probability. If a triangle occupies 80% of the original polygon, it should be chosen roughly 80% of the time.

At this point in the process I was able to do some research and find some variations, the simplest of which is the one I chose (as seen below).

3) Once you have chosen a triangle, generate a uniformly random point within this triangle. This can be accomplished through the use of this formula:

P = (1 - sqrt(r1)) * A + (sqrt(r1) * (1 - r2)) * B + (sqrt(r1) * r2) * C

Where r1 and r2 are random numbers between 0 and 1 as described in this article section 4.2... http://www.cs.princeton.edu/~funk/tog02.pdf

You are done, that's all it takes!

Alternatively, you can continue with what Matt suggested, both methods seem to work perfectly in any case.. Which is...

3) Copy the triangle and create a parallelogram with it and the original triangle. Using the following formulas:

M=(A+C)/2
P4=M-(B-M)

Where...
M is a midpoint in the original triangle where the copied triangle will connect.
A,B,C are the 3 vertices in the original triangle
P4 is the new point the forms the parallelogram with the other 3 points of the original triangle.

4) Generate a random number from within the parallelogram by generating a random x and y value between the min and max x and y values of the parallelogram until you are within the parallelogram. 5) If the random coordinate is INSIDE the COPIED triangle, map it to the corresponding point in the original triangle, if not you're done.

  1. Divide the polygon into triangles
  2. Randomly choose a triangle, giving each triangle a probability proportional to its area
  3. Copy the triangle to make a parallelogram
  4. Pick a random point in the parallelogram by randomly choosing coordinates in the base and height directions
  5. If the random point is in the copy of the triangle, not in the original triangle, then map it to the corresponding point in the original triangle.
  6. Done -- you're left with a random point in the chosen triangle, which is a random point chosen uniformly from the polygon.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM