1

I'm working on a program in which I need to find all lines which are in a circles located at some cartesian point of some radius.

At the moment, for every circle, I am iterating over all the lines and checking if the line enters/contacts the circle at any point.

The code essentially looks like this.

for (int i = 0; i < num_circles; i++) 
{
    for (int j = 0; j < num_lines; j++) 
    {
        if(lineIntersectWithCircle(circle[i], lines[j])) 
        {
            //Append line[j] to a list of lines intersecting with circle[i];

            //some code
        }
    }
}

I've been thinking of many way to optimize this, but I'm having trouble. I have sorted the circles by minimum Cartesian distance and sorted lines by maximum distance away. This way you can somewhat optimize, but it's quite minimal because once you reach the point where line[j].max > circle[i].min, you still have to iterate through all the rest of the lines.

I am fine with my intersection checking method, I just would like to minimize the amount of times I need to call it.

Is there a good way of doing this?

WedaPashi
  • 3,561
  • 26
  • 42
Erik
  • 13
  • 3
  • how's your circles and lines defined? – Taha Paksu Nov 16 '17 at 12:11
  • Each circle is represented by a cartesian point and radius. Each line is a coordinate pair (x1, y1), (x2, y2) – Erik Nov 16 '17 at 12:14
  • (1) Lines which have both ends inside the circle (distance to center <= radius) are in the circle. (2) No other lines can be fully inside the circle. – AlexP Nov 16 '17 at 12:20
  • As a first step, I would rule out all lines outside the bounding box of the circle(s). This can be done very efficiently. – Ctx Nov 16 '17 at 12:23
  • Lines outside the bounding box, how can I do this without comparing every line to the bounding box? As in, I would still have a num_circles * num_lines loop right? – Erik Nov 16 '17 at 12:26
  • @Erik Not necessarily; if you first have a look, if the line is inside the common bounding box of _all_ circles, you might save some comparisons. – Ctx Nov 16 '17 at 12:27
  • You can use memoization, if a circle is inside another circle, you can eliminate the inner contacting line checks for the outside circle.. – Taha Paksu Nov 16 '17 at 12:27
  • Perhaps making a bucket of the grid array will work. Then for I only have to check lines in the buckets in which the circle is also in. Putting in buckets would be nun_circles + num_lines, instead of multiplied. – Erik Nov 16 '17 at 12:29
  • Hm, okay, I will try some of the suggestions. – Erik Nov 16 '17 at 12:30
  • There are optimized ways to find the shortest distance from a point to a line. Just check if that distance is <= the radius.(using the center of your circle as the point) – technosaurus Nov 16 '17 at 19:16

2 Answers2

0

Cheapest way is just check the bounding extents/rectangles of the two shapes (line and circle) prior to the more expensive intersection test. Chances are that you can even compute the extents on the fly of the line/circle, not precompute, and still get a decent performance boost unless your line/circle intersection is already dirt cheap.

A really effective approach but one that requires a bit more work is to just create a grid. You can use the bounding rectangles computed above to cheaply see which grid cells your shapes intersect.

struct GridNode
{
    // Points to the index of the next node in the grid cell
    // or -1 if we're at the end of the singly-linked list.
    int next_node;

    // Points to the index of the shape being stored.
    int shape;
};

struct GridCell
{
    // Points to the first node or -1 if the cell is empty.
    int first_node;
};

struct Grid
{
    // Stores the cells in the grid. This is just illustrative
    // code. You should dynamically allocate this with adjustable
    // grid widths and heights based on your needs.
    struct GridCell cells[grid_width * grid_height];

    // Stores the nodes in the grid (one or more nodes per shape
    // inserted depending on how many it intersects). This is 
    // a variable-sized array you can realloc needed (ex: double 
    // the size when you're out of room).
    struct GridNode* nodes;

    // The maximum number of nodes we can store before realloc.
    int node_cap;

    // The number of nodes inserted so far. realloc when this
    // exceeds node_cap.
    int node_num;
};

... something to this effect. This way, most of the time you can insert elements to the grid doing nothing more than just some integer (emulating pointers) operations and adding some grid node entries to this variable-sized nodes array. Heap allocations occur very infrequently.

I find in practice this outperforms quad-trees if you have many dynamic elements moving from one cell to the next like in a 2D video game where everything is moving around all the time while we need rapid collision detection, and can even rival quad-trees for searching if you are careful with the memory layout of the nodes to minimize cache misses when iterating through grid cells that intersect the shape you are testing against. You can even do a post-pass after the grid is constructed to rearrange the memory of each node for cache-friendly list iteration based on how efficient you need the intersection searches to be. If you want to get fancy, you can use Bresenham to figure out exactly what grid cells a line intersects, e.g., but given the quadratic complexity of what you're doing, you stand to improve exponentially without bothering with that and just doing it in a very simple way with bounding rectangles.

Basically to find an intersection, first grab the bounding rect of the shape. Then see which cells it intersects in the grid. Now check for intersection with the shapes contained in the grid cells the original shape intersects. This way you can work towards constant-time complexity except for gigantic shapes (worst-case with O(n)) which are hopefully a rare case.

I even find use for these in 3 dimensions when things are moving around a lot. They're often cheaper than the octree, BVH, and kd-tree variants which provide extensive search acceleration but at the cost of more expensive builds and updates, and if you use this strategy of a singly-linked list for each grid cell which doesn't have to individually allocate nodes, you can store it in a very reasonable amount of memory even with the 3rd dimension. I wouldn't use a 3-dimensional version of this for raytracing, but it can be very useful for 3D collision detection, like detecting collision between particles moving every single frame.

  • 1
    Hey! I ended up doing something like this. I divided the region into grids and put the lines in grids. Ended up with amazing performance. This is the way to go I believe. – Erik Dec 08 '17 at 10:27
0

As with anything it depends on your use case. If you have a fixed number of lines or infrequently added, you may want to precompute some of the calculations needed to find out if any part of the line is within radius distance of the center of the circle

Starting with the equation for the shortest distance between a line and a point and comparing that distance is less than the radius of the circle:

//abs(Cx*(y1-y0)-Cy*(x1-x0)+x1*y0-y1*x0)/sqrt((y1-y0)*(y1-y0)+(x1-x0)*(x1-x0))<R
//pull out some constants and cache these as they are initialized
//int y10 = y1-y0, //add to the line struct
//  x10 = x1 -x0,
//  delta = x1*y0-y1*x0,
//  sides = (y10)*(y10)+(x10)*(x10);
//  R2 = R*R; //add to the circle struct
//now the equation factors down to
//abs(Cx*(y10)-Cy*(x10)+delta)/sqrt(sides)< R //replace constants
//abs(Cx*(y10)-Cy*(x10)+delta) < sqrt(sides) * R //remove division
//pow(Cx*(y10)-Cy*(x10)+delta , 2.0) < sides * R * R //remove sqrt()
//int tmp = Cx*(y10)-Cy*(x10)+delta //factor out pow data
//tmp * tmp < sides * R2 //remove pow() and use cache R squared
//now it is just a few cheap instructions

Now the check should be just 4 integer multiplies, 2 add/subtract and a compare.

lineIntersectWithCircle(size_t circle, size_t line){
  struct circle C = circle_cache[circle]; //these may be separate arrays
  struct line L = line_cache[line];       //from your point data

  long tmp = C.x * L.y10 - C.y * L.x10 + L.delta;
  return (tmp*tmp < L.sides * C.R2);
}

... but you may want to check my math - its been a while. Also I assumed the points would be integers - change to float as needed - it should still be relatively fast.

If that isn't fast enough you can add additional data for the bounding boxes of the circle and line

bool lineIntersectWithCircle(size_t circle, size_t line){
  struct circle C = circle_cache[circle]; //these may be separate arrays
  struct line L = line_cache[line];       //from your point data

  //if the bounding boxes don't intersect neither does the line
  //this may not be _that_ helpful and you would need to:
  // figure out the bounding boxes for each line/circle
  // and cache additional data
  if (C.leftx > L.rightx || L.leftx > C.rightx) //a box is to the side
    return 0;
  if (C.topy < L.boty || L.topy < C.boty) //a box is below/above
    return 0;

  //the bounding boxes intersected so check exact calculation
  long tmp = C.x * L.y10 - C.y * L.x10 + L.delta;
  return (tmp*tmp < L.sides * C.R2);
}
technosaurus
  • 7,676
  • 1
  • 30
  • 52