package ProGAL.geom3d.volumes; import java.awt.Color; import java.util.Arrays; import java.util.Collection; import java.util.List; import ProGAL.geom3d.Circle; import ProGAL.geom3d.Line; import ProGAL.geom3d.Plane; import ProGAL.geom3d.Point; import ProGAL.geom3d.PointList; import ProGAL.geom3d.LineSegment; import ProGAL.geom3d.PointWeighted; import ProGAL.geom3d.Vector; import ProGAL.geom3d.complex.CTetrahedron; import ProGAL.geom3d.kineticDelaunay.Vertex; //import ProGAL.geom3d.viewer.J3DScene; import ProGAL.math.Constants; /** * A sphere represented by a center-point and a radius. */ public class Sphere implements Volume{ protected Point center; protected double radius; /** Constructs a sphere with the specified center and the specified radius. */ public Sphere(Point center, double radius) { this.center = center; this.radius = radius; } public Sphere(double x, double y, double z, double radius) { center = new Point(x, y, z); this.radius = radius; } public Sphere(Point[] ps) { this(0,0,0,0); radius = computeSphere_fast(ps[0],ps[1],ps[2],ps[3], center); } public Sphere(Point p0, Point p1, Point p2, Point p3) { this(0,0,0,0); radius = computeSphere_fast(p0,p1,p2,p3, center); } public Sphere(CTetrahedron tetr) { this(0,0,0,0); Point[] ps = tetr.getCorners(); radius = computeSphere_fast(ps[0],ps[1],ps[2],ps[3], center); } /** Constructs a sphere with the weighted point as center and a radius with * the square root of the points weight. */ public Sphere(PointWeighted p) { this.center = p; this.radius = Math.sqrt(p.getWeight()); } /** creates a sphere with the specified circle as equator */ public Sphere(Circle c) { center = c.getCenter(); radius = c.getRadius(); } /** * Calculates the radius and center of the sphere touching the four specified points. The radius is * returned and if center!=null the coordinates of center are overwritten * with the coordinates of the circumsphere. Uses 46 "heavy" operations (multiplication/division). */ public static double computeSphere_fast(Point p0, Point p1, Point p2, Point p3, Point center ) { double x1 = p1.x()-p0.x(), y1 = p1.y()-p0.y(), z1 = p1.z()-p0.z(); double x2 = p2.x()-p0.x(), y2 = p2.y()-p0.y(), z2 = p2.z()-p0.z(); double x3 = p3.x()-p0.x(), y3 = p3.y()-p0.y(), z3 = p3.z()-p0.z(); double xx1 = x1*x1 + y1*y1 + z1*z1; //3HOp double xx2 = x2*x2 + y2*y2 + z2*z2, xx3 = x3*x3 + y3*y3 + z3*z3; //6HOp double x1y2 = x1*y2, x1y3 = x1*y3, x1z2 = x1*z2, x1z3 = x1*z3; //4HOp double x2y1 = x2*y1, x2y3 = x2*y3, x2z1 = x2*z1, x2z3 = x2*z3; //4HOp double x3y2 = x3*y2, x3y1 = x3*y1, x3z2 = x3*z2, x3z1 = x3*z1; //4HOp double y1z2 = y1*z2, y1z3 = y1*z3; //2HOp double y2z1 = y2*z1, y2z3 = y2*z3; //2HOp double y3z1 = y3*z1, y3z2 = y3*z2; //2HOp double m11 = -((x1y2-x2y1)*z3 + (x3y1-x1y3)*z2 + (x2y3-x3y2)*z1); //3HOp if (m11 != 0.0) { double m12 = -(xx1*(y2z3-y3z2) + xx3*(y1z2-y2z1) + xx2*(y3z1-y1z3)); //3HOp double m13 = -(xx1*(x2z3-x3z2) + xx3*(x1z2-x2z1) + xx2*(x3z1-x1z3)); //3HOp double m14 = -(xx1*(x2y3-x3y2) + xx3*(x1y2-x2y1) + xx2*(x3y1-x1y3)); //3HOp double m11x2 = 0.5/m11; //1HOp double x = m12*m11x2; //1HOp double y =-m13*m11x2; //1HOp double z = m14*m11x2; //1HOp if(center!=null){ center.setX(x+p0.x()); center.setY(y+p0.y()); center.setZ(z+p0.z()); } return Math.sqrt(x*x + y*y + z*z); //3HOp } else throw new RuntimeException("Points are coplanar"); } /** * 96 HOps */ private void computeSphere(Point p0, Point p1, Point p2, Point p3 ) { double x0 = p0.x(); double y0 = p0.y(); double z0 = p0.z(); double x1 = p1.x(); double y1 = p1.y(); double z1 = p1.z(); double x2 = p2.x(); double y2 = p2.y(); double z2 = p2.z(); double x3 = p3.x(); double y3 = p3.y(); double z3 = p3.z(); double xx0 = x0*x0 + y0*y0 + z0*z0, xx1 = x1*x1 + y1*y1 + z1*z1; // 6HOps double xx2 = x2*x2 + y2*y2 + z2*z2, xx3 = x3*x3 + y3*y3 + z3*z3; // 6HOps double x1y2 = x1*y2, x1y3 = x1*y3, x1z2 = x1*z2, x1z3 = x1*z3; // 4HOps double x2y1 = x2*y1, x2y3 = x2*y3, x2z1 = x2*z1, x2z3 = x2*z3; // 4HOps double x3y2 = x3*y2, x3y1 = x3*y1, x3z2 = x3*z2, x3z1 = x3*z1; // 4HOps double y1z2 = y1*z2, y1z3 = y1*z3; // 2HOps double y2z1 = y2*z1, y2z3 = y2*z3; // 2HOps double y3z1 = y3*z1, y3z2 = y3*z2; // 2HOps double m11 = x0*(y1z2 + y3z1 + y2z3 - y1z3 - y2z1 - y3z2) -y0*(x1z2 + x3z1 + x2z3 - x1z3 - x2z1 - x3z2) +z0*(x1y2 + x3y1 + x2y3 - x1y3 - x2y1 - x3y2) -((x1y2-x2y1)*z3 + (x3y1-x1y3)*z2 + (x2y3-x3y2)*z1); // 6HOps if (m11 != 0.0) { double m12 = xx0*(y1z2 + y3z1 + y2z3 - y1z3 - y2z1 - y3z2) -y0*(xx1*(z2-z3) + xx3*(z1-z2) + xx2*(z3-z1)) +z0*(xx1*(y2-y3) + xx3*(y1-y2) + xx2*(y3-y1)) -(xx1*(y2z3-y3z2) + xx3*(y1z2-y2z1) + xx2*(y3z1-y1z3)); // 12HOps double m13 = xx0*(x1z2 + x3z1 + x2z3 - x1z3 - x2z1 - x3z2) -x0*(xx1*(z2-z3) + xx3*(z1-z2) + xx2*(z3-z1)) +z0*(xx1*(x2-x3) + xx3*(x1-x2) + xx2*(x3-x1)) -(xx1*(x2z3-x3z2) + xx3*(x1z2-x2z1) + xx2*(x3z1-x1z3)); // 12HOps double m14 = xx0*(x1y2 + x3y1 + x2y3 - x1y3 - x2y1 - x3y2) -x0*(xx1*(y2-y3) + xx3*(y1-y2) + xx2*(y3-y1)) +y0*(xx1*(x2-x3) + xx3*(x1-x2) + xx2*(x3-x1)) -(xx1*(x2y3-x3y2) + xx3*(x1y2-x2y1) + xx2*(x3y1-x1y3)); // 12HOps double m15 = xx0*(z3*(x1y2-x2y1) + z2*(x3y1-x1y3) + z1*(x2y3-x3y2)) -x0*(xx1*(y2z3-y3z2) + xx3*(y1z2-y2z1) + xx2*(y3z1-y1z3)) +y0*(xx1*(x2z3-x3z2) + xx3*(x1z2-x2z1) + xx2*(x3z1-x1z3)) -z0*(xx1*(x2y3-x3y2) + xx3*(x1y2-x2y1) + xx2*(x3y1-x1y3)); // 16HOps double m11x2 = 0.5/m11; // 1HOps double x = m12*m11x2; // 1HOps double y =-m13*m11x2; // 1HOps double z = m14*m11x2; // 1HOps center = new Point(x, y, z); radius = Math.sqrt(x*x + y*y + z*z - m15/m11); // 4HOps } else System.out.println("Points are coplanar"); } /** Get the center */ public Point getCenter() { return center; } /** Get the radius */ public double getRadius() { return radius; } /** Get the squared radius */ public double getRadiusSquared() { return radius*radius; } /** Get the surface area */ public double getSurfaceArea() { return 4*Math.PI*radius*radius; } /** Get the volume */ public double getVolume() { return getSurfaceArea()*radius/3; } /** Returns true if the point is inside this sphere */ public boolean isInside(Point p) { return center.distanceSquared(p) < getRadiusSquared(); } /** Returns true iff the squared distance from the point to the sphere center is less than the squared * radius minus eps */ public boolean isInside(Point p, double eps) { return center.distanceSquared(p) < radius*radius - eps; } public boolean isEmpty(Point[] points, double eps) { for (int i = 0; i < points.length; i++) if (isInside(points[i], eps)) return false; return true; } /** returns TRUE if the interior of the sphere (for a given eps reduction of the radius) is empty */ public boolean isEmpty(List points, double eps) { for (Point p : points) if (isInside(p, eps)) return false; return true; } public void contains(List points, double eps) { for (Vertex p : points) if (isInside(p, eps)) System.out.print(p.getId() + " " + (radius*radius) + " " + center.distanceSquared(p) + ", "); System.out.println(); } public void setCenter(Point center) { this.center = center; } public void setCenter(Point p0, Point p1, Point p2, Point p3) { computeSphere(p0, p1, p2, p3); } public void setRadius(double radius) { this.radius = radius; } /** Returns true if this sphere is intersected or touched by another sphere. */ public boolean isIntersected (Sphere sphere) { return overlaps(sphere); } /** Gets the secant on the line. TODO: Rename.*/ public LineSegment getIntersection(Line line) { Point p1 = line.getP(); Point p2 = line.getPoint(1.0); double dx = p2.x() - p1.x(); double dy = p2.y() - p1.y(); double dz = p2.z() - p1.z(); double ex = p1.x() - center.x(); double ey = p1.y() - center.y(); double ez = p1.z() - center.z(); double a = dx*dx + dy*dy + dz*dz; double b = 2*(dx*ex + dy*ey + dz*ez); double c = center.x()*center.x() + center.y()*center.y() + center.z()*center.z() + p1.x()*p1.x() + p1.y()*p1.y() + p1.z()*p1.z() - 2*(center.x()*p1.x() + center.y()*p1.y() + center.z()*p1.z()) - radius*radius; double delta = b*b - 4*a*c; if (delta < 0) return null; double u1, u2; if (delta == 0) u1 = u2 = - b/(2*a); else { double sqr = Math.sqrt(delta); u1 = (-b + sqr)/(2*a); u2 = (-b - sqr)/(2*a); } return new LineSegment(new Point(p1.x() + u1*dx, p1.y() + u1*dy, p1.z() + u1*dz), new Point(p1.x() + u2*dx, p1.y() + u2*dy, p1.z() + u2*dz)); } /** * Returns the two line-parameters that indicate where line intersects * this sphere. TODO: Coordinate line-intersection methods (see above). */ public double[] intersectionParameters(Line line) { Vector l = line.getDir();//.norm(); Vector c = line.getP().vectorTo(center); double lc = l.dot(c); double cc = c.dot(c); double rr = radius*radius; double tmp = lc*lc-cc+rr; if(tmp<0) return new double[0]; else if(tmp==0) return new double[]{lc}; else { double d1 = lc-Math.sqrt(tmp); double d2 = lc+Math.sqrt(tmp); return new double[]{d1, d2}; } } public Point[] getIntersections(Circle c) { Plane plane = new Plane(c.getCenter(), c.getNormalVector()); Circle c2 = plane.getIntersection(this); if (c2 != null) return c.getIntersection(c2); else return null; } public Double getIntersectionAngle(Circle c, Point p, Vector dir) { Plane plane = new Plane(c.getCenter(), c.getNormalVector()); Circle c2 = plane.getIntersection(this); if (c2 != null) return c.getFirstIntersection(c2, p, dir); else return null; } /** Returns true if none of the given points is in the sphere. */ public boolean containsNone(List points) { double rr = radius*radius-0.000000001; for(Point p: points) if(p.distanceSquared(center) points) { int count = 0; double rr = radius*radius-0.000000001; for(Point p: points) if(p.distanceSquared(center) points) { double rr = radius*radius-0.000000001; for(Vertex p: points) if ((p.distanceSquared(center) < rr) && (p != v)) { System.out.println("Contains vertex " + p.getId()); return false; } return true; } /** Returns true if the given point is in the sphere. */ public boolean contains(Point p) { double rr = radius*radius-0.000000001; return p.distanceSquared(center)dec decimals precision */ public String toString(int dec) { return String.format("Sphere3d[%s,%"+dec+"f]",center.toString(dec), radius); } /** Writes this sphere to System.out. */ public void toConsole() { System.out.println(toString()); } /** Writes this sphere to System.out with dec decimals precision. */ public void toConsole(int dec) { System.out.println(toString(dec)); } // public void toScene(J3DScene scene, Color clr) { // scene.addShape(this, clr); // } // // public void toScene(J3DScene scene, Color clr, int res) { // scene.addShape(this, clr, res); // } // public void toSceneSpiral(J3DScene scene, Color clr, int s, int step, double width) { // double alpha; // double delta; // double t; // double iPI; // double tStep = 2.0/step; // double r2 = radius * radius; // for (int i = 1; i <= s; i++) { // iPI = i*Math.PI; // t = -1; // for (int j = 0; j < step; j++) { // delta = Math.sqrt(r2 - r2 * t * t); // alpha = t * iPI; // new Point(delta*Math.cos(alpha) + center.x(), delta*Math.sin(alpha) + center.y(), radius*t + center.z()).toScene(scene, width, clr); // // t += tStep; // } // } // // } /** Returns true if the sphere overlaps with vol. TODO: Implement for all volumes. */ public boolean overlaps(Volume vol) { if(vol instanceof Sphere) return ((Sphere)vol).center.distance(this.center)<=((Sphere)vol).radius+radius; throw new IllegalArgumentException(); } /** Returns a deep clone of this sphere. */ public Sphere clone(){ return new Sphere(center.clone(), radius); } /** Get the sphere with the specified circle as equator */ public static Sphere getMinSphere(Circle c) { return new Sphere(c.getCenter(), c.getRadius()); } /** Get the smallest sphere through two given points. */ public static Sphere getMinSphere(Point p1, Point p2) { return new Sphere( Point.getMidpoint(p1,p2), p1.distance(p2)/2 ); } /** Get the smallest sphere through three points. */ public static Sphere getMinSphere(Point p0, Point p1, Point p2) { Point center = new Point((p0.x()+p1.x()+p2.x())/3, (p0.y()+p1.y()+p2.y())/3, (p0.z()+p1.z()+p2.z())/3); double radius = p0.distance(center); return new Sphere(center, radius); } /** Constructs the sphere through four points. An error is thrown * if the points are coplanar. */ public static Sphere getMinSphere(Point p0, Point p1, Point p2, Point p3) { Sphere ret = new Sphere(0,0,0,0); ret.radius = computeSphere_fast(p0, p1, p2, p3, ret.center); return ret; // double x0 = p0.x(); double y0 = p0.y(); double z0 = p0.z(); // double x1 = p1.x(); double y1 = p1.y(); double z1 = p1.z(); // double x2 = p2.x(); double y2 = p2.y(); double z2 = p2.z(); // double x3 = p3.x(); double y3 = p3.y(); double z3 = p3.z(); // // double xx0 = x0*x0 + y0*y0 + z0*z0, xx1 = x1*x1 + y1*y1 + z1*z1; // double xx2 = x2*x2 + y2*y2 + z2*z2, xx3 = x3*x3 + y3*y3 + z3*z3; // // double x1y2 = x1*y2, x1y3 = x1*y3, x1z2 = x1*z2, x1z3 = x1*z3; // double x2y1 = x2*y1, x2y3 = x2*y3, x2z1 = x2*z1, x2z3 = x2*z3; // double x3y2 = x3*y2, x3y1 = x3*y1, x3z2 = x3*z2, x3z1 = x3*z1; // // double y1z2 = y1*z2, y1z3 = y1*z3; // double y2z1 = y2*z1, y2z3 = y2*z3; // double y3z1 = y3*z1, y3z2 = y3*z2; // // // double m11 = x0*(y1z2 + y3z1 + y2z3 - y1z3 - y2z1 - y3z2) // -y0*(x1z2 + x3z1 + x2z3 - x1z3 - x2z1 - x3z2) // +z0*(x1y2 + x3y1 + x2y3 - x1y3 - x2y1 - x3y2) // -((x1y2-x2y1)*z3 + (x3y1-x1y3)*z2 + (x2y3-x3y2)*z1); // // if (m11 != 0.0) { // // double m12 = xx0*(y1z2 + y3z1 + y2z3 - y1z3 - y2z1 - y3z2) // -y0*(xx1*(z2-z3) + xx3*(z1-z2) + xx2*(z3-z1)) // +z0*(xx1*(y2-y3) + xx3*(y1-y2) + xx2*(y3-y1)) // -(xx1*(y2z3-y3z2) + xx3*(y1z2-y2z1) + xx2*(y3z1-y1z3)); // // double m13 = xx0*(x1z2 + x3z1 + x2z3 - x1z3 - x2z1 - x3z2) // -x0*(xx1*(z2-z3) + xx3*(z1-z2) + xx2*(z3-z1)) // +z0*(xx1*(x2-x3) + xx3*(x1-x2) + xx2*(x3-x1)) // -(xx1*(x2z3-x3z2) + xx3*(x1z2-x2z1) + xx2*(x3z1-x1z3)); // // double m14 = xx0*(x1y2 + x3y1 + x2y3 - x1y3 - x2y1 - x3y2) // -x0*(xx1*(y2-y3) + xx3*(y1-y2) + xx2*(y3-y1)) // +y0*(xx1*(x2-x3) + xx3*(x1-x2) + xx2*(x3-x1)) // -(xx1*(x2y3-x3y2) + xx3*(x1y2-x2y1) + xx2*(x3y1-x1y3)); // // double m15 = xx0*(z3*(x1y2-x2y1) + z2*(x3y1-x1y3) + z1*(x2y3-x3y2)) // -x0*(xx1*(y2z3-y3z2) + xx3*(y1z2-y2z1) + xx2*(y3z1-y1z3)) // +y0*(xx1*(x2z3-x3z2) + xx3*(x1z2-x2z1) + xx2*(x3z1-x1z3)) // -z0*(xx1*(x2y3-x3y2) + xx3*(x1y2-x2y1) + xx2*(x3y1-x1y3)); // // // double x = 0.5*m12/m11; // double y = -0.5*m13/m11; // double z = 0.5*m14/m11; // return new Sphere(new Point(x, y, z), Math.sqrt(x*x + y*y + z*z - m15/m11)); // } // throw new Error("Points are coplanar"); } /** * Gets the smallest sphere containing a set of points. Uses a * randomized, O(n) expected time algorithm. */ public static Sphere getMinSphere(PointList points) { return getMinSphere(points.getRandomPermutation(), points.size(), new PointList()); } private static Sphere getMinSphere(PointList points, int n, PointList boundaryPoints) { Sphere sphere = null; int k = 0; switch (boundaryPoints.size()) { case 0: sphere = getMinSphere(points.get(0), points.get(1)); k = 2; break; case 1: sphere = getMinSphere(points.get(0), boundaryPoints.get(0)); k = 1; break; case 2: sphere = getMinSphere(boundaryPoints.get(0), boundaryPoints.get(1)); break; case 3: sphere = getMinSphere(boundaryPoints.get(0), boundaryPoints.get(1), boundaryPoints.get(2)); break; } for (int i = k; i < n + boundaryPoints.size(); i++) { Point p = (Point)points.get(i); if (!boundaryPoints.contains(p)) { if (!sphere.isInside(p)) { if (boundaryPoints.size() < 3) { boundaryPoints.add(p); sphere = getMinSphere(points, i-1, boundaryPoints); boundaryPoints.remove(p); } else sphere = getMinSphere(boundaryPoints.get(0), boundaryPoints.get(1), boundaryPoints.get(2), p); } } } return sphere; } public static Circle getIntersection(Sphere s1, Sphere s2){ double r1 = s1.radius; double r2 = s2.radius; double d = s1.center.distance(s2.center); if(d>r1+r2) return null; double h = ProGAL.geom2d.Triangle.calculateHeight(r1,r2,d); double d1 = Math.sqrt(r1*r1 - h*h); double d2 = Math.sqrt(r2*r2 - h*h); if(d2>d) d1*=-1; Vector normal = s1.center.vectorTo(s2.center).normalizeThis(); Point center = s1.center.add(normal.multiply(d1)); return new Circle(center, h, normal); } /** * Find the two, one or zero points that is at the intersection of the three sphere shells. * If the three spheres intersect in more than two points or one sphere contains another, * the result of this method not specified. * @hops 57-70 */ public static Point[] getIntersections(Sphere s1, Sphere s2, Sphere s3){ Circle i12 = getIntersection(s1,s2); if(i12==null) return new Point[]{}; return s3.getIntersections(i12); //Inspired by http://mathforum.org/library/drmath/view/63138.html //Theres a bug. Above works // double x1 = s1.center.x(); // double y1 = s1.center.y(); // double z1 = s1.center.z(); // double c1Sq = s1.center.dot(s1.center); //3HOp // double c2Sq = s2.center.dot(s2.center); //3HOp // double r1 = s1.radius, r1Sq = r1*r1; //2HOp // Vector v12 = s2.center.vectorTo(s1.center); // Vector v23 = s3.center.vectorTo(s2.center); // double c12 = c1Sq-c2Sq; // double c23 = c2Sq-s3.center.dot(s3.center); //3HOp // double r2Sq = s2.radius*s2.radius; //1HOp (12) // double r12 = r2Sq-r1Sq + c12; // double r23 = s3.radius*s3.radius - r2Sq + c23; //3HOp // double Dyz = v12.y()*v23.z()-v23.y()*v12.z(), DyzSq = Dyz*Dyz; //3HOp // double Dry = r12*v23.y()-r23*v12.y(), DrySq = Dry*Dry; //3HOp // double Dzx = v12.z()*v23.x()-v23.z()*v12.x(), DzxSq = Dzx*Dzx; //3HOp // double Dxr = v12.x()*r23-v23.x()*r12, DxrSq = Dxr*Dxr; //3HOp // double Dxy = v12.x()*v23.y()-v23.x()*v12.y(), DxySq = Dxy*Dxy; //3HOp (30) // // double k2 = (DyzSq+DzxSq)/DxySq + 1; //1HOp // double k1 = (Dyz*Dry+Dzx*Dxr)/DxySq - 4*(x1*Dyz+y1*Dzx)/Dxy - z1; //7HOp // double k0 = (DrySq+DxrSq)/(4*DxySq) + c1Sq-r1Sq - 2*(x1*Dry+y1*Dxr)/Dxy; //6HOp // // double d = k1*k1-4*k2*k0; //3HOp // if(d<-Constants.EPSILON) return new Point[]{}; // if(d