package ProGAL.geom3d; import java.awt.Color; import ProGAL.Function; import ProGAL.geom3d.volumes.Cylinder; import ProGAL.math.Constants; import ProGAL.math.Functions; import ProGAL.math.RootFinding; /* * If the circle has center c, radius r, and unit-length normal vector n, compute unit-length vectors u and v so that {u,v,n} are mutually orthogonal. The circle is then parameterized by P(t) = c + rcos(t)u + rsin(t)v for 0 <= t < 2*pi. */ /** * A circle in (x,y,z)-space represented by a center-point, a radius and a normal-vector. */ public class Circle implements Shape{ private Point center; private double radius; private Vector normal; public Circle(Point center, double radius, Vector normal) { this.center = center; this.radius = radius; if (normal!=null) this.normal = normal.normalize(); } /** A circle with given center through a given point and with specified normal vector */ public Circle(Point center, Point through, Vector normal) { this.center = center; radius = center.distance(through); this.normal = normal.normalize(); } /** Circle in the plane through p0, p1, p2 */ public Circle(Point p0, Point p1, Point p2) { center = Point.getCircumCenter(p0, p1, p2); radius = center.distance(p0); Vector v0 = new Vector(center, p0); Vector v1 = new Vector(center, p1); normal = v0.cross(v1).normalize(); } /* * Given three points p0, p1, p2 and a vector v, find the circle or just the radius of the circle through the * projection of the points onto the plane with v as its normal vector. Claim: radius is minimized when of the * plane goes through p0, p1, p2 */ /*public Circle3d(Point3d p0, Point3d p1, Point3d p2, Vector3d v) { // create the plane through the origo with v as its normal vector Plane3d plane = new Plane3d(v, new Point3d(0,0,0)); Point3d q0 = plane.projectPoint(p0); Point3d q1 = plane.projectPoint(p1); Point3d q2 = plane.projectPoint(p2); double a = q0.getSquaredDistance(q1); double b = q1.getSquaredDistance(q2); double c = q2.getSquaredDistance(q0); radius = Math.sqrt(a*b*c)/Math.sqrt(2*a*b + 2*b*c + 2*c*a - a*a - b*b - c*c); normalVector = Vector3d.crossProduct(new Vector3d(p0,p1), new Vector3d(p0,p2)); }*/ /* * returns the radius of the circle through 3 given points (without creating the circle) */ /*public static double getRadius(Point3d p0, Point3d p1, Point3d p2) { // get the plane through q0, q1, q2 Point3d q0 = new Point3d(0,0,0); Point3d q1 = new Point3d(p1.x-p0.x, p1.y-p0.y, p1.z-p0.z); Point3d q2 = new Point3d(p2.x-p0.x, p2.y-p0.y, p2.z-p0.z); // get the plane through q0, q1, q2 Plane3d plane = new Plane3d(q0,q1,q2); Vector3d verticalVector = new Vector3d(0,0,1); double angle = Vector3d.getAngle(plane.n, verticalVector); Vector3d rotationVector = Vector3d.crossProduct(plane.n, verticalVector); rotationVector.normalizeThis(); q1.rotation(rotationVector, angle); q2.rotation(rotationVector, angle); Point2d r0 = new Point2d(0,0); Point2d r1 = new Point2d(q1.x, q1.y); Point2d r2 = new Point2d(q2.x, q2.y); Circle2d circle2 = new Circle2d(r0, r1, r2); return circle2.getRadius(); }*/ /** Get the center of the circle. */ public Point getCenter() { return center; } /** Get the radius of the circle. */ public double getRadius() { return radius; } /** Get the normal of the circle. */ public Vector getNormalVector() { return normal; } public Vector getNormal() { return normal; } /** Sets the center of the circle */ public void setCenter(Point p) { center = p; } /** return a point on the circle */ public Point getPoint() { return center.add(normal.getOrthonormal().scaleToLength(radius)); } /** returns plane through this circle */ public Plane getPlane() { return new Plane(center, normal); } /** returns the point on the circle closets to the query point p. If p is equidistant to all points on the circle, * an arbitrary point of the circle is returned. */ public Point getClosestPoint(Point p) { Point pr = new Plane(center, normal).projectPoint(p); if (center.distanceSquared(pr) <= Constants.EPSILON) return getPoint(); else return center.add(new Vector(center, pr).scaleToLength(radius)); } /** returns the point on the circle farthest from the query point p. If p is equidistant to all points on the circle, * an arbitrary point of the circle is returned. */ public Point getFarthestPoint(Point p) { Point pr = new Plane(center, normal).projectPoint(p); if (center.distanceSquared(pr) <= Constants.EPSILON) return getPoint(); else return center.add(new Vector(center, pr).scaleToLength(radius).multiply(-1)); } public double getClosestDistance(Circle cb) { Vector u = cb.normal.getOrthonormal(); Vector v = cb.normal.cross(u); double ra2 = radius*radius; double ca2 = center.dot(center); double cacb = center.dot(cb.center); double cau = center.dot(u); double cav = center.dot(v); double naca = normal.dot(center); double nacb = normal.dot(cb.center); double nau = normal.dot(u); double nav = normal.dot(v); double rb2 = cb.radius*cb.radius; double cb2 = cb.center.dot(cb.center); double cbu = cb.center.dot(u); double cbv = cb.center.dot(v); double a0 = rb2 + cb2 + ca2 - 2*cacb - (nacb - naca)*(nacb - naca); double a1 = -2*rb2*nau*nav; double a2 = 2*cb.radius*(cbv - cav - (nacb - naca)*nav); double a3 = 2*cb.radius*(cbu - cau - (nacb - naca)*nau); double a4 = -rb2*nav*nav; double a5 = -rb2*nau*nau; double a6 = -2*radius; double a7 = ra2 + rb2 + ca2 + cb2 - 2*cacb; double a8 = 2*cb.radius*(cbv - cav); double a9 = 2*cb.radius*(cbu - cau); double[] c = new double[5]; c[0] = a9*a9-a6*a6*a5 + 2*a7*a9 - a6*a6*a3 + a7*a7 - a6*a6*a0; c[1] = 2*(2*a7*a8 - a6*a6*a2) + 2*(2*a8*a9 - a6*a6*a1); c[2] = 2*(a6*a6*a5 - a9*a9) + 4*(a8*a8 -a6*a6*a4) + 2*(a7*a7 - a6*a6*a0); c[3] = 2*(2*a7*a8 - a6*a6*a2) - 2*(2*a8*a9 - a6*a6*a1); c[4] = a9*a9-a6*a6*a5 - 2*a7*a9 + a6*a6*a3 + a7*a7 -a6*a6*a0; Function f = new Function(c); double root = RootFinding.brent(f, 0.5, 0.8, 0.000001, 0.00001, 1000); return root; } /** Create the equilateral circle of two points. */ public static Circle getEquilateralCircle(Point a, Point b) { Point center = Point.getMidpoint(a, b); Vector ab = a.vectorTo(b); double radius = Math.sqrt(3)*ab.length()/2; return new Circle(center, radius, ab); } /** Returns a string-representation of this circle formatted with two decimals precision. */ public String toString(){ return toString(2); } /** Returns a string-representation of this circle formatted with dec decimals precision. */ public String toString(int dec) { return String.format("Circle[center=%s,radius=%"+dec+"f,normal=%s]",center.toString(dec),radius,normal.toString(dec)); } /** Writes this circle to System.out with 2 decimals precision. */ public void toConsole() { toConsole(2); } /** Writes this circle to System.out with dec decimals precision. */ public void toConsole(int dec) { System.out.println(toString(dec)); } /** Intersection of 2 circles in the same plane */ public Point[] getIntersection(Circle c) { Point[] intersectionPoints = null; double dist = center.distance(c.getCenter()); double sum = radius + c.getRadius(); if (dist - sum > Constants.EPSILON) return intersectionPoints; else { double diff = Math.abs(radius - c.getRadius()); if (dist - diff < -Constants.EPSILON) return intersectionPoints; else { if (Math.abs(dist - sum) < Constants.EPSILON) { // one intersection point Vector v = new Vector(center, c.getCenter()); double fraction = radius/(radius+c.getRadius()); intersectionPoints = new Point[1]; intersectionPoints[0] = center.addThis(v.multiply(fraction)); // System.out.println("Scale vector correct in Circle class? "+(center.distance(center.addThis(v.multiply(fraction)))==radius)); return intersectionPoints; } else { if (dist - diff < Constants.EPSILON) { Vector v; intersectionPoints = new Point[1]; if (radius > c.radius) { v = new Vector(center, c.getCenter()); v.scaleToLengthThis(radius); intersectionPoints[0] = center.add(v); } else { v = new Vector(c.getCenter(), center); v.scaleToLengthThis(c.getRadius()); intersectionPoints[0] = c.getCenter().add(v); } } else { double alpha = Math.acos((radius*radius + dist*dist - c.getRadius()*c.getRadius())/(2*radius*dist)); Vector v = new Vector(center, c.getCenter()); v.scaleToLengthThis(radius); intersectionPoints = new Point[2]; getNormalVector().rotateIn(v, alpha); intersectionPoints[0] = center.add(v); getNormalVector().rotateIn(v, -2*alpha); intersectionPoints[1] = center.add(v); } } } } return intersectionPoints; } /** returns the smallest rotation angle (direction determined by vector dir) * needed to bring point p on this circle to be on the line l as well. Returns null if * there is no intersection or just one intersection. * @return */ public Double getFirstIntersection(Line line, Point p, Vector dir ) { double dist = line.getDistance(center); if (dist > radius - Constants.EPSILON) return null; Vector op = new Vector(center, p); double r2 = radius*radius; if (dist < 0.001) { Vector oco = line.dir.multiply(radius); double cosBeta = op.dot(oco)/r2; Vector cr = op.cross(oco); cr.divideThis(r2); double sinBeta = cr.length(); double beta = Math.atan2(sinBeta,cosBeta); System.out.println("beta = " + Functions.toDeg(beta)); if (normal.dot(dir) > 0) { if (normal.dot(cr) > 0) return beta; else return 2*Math.PI - beta; } else { if (normal.dot(cr) > 0) return 2*Math.PI - beta; else return beta; } } Point co = line.orthogonalProjection(center); double oco2 = dist*dist; double pco2 = co.distanceSquared(p); // double cosAlpha = Math.sqrt(1-oco2/r2); double cosAlpha = dist/radius; double cosBeta = (r2 + oco2 - pco2)/(2*radius*dist); Vector oco = new Vector(center, co); // LineSegment seg1 = new LineSegment(center, co); // seg1.toScene(scene,0.03, Color.red); // LineSegment seg2 = new LineSegment(center, p); // seg2.toScene(scene, 0.03, Color.pink); Vector cr = op.cross(oco); cr.divideThis(op.length()*oco.length()); double sinBeta = cr.length(); double alpha = Math.acos(cosAlpha); System.out.println("alpha = " + Functions.toDeg(cosAlpha)); double beta = Math.atan2(sinBeta,cosBeta); System.out.println("beta = " + Functions.toDeg(beta)); if (normal.dot(dir) > 0) { if (normal.dot(cr) > 0) return beta-alpha; else return 2*Math.PI - beta - alpha; } else { if (normal.dot(cr) > 0) return 2*Math.PI - beta - alpha; else return beta - alpha; } } //Daisy public double getDistanceSquared(Point p) { double NPC = normal.dot(p.subtract(center)); double secondTerm = Math.sqrt(p.distanceSquared(center)-NPC*NPC)-radius; return NPC*NPC+secondTerm*secondTerm; } //Daisy public double getDistance(Point p) { return Math.sqrt(getDistanceSquared(p)); } /** returns the smallest rotation angle (direction determined by vector dir) * needed to bring point p on this circle to be on the circle c as well. Returns null if * there is no intersection or just one intersection. * @return */ public Double getFirstIntersection(Circle c, Point p, Vector dir) { double dist = center.distance(c.getCenter()); if (dist > radius + c.getRadius() - Constants.EPSILON) return null; double r2d2 = radius*radius + dist*dist; double rd2 = 2*radius*dist; double cosAlpha = (r2d2- c.getRadius()*c.getRadius())/rd2; System.out.println("cosAlpha =" + cosAlpha + " " + Math.acos(cosAlpha)); double distP2 = p.distanceSquared(c.getCenter()); double cosBeta = (r2d2 - distP2)/rd2; System.out.println("cosBeta =" + cosAlpha + " " + Math.acos(cosBeta)); Vector op = new Vector(center, p); Vector oco = new Vector(center, c.getCenter()); Vector cr = op.cross(oco); cr.divideThis(op.length()*oco.length()); double sinBeta = cr.length(); double alpha = Math.acos(cosAlpha); System.out.println("alpha = " + Functions.toDeg(cosAlpha)); double beta = Math.atan2(sinBeta,cosBeta); System.out.println("beta = " + Functions.toDeg(beta)); if (normal.dot(dir) > 0) { if (normal.dot(cr) > 0) return beta-alpha; else return 2*Math.PI - beta - alpha; } else { if (normal.dot(cr) > 0) return 2*Math.PI - beta - alpha; else return beta - alpha; } } // private static void intersectionInPlane() { // Circle c1 = new Circle(new Point(0,0,0), 0.5, new Vector(0,0,1)); // Circle c2 = new Circle(new Point(-0.1,0.3,0), 0.6, new Vector(0, 0, 1)); // J3DScene scene = J3DScene.createJ3DSceneInFrame(); // c1.toScene(scene, 0.005, Color.blue); // c2.toScene(scene, 0.005, Color.red); // new LineSegment(c1.center, c2.center).toScene(scene, 0.002, Color.black); // double cosg = (c1.center.distanceSquared(c2.center) + c1.radius*c1.radius - c2.radius*c2.radius)/(2*c1.radius*c1.center.distance(c2.center)); // double alpha = Functions.toDeg(Math.acos(cosg)); // Vector cc = new Vector(c1.center, c2.center).scaleToLength(c1.radius); // cc = c1.normal.rotateIn(cc, Math.acos(cosg)); // Point q = c1.center.add(cc); // q.toScene(scene, 0.03, Color.green); // scene.addText("q", q.add(-0.075, 0,0)); // new LineSegment(c1.center, q).toScene(scene, 0.002, Color.black); // new LineSegment(c2.center, q).toScene(scene, 0.002, Color.black); // cc = c1.normal.rotateIn(cc, -2*Math.acos(cosg)); // q = c1.center.add(cc); // q.toScene(scene, 0.03, Color.green); // scene.addText("c1", c1.center.add(0,-0.075,0)); // scene.addText("c2", c2.center.add(0,-0.075,0)); // // } public static LineSegment getFurthestDistance_centers(Circle c1, Circle c2) { Vector v = c1.center.vectorTo(c2.center); Vector y2 = c2.normal.cross(v); Vector x2 = c2.normal.cross(y2); x2.multiplyThis(c2.radius/x2.length()); Point p21 = c2.center.add(x2); Point p22 = c2.center.subtract(x2); Vector y1 = c1.normal.cross(v).normalizeThis(); Vector x1 = c1.normal.cross(y1); x1.multiplyThis(c1.radius/x1.length()); Point p11 = c1.center.add(x1); Point p12 = c1.center.subtract(x1); double d11_21 = p11.distanceSquared(p21); double d12_21 = p12.distanceSquared(p21); double d11_22 = p11.distanceSquared(p22); double d12_22 = p12.distanceSquared(p22); if(d11_21>d12_21 && d11_21>d11_22 && d11_21>d12_22) return new LineSegment(p11, p21); if(d12_21>d11_22 && d12_21>d12_22) return new LineSegment(p12, p21); if(d11_22>d12_22) return new LineSegment(p11, p22); return new LineSegment(p12, p22); } public static Point getFurthestPoint_bruteForce(Circle c, Point p){ if(c.radiusMath.PI/10){ for(double t=maxT-range/2;tmaxDist){ maxT = t; maxDist = dist; } } range/=8; } Point pc = c.center.add( x.multiply(c.radius*Math.cos(maxT)).addThis(y.multiply(c.radius*Math.sin(maxT))) ); return pc; } public static LineSegment getFurthestDistance_bruteForce(Circle c1, Circle c2) { if(c1.radiusMath.PI/10){ for(double t1=maxT1-range/2;t1maxDist){ maxT1 = t1; maxT2 = t2; maxDist = dist; } } } range/=8; } Point p1 = c1.center.add( x1.multiply(c1.radius*Math.cos(maxT1)).addThis(y1.multiply(c1.radius*Math.sin(maxT1))) ); Point p2 = c2.center.add( x2.multiply(c2.radius*Math.cos(maxT2)).addThis(y2.multiply(c2.radius*Math.sin(maxT2))) ); return new LineSegment(p1, p2); } }