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