Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to calculate both positive and negative angle between two lines?

Tags:

math

geometry

2d

There is a very handy set of 2d geometry utilities here.

The angleBetweenLines has a problem, though. The result is always positive. I need to detect both positive and negative angles, so if one line is 15 degrees "above" or "below" the other line, the shape obviously looks different.

The configuration I have is that one line remains stationary, while the other line rotates, and I need to understand what direction it is rotating in, by comparing it with the stationary line.

EDIT: in response to swestrup's comment below, the situation is actually that I have a single line, and I record its starting position. The line then rotates from its starting position, and I need to calculate the angle from its starting position to current position. E.g if it has rotated clockwise, it is positive rotation; if counterclockwise, then negative. (Or vice versa.)

How to improve the algorithm so it returns the angle as both positive or negative depending on how the lines are positioned?

like image 688
Jaanus Avatar asked Apr 18 '10 19:04

Jaanus


People also ask

Can the angle between two lines be negative?

Apparently, when tan(θ) (where θ is the angle between the two lines) has a positive value, the angle you get from the formula will be acute. But when tan(θ) is negative, the angle you get from the formula will be an obtuse angle.

What is the formula to find angle between two lines?

Formulas for Angle Between Two Lines The angle between two lines, of which one of the line is y = mx + c and the other line is the x-axis, is θ = Tan-1m.

How do you find the positive value of a negative angle?

When measured in the negative direction its will be -90°. We simply subtract 270 from 360. Given a negative angle, we add 360 to get its corresponding positive angle.

How do you find the angle between two angles?

To calculate the angle between two vectors in a 2D space: Find the dot product of the vectors. Divide the dot product by the magnitude of the first vector. Divide the resultant by the magnitude of the second vector.


3 Answers

This is an easy problem involving 2D vectors. The sine of the angle between two vectors is related to the cross-product between the two vectors. And "above" or "below" is determined by the sign of the vector that's produced by the cross-product: if you cross two vectors A and B, and the cross-product produced is positive, then A is "below" B; if it's negative, A is "above" B. See Mathworld for details.

Here's how I might code it in Java:

package cruft;

import java.text.DecimalFormat;
import java.text.NumberFormat;

/**
 * VectorUtils
 * User: Michael
 * Date: Apr 18, 2010
 * Time: 4:12:45 PM
 */
public class VectorUtils
{
    private static final int DEFAULT_DIMENSIONS = 3;
    private static final NumberFormat DEFAULT_FORMAT = new DecimalFormat("0.###");

    public static void main(String[] args)
    {
        double [] a = { 1.0, 0.0, 0.0 };
        double [] b = { 0.0, 1.0, 0.0 };

        double [] c = VectorUtils.crossProduct(a, b);

        System.out.println(VectorUtils.toString(c));
    }

    public static double [] crossProduct(double [] a, double [] b)
    {
        assert ((a != null) && (a.length >= DEFAULT_DIMENSIONS ) && (b != null) && (b.length >= DEFAULT_DIMENSIONS));

        double [] c = new double[DEFAULT_DIMENSIONS];

        c[0] = +a[1]*b[2] - a[2]*b[1];
        c[1] = +a[2]*b[0] - a[0]*b[2];
        c[2] = +a[0]*b[1] - a[1]*b[0];

        return c;
    }

    public static String toString(double [] a)
    {
        StringBuilder builder = new StringBuilder(128);

        builder.append("{ ");

        for (double c : a)
        {
            builder.append(DEFAULT_FORMAT.format(c)).append(' ');
        }

        builder.append("}");

        return builder.toString();
    }
}

Check the sign of the 3rd component. If it's positive, A is "below" B; if it's negative, A is "above" B - as long as the two vectors are in the two quadrants to the right of the y-axis. Obviously, if they're both in the two quadrants to the left of the y-axis the reverse is true.

You need to think about your intuitive notions of "above" and "below". What if A is in the first quadrant (0 <= θ <= 90) and B is in the second quadrant (90 <= θ <= 180)? "Above" and "below" lose their meaning.

The line then rotates from its starting position, and I need to calculate the angle from its starting position to current position. E.g if it has rotated clockwise, it is positive rotation; if counterclockwise, then negative. (Or vice versa.)

This is exactly what the cross-product is for. The sign of the 3rd component is positive for counter-clockwise and negative for clockwise (as you look down at the plane of rotation).

like image 142
duffymo Avatar answered Oct 28 '22 02:10

duffymo


Here's the implementation of brainjam's suggestion. (It works with my constraints that the difference between the lines is guaranteed to be small enough that there's no need to normalize anything.)

CGFloat angleBetweenLinesInRad(CGPoint line1Start, CGPoint line1End, CGPoint line2Start, CGPoint line2End) {
    CGFloat a = line1End.x - line1Start.x;
    CGFloat b = line1End.y - line1Start.y;
    CGFloat c = line2End.x - line2Start.x;
    CGFloat d = line2End.y - line2Start.y;

    CGFloat atanA = atan2(a, b);
    CGFloat atanB = atan2(c, d);

    return atanA - atanB;
}

I like that it's concise. Would the vector version be more concise?

like image 38
Jaanus Avatar answered Oct 28 '22 00:10

Jaanus


@duffymo's answer is correct, but if you don't want to implement cross-product, you can use the atan2 function. This returns an angle between -π and π, and you can use it on each of the lines (or more precisely the vectors representing the lines).

If you get an angle θ for the first (stationary line), you'll have to normalize the angle φ for the second line to be between θ-π and θ+π (by adding ±2π). The angle between the two lines will then be φ-θ.

like image 9
brainjam Avatar answered Oct 28 '22 00:10

brainjam