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'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²).
}
I'm making a rudimentary particle simulator in Java. For now, all I've done is make the particles atract each other with an equivalent to the electrical force. This part works fine (or at least as well as you would expect for such a basic model).
However, when I add a few particles, the program loses their values for position, velocity and acceleration, but does not lose other data (like, for example, their ID number). This does not always happens with the same amount of particles. Sometimes it happens when I add the fourth, fifth, second or third particle, but never with the first one. It always happens when I click to add a particle, and after it fails, I can no longer add anything (which is odd), and the particles don't move anymore (as you would expect, being their velocities and accelerations 0).
I am storing the particles in an ArrayList. The array does not lose the data (I've checked, the objects are in there, and I can even call their toString() method and retrieve their ID). The problem seems to be related to synchronization (given that it doesn't always happen at the same moment, it seems to be a bit random), but I can't figure out what it is.
I leave all the relevant code below.
public class Scene implements KeyListener, MouseListener, MouseMotionListener{
public static ArrayList<Particle> particleArray = new ArrayList<Particle>();
public static Object particleLock = new Object();
public void update() {
synchronized(particleLock) {
for(Particle particle: particleArray) {
double resultX = 0;
double resultY = 0;
for(int i = 0; i<particleArray.size(); i++) {
if(i != particleArray.indexOf(particle)) {
double[] result = PhysicsEngine.applyElectircalForce(particle, particleArray.get(i));
resultX += result[0];
resultY += result[1];
}
}
particle.netForceX = resultX;
particle.netForceY = resultY;
particle.update();
}
}
}
public void mousePressed(MouseEvent e) {
int mouseX = e.getX();
int mouseY = e.getY();
boolean positive = true;
if(e.getButton() == MouseEvent.BUTTON1) {
positive = true;
} else if(e.getButton() == MouseEvent.BUTTON3) {
positive = false;
}
synchronized(particleLock){
particleArray.add(new Particle(mouseX, mouseY, positive));
System.out.println("New particle added at " + mouseX + ", " + mouseY);
}
}
}
public class Particle{
public double x;
public double y;
public Point2D position;
public double velX;
public double velY;
public double acX;
public double acY;
private Color particleColor;
private int radius = 10;
// PHYSICS
public double mass;
public double charge;
public double netForceX;
public double netForceY;
private boolean positive;
public Particle(double x, double y, boolean positive) {
this.x = x - radius;
this.y = y - radius;
this.velX = 3;
this.velY = 2;
this.acX = 0;
this.acY = 0;
this.mass = 100;
this.positive = positive;
if(positive) {
this.charge = defaultCharge;
} else {
this.charge = defaultCharge*(-1);
}
this.position = new Point2D.Double(x, y);
particleColor = Color.WHITE;
}
public void update() {
acX = netForceX / mass;
acY = netForceY / mass;
velX += acX;
velY += acY;
if(x<=0 || x>=Simulation.WIDTH - 23){
velX = velX * -1;
x+= velX;
}
if(y<=0 || y>=Simulation.HEIGHT - 35){
velY = velY * -1;
y+= velY;
}
synchronized(Scene.particleLock) {
for(Particle otherPart: Scene.particleArray) {
if(otherPart.equals(this)) {
continue;
}
double distance = otherPart.position.distance(position);
if(distance <= radius + otherPart.radius) {
//aplicar lo que sé de choques de alguna manera
}
}
}
x+= velX;
y+= velY;
position.setLocation(x, y);
}
}
public class PhysicsEngine {
static double electricalConstant = 100000;
public static double[] applyElectircalForce(Particle thisPart, Particle otherPart) {
double distance = otherPart.position.distance(thisPart.position);
double angle = Math.asin(Math.abs(thisPart.y - otherPart.y)/distance);
double force = (electricalConstant * thisPart.charge * otherPart.charge)/Math.pow(distance, 2);
double forceX = force * Math.cos(angle);
double forceY = force * Math.sin(angle);
if(otherPart.x < thisPart.x) {
forceX = forceX*(-1);
}
if(otherPart.y < thisPart.y) {
forceY = forceY*(-1);
}
double[] result = {forceX, forceY};
return result;
}
}
I once had a similar problem with synchronization when I was working on an android project, try declaring particleArray volatile so that the compiler knows that particleArray will be changed on other or multiple threads. If that does not work I would suggest using a queue to push changes to the particle array from different threads and then pulling the intended changes to the array list in the update method to update your particle array list. In my experience changing values directly between different threads almost always causes problems.
I'm currently trying to create a method which can make a multilinear interpolation for an unknown number of points (the number is equal to 2^n) in n-dimensional space.
The cases for n = 1 (no interpolation) and n = 2 (linear interpolation) are already implemented and seem to work. But now I'm already struggling to make the bilinear interpolation (for the case n = 4) dimension agnostic and I have no idea how I should progress from that point on (n = 8, ..., 2^n).
Is there a general approach for this problem or should I hardcode some of the cases and throw an UnsupportedOperationException otherwise?
Below I added an SSCCE which should hopefully clarify my problem. It consists of a point class which stores the coordinates and a value and also contains some functionality like calculating the distance to another point and the interpolation method which already contains my implementation of the cases for n = 1 and n = 2.
Code
import java.util.ArrayList;
import java.util.List;
public class Interpolator {
public static void main(String[] args) {
Interpolator ip = new Interpolator();
Point currentPoint = new Point(new long[]{5, 5}, 0);
List<Point> neighbors = new ArrayList<Point>() {{
add(new Point(new long[]{3, 3}, 7));
add(new Point(new long[]{10, 10}, 4));
}};
System.out.println(ip.interpolate(currentPoint, neighbors));
}
public float interpolate(Point currentPoint, List<Point> neighbors) {
if (neighbors.size() == 1) {
// no interpolation necessary with only one neighbor
return neighbors.get(0).getValue();
} else if (neighbors.size() == 2) {
// distance between point and the two neighbors
float distanceOne = currentPoint.distance(neighbors.get(0));
float distanceTwo = currentPoint.distance(neighbors.get(1));
float completeDistance = distanceOne + distanceTwo;
// calculate weights
float weightOne = 1 - distanceOne / completeDistance;
float weightTwo = 1 - distanceTwo / completeDistance;
// linear interpolation
return neighbors.get(0).getValue() * weightOne
+ neighbors.get(1).getValue() * weightTwo;
} else if (neighbors.size() == 4) {
//TODO: bilinear interpolation
} else if(neighbors.size() == 8){
//TODO: trilinear interpolation
}
//TODO: quadlinear interpolation or higher?
return -1;
}
public static class Point {
private long[] m_coordinates;
private float m_value;
public Point(final long[] coordinates, float value) {
this.m_coordinates = coordinates;
this.m_value = value;
}
public long[] getCoordinates() {
return this.m_coordinates;
}
public int numDim() {
return m_coordinates.length;
}
public long dim(final int i) {
return this.m_coordinates[i];
}
public float distance(final Point otherPoint) {
if (this.numDim() != otherPoint.numDim()) {
throw new IllegalArgumentException(
"Can't measure distance between points with different dimensionality");
}
float sum = 0;
for (int i = 0; i < this.numDim(); i++) {
sum += Math.pow(this.dim(i) - otherPoint.dim(i), 2);
}
return (float) Math.sqrt(sum);
}
public float getValue() {
return m_value;
}
}
}
This seems to do what your algorithm does but is dimension agnostic. I am not saying your calculation is correct - I am just saying that I think this is the same calculation but for n dimensions:
public float interpolate(Point currentPoint, List<Point> neighbors) {
int dimensions = neighbors.size();
float[] distance = new float[dimensions];
float sumDistances = 0;
for (int i = 0; i < dimensions; i++) {
distance[i] = currentPoint.distance(neighbors.get(i));
sumDistances += distance[i];
}
float[] weight = new float[dimensions];
for (int i = 0; i < dimensions; i++) {
weight[i] = 1 - distance[i] / sumDistances;
}
float interpolatedDistance = 0;
for (int i = 0; i < dimensions; i++) {
interpolatedDistance += neighbors.get(i).getValue() * weight[i];
}
return interpolatedDistance;
}
Why am I not getting a circle printed to the screen when I run the following block of code?
It doesn't print it accurately, seems like something I'm doing wrong when it's scanning the coordinates.
public class Question2 {
public static void main(String[] args) {
DrawMeACircle(3, 3, 3); // should print to the screen an ellipse looking circle
}
public static void DrawMeACircle(double posX, double posY, double radius) {
double xaxis = 20; // scanning the coordinates
double yaxis = 20; // " "
for (double x = 0; x < xaxis; x++) {
for (double y = 0; y < yaxis; y++) {
//using the formula for a cicrle
double a = Math.abs((posX - x) * (posX - x));
double b = Math.abs((posY - y) * (posY - y));
double c = Math.abs(a + b);
double d = Math.abs(radius * radius);
// checking if the formula stands correct at each coordinate scanned
if ( c == d) {
System.out.print('#');
}
else {
System.out.print(' ');
}
}
System.out.println();
}
}
}
I'm afraid that comparing doubles like this
if ( c == d) {
System.out.print('#');
}
is a very unreliable, they're probably missing by a little bit, and you're not printing the circle where you need to.
I'd recommend checking for a range instead
double arbitraryNumber = 2;
if ( math.abs(c - d) < arbitraryNumber)) {
System.out.print('#');
}
Or, if you want a more reliable way, I'd make a 2-d char array and treat it as a coordinate system, and then fill the 2-d array with the circle and print the array.
You can fill the array with a little bit of trigonometry. Just figure out where the dot should be every few degrees(or radians) until you've gone 360 degrees