4

I have a subject that has a position in a 2D space and a velocity, both represented by a vector. The subject has a field of vision of 135 degree to each side. It looks in the same direction as it is moving (the velocity vector).

I have objects with a position in a 2D space represented by a vector.

In the drawing the objects on blue background are visible, the objects on red background are not visible to the subject.

How can i calculate which objects lie in the subject's field of vision at a given moment?

Which objects lie in field of vision

MartinW
  • 4,966
  • 2
  • 24
  • 60
  • Roughly how many objects are there and do the objects move as well? – Nuclearman Mar 21 '14 at 07:40
  • As you may have guessed i am implementing a boid simulation. There are many objects and they move as well — but that's not the point of my question. I am just seeking to understand the simple math. – MartinW Mar 21 '14 at 09:14
  • True, I was just going to point out a possible optimization if the number of objects make the algorithm slower than you'd like. Although, since everything is moving, it's only beneficial if the field of view calculations are needed for every object. It would take O(N log N) preprocessing but allow for the ratio of objects checked vs objects outputted to be low on average. Especially if the area of the field of view is rather small compared to the area covered by the objects. Essentially, I was thinking of how to perhaps calculate it more efficiently. Gassa's answer is sufficient otherwise. – Nuclearman Mar 21 '14 at 15:09
  • @Nuclearman Yes, please do answer. I am curious. – MartinW Mar 22 '14 at 15:58
  • 1
    I've added my answer. Although, now that I think about it, it may have been better to ask another question where my answer would be more appropriate. Still, since you asked, I gave. – Nuclearman Mar 24 '14 at 08:33
  • What programming language are you using? – Nuclearman Mar 24 '14 at 17:36
  • Also, it probably wouldn't be a bad idea to for you Google any the terms in my answer you aren't familiar with. – Nuclearman Mar 24 '14 at 19:30

2 Answers2

6

You just need to find the distance to the objects and the angle towards them. Then, check that the distance is not greater than the radius of that light blue circle, and the angle between the speed vector and the vector to the object is not too large.

The Euclidean distance is simply sqrt ((x2 - x1)^2 + (y2 - y1)^2) (link).

As for the angle, represent the vector (x2 - x1, y2 - y1) and the speed (vx, vy) in polar coordinate system, and then check that the absolute difference between the angles is no more than 135 degrees. In programming languages, there is often a convenient function atan2 (y, x) to find the polar angle of a single vector (link).


The polar angle of a point is measured from vector Ox in counterclockwise direction. Consider we have two points: endpoint of the velocity vector (vx, vy) (left pic) and endpoint of a vector from our object (x1, y1) to the object in question (x2, y2): it's the vector (x2 - x1, y2 - y1) (center pic). Let us say the polar angles are alpha and beta, respectively. Then the angle between (vx, vy) and (x2 - x1, y2 - y1) is the difference of these polar angles (right pic).

Figure 1 Figure 2 Figure 3

There's a trick however. The alpha and beta values you get are typically either from -PI to +PI, or from 0 to 2PI. So, the difference beta - alpha will be between -2PI and +2PI, and you'll want it be between -PI (180 degree clockwise from current direction) and +PI (180 degree counterclockwise from current direction). For that, some simple transformation will suffice, such as this pseudocode:

if angle > +PI:
    angle := angle - 2PI
if angle < -PI:
    angle := angle + 2PI
Gassa
  • 8,546
  • 3
  • 29
  • 49
  • The answer would have more didactic value if you expand a bit on how the angle is calculated with a picture. Also atan is not enough since its symmetric around origin so you should look at the sign of cos also. – Shahbaz Mar 20 '14 at 19:18
  • @Visa is Racism: `atan2` does exactly that distinction, hence the two arguments instead of one. As for the picture, well, there is one [down the link](https://en.wikipedia.org/wiki/Atan2#Illustrations) I already gave, should it be more visible? – Gassa Mar 20 '14 at 19:22
  • The problem with links is that [links rot](http://meta.stackexchange.com/q/105128/169090). So it's a good idea to extract (or even quote if short enough) the most important part of it. Furthermore, `atan2` is a C function. While it's usable, it doesn't really teach the OP how and why it works for his case. – Shahbaz Mar 20 '14 at 19:28
  • Thank you. I understand the distance. But for the angle i do have to somehow take the heading (given via it's velocity) of my subject into account? – MartinW Mar 20 '14 at 19:47
  • @MartinW: Oh, sorry, I misunderstood that part. Then you should take the absolute difference of polar angles. I'll update the answer with that now. – Gassa Mar 20 '14 at 20:30
  • @VisaisRacism Anyone can look that up easily. There is no real point in restating knowledge that is readily available all over the internet. The core solution idea is the interesting part here, not the technical details – Niklas B. Mar 20 '14 at 21:04
  • @NiklasB., more than half of the questions on stack overflow could have easily been looked up all over the internet. Yet there is stackoverflow restating all that knowledge. Anyway, if the OP didn't make the effort of understanding the math, then telling him to use `atan2` won't make him go after learning the math. In fact, the OP would then simply copy the function without understanding and come back with related questions because things still don't work. If the answer is complete, in the sense that it makes the OP understand what the solution does, that wouldn't happen. – Shahbaz Mar 20 '14 at 23:16
  • @VisaisRacism In that case I suggest you to edit this answer so that it is as complete as you would like it to be – Niklas B. Mar 20 '14 at 23:18
  • @VisaisRacism But I disagree with you strongly. Stack Overflow is *not* there to restate knowledge that is easily accessible. Questions like "what is `atan2`" or about other trivialities have no place here and should be closed/deleted – Niklas B. Mar 20 '14 at 23:22
  • @NiklasB., I was actually going to write an answer, but I thought persuading Gassa to learn to provide higher quality answers would have higher benefit in the long run. In this case actually the best way would be to close this question as [duplicate](http://stackoverflow.com/a/3487062/912144). – Shahbaz Mar 20 '14 at 23:22
  • @NiklasB., there is a misunderstanding. I didn't mean for Gassa to explain what `atan2` is. I meant for him to show why `atan2` would give the angle the OP is looking for. – Shahbaz Mar 20 '14 at 23:23
  • @VisaisRacism please do answer. I am asking here exactly in order to understand and investigate the math. I already have seen solutions in code around the web and i want to know how/why they work. – MartinW Mar 21 '14 at 09:08
  • @MartinW: I added more explanation for the angles stuff, does it make more sense now? – Gassa Mar 21 '14 at 11:07
  • @Gassa this is a throwback but worth clarifying, I believe. If *The subject has a field of vision of 135 degree to each side. It looks in the same direction as it is moving (the velocity vector).* Shouldn't `beta - alpha` be compared against the half-angle? Namely, `bool inFov = (beta - alpha)*180/PI <= 135/2` ? – Kristof Pal Oct 06 '16 at 21:53
  • @KristofPal Why half? It's _135 degree to each side_, so, 270 in total. Half that, and you get 135 again. If you still think we should half 135, please explain more. – Gassa Oct 06 '16 at 23:43
  • @Gassa No, I misread "135 degree **to each side**" ! Your calculation is correct, pardon. – Kristof Pal Oct 07 '16 at 08:31
1

A possible optimization assuming that every object's FOV needs to be tested at the same time (aka every object becomes a subject) is to use the Delaunay triangulation of the objects treated as a graph to allow for a BFS search of sorts. There are two restrictions on this BFS.

  1. Objects that are too far away from the subject are not expanded.
  2. Objects that are not within the FOV are also not expanded. There is an exception for cases where the edge of the object intersects at least one of the FOV angle lines.

The example below shows why this is important, as well as giving an example of an unlikely worst case using a narrow FOV and where the FOV distance is too large to have an effect. Without the exception, it would never reach the lone object in the FOV.

Narrow FOV worst case example

Quick Edit: Added numbers to the example image to make it clearer how the BFS search would work. The object labeled 1 is the nearest object. It is expanded which leads to objects labeled 2. Those objects are expended to resulting in objects labeled 3 and so on. The thicker lines show which edges (directed from lower to higher label) are used during the expansion. Thus only the left most object is not expanded.

The performance of the algorithm, if the FOV needs to be tested for every object is:

O(N log N + NT)

Where N is the number of objects, and T is the average number of objects tested. This is compared to an ideal output sensitive algorithm of O(N*V), where V is the average number of objects in the FOV. On average, O(T) should be O(V). The downside is that since everything is up to the FOV area, V is likely to be N/C, where C is some constant factor, making it technically O(N), but that constant could be very low indeed. One way to approximate C is "(area of the convex hull of the objects) / (area of the FOV)". The basis of which is that a fraction of the area of the convex hull is on average likely to contain a roughly equal fraction of the objects.

The psudeocode assumes that each of the objects (including the subject) are part of a class, for simplicity call it Object, that provides a few class methods and class members. The methods and members are summarized below:

Class Members:

  • pos: Objects 2D position
  • minDistFOV: Minimum FOV distance. Most likely 0.0 but may be higher.
  • maxDistFOV: Maximum FOV distance. Unknown, but algorithm assumes it is a fixed distance.

Class Methods:

  • isWithinFOV(Object): Determines if given Object is within this Object's FOV. Returns True if it is.

The below functions are also not included here but links are included as to how they can be coded. It also shouldn't be too difficult to find them online.

  • offset(point, angle, direction): Returns the 2D point resulting from offsetting a 2D point by a given angle and distance.
  • intersects(segment1, segment2): Determines if the two line segments intersect.

The pseudocode looks something like below.

function lineSegmentFOV(subject, angle):
    # This function gets the one of line segments that represent the edges of the FOV
    # That is one of the red lines in the image
    segmentNearPos = offset(subject.pos, angle, subject.minDistFOV)
    segmentFarPos = offset(subject.pos, angle, subject.maxDistFOV)
    return (segmentNearPos, segmentFarPos)

function findObjectsInFOV(subject, objects, triangulation, kdTree):
    objectsInFOV = new Set() # A set seemed like the best overall choice here
    checkedObjects = new Set()

    # Get subject's edge of FOV line segments
    halfFOV = subject.FOV / 2
    lowerSegment = lineSegmentFOV(subject, subject.dir - halfFOV)
    higherSegment = lineSegmentFOV(subject, subject.dir + halfFOV)

    # Check nearest object to subject
    nearestNeighbor = kdTree.nearestNeighbor(subject)
    if not subject.equals(nearestNeighbor): # Subject cannot be in it's FOV
        if subject.isWithinFOV(nearestNeighbor):
            objectsInFOV.add(nearestNeighbor)
        checkedObjects.add(nearestNeighbor)

    # Begin the search for objects within the FOV
    objectsToExpand = new Queue(nearestNeighbor) # Always expand subject's nearest neighbor
    while objectsToExpand.length > 0:
        object = objectsToExpand.dequeue() # Get the next object to expand
        if object not in checkedObjects: # Don't check an object twice
            # Find expandable objects and note those that are in the FOV as well.
            for adjacent in triangulation[object]:
                edge = (object.pos, adjacent.pos)
                # Check if object in FOV
                if subject.isWithinFOV(object):
                    objectsInFOV.add(adjacent)
                    objectsToExpand.enqueue(adjacent)
                # Check if object-adjacent edge intersects one of the FOV line segments
                else if intersects(edge, lowerSegment) or intersects(edge, higherSegment):
                    objectsToExpand.enqueue(adjacent)
                checkedObjects.add(adjacent)
    return objectsInFOV 

function findObjectsInAllFOVs(objects):
    triangulation= new DelaunayTriangulation(objects)
    kdTree = new KDTree(objects)
    allObjectsInFOVs = new Dictionary()

    # Cycle through each object to find other objects in it's FOV
    for subject in objects:
        allObjectsInFOVs = findObjectsInFOV(subject, objects, triangulation, kdTree)
    return allObjectsInFOVs
Community
  • 1
  • 1
Nuclearman
  • 5,029
  • 1
  • 19
  • 35
  • Thank you. This sounds intriguing but at the moment it is way beyond the scope of my knowledge - but i will revisit your answer later :) – MartinW Mar 24 '14 at 14:41
  • @MartinW: I can't say I'm surprised it's beyond your knowledge. If you include an example of what the point set for the objects look like (direction not needed), I might be able to help with determining if it's worth your while to implement when you do have the skills. Although, the first requirement is to have a large number of objects. Looking at the purpose of most Boids simulations, it seems like you probably won't enough objects to be an issue. However, if seems like the simulation is running slowly then you might want it to. Then you may want to try to impliment the optimization. – Nuclearman Mar 24 '14 at 17:36