Related
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.
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.
I am trying to solve a system of trigonometric equations in Java, but I don't know where to start. I've used commons-math3 before to solve simple linear sets of equations, but this is above my head. Equations I am trying to solve:
a - e + bcosθ1 + csinθ1 + d*sin(θ2+θ1)= z
( bsinθ1 + ccosθ1 + d*cos(θ2-θ1) * sinθ0 = x
( bsinθ1 + ccosθ1 + d*cos(θ2-θ1) * sinθ0 = y
, where a,b,c,d and e are constants. In practical terms, given x, y, and z, I need to solve for θ0, θ1, θ2.
You need to use the root-finding algorithm.
It is usually studied in calculus as the Newton's method or Newton Raphson method.
You will have to use a multi-dimensional secant method or Muller's method. Numerical recipes has something on it.
You can use the least-squares-in-java project for this. Here’s the code that will solve your problem:
import org.junit.Assert;
import org.junit.Test;
import org.orangepalantir.leastsquares.Function;
public class NonLinearTrigonometricSolver {
// Solves the following non-linear set of equations:
// a - e + bcosθ1 + csinθ1 + d * sin(θ1 + θ2) ) = z
// ( bsinθ1 + ccosθ1 + d * cos(θ1 + θ2) ) * sinθ0 = x
// ( bsinθ1 + ccosθ1 + d * cos(θ1 + θ2) ) * cosθ0 = y
// given x, y, z, solve for θ0, θ1, θ2
static final double a = 125;
static final double b = 143;
static final double c = 50;
static final double d = 142;
static final double e = 96;
static final double x = 0;
static final double y = 192;
static final double z = 172;
#Test
public void testNonLinearTrigonometricSolver() {
double[][] xs = { { -1 }, { 0 }, { 1 } };
double[] zs = { z, x, y };
double r = Math.sqrt(x * x + y * y);
final double sinTheta0 = x / r;
final double cosTheta0 = y / r;
Function f = new Function() {
#Override
public double evaluate(double[] values, double[] parameters) {
double t1 = parameters[0];
double t2 = parameters[1];
if (values[0] == -1) {
return a - e + b * Math.sin(t1) + c * Math.cos(t1) + d * Math.cos(t2 + t1);
} else if (values[0] == 0) {
return (b * Math.sin(t1) + c * Math.cos(t1) + d * Math.cos(t2 + t1)) * sinTheta0;
} else {
return (b * Math.sin(t1) + c * Math.cos(t1) + d * Math.cos(t2 + t1)) * cosTheta0;
}
}
#Override
public int getNParameters() {
return 2;
}
#Override
public int getNInputs() {
return 1;
}
};
NonLinearSolver fit = new NonLinearSolver(f);
fit.setData(xs, zs);
double[] params = { 0, 0 };
fit.setParameters(params);
fit.fitData();
// improving results.
fit.setMinChange(1e-32);
fit.setMinError(1e-32);
fit.setStepSize(0.5);
fit.fitData();
double t1 = fit.getParameters()[0];
double t2 = fit.getParameters()[1];
double arg = y / (b * Math.sin(t1) + c * Math.cos(t1) + d * Math.cos(t2 + t1));
// System.out.println(" " + arg);
double theta0 = Math.acos(arg) * Math.signum(x);
System.out.println(Math.toDegrees(theta0));
System.out.println(Math.toDegrees(fit.getParameters()[0]));
System.out.println(Math.toDegrees(fit.getParameters()[1]));
Assert.assertEquals(0, Math.toDegrees(theta0), 1e-16);
Assert.assertEquals(0, Math.toDegrees(fit.getParameters()[0]), 1e-16);
Assert.assertEquals(0, Math.toDegrees(fit.getParameters()[1]), 1e-16);
}
}
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.
public class Vector {
private int x, y, z;
public Vector(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public void add(Vector v) {
x += v.x;
y += v.y;
z += v.z;
}
public void silly(int x, int y, int z) {
this.x = ++x;
this.y = y + 1;
this.z += z;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
#Override
public String toString() {
return "Vector, <x = " + x + ", y = " + y + ", z = " + z + ">";
}
public static void main(String[] args) {
Vector a = new Vector(1, 0, 0);
Vector b = new Vector(0, 1, 0);
Vector c = a;
int x = 1;
int y = 2;
int z = 3;
a.add(b);
b.add(b);
c.add(c);
c.silly(x, y, z);
System.out.println("a: " + a);
System.out.println("b: " + b);
System.out.println("c: " + c);
System.out.println("x: " + x + "\ty: " + y + "\tz: " + z);
}
}
I have obviously been unclear in my question, sorry about that. I got this as practice from my teacher and I am supposed to explain the output of the last 4 lines in the code. I have no idea why the output looks as it does. I'm not very good at alias and so on. Someone might be able to give me an explanation? Thanks.
Vector c = a;
means that you create reference which is linked to reference a and its object. You don't call a constructor there. You don't create any object there. Just new reference
The only question I can see is 'What is the relation between Vectors a and c?' So I'll answer that.
When you use the 'new' keyword you are creating a new object which is stored in the heap. So 'a' and 'b' are two separate objects when they have been instantiated. When you say:
Vector c = a;
You are not creating a new object in the heap, merely making a new reference to the same object. So now both 'a' and 'c' are referencing the same thing. If you change a, c will change, and vice versa.
When:
c.add(c);
Is called then the ints in c are simply being added to themselves.