LIBGDX Where does a Polygon and a Line collide? - java

I want to get the Point where a Polygon and a Line collides. I know there is a class called Intersector, but in this there is only a method for checking wether they collide or not, but I need the point where they are colliding.
I am happy with any help
public static List<RayTrace> rayTrace(Line2D line, boolean quick, Collisions... collisions) {
List<RayTrace> l = new ArrayList<RayTrace>();
for (Collisions collisions1 : collisions) {
for (Collision3D collision3D : collisions1) {
RayTrace rayTrace = new RayTrace();
if (quick) {
if (Intersector.intersectLinePolygon(line.getStartV(), line.getEndV(), collision3D.getBoundingPolygon())) {
rayTrace.collisionHit = collision3D;
rayTrace.hasHit = true;
l.add(rayTrace);
}
} else {
Point2f hit = new Point2f();
if (CollisionHelper.getLinePolygonIntersection(collision3D.getBoundingPolygon(), line, hit)) {
rayTrace.collisionHit = collision3D;
rayTrace.hasHit = true;
rayTrace.hitX = hit.x;
rayTrace.hitZ = hit.y;
l.add(rayTrace);
}
}
}
}
return l;
}
public static List<Vector2> getLinePolygonIntersections(Polygon polygon, Line2D line) {
float f[] = polygon.getTransformedVertices();
//Go through every side
List<Vector2> intersections = new ArrayList<Vector2>();
for (int i = 0; i < f.length - 2; i += 2) {
Vector2 intersection = new Vector2();
Intersector.intersectLines(line.x, line.y, line.x2, line.y2, f[i], f[i + 1], f[i + 2], f[i + 3], intersection);
intersections.add(intersection);
}
return intersections;
}
public static boolean getLinePolygonIntersection(#NotNull Polygon polygon, #NotNull Line2D line, #NotNull Point2f point) {
List<Vector2> list = getLinePolygonIntersections(polygon, line);
if (list.size() == 0) return false;
double shortestDistance = line.getStart().distance(new Point2f(list.get(0).x, list.get(0).y));
int indexClosest = 0;
for (int i = 1; i < list.size(); i++) {
double d = new Point2f(list.get(i).x, list.get(i).y).distance(line.getStart());
if (shortestDistance > d) {
indexClosest = i;
shortestDistance = d;
}
}
point.set(list.get(indexClosest).x, list.get(indexClosest).y);
return true;
}

Here is the method from the LibGDX Intersector class that could be modified:
public static boolean intersectLinePolygon (Vector2 p1, Vector2 p2, Polygon polygon) {
float[] vertices = polygon.getTransformedVertices();
float x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y;
int n = vertices.length;
float x3 = vertices[n - 2], y3 = vertices[n - 1];
for (int i = 0; i < n; i += 2) {
float x4 = vertices[i], y4 = vertices[i + 1];
float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (d != 0) {
float yd = y1 - y3;
float xd = x1 - x3;
float ua = ((x4 - x3) * yd - (y4 - y3) * xd) / d;
if (ua >= 0 && ua <= 1) {
return true;
}
}
x3 = x4;
y3 = y4;
}
return false;
}
What this method is actually doing is finding the intersection between the line segment from p1 to p2 with the edge of the polygon. (In particular, it is determining if there is any intersection between the given line segments, which will be important later.) In particular, the computations are being performed on the parametric equation of these two line segments; for example, the line segment from (x1,y1) to (x2,y2) has parametric equation
L(t) = [ x2-x1, y2-y1 ] * t + [ x1, y1 ]
where t ranges from 0 to 1.
The intersection of the the lines is calculated using Cramer's rule; the variable d above represents the determinant of the matrix appearing in the denominator of that formula. When d is nonzero, there is guaranteed to be an intersection between the lines, but we aren't done yet, because we are interested in the intersection of the line segments. The variable ua in the method yields the value of t in the parametric equation above when the intersection occurs; it must be between 0 and 1 for the intersection point to lie between the endpoints of the line segment.
Thus, the coordinates of the point of intersection can be calculated by evaluating L(t) when t = ua. To find the point of intersection, therefore, you could create your own version of this function that returns the value
Vector2( (x2-x1)*ua + x1, (y2-y1)*ua + y1)
Hope this helps!

IMHO: In the Intersector class many methods calculate collisions and generally the last parameter is a Vector3 filled with the coordinates of the collision.
I've never used this library, but at first glance, it's the way it works.

Related

Unable to find solution of a ray colliding a list of circles

I am coding a method that calculates the intersection of a line and a circle as a first step to write some kind of ray casting demo. In case an intersection is calculated it gets the shortest distance to the two points of intersection that will be the collision point, then it repeats the process where the new line originates from the collision point.
I was motivated by this video of a laser hitting different circles.
The method receives the angle of the line, the point where it originates, the size of the window, the radius of the circles, the array of centers of the circles and the GraphicsContext object from JavaFX.
The method has a couple of booleans to determine whether a collision has been made or not, and an ArrayList to store the collisions that will be later drawn on a JavaFX Canvas.
Inside a while loop the equation of the line is defined with the form y = m*x + b. Then checks which of the circles has a distance between the circle center and the line smaller than the radius of the line, this is calculated with the method explained here: math.stackexchange.com.
In case the distance to the center is smaller than the radius a collision occurs against that circle. As far as I know to find the intersection between a line and a circle you need to solve the equation system: y = m*x + b, (x-x1)^2 + (y-y1)^2 = r^2, that I solved via substitution. This results in a second degree polinomial equation that has a real solution if: p1*p1 >= 4*p0*p2.
The solution with the shortest distance to the origin point is the one that the line hits first and is the solution to our problem. A new angle is calculated with the center of the circle, the collision point and the origin point. With this a new line is defined and the loop repeats until no collision against the circles is calculated, situation where the collision against the borders of the window is calculated.
At the end a for loop draws all of the lines defined as couples of points inside collisionList.
This is the code, I've tried to comment it as best as I could:
private void extendPoint(double angle, Point origin, double x, double y, double radius, ArrayList<Point> pointList) {
double newAngle = angle; //Angle that defines the direction of the line
//This is used if the line does not hit a circle
double angle11 = Math.atan2(origin.getY(), origin.getX());
double angle_11 = Math.atan2(origin.getY(), -origin.getX());
double angle_1_1 = angle11 + Math.PI;
double angle1_1 = angle_11 + Math.PI;
boolean noCollision = true; //Will be true if the line does not hit a circle
boolean repeat = true; //If no collision has been made the while loop stops with this
Point currentPoint = Point.copy(origin); // (x0, y0)
Point collision = new Point(-1,-1); //Stores the collision point
Point newDirection = new Point(-1,-1); //Stores the new direction after a collision, returns(magnitud, angle) of a vector
ArrayList <Point> collisionList = new ArrayList<>(); //ArrayList of collision points that will be drawn later
collisionList.add(origin); //The origin point is added as a collision for representation purposes
while(repeat == true) {
//Line equation that passes through a point with an angle
//y = a*x - a*x0 + y0; -> y = m*x + b;
double m = Math.tan(-newAngle);
double a = m;
double b = -m*currentPoint.getX() + (currentPoint.getY());
for(int i = 0; i < pointList.size(); i++) {
Point gridPoint = pointList.get(i); //(x1, y1)
//From: https://math.stackexchange.com/questions/2552687/distance-between-line-and-point
//Given a line defined as A*x + B*y + C = 0
//x*(y1-y0)+y*(x1-x0)+(-y0*(x1-x0)-x0*(y1-y0)
double A = gridPoint.getY()-currentPoint.getY();
double B = gridPoint.getX()-currentPoint.getX();
double C = -currentPoint.getY()*B + currentPoint.getX()*A;
// double d_cp_gp = Math.abs(m*gridPoint.getX()-b*(gridPoint.getY()))/(Math.sqrt(m*m + 1));
double d_cp_gp = Math.abs(A + B + C)/Math.sqrt(A*A + B*B);
if(d_cp_gp < radius) {
System.out.println("radio " + d_cp_gp);
//The intersection between a line and a circunference:
//Circunference: (x-x1)^2 + (y-y1)^2 = r^2
//Line: y = tan(alpha)*(x-x0)+y0 -> y = a*x + b; a = tan(alfa), b = -tan(alfa)*x0 + y0
//Substituting the line equation in the circunference equation:
//x^2*(1+a^2) + x*(-2x1 + 2*a*b) + 2*a*b + x1^2+b^2-r^2 = 0
double p2 = 1 + a*a;
double p1 = -2*gridPoint.getX() + 2*a*b;
double p0 = gridPoint.getX()*gridPoint.getX() + b*b - radius*radius;
double p0_ = 4*p0*p2;
System.out.println(p1*p1 + " " + p0_);
//Check if the second order equation has solutions
if(p1*p1 >= p0_) {
System.out.println("IT HAS SOLUTION");
//Solution
double root = Math.sqrt(p1*p1 - p0_);
double sol1x = (-p1 + root)/(2*p2);
double sol2x = (-p1 - root)/(2*p2);
double sol1y = a*sol1x - a*currentPoint.getX() + currentPoint.getY();
double sol2y = a*sol1x - a*currentPoint.getX() + currentPoint.getY();
//The line will intersect twice with the circle, we want the solution
//with the shortest distance to currentPoint (x0,y0)
double distSol1 = Math.sqrt(Math.pow(currentPoint.getX()- sol1x, 2) +
Math.pow(currentPoint.getY() - sol1y, 2));
double distSol2 = Math.sqrt(Math.pow(currentPoint.getX()- sol2x, 2) +
Math.pow(currentPoint.getY() - sol2y, 2));
//The collision point is the point that the line hits first
if(distSol1 < distSol2) {
collision.setXY(sol1x, sol1y);
}
else {
collision.setXY(sol2x, sol2y);
}
//newAngle returns a vector with the form (magnitude, angle)
newDirection = newAngle(currentPoint, gridPoint, collision, radius);
currentPoint = collision;
//The new line after the collision is defined here
m = Math.tan(-newDirection.getY());
a = m;
b = -m*collision.getX() + (collision.getY());
collisionList.add(collision);
System.out.println("A collision has been calculated successfully: " + collision.toString());
//If a collision
noCollision= false;
}
}
//If no collisions have been detected at the end of the for loop exit the while loop
if(i == pointList.size() - 1 && noCollision == true) {
repeat = false;
}
}
//If no collision has been calculated with the circles this
//calculates the collision with the limits of the window
if(noCollision == true && repeat == false) {
if(angle<angle11 || angle > angle1_1) {
collision.setXY(x, m*x + b);
}
else if(angle > angle11 && angle < angle_11){
collision.setXY((0 - b)/m, 0);
}
else if(angle > angle_11 && angle < angle_1_1) {
collision.setXY(0, m*0 + b);
}
else if(angle> angle_1_1 && angle < angle1_1) {
collision.setXY((y - b)/m, y);
}
collisionList.add(collision);
}
}
System.out.println("Number of collisions: " + (int)(collisionList.size() - 1));
}
My main problem is that the shortest distance to a circle doesn't seem to be calculated properly, which directly difficults if the rest of the code works properly.
I've tried different methods to find the shortest distance and this is the one that I liked the most as I find it easy to understand, however the implementation doesn't work properly. I've thought that this could be because of JavaFX coordinate system (x increases to the right and y to the bottom) but I'm not sure, I'm a bit lost at this point.
Thanks for your time.
Edit:
As suggested I am adding some extra code to facilitate reproducibility.
The Point and Vector classes are defined as follows:
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;}
public double getX() {
return x;}
public double getY() {
return y;}
public void setX(double x) {
this.x = x;}
public void setY(double y) {
this.y = y;}
public void setXY(double x, double y) {
this.x = x;
this.y = y;}
#Override
public String toString() {
return("(" + this.x + "," + this.y + ")");
}
public static Point copy(Point a) {
return new Point(a.getX(), a.getY());
}
}
public class Vector {
private double vx;
private double vy;
private double ptoApX;
private double ptoApY;
private double angle;
private double modulo;
public Vector(double vx, double vy) {
this.vx = vx;
this.vy = vy;
this.ptoApX = 0;
this.ptoApY = 0;
this.angle = angle(vx,vy);
this.modulo = modulo(vx,vy);
}
//Getters
public double getVx() {
return this.vx;
}
public double getVy() {
return this.vy;
}
public double getPtoApX() {
return this.ptoApX;
}
public double getPtoApY() {
return this.ptoApY;
}
public double getAngle() {
return this.angle;
}
public double getModulo() {
return this.modulo;
}
//Setters
public void setVx(double vx) {
this.vx = vx;
}
public void setVy(double vy) {
this.vy = vy;
}
public void setPtoApX(double ptoApX) {
this.ptoApX = ptoApX;
}
public void setPtoApY(double ptoApY) {
this.ptoApY = ptoApY;
}
public void setAngle(double angle) {
this.angle = angle;
}
public void setModulo(double modulo) {
this.modulo = modulo;
}
//To String
#Override
public String toString() {
return "("+this.getVx()+","+this.getVy()+")";
}
public static double dotProduct(Vector a, Vector b) {
return a.getVx()*b.getVx() + a.getVy()*b.getVy();
}
public static Vector escalarProduct(Vector v, double n) {
return new Vector(n*v.getVx(), n*v.getVy());
}
public static Vector vectorWith2Points(Point a, Point b) {
Point p = Point.resta(a,b);
return new Vector(p.getX(),p.getY());
}
public static Vector vectorPointAngle(Point a, double angle, double modulo) {
double angleRadians = Math.toRadians(angle);
Point b = new Point(Math.cos(angleRadians)*modulo, Math.sin(angleRadians)*modulo);
return vectorWith2Points(a,b);
}
public static double modulo(double vx, double vy) {
return Math.sqrt(vx*vx + vy*vy);
}
public static double angle(double vx, double vy) {
return Math.atan2(vy, vx);
}
public static Vector normalize(Vector v) {
return new Vector(v.getVx()/v.getModulo(),v.getVy()/v.getModulo());
}
public static double angle2vectors(Vector u, Vector v) {
double argument = dotProduct(u,v)/(u.getModulo()*v.getModulo());
return Math.acos(argument);
}
public static Point polar2cart(double r, double angle) {
return new Point(r*Math.cos(angle), r*Math.sin(angle));
}
public static Point cart2polar(Point p) {
return new Point(modulo(p.getX(), p.getY()), angle(p.getX(), p.getY()));
}
}
And the method to obtain the new angle after a collision:
private Point newAngle(Point origin, Point center, Point c, double radius) {
//Normal vector
Vector n = Vector.vectorWith2Points(c, center);
Vector nNorm = Vector.normalize(n);
//Incident vector
Vector d = Vector.vectorWith2Points(c, origin);
//Tangent vector
Vector tg = new Vector(-nNorm.getVy(), nNorm.getVx());
//Reflected vector
double product = Vector.dotProduct(d,tg);
Vector r = new Vector(d.getVx()-2*product*tg.getVx(),
d.getVy() - 2*product*tg.getVy());
return new Point(r.getModulo(), r.getAngle());
}
An example of the code of different angles where a collision should be detected:
double x = 600;
double y = 400;
double radius = 10;
Point origin = new Point(x/2, y/2);
ArrayList<Point> pointList = new ArrayList<>();
pointList.add(new Point(40,40));
pointList.add(new Point(500,100));
pointList.add(new Point(40,330));
pointList.add(new Point(450,300));
//This should return a solution
extendPoint(0.4363323129985824, origin, x, y, radius, pointList);
extendPoint(2.6179938779914944, origin, x, y, radius, pointList);
//this returns a solution when it should not
extendPoint(1.5707963267948966, origin, x, y, radius, pointList);
extendPoint(-1.5707963267948966, origin, x, y, radius, pointList);
I wrote an class with everything needed to run the code here: https://pastebin.com/wMjUh9pZ
I think you should create a class that represents an intersection by a ray.
class Intersection{
double distance;
Point loc;
double normal;
}
That way, distance is along the ray and normal is the normal of the object intersected.
Then I would have a method for finding the intersetion of a circle and a point.
List<Intersection> lineAndCircle( Point org, double angle, Point center, double radius){...}
You seem to have a similar method but you're doing more work in it.
Then you also want to check the edge of the screen.
Intersection lineAndBoundary( Point org, double angle){ ... }
You have a very similar method, but you seem to be doing a lot more work in the method. . This way you are testing separate methods. Then your algorithm works as.
1 go through circles and find intersections.
2 get the intersection with the boundary.
3 find the closest intersection ( the smallest distance greater than 0 )
Doing it this way makes it a bit more extensible. First our ray is re-used a lot. Lets make a class.
class Ray{
Point origin;
double angle;
}
Then we collide a ray with multiple objects.
interface Interceptable{
List<Intersection> intercepts(Ray r);
}
Then we can use different classes.
class Circle implements Interceptable{
Point pos;
double radius;
#Override
List<Intersection> collides(Ray r){
...
}
}
Now you can right collides and testable.
Circle a = new Circle( new Point( 40, 40 ), 5 );
List<Intersection> yes = a.collides( new Ray( new Point(0, 0), 3.14/4 ) );
List<Intersection> no = a.collides( new Ray( new Point(0, 0), 0) ) );
Then you can narrow your example down to. "How do I write a collide method?" or "Why doesn't my collide method work for this ray/circle pair? I expect it to hit at two points, but it misses." etc.
Here is a complete runnable example that creates a swing window. I kinda enjoy making toy programs like this.
Note that I used an interface for the Intersectable. So now it is circles, but it could be anything that returns a list of Intersection
import javax.swing.*;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.event.*;
import java.util.*;
public class RayAndCircle{
public static void main(String[] args){
List<Intersectable> circles = new ArrayList<>();
for(int i = 0; i<250; i++){
double r = Math.random()*50 + 50;
double x = 2048*Math.random();
double y = 2048*Math.random();
circles.add( new Circle( r, new double[]{x,y}));
}
List<LineSegment> segments = new ArrayList<>();
JFrame frame = new JFrame("Ray caster");
JPanel panel = new JPanel(){
#Override
public Dimension getPreferredSize(){
return new Dimension(2048, 2048);
}
#Override
public void paintComponent( Graphics g){
g.setColor(Color.RED);
for( Intersectable c: circles ){
c.draw(g);
}
g.setColor(Color.BLACK);
for( LineSegment segment: segments){
g.drawLine( (int) segment.a[0], (int) segment.a[1],(int)segment.b[0], (int)segment.b[1]);
}
}
};
panel.addMouseListener( new MouseAdapter(){
#Override
public void mouseClicked( MouseEvent evt ){
double x = evt.getPoint().getX();
double y = evt.getPoint().getY();
double theta = Math.random() * Math.PI * 2;
double dx = Math.cos( theta );
double dy = Math.sin( theta );
Ray ray = new Ray( new double[] {x, y}, new double[]{ dx, dy } );
int count = 500;
Intersectable last = null;
while( ray != null && count > 0 ){
Intersection hit = null;
Intersectable next = null;
for(Intersectable c: circles){
if(c == last){
continue;
}
List<Intersection> intersections = c.intersects(ray);
for(Intersection i : intersections){
if( hit == null ){
hit = i;
next = c;
} else{
if( hit.s > i.s ){
hit = i;
next = c;
}
}
}
}
if(hit != null){
last = next;
segments.add( new LineSegment( ray.origin, new double[]{ hit.pos[0], hit.pos[1] } ) );
count--;
//reflected portion of ray.
double dot = hit.normal[0]*ray.direction[0] + hit.normal[1]*ray.direction[1];
double rx = ray.direction[0] - 2 * hit.normal[0]*dot;
double ry = ray.direction[1] - 2 * hit.normal[1]*dot;
double z = Math.sqrt(rx*rx + ry*ry);
ray = new Ray(hit.pos, new double[] { rx/z, ry/z});
} else{
ray = null;
}
}
panel.repaint();
}
});
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class Ray{
double[] origin; double[] direction;
public Ray( double[] origin, double[] direction){
this.origin = new double[]{origin[0], origin[1]};
this.direction = new double[]{direction[0], direction[1]};
}
}
class Intersection{
double s;
double[] pos;
double[] normal;
Circle b;
public Intersection(double s, double[] pos, double[] normal){
this.s = s;
this.pos = pos;
setNormal(normal);
}
public void setNormal(double[] normal){
double m = Math.sqrt(normal[0]*normal[0] + normal[1]*normal[1]);
if( Double.isNaN(m) || m == 0) throw new RuntimeException("Invalid normal! Magnitude of" + m);
this.normal = new double[] { normal[0]/m , normal[1]/m };
}
}
interface Intersectable{
List<Intersection> intersects(Ray ray);
void draw(Graphics g);
}
class Circle implements Intersectable{
double[] origin;
double radius;
public Circle( double radius, double[] origin){
this.radius = radius;
this.origin = new double[]{origin[0], origin[1]};
}
Intersection intersectionAt(Ray ray, double s){
//intersection.
double locx = ray.origin[0] + s*ray.direction[0];
double locy = ray.origin[1] + s*ray.direction[1];
double nx = (locx - origin[0])/radius;
double ny = (locy - origin[1])/radius;
return new Intersection( s, new double[]{ locx, locy }, new double[]{nx, ny} );
}
public List<Intersection> intersects(Ray ray){
double rx = origin[0] - ray.origin[0];
double ry = origin[1] - ray.origin[1];
double m2 = rx*rx + ry*ry;
double m = Math.sqrt(m2);
//position along ray that is closest to circles origin.
double s = rx*ray.direction[0] + ry*ray.direction[1];
//closest distance to circle.
double approach = Math.sqrt(m2 - s*s);
List<Intersection> result = new ArrayList<>();
if( approach < radius ){
//two intersections at points on circle.
//radius is hypotenuse and approach is one of the lengths.
double l = Math.sqrt( radius*radius - approach*approach);
double s1 = s - l;
if(s1 > 0){
result.add( intersectionAt(ray, s1) );
}
double s2 = s + l;
if(s2 > 0){
//intersection!
result.add(intersectionAt(ray, s2) );
}
} else if(approach == radius){
//one intersection tangent.
if( s > 0 ){
result.add( intersectionAt(ray, s) );
}
} else{
//miss.
}
return result;
}
public void draw(Graphics g){
g.fillOval(
(int)(origin[0] - radius),
(int)(origin[1] - radius),
(int)radius*2,
(int)radius*2
);
}
}
class LineSegment{
double[] a, b;
public LineSegment( double[] a, double[] b){
this.a = new double[]{a[0], a[1]};
this.b = new double[]{b[0], b[1]};
}
}
You'll probably be most interested in the intersects method of the Circle class, and the small chunk of code burried in the mouseClicked method that calculates the reflected ray.
If you only want to know if the line intersects if a given circle, create a second line which originates at the center of the given circle and the direction is the direction of your initial line rotated by 90 degrees. Then compute the intersection of the two lines. If then the distance between the intersection point and the center of the circle is smaller then the radius, both intersect.
A while ago I wrote a small Geometry lib, I striped out the sections which are relevant for you, here is my code:
Line class
public class Line {
final Vector2D positionVector;
final Vector2D directionVector;
public Line(final Vector2D positionVector, final Vector2D directionVector) {
this.positionVector = positionVector;
this.directionVector = directionVector;
}
public OptionalDouble computeIntersection(final Line line) {
final double numerator = line.getPositionVector().subtract(this.positionVector).cross(this.directionVector);
final double denominator = this.directionVector.cross(line.directionVector);
if (Math.abs(numerator) < 1e-10 && Math.abs(denominator) < 1e-10) {
// collinear
return OptionalDouble.of(Double.POSITIVE_INFINITY);
} else if (Math.abs(denominator) < 1e-10) {
// parallel
return OptionalDouble.empty(); // Lines are parallel.
}
final double t = line.getPositionVector().subtract(this.positionVector).cross(line.directionVector) / denominator;
return OptionalDouble.of(t);
}
public Vector2D getPositionVector() {
return positionVector;
}
public Vector2D getDirectionVector() {
return directionVector;
}
public Point2D getClosestPointOnLine(final Point2D point) {
final Line line = new Line(new Vector2D(point.getX(), point.getY()), this.directionVector.turn90DegreeClockwise());
final OptionalDouble intersection = this.computeIntersection(line);
final Vector2D result = this.positionVector.add(this.directionVector.lerp(intersection.getAsDouble()));
return new Point2D(result.getX(), result.getY());
}
}
intersection function
public static PointResult intersection(final Line l1, final Circle c1) {
final Point2D intersection = l1.getClosestPointOnLine(c1.getCenter());
final double dist = intersection.distance(c1.getCenter());
if (Math.abs(dist - c1.getRadius()) < 1e-10) {
final List<Point2D> result = new LinkedList<>();
result.add(intersection);
return new PointResult(Collections.unmodifiableList(result));
} else if (dist < c1.getRadius()) {
// we have two points
final double adjacentLeg = Math.sqrt(c1.getRadius() * c1.getRadius() - dist * dist);
final Point2D pt1 = intersection.pointAt(l1.getDirectionVector().angle(), adjacentLeg);
final Point2D pt2 = intersection.pointAt(l1.getDirectionVector().angle() + Math.PI, adjacentLeg);
final List<Point2D> result = new LinkedList<>();
result.add(pt1);
result.add(pt2);
return new PointResult(Collections.unmodifiableList(result));
}
return new PointResult();
}
TestCase
#Test
void testIntersectionLineCircleTwoPoints() {
final Point2D ptCircleCenter = new Point2D(2.0, 5.0);
final Point2D ptLineCircleIntersection = new Point2D(5.0, 2.0);
final Point2D pt1 = new Point2D(3.0, 0.0);
final Point2D pt2 = new Point2D(7.0, 4.0);
final double a = Math.sqrt((2.0 * 2.0) + (2.0 * 2.0));
final double b = ptCircleCenter.diff(ptLineCircleIntersection).norm();
final double radius = Math.sqrt((a * a) + (b * b));
final Line l1 = new Line(pt1, pt2);
final Circle circle = new Circle(ptCircleCenter, radius);
PointResult intersection = GeometryOperation.intersection(l1, circle);
assertTrue(intersection.getPoints().isPresent());
assertEquals(2, intersection.getPoints().get().size());
assertEquals(7.0, intersection.getPoints().get().get(0).getX(), 1e-10);
assertEquals(4.0, intersection.getPoints().get().get(0).getY(), 1e-10);
assertEquals(3.0, intersection.getPoints().get().get(1).getX(), 1e-10);
assertEquals(0.0, intersection.getPoints().get().get(1).getY(), 1e-10);
}
I did not add the Circle, Vector2D and Point2D class because they are trivial. And the class PointResult is just a list.

Get all nearest points in array

I'v two dimensional array, am storing some points in it in order to get the nearest two points e.g :
"(-1, 3), (-1, -1), (1, 1), (2, 0.5) , (2, -1) , (3, 3) ,(4, 2) ,(4, 0.5)"
The result is : "(1.0, 1.0) and (2.0, 0.5)"
And that worked very-well:
Scanner scanner = new Scanner(System.in);
System.out.println("Enter the number of points");
int numberOfPoints = scanner.nextInt();
//setting number of rows, number of column is unable to change
double[][] points = new double[numberOfPoints][2];
for (int i = 0; i < points.length; i++) {
points[i][0] = scanner.nextDouble();
points[i][1] = scanner.nextDouble();
}
int point1 = 0, point2 = 1;
double shortestDistance = distance(points[point1][0], points[point1][1],
points[point2][0], points[point2][1]);
//get shortest distance
for (int i = 0; i < points.length; i++) {
for (int j = i + 1; j < points.length; j++) {
double distance = distance(points[i][0], points[i][1],
points[j][0], points[j][1]);
if (shortestDistance > distance) {
point1 = i;
point2 = j;
shortestDistance = distance;
}
}
}
System.out.println("The closest two points is" +
"(" + points[point1][0] + ", " + points[point1][1] + ") and (" +
points[point2][0] + ", " + points[point2][1] + ")");
}
public static double distance(double x1, double y1, double x2, double y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
Am trying to get all nearest points not only two points.
I'v tried to get it by this way, but it doesn't cover all cases and doesn't display all points:
for (int i = 0; i < points.length; i++) {
for (int j = 0; j < points.length; j++) {
if (distance(points[i][0], points[i][1],
points[j][0], points[j][1]) == shortestDistance)
System.out.println("The closest two points are " +
"(" + points[i][0] + ", " + points[i][1] + ") and (" +
points[j][0] + ", " + points[j][1] + ")");
}
}
Also I'v tried to initialize new array and store distance into it then sort it, but it's fail.
How i can display all nearest points?
Note:
I didn't find this question useful for me.
The following comment clarifies the actual goal of the question:
So you mean that when you've found the 2 points that are closest to each other ((1.0, 1.0) - (2.0, 0.5)), you want to eliminate those then find the next "pair" that are now closest to each other ((3.0, 3.0) - (4.0, 2.0)), then repeat ((2.0, -1.0) - (4.0, 0.5)), and finally get last remaining pair ((-1.0, 3.0) - (-1.0, -1.0))?
To do that, you should first create a Point class, so you can easily keep track of points that have already been used. You could use java.awt.geom.Point2D.Double if you don't want to write your own.
You should also create a class for tracking pairs of points, so you can easily sort the pairs by distance. It can be named anything, but I named it Distance below.
Now the logic is that you create a list of all the possible Distance objects, i.e. pairs of points. You then sort it by distance.
The first list element is now the pair that is nearest to each other. You then iterate the list to find the next pair of points that are nearest, skipping any that use points already used.
If more than one pair are equally "nearest", the logic below will just pick one of them. You can change that behavior once you define what should actually happen in that scenario.
public static void main(String[] args) {
double[][] pointValues = { {-1, 3}, {-1, -1}, {1, 1}, {2, 0.5}, {2, -1}, {3, 3}, {4, 2}, {4, 0.5} };
Point[] points = new Point[pointValues.length];
for (int i = 0; i < points.length; i++)
points[i] = new Point(pointValues[i][0], pointValues[i][1]);
List<Distance> distances = new ArrayList<>();
for (int i = 0; i < points.length; i++)
for (int j = i + 1; j < points.length; j++)
distances.add(new Distance(points[i], points[j]));
Collections.sort(distances);
Set<Point> used = new HashSet<>();
for (Distance distance : distances) {
if (! used.contains(distance.getPoint1()) && ! used.contains(distance.getPoint2())) {
System.out.println(distance);
used.add(distance.getPoint1());
used.add(distance.getPoint2());
}
}
}
public final class Point {
private final double x;
private final double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return this.x;
}
public double getY() {
return this.y;
}
#Override
public String toString() {
return "(" + this.x + ", " + this.y + ")";
}
#Override
public int hashCode() {
return Double.hashCode(this.x) * 31 + Double.hashCode(this.y);
}
#Override
public boolean equals(Object obj) {
if (! (obj instanceof Point))
return false;
Point that = (Point) obj;
return (Double.doubleToLongBits(this.x) == Double.doubleToLongBits(that.x)
&& Double.doubleToLongBits(this.y) == Double.doubleToLongBits(that.y));
}
}
public final class Distance implements Comparable<Distance> {
private final Point p1;
private final Point p2;
private final double distance;
public Distance(Point p1, Point p2) {
this.p1 = p1;
this.p2 = p2;
this.distance = Math.hypot(p2.getX() - p1.getX(), p2.getY() - p1.getY());
}
public Point getPoint1() {
return this.p1;
}
public Point getPoint2() {
return this.p2;
}
public double getDistance() {
return this.distance;
}
#Override
public String toString() {
return String.format("%-12s - %-12s: %s", this.p1, this.p2, this.distance);
}
#Override
public int compareTo(Distance that) {
return Double.compare(this.distance, that.distance);
}
}
Output
(1.0, 1.0) - (2.0, 0.5) : 1.118033988749895
(3.0, 3.0) - (4.0, 2.0) : 1.4142135623730951
(2.0, -1.0) - (4.0, 0.5) : 2.5
(-1.0, 3.0) - (-1.0, -1.0): 4.0
First a optimizing trick: instead of distance, use its square (dropping taking the root).
public static double distance2(double x1, double y1, double x2, double y2) {
return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
}
Taking all nearest points one needs to maintain a list of (i, j).
List<int[]> nearestPointIndices = new ArrayList<>();
double shortestDistance2 = Double.MAX_VALUE;
for (int i = 0; i < points.length; i++) {
for (int j = i + 1; j < points.length; j++) {
double distance2 = distance2(points[i][0], points[i][1],
points[j][0], points[j][1]);
if (shortestDistance2 >= distance2) {
if (shortestDistance2 > distance2) {
nearestPointIndices.clear();
shortestDistance2 = distance2;
}
nearestPointIndices.add(new int[] { i, j });
}
}
}
That is one collects a list of nearest points upto (i, j) and
either clears that list on a more near point, or on the same near point add to the list.
Also worth mentioning is another the function of the Math class like sqrt:
public static double distance(double x1, double y1, double x2, double y2) {
return Math.hypot((x2 - x1), (y2 - y1)); // Hypotenuse sqrt(a² + b²).
}

Is distance() method written as it should

regarding the following code, can I make it better for distance() method?
It feels like it's not completely OOP with this method.. how can I change code to be better OOD for this one ?
Thanks !!
public class Line extends Shape {
private Point lineP1;
private Point lineP2;
public Line(int x1, int x2, int y1, int y2, Color myColor) {
super(x1, x2, y1, y2, myColor);
lineP1 = new Point(this.getX1(),this.getY1());
lineP2 = new Point(this.getX2(),this.getY2());
}
#Override
public void draw(Graphics g) {
g.drawLine(this.getX1(), this.getY1(), this.getX2(), this.getY2());
g.setColor(Color.GREEN);
}
#Override
public boolean contains(Point p) {
if((this.distance(lineP1, p)+this.distance(lineP2, p)) == this.distance(lineP1, lineP2))
return true;
return false;
}
/**#return distance between two given points
* This method return the distance between two given points*/
private double distance(Point p1,Point p2 ){
return Math.sqrt(Math.pow((p1.getX()-p2.getX()), 2) + Math.pow((p1.getY()-p2.getY()), 2));
}
}//class Line
Your distance method seems to be ok (but it would be more performant, if you saved the differences in variables and used the * operator to multiply those values with themselfs instear of using Math.pow).
However, since floating point calculations tend to return inexact results, I don't recomend using the sum of the distances between the end node and the point to test as criterium.
But there's another good way determining, if a point is near a line or not: using the hesse normal form. It works like this:
Let P1 and P2 be vectors corresponing to the end points. * denotes the scalar multiplication and || the length of a vector:
D = (P2 - P1) / |P2 - P1|;
Let N be the vector D with coordinates swaped and the new x coordinate multiplied with -1 (i.e. a vector ortogonal to D).
Then the distance of a point H to the line can be determined like this
| N * H - N * P1 |
Also if H is between P1 and P2 can be checked like this (assuming without loss of generality D * P1 < D * P2):
D * P1 <= D * H <= D * P2
Using scalar products has the additional benefit, that calculating a scalar product only takes 2 multiplication and 1 addition in a 2D space.
This is how you could do this in java code
// components of normal vector
private double normalX;
private double normalY;
// components of direction vector
private double directionX;
private double directionY;
// the value of (N * P) for all points P on the line
private double normalScalarProduct;
// the range allowed for (D * P) for points on the line
private double minDirectionScalarProduct;
private double maxDirectionScalarProduct;
// error ranges; adjust as appropriate
private static final double directionAllowedError = 0.1;
private static final double normalAllowedError = 0.1;
public Line(int x1, int x2, int y1, int y2, Color myColor) {
...
double dx = x2 - x1;
double dy = y2 - y1;
double length = distance(dx, dy);
if (length == 0) {
// choose arbitrary direction, if length == 0
length = 1;
dx = 1;
}
// normalize direction
dx /= length;
dy /= length;
// set D and N values
this.directionX = dx;
this.directionY = dy;
this.normalX = -dy;
this.normalY = dx;
double prod1 = scalarProduct(directionX, directionY, x1, y1);
double prod2 = scalarProduct(directionX, directionY, x2, y2);
if (prod1 < prod2) {
minDirectionScalarProduct = prod1 - directionAllowedError;
maxDirectionScalarProduct = prod2 + directionAllowedError;
} else {
minDirectionScalarProduct = prod2 - directionAllowedError;
maxDirectionScalarProduct = prod1 + directionAllowedError;
}
normalScalarProduct = scalarProduct(x1, y1, normalX, normalY);
}
private static double scalarProduct(double x1, double y1, double x2, double y2) {
return x1*x2 + y1*y2;
}
public boolean contains(Point p) {
if (Math.abs(scalarProduct(p.getX(), p.getX(), normalX, normalY) - normalScalarProduct) <= normalAllowedError) {
// close enough to the line -> check, if between end points
double d = scalarProduct(p.getX(), p.getX(), directionX, directionY);
return minDirectionScalarProduct <= d && d <= maxDirectionScalarProduct;
}
return false;
}
private double distance(double dx, double dy) {
return Math.sqrt(dx*dx + dy*dy);
}

How to make a parallelogram shaped vertex in JGraphx?

I'm trying to display a flowchart via JGraphx, and I need a parallelogram for input/output block. But JGraphx doesn't seem to know "shape=parallelogram". It seems odd not to have a parallelogram in a library for graphs and flowcharts (and it even has "actor" shape, how come it doesn't have a parallelogram?). Maybe it's only named some other way? Or in the case there is indeed no predefined parallelogram shape, what do I do to make a vertex into parallelogram?
Finally, I've found a way to make a parallelogram, yay! Here is how I made it work.
First of all, to make a custom shape I had to create my own class extending mxBasicShape and override the createShape method.
public class Parallelogram extends mxBasicShape {
public Shape createShape(mxGraphics2DCanvas canvas, mxCellState state){
mxCell cell = (mxCell)state.getCell();
Polygon polygon = new Polygon();
if(cell != null && cell.getGeometry() != null) {
mxGeometry g = cell.getGeometry();
int dx = (int) (cell.getGeometry().getHeight()/4.0);
polygon.addPoint((int)(g.getX()+dx), (int)g.getY());
polygon.addPoint((int)(g.getX()+g.getWidth()+dx), (int)g.getY());
polygon.addPoint((int)(g.getX()+g.getWidth()-dx), (int)(g.getY()+g.getHeight()));
polygon.addPoint((int)((int)g.getX()-dx), (int)(g.getY()+g.getHeight()));
}
return polygon;
}
}
The second step is adding it to a list of shapes which appeared to be stored in mxGraphics2DCanvas.
mxGraphics2DCanvas.putShape("parallelogram", new Parallelogram());
And now "shape=parallelogram" works just fine!
UPD
It appeared creating just a shape is not enough and a perimeter is also to be created. This is how I've done it:
public class ParallelogramPerimeter implements mxPerimeterFunction {
#Override
public mxPoint apply(mxRectangle bounds, mxCellState vertex, mxPoint next,
boolean orthogonal) {
double cx = bounds.getCenterX();
double cy = bounds.getCenterY();
double nx = next.getX();
double ny = next.getY();
double pi = Math.PI;
double pi2 = Math.PI/2.0;
double h = bounds.getHeight();
double w = bounds.getWidth();
double alpha = Math.atan2(h/2.0, w/2.0+h/4.0);
double beta = Math.atan2(h/2.0, w/2.0-h/4.0);
double t = Math.atan2(ny-cy, nx-cx);
mxPoint p = new mxPoint();
//Left
if (t > pi - alpha || t < (-pi)+beta){
Line border = new Line(cx-w/2.0+h/4.0, cy-h/2.0, cx-w/2.0-h/4.0, cy+h/2.0);
Line line = new Line(cx, cy, nx, ny);
p = Line.intersection(border, line);
}
//Top
else if (t > (-pi)+beta && t < (0-alpha)){
p.setY(cy-h/2.0);
p.setX(cx + h/2.0*Math.tan(pi2+t));
}
//Right
else if (t > (0-alpha) && t < beta){
Line border = new Line(cx+w/2.0+h/4.0, cy-h/2.0, cx+w/2.0-h/4.0, cy+h/2.0);
Line line = new Line(cx, cy, nx, ny);
p = Line.intersection(border, line);
}
//Bottom
else {
p.setY(cy+h/2.0);
p.setX(cx + h/2.0*Math.tan(pi2-t));
}
if (orthogonal){
//Top
if (nx >= cx-w/2.0+h/4.0 && nx <= cx+w/2.0+h/4.0 && ny <= cy-h/2.0){
p.setX(nx);
}
//Bottom
else if (nx >= cx - w/2.0-h/4.0 && nx <= cx+w/2.0-h/4.0 && ny >= cy+h/2.0){
p.setX(nx);
}
//Left or right
else{
Line left = new Line(cx-w/2.0+h/4.0, cy-h/2.0, cx-w/2.0-h/4.0, cy+h/2.0);
Line right = new Line(cx+w/2.0+h/4.0, cy-h/2.0, cx+w/2.0-h/4.0, cy+h/2.0);
p = left.projection(nx, ny);
mxPoint p1 = right.projection(nx, ny);
boolean r = false;
if (distance(nx, ny, p.getX(), p.getY()) > distance(nx, ny, p1.getX(), p1.getY()))
{
p = p1;
r = true;
}
//Upper corners
if (p.getY() < cy-h/2.0){
p.setY(cy-h/2.0);
if(r){
p.setX(cx+w/2.0+h/4.0);
}
else
{
p.setX(cx-w/2.0+h/4.0);
}
}
//Lower corners
else if (p.getY() > cy+h/2.0){
p.setY(cy+h/2.0);
if(r){
p.setX(cx+w/2.0-h/4.0);
}
else
{
p.setX(cx-w/2.0-h/4.0);
}
}
}
}
return p;
}
private double distance(double x1, double y1, double x2, double y2){
return Math.sqrt(Math.pow(x2-x1, 2)+Math.pow(y2-y1, 2));
}
}
class Line{
private double a;
private double b;
private double c;
Line(double x1, double y1, double x2, double y2){
a = y1-y2;
b = x2-x1;
c = x1*y2-x2*y1;
}
private Line(double a, double b, double c){
this.a = a;
this.b = b;
this.c = c;
}
static private double determinant(double i, double j, double k, double l){
return i*l - k*j;
}
static mxPoint intersection(Line first, Line second){
double x,y;
double denominator = determinant(first.a, first.b, second.a, second.b);
x = 0 - determinant(first.c, first.b, second.c, second.b)/denominator;
y = 0 - determinant(first.a, first.c, second.a, second.c)/denominator;
return new mxPoint(x,y);
}
mxPoint projection(double x, double y){
double a,b,c;
if (this.b!=0){
a=1;
b=-(this.a*a)/this.b;
}
else{
b = 1;
a=-(this.b*b)/this.a;
}
c = -(a*x+b*y);
Line line = new Line(a,b,c);
return intersection(this, line);
}
}
Then I had to add it to the perimeters in use, whose list appeared to be not in the same place as with shapes, but in mxStyleRegistry:
mxStyleRegistry.putValue("parallelogramPerimeter", new ParallelogramPerimeter());
And finally I've used "shape=parallelogram;perimeter=parallelogramPerimeter" for a style, which now works not only for displaying the parallelogram, but also for connecting edges to it properly.
For completeness: equilateral parallelogram is predefined: SHAPE_RHOMBUS.

How can I optimize bisection-method for polynomial root finding in Java?

double findaroot(double x1, double x2){ //finds the root between two values
double gap = Math.abs(x1 - x2); //find the initial interval
while(gap > INTERVAL) { //check for precision
gap = gap / 2; //halve the interval
double x3 = x1 + gap;
if (f(x3) == 0) { //check for symmetry
return x3;
} else if (Math.signum(f(x1)) == Math.signum(f(x3))){
x1 = x3; //redefine the interval
} else {
x2 = x3; //redefine the interval
}
findaroot(x1, x2); //call again
}
return (x1 + x2) / 2; //return the mean
}
I am trying to find a solution for f(x)=-21x^2+10x-1 in the intervals (-143, 0.222222). The guidelines state that I should implement a bisection method to solve this. Currently this method works fine for 8 of the 10 test cases that I must pass, but it gives a "Time-limit exceeded" error for the aforementioned values. It takes 15 seconds to approximate the root given a precision level of at least "0.000001" between the intervals.
I'm not sure how I can make this more efficient without changing the method. I have already implemented Horner's method to calculate the function because Math.pow(x1, x2) was taking too long.
Just remove the line findaroot(x1, x2);. You are not using the result of this recursive function call anyway.
EDIT: This is the recursive version of your code (not tested)
double findaroot(double x1, double x2){ //finds the root between two values
double gap = Math.abs(x1 - x2); //find the initial interval
if (gap > INTERVAL) { //check for precision
gap = gap / 2; //halve the interval
double x3 = x1 + gap;
if (f(x3) == 0) { //check for symmetry
return x3;
} else if (Math.signum(f(x1)) == Math.signum(f(x3))){
x1 = x3; //redefine the interval
} else {
x2 = x3; //redefine the interval
}
return findaroot(x1, x2);
}
else
return (x1 + x2) / 2; //return the mean
}
As others already said: The recursive invocation of findaroot is wrong/not required. This code works for me:
private final int NMAX = 100;
public double solve(Function f, double a, double b, double tolerance) {
if (a >= b) throw new IllegalArgumentException("illegal interval!");
double fa = f.value(a);
if (Math.signum(fa) == Math.signum(f.value(b))) throw new IllegalArgumentException("solution not within interval!");
for (int n = 0; n < NMAX; n++) {
double c = (a + b) / 2;
double fc = f.value(c);
if ((fc == 0.0) || ((b - a) / 2 < tolerance)) {
// solution found
return c;
}
// adapt interval
if (Math.signum(fc) == Math.signum(fa)) {
a = c;
fa = fc;
} else {
b = c;
}
}
return Double.NaN;
}

Categories

Resources