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'm working on a program that takes a ship object and it moves it. The trouble I am having is that if it goes past a side, then it is supposed to wrap back around on the other side.
Any help would be great :)
Here is my ship Class: The move method is what I need help with. The code I have doesnt work :/
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import junit.framework.TestCase;
public class Ship {
private BufferedImage _image;
private static final int WIDTH = 50;
private Point location;
private Vector speed = new Vector();
private double facing;
/**
* Generate ship at the given starting location and currently stopped
*
* #param starting
* location to copy for this ship
*/
public Ship(Point starting) {
try {
// Use the RunConfigurations >> Arguments > Working Directory tab so
// that this works. Don't just place the nave.png file in the bin
// directory!
_image = ImageIO.read(new File("nave.png"));
} catch (IOException e) {
System.err.println("Cannot find ship _image: " + e.getMessage());
}
location = starting.clone();
facing = Math.PI;
}
public void accelerate(double force) {
// TODO change the speed (velocity, really) by force in the direction
// the ship is facing.
// add a vector of appropriate magnitude by the facing direction
Vector acc = new Vector(facing);
acc = acc.scale(force);
speed = speed.add(acc);
}
public void rotate(double angle) {
// TODO change the direction the ship is facing. Can accept any angle
// as a parameter but should store it as in [0,2*pi)
while (angle <= 0.0f) {
angle += (Math.PI * 2);
}
while (angle >= Math.PI) {
angle -= (Math.PI * 2);
}
facing += angle;
}
public void move(Dimension bounds) {
// TODO Move the ship its speed. The ship should wrap around
// within its box. (Hint: move the ship by the size of the
// bounding area to wrap it around; you may need to do this
// more than once if the ship is moving fast enough.)
location = speed.move(location);
while (location.getX() > bounds.width) {
Vector v = new Vector(location.getX() - WIDTH);
location = v.move(location);
}
while (location.getX() < -WIDTH) {
Vector v = new Vector(location.getX() + WIDTH);
location = v.move(location);
}
while (location.getY() > bounds.height) {
Vector v = new Vector(location.getY() - WIDTH);
location = v.move(location);
}
while (location.getY() < -WIDTH) {
Vector v = new Vector(location.y() + WIDTH);
location = v.move(location);
}
}
public void draw(Graphics g2d) {
double locationX = _image.getWidth() / 2;
double locationY = _image.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(facing,
locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx,
AffineTransformOp.TYPE_BILINEAR);
// Drawing the rotated image at the required drawing locations
// Code for rotating adapted from StackOverflow.
g2d.drawImage(op.filter(_image, null), location.getX(),
location.getY(), null);
}
And here is my vector class: All this code works :)
public class Vector {
private final double _dx, _dy;
public Vector() {
_dy = 0.0;
_dx = 0.0;
}
public Vector(double x, double y) {
_dx = x;
_dy = y;
}
public Vector(Point a, Point b) {
_dx = b.x() - a.x();
_dy = b.y() - a.y();
}
public Vector(double angle) {
_dx = Math.cos(angle);
_dy = Math.sin(angle);
}
public double dx() {
return _dx;
}
public double dy() {
return _dy;
}
public Point move(Point b) {
double x = b.x();
double y = b.y();
x += _dx;
y += _dy;
return new Point(x, y);
}
public Vector add(Vector a) {
double x = (a._dx + _dx);
double y = (a._dy + _dy);
return new Vector(x, y);
}
public Vector scale(double s) {
double x = _dx * s;
double y = _dy * s;
return new Vector(x, y);
}
public double magnitude() {
double x = Math.pow(_dx, 2);
double y = Math.pow(_dy, 2);
return Math.sqrt(x + y);
}
public Vector normalize() {
double x = _dx / magnitude();
double y = _dy / magnitude();
return new Vector(x, y);
}
public Vector rotate(double rads) {
double theta = angle();
theta += rads;
return new Vector(theta);
}
public double angle() {
double alpha = Math.acos(dx() / magnitude());
if (dy() < 0)
alpha = Math.PI - alpha;
return alpha;
}
#Override
public String toString() {
String vector = "[" + _dx + "," + _dy + "]";
return vector;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Vector) {
Vector vector = (Vector) obj;
if ((Math.abs(_dx - vector._dx) <= (1 / 10000000000f))
&& (Math.abs(_dy - vector._dy) <= (1 / 10000000000f)))
return true;
else
return false;
} else
return false;
}
#Override
public int hashCode() {
return (int) Math.round((angle() * 180) / Math.PI);
}
}
Expanding upon the suggestion to use modulo, you can use it as follows to wrap around easily without loops:
// Assuming move is called for each time frame
// We can update the location of ship using modulo when it exceeds the bounds
public void move(Dimension bounds) {
// TODO Move the ship its speed. The ship should wrap around
// within its box. (Hint: move the ship by the size of the
// bounding area to wrap it around; you may need to do this
// more than once if the ship is moving fast enough.)
location = speed.move(location);
if (location.getX() > bounds.width) {
location.setLocation(location.getX() % bounds.width), location.getY());
}
else if (location.getX() < 0) {
location.setLocation(bounds.width - location.getX(), location.getY());
}
if (location.getY() > bounds.height) {
location.setLocation(location.getX(), location.getY() % bounds.height);
}
else if (location.getY() < 0) {
location.setLocation(location.getX(), bounds.height - location.getY());
}
}
You've provided a lot of code so I may have missed why you need to do this, but rather than create a new delta Vector to move the location, you can alternatively just determine the new wrapped position that the ship should be at set it per setLocation method.
I hope this helps.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions concerning problems with code you've written must describe the specific problem — and include valid code to reproduce it — in the question itself. See SSCCE.org for guidance.
Closed 9 years ago.
Improve this question
Well since you folks helped me a lot with my last project, I thought I might find some assistance with the current one :)
The project has us practicing recursion and objects (just started learning about the latter). So we first create a "BasicStar", later a "Snowflake", then comes the "SuperSnowflake" and finally the dreaded "KochCurve".
So I the "BasicStar" was quite easy, and now the idea of the "Snowflake" is to recursively draw "BasicStar"s with smaller radiuses. I have uploaded three images (basic star, which I did successfully, snowflake the way it should be, and my snowflake) so it's easy to understand what I mean. My recursive method draws something very different, and I have no idea what I'm doing wrong. Any help would be great.
Thanks!
(P.S. The Main and Painter classes were made by the university faculty so even if there are things to improve there it won't be relevant. The rest was written by myself)
Main:
package recursion;
import java.util.Scanner;
/*
* the class main get from the user the shape he wish to draw,
* and call the drew method of the desired shape .
*/
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("Please enter the number of the shape you wish to draw:\n" +
" 1-example\n" +
" 2-BasicStar\n" +
" 3-Snowflake\n" +
" 4-SuperSnowflake\n" +
" 5-KochCurve\n" +
" 6-KochSnowflake\n");
int shape = sc.nextInt();
// chooses which shape to draw based on the number received
switch(shape){
/*
* An example given to you so you can see how the painted works.
* This example opens a frame, and draws a red line.
*/
case 1:
drawExample();
break;
case 2:
drawBasicStar();
break;
case 3:
drawSnowflake();
break;
case 4:
drawSuperSnowflake();
break;
case 5:
drawKochCurve();
break;
case 6:
drawKochSnowflake();
break;
default: System.out.println("invalid shape");
}
sc.close();
}
// Draw the example line
public static void drawExample(){
Painter.draw("example");
}
// Draw a BasicStar
public static void drawBasicStar(){
Painter.draw("BasicStar");
}
// Draw a Snowflake
public static void drawSnowflake(){
Painter.draw("Snowflake");
}
// Draw a SuperSnowflake
public static void drawSuperSnowflake(){
Painter.draw("SuperSnowflake");
}
// Draw a KochCurve
public static void drawKochCurve(){
Painter.draw("KochCurve");
}
// Draw a KochSnowflake
public static void drawKochSnowflake(){
Painter.draw("KochSnowflake");
}
}
Painter:
package recursion;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
/*
* open a frame named aShape and drew the given shape
*/
public class Painter extends Component {
private static final long serialVersionUID = 1L;
private static int SIZE = 600;
private static Painter painter;
private static Graphics g;
private static String shape = null;
// Create a frame and display it
public static void draw(String aShape) {
shape = aShape;
JFrame frame = new JFrame(shape);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
painter = new Painter();
frame.add(painter, null);
frame.pack();
frame.setVisible(true);
}
// returns the Frame's width
public static int getFrameWidth () {
return painter.getSize().width;
}
// returns the Frame's height
public static int getFrameHeight () {
return painter.getSize().height;
}
// changes the color of the lines to be drawn
public static void setColor (String color) {
if (color.equals("red")){
g.setColor(Color.red);
}
else if (color.equals("blue")){
g.setColor(Color.blue);
}
else if (color.equals("green")){
g.setColor(Color.green);
}
}
public static void drawLine (Pixel p1, Pixel p2) {
drawLine((int)Math.round(p1.getX()),(int)Math.round(p1.getY()),(int)Math.round(p2.getX()),(int)Math.round(p2.getY()));
}
// Draw a line on the frame
public static void drawLine (int x1, int y1, int x2, int y2) {
g.drawLine(x1, getFrameHeight()-y1, x2, getFrameHeight()-y2);
}
// Set the default size of the window frame to SIZE*SIZE pixels
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
// paint the frame - draw the shape given (call the draw method in that shape object)
public void paint(Graphics g) {
Painter.g = g;
try{
Object myShape = (Class.forName("recursion." + shape)).newInstance();
Object [] objs = null;
Class [] classes = null;
(Class.forName("recursion." + shape)).getMethod("draw", classes).invoke(myShape, objs);
}
catch(Exception e)
{
System.out.println("Can't handle shape " + shape);
System.out.println(e.toString());
System.out.println(e.getCause());
}
}
}
Pixel:
package recursion;
public class Pixel {
private double x;
private double y;
public Pixel(){
x = 0;
y = 0;
}
public Pixel(double x, double y){
this.x = x;
this.y = y;
}
public Pixel(Pixel center){
this();
if(center != null){
this.x = center.x;
this.y = center.y;
}
}
public double getX(){
return x;
}
public double getY(){
return y;
}
public void translate(Pixel p){
this.x = this.x + p.x;
this.y = this.y + p.y;
}
public void rotateRelativeToAxesOrigin(double theta){
double tempX = this.x;
double tempY = this.y;
this.x = ((tempX)*(Math.cos(theta)) - ((tempY)*(Math.sin(theta))));
this.y = ((tempX)*(Math.sin(theta)) - ((tempY)*(Math.cos(theta))));
}
public void rotateRelativeToPixel(Pixel p1, double theta){
double tempX = this.x;
double tempY = this.y;
Pixel translatedPixel = new Pixel(tempX-p1.getX(), tempY-p1.getY());
translatedPixel.rotateRelativeToAxesOrigin(theta);
this.x = translatedPixel.getX() + p1.getX();
this.y = translatedPixel.getY() + p1.getY();
}
}
BasicStar:
package recursion;
public class BasicStar {
private Pixel center;
private double radius;
public BasicStar(){
double height = Painter.getFrameHeight()/2;
double width = Painter.getFrameWidth()/2;
this.center = new Pixel (width, height);
double maxRadius = Math.min(width, height)/2;
this.radius = maxRadius/4;
}
public BasicStar(Pixel center, double radius){
this.center = new Pixel(center);
this.radius = radius;
}
public Pixel getCenter(){
return new Pixel(center);
}
public double getRadius(){
return this.radius;
}
public void draw(){
Pixel begin = new Pixel(this.center);
Pixel end = new Pixel(center.getX() + getRadius(), center.getY());
Painter.drawLine(begin, end);
end.rotateRelativeToPixel(center, (2*Math.PI)/6);
Painter.drawLine(begin, end);
end = new Pixel(center.getX() + getRadius(), center.getY());
end.rotateRelativeToPixel(center, (4*Math.PI)/6);
Painter.drawLine(begin, end);
end = new Pixel(center.getX() + getRadius(), center.getY());
end.rotateRelativeToPixel(center, (6*Math.PI)/6);
Painter.drawLine(begin, end);
end = new Pixel(center.getX() + getRadius(), center.getY());
end.rotateRelativeToPixel(center, (8*Math.PI)/6);
Painter.drawLine(begin, end);
end = new Pixel(center.getX() + getRadius(), center.getY());
end.rotateRelativeToPixel(center, (10*Math.PI)/6);
Painter.drawLine(begin, end);
}
}
Snowflake:
package recursion;
public class Snowflake {
private BasicStar basic;
private int depth;
public Snowflake(){
double height = Painter.getFrameHeight()/2;
double width = Painter.getFrameWidth()/2;
Pixel center = new Pixel (width, height);
double maxRadius = Math.min(width, height)/2;
double radius = maxRadius/4;
this.basic = new BasicStar(center, radius);
this.depth = 2;
}
public Snowflake(BasicStar basic, int depth){
this();
if(basic!=null){
this.basic = basic;
this.depth = depth;
}
}
public int getDepth(){
return this.depth;
}
public BasicStar getBasic(){
return this.basic;
}
public double getRadius(BasicStar basic){
return this.basic.getRadius();
}
public Pixel getBasicCenter(BasicStar basic){
return this.basic.getCenter();
}
public void draw(){
draw(this.depth, basic.getCenter(), basic.getRadius());
}
private void draw(int depth, Pixel center, double radius){
BasicStar basic = new BasicStar(center, radius);
if(depth==1){
basic.draw();
}
else{
Pixel p = new Pixel(center.getX() + radius, center.getY());
draw(depth - 1, p, (radius/3));
for(int i=0; i<6; i=i+1){
p.rotateRelativeToPixel(center, (2*Math.PI)/6);
BasicStar temp = new BasicStar(p, radius/3);
temp.draw();
}
}
}
}
This looks overly complicated to me. To be honest, I did not read all your code, but you can create a simple recursive function for drawing a snowflake just like this:
public void drawSnowflake(Graphics g, int x, int y, int size, int level) {
for (int a = 0; a < 360; a += 60) {
double rad = a * Math.PI / 180;
int x2 = (int) (x + Math.cos(rad) * size);
int y2 = (int) (y + Math.sin(rad) * size);
g.drawLine(x, y, x2, y2);
if (level > 0) {
drawSnowflake(g, x2, y2, size/3, level-1);
}
}
}
What this code does is: It draws the lines of a star using basic trigonometry (don't forget to convert angles to radians!), and then calls itself with a smaller size and level for the positions at the ends of the spikes. Embedding this into an actual GUI is left as an excercise to the reader.
I am trying to paint a cube on a JFrame.
Sounds simple, but lags a lot. The 7th and 8th lines usually flash pretty bad.
here is the code:
http://pastebin.com/ncDasST6
if someone can give me a hint or two on how to stop this lag from occurring, that would be great :D.
Originally was for Applet, but i wanted it to execute through a .jar file.
Also, any way to add an Applet to a JFrame?
I tried doing: add(new Rotational()); //name of JApplet it is based off of.
Thanks, Fire
Does this variant work to your expectation? There are a number of changes which I did not bother to document (as I was 'just playing' with the code). Do a diff. to reveal the extent and nature of the changes.
It shows no lag or rendering artifacts here at 700x700.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.GroupLayout.Alignment;
import javax.swing.border.EmptyBorder;
public class Square extends JPanel implements MouseListener,
MouseMotionListener {
private static final long serialVersionUID = 1L;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
JFrame f = new JFrame("Cube Rotational");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Square square = new Square();
square.setBorder(new EmptyBorder(5,5,5,5));
f.setContentPane(square);
f.pack();
f.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Square() {
init();
setPreferredSize(new Dimension(700,700));
}
class Point3D {
public int x, y, z;
public Point3D(int X, int Y, int Z) {
x = X;
y = Y;
z = Z;
}
}
class Edge {
public int a, b;
public Edge(int A, int B) {
a = A;
b = B;
}
}
static int width, height;
static int mx, my;
static int azimuth = 45, elevation = 45;
static Point3D[] vertices;
static Edge[] edges;
public void init() {
width = 500;
height = 500;
vertices = new Point3D[8];
vertices[0] = new Point3D(-1, -1, -1);
vertices[1] = new Point3D(-1, -1, 1);
vertices[2] = new Point3D(-1, 1, -1);
vertices[3] = new Point3D(-1, 1, 1);
vertices[4] = new Point3D(1, 1, -1);
vertices[5] = new Point3D(1, 1, 1);
vertices[6] = new Point3D(1, -1, -1);
vertices[7] = new Point3D(1, -1, 1);
edges = new Edge[12];
edges[0] = new Edge(0, 1);
edges[1] = new Edge(0, 2);
edges[2] = new Edge(0, 6);
edges[3] = new Edge(1, 3);
edges[4] = new Edge(1, 7);
edges[5] = new Edge(2, 3);
edges[6] = new Edge(2, 4);
edges[7] = new Edge(3, 5);
edges[8] = new Edge(4, 5);
edges[9] = new Edge(4, 6);
edges[10] = new Edge(5, 7);
edges[11] = new Edge(6, 7);
setCursor(new Cursor(Cursor.HAND_CURSOR));
addMouseListener(this);
addMouseMotionListener(this);
setVisible(true);
}
void drawWireframe(Graphics g) {
double theta = Math.PI * azimuth / 180.0;
double phi = Math.PI * elevation / 180.0;
float cosT = (float) Math.cos(theta);
float sinT = (float) Math.sin(theta);
float cosP = (float) Math.cos(phi);
float sinP = (float) Math.sin(phi);
float cosTcosP = cosT * cosP;
float cosTsinP = cosT * sinP;
float sinTcosP = sinT * cosP;
float sinTsinP = sinT * sinP;
Point[] points;
points = new Point[vertices.length];
float scaleFactor = (getWidth() + getHeight()) / 8;
float near = (float) 6;
float nearToObj = 1.5f;
for (int j = 0; j < vertices.length; ++j) {
int x0 = vertices[j].x;
int y0 = vertices[j].y;
int z0 = vertices[j].z;
float x1 = cosT * x0 + sinT * z0;
float y1 = -sinTsinP * x0 + cosP * y0 + cosTsinP * z0;
float z1 = cosTcosP * z0 - sinTcosP * x0 - sinP * y0;
x1 = x1 * near / (z1 + near + nearToObj);
y1 = y1 * near / (z1 + near + nearToObj);
points[j] = new Point(
(int) (getWidth() / 2 + scaleFactor * x1 + 0.5),
(int) (getHeight() / 2 - scaleFactor * y1 + 0.5));
}
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.white);
for (int j = 0; j < edges.length; ++j) {
int x1 = points[edges[j].a].x;
int x2 = points[edges[j].b].x;
int y1 = points[edges[j].a].y;
int y2 = points[edges[j].b].y;
((Graphics2D) g).setStroke(new BasicStroke(5));
g.drawLine(x1, y1, x2, y2);
}
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
mx = e.getX();
my = e.getY();
e.consume();
}
public void mouseReleased(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
int new_mx = e.getX();
int new_my = e.getY();
azimuth -= new_mx - mx;
azimuth %= 360;
elevation += new_my - my;
elevation %= 360;
repaint();
mx = new_mx;
my = new_my;
repaint();
e.consume();
}
#Override
public void paintComponent(Graphics g) {
drawWireframe(g);
}
}
Originally was for Applet, but i wanted it to execute through a .jar file.
Good idea converting an applet to something more sensible, but note that an applet can (and usually should) be packed into a Jar.
Also, any way to add an Applet to a JFrame?
This is possible, relatively easy with this code (barring mixing Swing (JFrame) & AWT (Applet) components), but not the best way to go. It is better to create a hybrid like (for example) the subway applet/application.
By moving the custom rendering from the frame to a JPanel, the code has been partially transformed into a hybrid, since the panel can be added to a frame or applet (or window or dialog, or another panel or..).
I absolutely love maths (or 'math' as most of you would say!) but I haven't done it to a level where I know the answer to this problem. I have a main circle which could have a centre point at any x and y on a display. Other circles will move around the display at will but at any given call to a render method I want to render not only those circles that intersect the main circle, but also only render the segment of that circle that is visible inside the main circle. An analogy would be a shadow cast on a real life object, and I only want to draw the part of that object that is 'illuminated'.
I want to do this preferably in Java, but if you have a raw formula that would be appreciated. I wonder how one might draw the shape and fill it in Java, I'm sure there must be some variation on a polyline with arcs or something?
Many thanks
Let A and B be the 2 intersection points (you can ignore it when there is no, or 1 intercetion point).
Then calculate the length of the circular line segment between A and B.
With this information, you should be able to draw the arc using Graphics' drawArc(...) method (if I'm not mistaken...).
EDIT
Well, you don't even need the length of the circular line segment. I had the line-intersection code laying around, so I built a small GUI around it how you could paint/view the ARC of such intersecting circles (there are a bit of comments in the code):
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Arc2D;
/**
* #author: Bart Kiers
*/
public class GUI extends JFrame {
private GUI() {
super("Circle Intersection Demo");
initGUI();
}
private void initGUI() {
super.setSize(600, 640);
super.setDefaultCloseOperation(EXIT_ON_CLOSE);
super.setLayout(new BorderLayout(5, 5));
final Grid grid = new Grid();
grid.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
Point p = new Point(e.getX(), e.getY()).toCartesianPoint(grid.getWidth(), grid.getHeight());
grid.showDraggedCircle(p);
}
});
grid.addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
Point p = new Point(e.getX(), e.getY()).toCartesianPoint(grid.getWidth(), grid.getHeight());
grid.released(p);
}
#Override
public void mousePressed(MouseEvent e) {
Point p = new Point(e.getX(), e.getY()).toCartesianPoint(grid.getWidth(), grid.getHeight());
grid.pressed(p);
}
});
super.add(grid, BorderLayout.CENTER);
super.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new GUI();
}
});
}
private static class Grid extends JPanel {
private Circle c1 = null;
private Circle c2 = null;
private Point screenClick = null;
private Point currentPosition = null;
public void released(Point p) {
if (c1 == null || c2 != null) {
c1 = new Circle(screenClick, screenClick.distance(p));
c2 = null;
} else {
c2 = new Circle(screenClick, screenClick.distance(p));
}
screenClick = null;
repaint();
}
public void pressed(Point p) {
if(c1 != null && c2 != null) {
c1 = null;
c2 = null;
}
screenClick = p;
repaint();
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, super.getWidth(), super.getHeight());
final int W = super.getWidth();
final int H = super.getHeight();
g2d.setColor(Color.LIGHT_GRAY);
g2d.drawLine(0, H / 2, W, H / 2); // x-axis
g2d.drawLine(W / 2, 0, W / 2, H); // y-axis
if (c1 != null) {
g2d.setColor(Color.RED);
c1.drawOn(g2d, W, H);
}
if (c2 != null) {
g2d.setColor(Color.ORANGE);
c2.drawOn(g2d, W, H);
}
if (screenClick != null && currentPosition != null) {
g2d.setColor(Color.DARK_GRAY);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
Circle temp = new Circle(screenClick, screenClick.distance(currentPosition));
temp.drawOn(g2d, W, H);
currentPosition = null;
}
if (c1 != null && c2 != null) {
g2d.setColor(Color.BLUE);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f));
Point[] ips = c1.intersections(c2);
for (Point ip : ips) {
ip.drawOn(g, W, H);
}
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f));
if (ips.length == 2) {
g2d.setStroke(new BasicStroke(10.0f));
c1.highlightArc(g2d, ips[0], ips[1], W, H);
}
}
g2d.dispose();
}
public void showDraggedCircle(Point p) {
currentPosition = p;
repaint();
}
}
private static class Circle {
public final Point center;
public final double radius;
public Circle(Point center, double radius) {
this.center = center;
this.radius = radius;
}
public void drawOn(Graphics g, int width, int height) {
// translate Cartesian(x,y) to Screen(x,y)
Point screenP = center.toScreenPoint(width, height);
int r = (int) Math.rint(radius);
g.drawOval((int) screenP.x - r, (int) screenP.y - r, r + r, r + r);
// draw the center
Point screenCenter = center.toScreenPoint(width, height);
r = 4;
g.drawOval((int) screenCenter.x - r, (int) screenCenter.y - r, r + r, r + r);
}
public void highlightArc(Graphics2D g2d, Point p1, Point p2, int width, int height) {
double a = center.degrees(p1);
double b = center.degrees(p2);
// translate Cartesian(x,y) to Screen(x,y)
Point screenP = center.toScreenPoint(width, height);
int r = (int) Math.rint(radius);
// find the point to start drawing our arc
double start = Math.abs(a - b) < 180 ? Math.min(a, b) : Math.max(a, b);
// find the minimum angle to go from `start`-angle to the other angle
double extent = Math.abs(a - b) < 180 ? Math.abs(a - b) : 360 - Math.abs(a - b);
// draw the arc
g2d.draw(new Arc2D.Double((int) screenP.x - r, (int) screenP.y - r, r + r, r + r, start, extent, Arc2D.OPEN));
}
public Point[] intersections(Circle that) {
// see: http://mathworld.wolfram.com/Circle-CircleIntersection.html
double d = this.center.distance(that.center);
double d1 = ((this.radius * this.radius) - (that.radius * that.radius) + (d * d)) / (2 * d);
double h = Math.sqrt((this.radius * this.radius) - (d1 * d1));
double x3 = this.center.x + (d1 * (that.center.x - this.center.x)) / d;
double y3 = this.center.y + (d1 * (that.center.y - this.center.y)) / d;
double x4_i = x3 + (h * (that.center.y - this.center.y)) / d;
double y4_i = y3 - (h * (that.center.x - this.center.x)) / d;
double x4_ii = x3 - (h * (that.center.y - this.center.y)) / d;
double y4_ii = y3 + (h * (that.center.x - this.center.x)) / d;
if (Double.isNaN(x4_i)) {
// no intersections
return new Point[0];
}
// create the intersection points
Point i1 = new Point(x4_i, y4_i);
Point i2 = new Point(x4_ii, y4_ii);
if (i1.distance(i2) < 0.0000000001) {
// i1 and i2 are (more or less) the same: a single intersection
return new Point[]{i1};
}
// two unique intersections
return new Point[]{i1, i2};
}
#Override
public String toString() {
return String.format("{center=%s, radius=%.2f}", center, radius);
}
}
private static class Point {
public final double x;
public final double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double degrees(Point that) {
double deg = Math.toDegrees(Math.atan2(that.y - this.y, that.x - this.x));
return deg < 0.0 ? deg + 360 : deg;
}
public double distance(Point that) {
double dX = this.x - that.x;
double dY = this.y - that.y;
return Math.sqrt(dX * dX + dY * dY);
}
public void drawOn(Graphics g, int width, int height) {
// translate Cartesian(x,y) to Screen(x,y)
Point screenP = toScreenPoint(width, height);
int r = 7;
g.fillOval((int) screenP.x - r, (int) screenP.y - r, r + r, r + r);
}
public Point toCartesianPoint(int width, int height) {
double xCart = x - (width / 2);
double yCart = -(y - (height / 2));
return new Point(xCart, yCart);
}
public Point toScreenPoint(int width, int height) {
double screenX = x + (width / 2);
double screenY = -(y - (height / 2));
return new Point(screenX, screenY);
}
#Override
public String toString() {
return String.format("(%.2f,%.2f)", x, y);
}
}
}
If you start the GUI above and then type 100 0 130 -80 55 180 in the text box and hit return, you'll see the following: ...
Changed the code so that circles can be drawn by pressing- and dragging the mouse. Screenshot:
Assuming you know the center point and the radius of the two circles:
Calculate the points where the circles intersect. This can easily be done with trigonometry. There may be no intersection (distance between the center points is longer than the sum of the radiuses, ignorable in your case), one point (distance between center points is equal to the sum of the radiuses, ignorable), or two. Special cases: the circles are identical, or the moving circle ist smaller and completely inside the main circle.
If there are two intersection points: take the center point from the moving circle and draw an arc between those points.
(I have no code for you, but since you love maths... ;-)