Simple 2D math problem - angle between two points

Hi All,
I have two objects on the Z=0 plane. I want to calculate the heading between them, relative to the “forward()” direction (the Y-axis grows towards angle 0, and the X-axis grows towards the 90 degrees direction).
Based on @brian and @rdb help in the Discord channel I wrote a simple function using the angle_deg method.
The problem with the function (see below): The results when the target is to the left (Xp1>Xp2) are between 0 and 180 degrees, similar to the points on the right. I expect them to be negative, or between 180 and 360. What is the cause of this? I can always add another if, but I want to understand whether I’m doing something wrong, or maybe missing a simple solution.

def heading(p1, p2):
    zero_heading = Vec3(0, 0, 0).forward()
    relative_vector = Vec3(p2 - p1).normalized()
    return zero_heading.angle_deg(relative_vector)

for p in [(110, 10, 0), (90, 10, 0), (90, -10, 0), (110, -10, 0),
          (100, 5, 0), (105, 0, 0), (95, 0, 0), (100, -5, 0), ]:
    p1 = LPoint3f(100, 0, 0)
    p2 = LPoint3f(p)
    print(f"heading from (100, 0, 0) to {p}: {heading(p1, p2)} deg")

resulting in:

heading from (100, 0, 0) to (110, 10, 0): 45.0 deg
heading from (100, 0, 0) to (90, 10, 0): 45.0 deg    # I expect -45 or 315
heading from (100, 0, 0) to (90, -10, 0): 135.0 deg    # I expect -135 or 225
heading from (100, 0, 0) to (110, -10, 0): 135.0 deg
heading from (100, 0, 0) to (100, 5, 0): 0.0 deg
heading from (100, 0, 0) to (105, 0, 0): 90.0 deg
heading from (100, 0, 0) to (95, 0, 0): 90.0 deg     # I expect -90 or 270
heading from (100, 0, 0) to (100, -5, 0): 180.0 deg
  • What am I missing? Any simpler way to implement this?
  • What is a common way to represent “left” angles - as negative or between 180 and 360?

While Vec3 doesn’t have a method that takes directionality into account, Vec2 does, I believe–it’s called “signedAngleDeg”.

Given that I gather that your vectors all lie in the same plane, what I’d suggest then is that you convert your 3D vectors to 2D vectors and use the “signedAngleDeg” to calculate the desired angles.

Something like this:

# (After your initial calculations of
#  "zero_heading" and "relative_vector",
#  as in your code above.)

# Vec3.getXy() returns a 2D vector--a Vec2--
# containing only the x-y coordinates
# of the 3D vector, I believe.
zero_heading = zero_heading.getXy()
relative_vector = relative_vector.getXy()

angle = zero_heading.signedAngleDeg(relative_vector)
2 Likes

For the record, Vec3 does have a signed_angle_deg method as well. It does need an additional “reference vector”, though. If you look in the direction of this vector, the angle between the first and second vectors will be positive if the rotation of the first to the second vector is clockwise, or negative if that rotation is anti-clockwise.

Although the API doesn’t clearly state that the reference vector needs to be perpendicular to the other two vectors, it seems best to use the normal vector to the plane you’re working in. In your case, it appears you can use Vec3.down().

So the code could look like this:

def heading(p1, p2):
    zero_heading = Vec3.forward()
    relative_vector = Vec3(p2 - p1).normalized()
    return zero_heading.signed_angle_deg(relative_vector, Vec3.down())

for p in [(110, 10, 0), (90, 10, 0), (90, -10, 0), (110, -10, 0),
          (100, 5, 0), (105, 0, 0), (95, 0, 0), (100, -5, 0), ]:
    p1 = LPoint3f(100, 0, 0)
    p2 = LPoint3f(p)
    print(f"heading from (100, 0, 0) to {p}: {heading(p1, p2)} deg")
1 Like

Aaah, you’re quite right! I’m not sure of how I missed that–especially as I think that I’ve used it before! Good point, and my mistake, then! ^^;;

1 Like

No worries, your solution is very appropriate in this case as well :slight_smile: !

But I personally prefer the Vec3 method, as the Vec2 version isn’t very clear about how it is decided whether an angle will be positive or negative.

2 Likes

That works beautifully.

Just to be clear, when you write:

I assume that the ref vector is parpandicular to the plane the two vectors “share”?

1 Like

Yes, that is what I meant :slight_smile: .

After some testing, it looks like it is indeed not necessary for the ref vector to be perfectly perpendicular to that plane (i.e. the “normal vector” to that plane). As long as it points to any point within the same 3D “half-space” as this normal vector, it appears to work just as well.
In your case, for example, instead of Vec3(0., 0., -1.0) (the same as Vec3.down()), you would get the same results using e.g. Vec3(-1.3, 2.5, -0.12) as reference vector. As long as its Z-component is negative, it doesn’t seem to matter much what values it contains exactly.

2 Likes