views:

189

answers:

6

Hello!

I need a function which takes a line (known by its coordinates) and return a line with same angle, but limited to certain length.

My code gives correct values only when the line is turned 'right'
(proven only empirically, sorry).

Am I missing something?

public static double getAngleOfLine(int x1, int y1, int x2, int y2) {
  double opposite = y2 - y1;
  double adjacent = x2 - x1;

  if (adjacent == Double.NaN) {
    return 0;
  }

  return Math.atan(opposite / adjacent);
}

// returns newly calculated destX and destY values as int array
public static int[] getLengthLimitedLine(int startX, int startY,
    int destX, int destY, int lengthLimit) {

  double angle = getAngleOfLine(startX, startY, destX, destY);

  return new int[]{
        (int) (Math.cos(angle) * lengthLimit) + startX,
        (int) (Math.sin(angle) * lengthLimit) + startY
      };
}

BTW: I know that returning arrays in Java is stupid, but it's just for the example.

A: 

Just use the Pythagorean theorem, like so:

public static int[] getLengthLimitedLine(int start[], int dest[], int lengthLimit) {
    int xlen = dest[0] - start[0]
    int ylen = dest[1] - start[1]
    double length = Math.sqrt(xlen * xlen + ylen * ylen)

    if (length > lengthLimit) {
        return new int[] {start[0], start[1],
                start[0] + xlen / lengthLimit,
                start[1] + ylen / lengthLimit}
    } else {
        return new int[] {start[0], start[1], dest[0], dest[1];}
    }
}
phihag
+1  A: 

It's an easy problem if you understand something about vectors.

Given two points (x1, y1) and (x2, y2), you can calculate the vector from point 1 to 2:

v12 = (x2-x1)i + (y2-y2)j

where i and j are unit vectors in the x and y directions.

You can calculate the magnitude of v by taking the square root of the sum of squares of the components:

v = sqrt((x2-x2)^2 + (y2-y1)^2)

The unit vector from point 1 to point 2 equals v12 divided by its magnitude.

Given that, you can calculate the point along the unit vector that's the desired distance away by multiply the unit vector times the length and adding that to point 1.

duffymo
+3  A: 

It would be easier to just treat it as a vector. Normalize it by dividing my its magnitude then multiply by a factor of the desired length.

In your example, however, try Math.atan2.

Gordon
Exactly, atan2 is the method to use here. It has more information to work with, since it gets x and y as separate arguments, and can correctly return angles from -π to +π. atan only has enough information to cover the a range of -π/2 to π/2.
erickson
+1  A: 

Encapsulate Line in a class, add a unit method and a scale method.

public class Line {
private float x;
private float y;

public Line(float x1, float x2, float y1, float y2) {
    this(x2 - x1, y2 - y1);
}

public Line(float x, float y) {
    this.x = x;
    this.y = y;
}

public float getLength() {
    return (float) Math.sqrt((x * x) + (y * y));
}

public Line unit() {
    return scale(1 / getLength());
}

public Line scale(float scale) {
    return new Line(x * scale, y * scale);

}
}

Now you can get a line of arbitrary length l by calling

Line result = new Line(x1, x2, y1, y2).unit().scale(l);
Jherico
+1  A: 

No need to use trig, which can have some nasty edge cases. Just use similar triangles:

public static int[] getLengthLimitedLine(int startX, int startY,
    int destX, int destY, int lengthLimit)
{
    int deltaX = destX - startX;
    int deltaY = destY - startY;
    int lengthSquared = deltaX * deltaX + deltaY * deltaY;
    // already short enough
    if(lengthSquared <= lengthLimit * lengthLimit)
        return new int[]{destX, destY};

    double length = Math.sqrt(lengthSquared);
    double newDeltaX = deltaX * lengthLimit / length;
    double newDeltaY = deltaY * lengthLimit / length;

    return new int[]{(int)(startX + newDeltaX), (int)(startY + newDeltaY)};
}
Adam Rosenfield
+2  A: 

In Python because I don't have a Java compiler handy:

import math

def getLengthLimitedLine(x1, y1, x2, y2, lengthLimit):
    length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
    if length > lengthLimit:
       shrink_factor = lengthLimit / length
       x2 = x1 + (x2-x1) * shrink_factor
       y2 = y1 + (y2-y1) * shrink_factor
    return x2, y2

print getLengthLimitedLine(10, 20, 25, -5, 12)
# Prints (16.17, 9.71) which looks right to me 8-)
RichieHindle
+1 - I'm starting to greatly appreciate the brevity and elegance of Python. Nice. No need for the "if" check on length - it's valid for all situations except length == 0. That's the check you need to make. No need for abs(), either, since squares are always positive.
duffymo
@duffymo: Good point about abs() - I've removed it. But I think the 'if' is necessary, otherwise it'll *grow* the line when it's shorter than the limit.
RichieHindle
Yes it will, but that's not necessarily an incorrect outcome. It only fails to meet the narrow requirements laid down by the poster. It's equally valid to extend the line in another use case.
duffymo