I have a ellipse with a mid-point 'mid' an a horizontal radius 'h' and a vertical radius 'v' and a Line2D.
Now I need some code to calculate the two intersection points of the two.
I already tried some code and tried it on my own but there always is a mistake.
Does somebody have some working code?
You will need to use algebra to solve the two equations, and it will get a little bit messy. First you have to write out the ellipse
(1) (x/h)^2 + (y/v)^2 = 1
and the line in the format
(2) y = ax + b
First, shift over your coordinate axis so the ellipse is centered on the origin. You can do that by subtracting mid from the line. Once you have calculated the points of intersection, shift them back by adding mid.
You can calculate the linear slope from delta-y/delta-x from the starting and ending points of the line. You will have to check if the slope is vertical. If the slope is vertical, you just have to check whether or not the x-value of the line points falls in the location of the ellipse, and then easily calculate the values. Draw it out on paper and see how to calculate it.
Assume now that the slope is not vertical. Since you know y in terms of x from the line, square that and substitute into (1). Simplifying gives a quadratic equation.
(3) ((ah)^2+v^2)x^2 + (2abh^2)x + ((hb)^2-(hv)^2) = 0
Using the quadratic formula gives the two values for the x coordinates of the intersection. If there are two real values for x, there are two intersections. If there is only one real solution for x, there is one intersection. If there are no real solutions for x, there is no intersection.
Given ax^2
+ bx + c = 0, x is given by
x = (1/2a)(-b +- Sqrt(b^2 - 4ac))
Let D = b^2 - 4ac
If D < 0, there are no intersections
If D = 0, there is one intersection
If D > 0, there are two intersections
Once you have calculated the values of the x intersection, substitute the values of x into (2) to get the y values.
Now, you need to make sure that these points fall within the line. To do this, just check that the x and y components of the calculated points satisfy x1 <= x <= x2 and y1 <= y <= y2 where x1 is the smallest and x2 the largest x-endpoint of the line, and y1 is the smallest and y2 the largest y-endpoint of the line.
Here is an example method that I made
public static ArrayList<Point2D> getIntersection(double x1, double x2, double y1, double y2, double midX, double midY, double h, double v) {
ArrayList<Point2D> points = new ArrayList();
x1 -= midX;
y1 -= midY;
x2 -= midX;
y2 -= midY;
if (x1 == x2) {
double y = (v/h)*Math.sqrt(h*h-x1*x1);
if (Math.min(y1, y2) <= y && y <= Math.max(y1, y2)) {
points.add(new Point2D(x1+midX, y+midY);
}
if (Math.min(y1, y2) <= -y && -y <= Math.max(y1, y2)) {
points.add(newPoint2D(x1+midX, -y+midY);
}
}
else {
double a = (y2 - y1) / (x2 - x1);
double b = (y1 - a*x1);
double r = a*a*h*h + v*v;
double s = 2*a*b*h*h;
double t = h*h*b*b - h*h*v*v;
double d = s*s - 4*r*t;
if (d > 0) {
double xi1 = (-s+Math.sqrt(d))/(2*r);
double xi2 = (-s-Math.sqrt(d))/(2*r);
double yi1 = a*xi1+b;
double yi2 = a*xi2+b;
if (isPointInLine(x1, x2, y1, y2, xi1, yi1)) {
points.add(new Point2D.Double(xi1+midX, yi1+midY);
}
if (isPointInLine(x1, x2, y1, y2, xi2, yi2)) {
points.add(new Point2D.Double(xi2+midX, yi2+midY);
}
}
else if (d == 0) {
double xi = -s/(2*r);
double yi = a*xi+b;
if (isPointInLine(x1, x2, y1, y2, xi, yi)) {
points.add(new Point2D.Double(xi+midX, yi+midY));
}
}
}
return points;
}
public static boolean isPointInLine(double x1, double x2, double y1, double y2, double px, double py) {
double xMin = Math.min(x1, x2);
double xMax = Math.max(x1, x2);
double yMin = Math.min(y1, y2);
double yMax = Math.max(y1, y2);
return (xMin <= px && px <= xMax) && (yMin <= py && py <= yMax);
}
Feel free to check my algebra and my code, but you should solve this problem by carefully going through each algebraic step.
I would use the built in ellipse and line/polygon classes there they both have methods to determine collisions and intersection
When the line is given by two points P0 and P1, any point along the line is (X, Y) = (X0, Y0) + t (X1 - X0, Y1 - Y0) = (X0, Y0) + t (DX, DY).
The ellipse is (X - Xm)²/h² + (Y - Ym)²/v² = 1.
We will use a trick to simplify computation: take all X, subtract Xm and divide by h, giving x; take all Y, subtract Ym and divide by h, giving y. This will turn the ellipse into a circle centered at the origin. (You can do all computations without this reduction of the coordinates if you prefer.)
Now, (x, y) = (x0, y0) + t (dx, dy) and x² + y² = 1.
Or (t dx + x0)² + (t dy + y0)² = 1.
Or (dx² + dy²) t² + 2 (dx x0 + dy y0) t + (x0² + y0² - 1) = 0.
Solve this second degree equation for t. If there are real roots, you can check if they belong to the line segment by the condition 0 <= t <= 1. The intersections themselves are given by the first equation.
Related
How can I make a algorithm that detects if a point (x, y) intercepts with a line. (x1, y1, x2, y2)?
I have already tried :
boolean onLine(float a, float b, float c, float d, float x, float y){
boolean answer = false;
float[] p1 = new float[] {a, b};
float[] p2 = new float[] {c, d};
float x_spacing = (p2[0] - p1[0]) / ((a+c)/2 + (b+d));
float y_spacing = (p2[1] - p1[1]) / ((a+c)/2 + (b+d));
List<float[]> line = new ArrayList();
float currentX = 0;
float currentY = 0;
while(currentX+a<c&¤tY+b<d){
currentX += x_spacing;
currentY += y_spacing;
line.add(new float[]{a+currentX, b+currentY});
}
for(int j = 0; j < line.size(); j++){
if(x > line.get(j)[0]-x_spacing && x < line.get(j)[0]+x_spacing && y > line.get(j)[1]-
y_spacing && y < line.get(j)[1]+y_spacing){
answer = true;
println("Hit line!");
break;
}
}
return answer;
}
This works sometimes, but is not always consistent.
I am putting this with a physics game, and I need this so the ball can roll down a line.
What are some ways I can improve it so that it works?.
EDIT: Thanks to Felix Castor I got it working. Here is the final Code:
boolean onLine(float x1, float y1, float x2, float y2, float xt, float yt,
float wid, float hit){
float Y = (y2 - y1)/(x2 - x1)* xt + y1 -(y2 - y1)/(x2 - x1) * x1;
boolean answer = false;
if(abs(Y - yt) < 5) answer = true;
if(abs(Y - yt-hit) < 5) answer = true;
if(abs(Y - yt-(hit/2)) < 5) answer = true;
if(abs(Y - yt+hit) < 5) answer = true;
if(abs(Y - yt+(hit/2)) < 5) answer = true;
return answer;
}
Using slope intercept form you can plug in your x and see if the y's are equal.
y = m*x + b
m = (y2 - y1)/(x2 - x1)
b = y1 - (y2 - y1)/(x2 - x1) * x1
So the equation becomes
Y = (y2 - y1)/(x2 - x1)* X + y1 -(y2 - y1)/(x2 - x1) * x1
given a point (xt, yt) you can plug in the xt into X and evaluate then compare the result to yt. If they are equal then the point is on the line.
if Y == yt given xt then the point is on the line.
You will need to handle the case where you have strictly horizontal lines as edge cases. Those will blow up the equation.
Edit: Conditions Changed
Since you are wanting to determine how far from the line a point is I would say the formula for the distance between a point and a line in cartesian space would be the way to go. See Distance from a point to a line section Line Defined By Two Points. The formula looks ugly but it's straight forward.
double numerator = Math.abs((y2 - y1) * xt - (x2 - x1) * yt + x2 * y1 - y2 * x1);
double denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2));
double distance = numerator / denominator;
As before your test point is (xt, yt) and your line is defined by two points (x1, y1) and (x2, y2). Because distance is always >= 0 your test would be:
if( distance <= tolerance) return true
I think this is a better approach if you are interested in a tolerance.
I have a Line(,) on canvas even it may be skewed, all i want to do is , to draw a new parallel line with respect to old Line(,) with equal Line Length.
Tried this one >>>>
double d = getLength(x1, y1, x2, y2);
double m = getSlope(x1, y1, x2, y2);
double r = Math.sqrt(1 + Math.pow(m, 2));
double endX = x + d / r;
double endY = y + ((d * m) / r);
return new double[]{x, y, endX, endY};
To make parallel segment, you need to build perpendicular vector of needed length.
Note that there are two solutions for both sided of segment
dx = x2 - x1
dy = y2 - y1
len = sqrt(dx*dx+dy*dy)
perpx = -dy * distance / len
perpy = dx * distance / len
// "left" line start
x1' = x1 + perpx
y1' = y1 + perpy
// "left" line end
x2' = x2 + perpx
y2' = y2 + perpy
// "right" line
x1'' = x1 - perpx
y1'' = y1 - perpy
x2'' = x2 - perpx
y2'' = y2 - perpy
Does anyone have a function in java for finding the shortest distance between a point and a line segment/edge? Every example I find is in another language and uses a bunch of sub functions. It can't be based on the assumption that they are perpendicular.
Update
I ported over a python function to java. If anyone is good at math and can verify I would appreciate it. x and y is the point, and other params are for the line segment.
public float pDistance(float x, float y, float x1, float y1, float x2, float y2) {
float A = x - x1;
float B = y - y1;
float C = x2 - x1;
float D = y2 - y1;
float dot = A * C + B * D;
float len_sq = C * C + D * D;
float param = -1;
if (len_sq != 0) //in case of 0 length line
param = dot / len_sq;
float xx, yy;
if (param < 0) {
xx = x1;
yy = y1;
}
else if (param > 1) {
xx = x2;
yy = y2;
}
else {
xx = x1 + param * C;
yy = y1 + param * D;
}
float dx = x - xx;
float dy = y - yy;
return (float) Math.sqrt(dx * dx + dy * dy);
}
We can simplify things a bit. You don't need to calculate param. What you can do is find a vector v at right angles to the line. The take the dot product of that with the vector (A,B). In 2D its easy enough to find the vector orthogonal to (C,D), its just (-D,C).
public float pDistance(float x, float y, float x1, float y1, float x2, float y2) {
float A = x - x1; // position of point rel one end of line
float B = y - y1;
float C = x2 - x1; // vector along line
float D = y2 - y1;
float E = -D; // orthogonal vector
float F = C;
float dot = A * E + B * F;
float len_sq = E * E + F * F;
return (float) Math.abs(dot) / Math.sqrt(len_sq);
}
If you are worried about performance is can be easier to work with the squared distances then the last line would be
return (float) dot * dot / len_sq;
This saves having to calculate a square root. So if you want to calculate the closest edge, find the squared distances to each edge and select the smallest.
This function find the distance to the infinite line rather than the line segment. This may not be what you want. The solution in the question differs in what happens if the point is beyond the two ends of the line segment. There it find the distance to the closest end point.
From http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
The distance (or perpendicular distance) from a point to a line is the
shortest distance from a point to a line in Euclidean geometry. It is
the length of the line segment which joins the point to the line and
is perpendicular to the line.
You say that "It can't be based on the assumption that they are perpendicular.", but the shortest distance between a point and a line segment represents another line which is perpendicular to the original line. Hence it is the height of the triangle formed by A B and C, where A - the point, B and C are the end points of the line segment.
We know the coordinates of all three points, therefore we can obtain lengths of sides of the triangle. Using Heron's formula: https://www.mathsisfun.com/geometry/herons-formula.html we can obtain the area which is also equal to 0.5 * b * h from: https://www.mathsisfun.com/algebra/trig-area-triangle-without-right-angle.html
private static float distBetweenPointAndLine(float x, float y, float x1, float y1, float x2, float y2) {
// A - the standalone point (x, y)
// B - start point of the line segment (x1, y1)
// C - end point of the line segment (x2, y2)
// D - the crossing point between line from A to BC
float AB = distBetween(x, y, x1, y1);
float BC = distBetween(x1, y1, x2, y2);
float AC = distBetween(x, y, x2, y2);
// Heron's formula
float s = (AB + BC + AC) / 2;
float area = (float) Math.sqrt(s * (s - AB) * (s - BC) * (s - AC));
// but also area == (BC * AD) / 2
// BC * AD == 2 * area
// AD == (2 * area) / BC
// TODO: check if BC == 0
float AD = (2 * area) / BC;
return AD;
}
private static float distBetween(float x, float y, float x1, float y1) {
float xx = x1 - x;
float yy = y1 - y;
return (float) Math.sqrt(xx * xx + yy * yy);
}
I do not know how correct it is, hopefully a real mathematician can correct or back up this solution
If your line passes through two points, you can determine the equation of the line exactly.
If your line is ax + by + c = 0 and your point is (x0, y0), then the distance is given by :
This gives the shortest distance between any line and a point. (a, b, c are real constants)
Edit : In order to find the exact equation from two points on the line, the steps are :
`y − y1 = m(x − x1)` where m is the slope of the line.
Simplifying from this, a = -m, b = 1 and c = m*x1 - y1
If you do not want to implement all this by yourself I can recommend to use JTS. Use the distance method from LineSegment (for lines) or Coordinate (for points) accordingly. Given Points p1 and p2 for your Line and a Point p3 (that you want to calculate the distance) the code would look like that:
// create Line
LineSegment ls = new LineSegment(p1.getX(), p1.getY(), p2.getX(), p2.getY());
//calculate distance between Line and Point
double distanceLinePoint = ls.distance(new Coordinate(p3.getX(), p3.getY()));
// calculate distance between Points (p1 - p3)
double distanceBetweenPoints = new Coordinate(p1.getX(), p1.getY()).distance(new Coordinate(p3.getX(), p3.getY()));
I'm writing a method to return a List of Points between 2 Points. Somehow the slope (y2-y1)/(x2-x1) keeps giving me 0.0 all the time regardless startPoint and endPoint positions.
Here is the method:
public ArrayList<Point> calculatePath(Point startPoint, Point endPoint) {
ArrayList<Point> calculatedPath = new ArrayList<>();
int x1 = startPoint.x;
int y1 = startPoint.y;
int x2 = endPoint.x;
int y2 = endPoint.y;
System.out.println("Run");
if ((x2 - x1) != 0) {
float ratio = ((y2 - y1) / (x2 - x1));
System.out.println(ratio);
int width = x2 - x1;
for (int i = 0; i < width; i++) {
int x = Math.round(x1 + i);
int y = Math.round(y1 + (ratio * i));
calculatedPath.add(new Point(x, y));
}
} else {
if (y1 < y2) {
while (y1 == y2) {
calculatedPath.add(new Point(x1, y1));
y1++;
}
} else {
while (y1 == y2) {
calculatedPath.add(new Point(x1, y1));
y1--;
}
}
}
return calculatedPath;
}
Can anyone point out what i'm doing wrong? Thanks
Try casting your ints into floats as well
During your caluclation you need to cast at least one element to float:
float ratio = ((float)(y2 - y1) / (float)(x2 - x1));
That is because:
float a = integer / integer
^^^^^^^^^^^^^^^^^ - The result will be an integer.
Therefore u need to cast at least one of the
to float
This examples shows it easly:
public static void main(String[] args)
{
float resultWithoutCast = 5 / 3;
float resultWithCast = (float)5 /3 ;
System.out.println(resultWithoutCast);
System.out.println(resultWithCast);
}
It will print
1.0
1.6666666
You forgot to cast your int during division. Try something like this:-
float ratio = ((float)(y2 - y1) / (x2 - x1));
When performing arithmetic you need to make sure you use types which allow for the expected result. For example, the issue with your code is you are looking for a floating point result but using int - the problem here is int will simply truncate any floating point.
There are a couple of ways to solving this problem - as already suggested you could use a cast
float ratio = ((float)(y2 - y1) / (x2 - x1));
Or you could use float variables, it makes for more readable code e.g.
float x1 = (float)startPoint.X;
float y1 = (float)startPoint.Y;
...
float ratio = (y2 - y1) / (x2 - x1);
However, this results in more casting.
Alternatively, you could swap out Point for PointF and eliminate casting completely.
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.
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.
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-)
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.
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);
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)};
}
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];}
}
}