package ProGAL.geom3d;

/**
 * A planar rectangle in 3d. The only real functionality so far is the Rectangle-Rectangle distance method that 
 * is used for RSS intersection checks. 
 * @author P.Winter and R.Fonseca
 */
public class Rectangle {
	public Point center;
	public final Vector[] bases;
	private final Vector[] normBases;
	private final double[] extents; 

	public Rectangle(Point center, Vector[] bases){
		this.center = center;
		this.bases = new Vector[]{bases[0], bases[1]};
		this.normBases = new Vector[]{bases[0].normalize(), bases[1].normalize(),bases[0].cross(bases[1]).normalize()};
		this.extents = new double[]{bases[0].length()*2,bases[1].length()*2};
	}


	public double distance(Rectangle rect){
		return distance_optimized(rect);
	}

	/* 196..766 ~*/
	public double distance_optimized(Rectangle rect){
		Point[] thisCorners = getCorners();		//counter-clockwise order starting with bases[0]+bases[1]
		Point[] rectCorners = rect.getCorners();
		Vector[] thisNormals = {bases[1],bases[0].multiply(-1), bases[1].multiply(-1),bases[0]};//6HOps
		Vector[] rectNormals = {rect.bases[1],rect.bases[0].multiply(-1), rect.bases[1].multiply(-1),rect.bases[0]};//6HOps

		boolean[][] inVoronoi1 = geninVoronoi(thisCorners, thisNormals, rectCorners, rectNormals);//48HOps
		boolean[][] inVoronoi2 = geninVoronoi(rectCorners, rectNormals, thisCorners, thisNormals);//48HOps

		int[] perm = {0,2,1,3};
		for(int i: perm){
			for(int j: perm){
				if(!inVoronoi1[i][j] && !inVoronoi1[i][(j+1)%4]) continue;
				if(!inVoronoi2[j][i] && !inVoronoi2[j][(i+1)%4]) continue;
				double c = checkEdgePair(//38HOps
						thisCorners[i],thisCorners[(i+1)%4],thisNormals[i], 
						rectCorners[j],rectCorners[(j+1)%4],rectNormals[j]);
//				System.out.println("c = " + c);
				if(c>=0) {
//					System.out.println("Stopped when i = " + i + " and j = " + j);
					return c;
				}
			}
		}//38 -> 16*38 = 38 -> 608HOps

		double sep1 = axisSeparation(thisCorners,rectCorners);//25HOps
		double sep2 = axisSeparation(rectCorners,thisCorners);//25HOps
		return Math.max(sep1,sep2);
	}

	/** 16*3 = 48HOps */
	private static boolean[][] geninVoronoi(Point[] corners1, Vector[] normals1, Point[] corners2, Vector[] normals2){
		boolean[][] ret = new boolean[4][4];

		//e_0
		ret[0][0] = corners1[0].vectorTo(corners2[0]).dot(normals1[0])>=0;
		ret[0][1] = corners1[0].vectorTo(corners2[1]).dot(normals1[0])>=0;
		ret[0][2] = corners1[0].vectorTo(corners2[2]).dot(normals1[0])>=0;
		ret[0][3] = corners1[0].vectorTo(corners2[3]).dot(normals1[0])>=0;

		//e_1
		ret[1][0] = corners1[2].vectorTo(corners2[0]).dot(normals1[1])>=0;
		ret[1][1] = corners1[2].vectorTo(corners2[1]).dot(normals1[1])>=0;
		ret[1][2] = corners1[2].vectorTo(corners2[2]).dot(normals1[1])>=0;
		ret[1][3] = corners1[2].vectorTo(corners2[3]).dot(normals1[1])>=0;

		//e_2
		ret[2][0] = corners1[2].vectorTo(corners2[0]).dot(normals1[2])>=0;
		ret[2][1] = corners1[2].vectorTo(corners2[1]).dot(normals1[2])>=0;
		ret[2][2] = corners1[2].vectorTo(corners2[2]).dot(normals1[2])>=0;
		ret[2][3] = corners1[2].vectorTo(corners2[3]).dot(normals1[2])>=0;

		//e_3
		ret[3][0] = corners1[0].vectorTo(corners2[0]).dot(normals1[3])>=0;
		ret[3][1] = corners1[0].vectorTo(corners2[1]).dot(normals1[3])>=0;
		ret[3][2] = corners1[0].vectorTo(corners2[2]).dot(normals1[3])>=0;
		ret[3][3] = corners1[0].vectorTo(corners2[3]).dot(normals1[3])>=0;
		return ret;
	}

	/** 670HOps */
	public double distance_nonoptimized(Rectangle rect){
		Point[] thisCorners = getCorners();		//counter-clockwise order starting with bases[0]+bases[1]
		Point[] rectCorners = rect.getCorners();
		Vector[] thisNormals = {bases[1],bases[0].multiply(-1), bases[1].multiply(-1),bases[0]};//6HOps
		Vector[] rectNormals = {rect.bases[1],rect.bases[0].multiply(-1), rect.bases[1].multiply(-1),rect.bases[0]};//6HOps

		int[] perm = {0,2,1,3};
		for(int i: perm){
			for(int j: perm){
				double c = checkEdgePair(//38HOps
						thisCorners[i],thisCorners[(i+1)%4],thisNormals[i], 
						rectCorners[j],rectCorners[(j+1)%4],rectNormals[j]);
				if(c>=0) {
					return c;
				}
			}
		}//16*38 = 608HOps

		double sep1 = axisSeparation(thisCorners,rectCorners);//25HOps
		double sep2 = axisSeparation(rectCorners,thisCorners);//25HOps
		return Math.max(sep1,sep2);
	}

	/** 25HOps */
	private static double axisSeparation(Point[] corners1, Point[] corners2){
		Vector n = corners1[1].vectorTo(corners1[0]).cross(corners1[1].vectorTo(corners1[2])).normalizeThis();//6+7=13HOps
		boolean negatives = false;
		boolean positives = false;
		double min = Double.POSITIVE_INFINITY;
		for(Point c: corners2){
			Vector v = corners1[0].vectorTo(c);
			double dot = v.dot(n);//3HOps
			min = Math.min(min, Math.abs(dot));
			if(dot>0) 	positives = true;
			else		negatives = true;
		}//4*3=12HOps
		if(positives&&negatives) return 0;
		else return min;
	}

	/** 38HOps */
	private double checkEdgePair(Point p1, Point p2, Vector n1, Point q1, Point q2, Vector n2){
		Point[] minDist = closestSegmentPoint(p1,p2,q1,q2);//28HOps
		Vector v = minDist[0].vectorTo(minDist[1]);
		if(v.dot(n1)>0 && v.dot(n2)<0) return v.length();//3+3+4=10HOps
		return -1;
	}

	/** 
	 * 28HOps at most. 
	 */
	public static Point[] closestSegmentPoint(Point p1, Point p2, Point q1, Point q2){
		Point startPoint1 = p1;
		Point startPoint2 = q1;

		Vector dir1 = p1.vectorTo(p2);
		Vector dir2 = q1.vectorTo(q2);

		if(dir1.length()<0.000001 && dir2.length()<0.00001 )
			return new Point[]{startPoint1,startPoint2};
		if(dir1.length()<0.000001) return new Point[]{startPoint1,closestSegmentPoint(startPoint2, q2, startPoint1)};
		if(dir2.length()<0.000001) return new Point[]{closestSegmentPoint(startPoint1, p2, startPoint2),startPoint2};
		//System.out.println("len1 "+d1.length()+" .. len2 "+d2.length());

		Vector r = startPoint2.vectorTo(startPoint1);
		double a = dir1.dot(dir1);//|S1| squared       .. 3HOp
		double e = dir2.dot(dir2);//|S2| squared       .. 3HOp
		double f = dir2.dot(r);//                    .. 3HOp
		double c = dir1.dot(r);//                    .. 3HOp
		double b = dir1.dot(dir2);//                   .. 3HOp
		double denom = a*e-b*b;//                  .. 2HOp

		//If segments not parallel, compute closest point on L1 and L2
		//and clamp to S1
		double s, t;
		if(denom!=0.0f)  s = clamp( (b*f-c*e)/denom );//      .. 3HOp
		else             s = 0.0f;

		//Compute point on L2 closest to S1(S)
		double tnom = b*s+f;//                     .. 1HOp

		//If t in [0,1] done. Else clamp t and recompute and clamp s
		//.. 1 HOp
		if(tnom<0.0f){
			t = 0.0f;
			s = clamp(-c/a);
		}else if(tnom>e){
			t = 1.0f;
			s = clamp( (b-c)/a );
		}else{
			t = tnom/e;
		}

		Point c1 = startPoint1.add(dir1.multiplyThis(s));//      vec-scalar mult  .. 3HOp
		Point c2 = startPoint2.add(dir2.multiplyThis(t));//      vec-scalar mult  .. 3HOp
		return new Point[]{c1,c2};	    
	}

	public static Point closestSegmentPoint(Point p11, Point p12, Point p2){
		Line l = new Line(p11, p11.vectorTo(p12));
		double t = l.orthogonalProjectionParameter(p2);
		t = clamp(t)*p11.distance(p12);
		return l.getPoint(t);
	}

	private static double clamp(double s){
		if(s<0) return 0;
		if(s>1) return 1;
		return s;
	}

	/** Return corners of rectangle in counter-clockwise order. */
	public Point[] getCorners() {
		return new Point[]{
				center.add( bases[0]).addThis( bases[1]),
				center.subtract( bases[0]).addThis(bases[1]),
				center.subtract(bases[0]).subtractThis(bases[1]),
				center.add(bases[0]).subtractThis( bases[1])
		};
	}

	/*
	 * returns the plane through the rectangle
	 */
	public Plane getPlane() {
		return new Plane(center,bases[0].cross(bases[1]).normalizeThis());
	}


	/* ************************************************************************\

	  Copyright 1999 The University of North Carolina at Chapel Hill.
	  All Rights Reserved.

	  Permission to use, copy, modify and distribute this software and its
	  documentation for educational, research and non-profit purposes, without
	  fee, and without a written agreement is hereby granted, provided that the
	  above copyright notice and the following three paragraphs appear in all
	  copies.

	  IN NO EVENT SHALL THE UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL BE
	  LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
	  CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE
	  USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY
	  OF NORTH CAROLINA HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
	  DAMAGES.

	  THE UNIVERSITY OF NORTH CAROLINA SPECIFICALLY DISCLAIM ANY
	  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
	  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE
	  PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
	  NORTH CAROLINA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
	  UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

	  The authors may be contacted via:

	  US Mail:             E. Larsen
	                       Department of Computer Science
	                       Sitterson Hall, CB #3175
	                       University of N. Carolina
	                       Chapel Hill, NC 27599-3175

	  Phone:               (919)962-1749

	  EMail:               geom@cs.unc.edu


	\**************************************************************************/

	private static double clipToRange(double val, double a, double b){
		if (val < a) return a;
		else if (val > b) return b;
		return val;
	}

	/** Finds the parameters t & u corresponding to the two closest points 
	 * on a pair of line segments 
	 *
	 * The first segment is defined as 
	 *
	 * Pa + A*t, 0 <= t <= a, 
	 * 
	 * where "Pa" is one endpoint of the segment, "A" is a unit vector 
	 * pointing to the other endpoint, and t is a scalar that produces
	 * all the points between the two endpoints. Since "A" is a unit
	 * vector, "a" is the segment's length.
	 *
	 * The second segment is 
	 *
	 * Pb + B*u, 0 <= u <= b
	 *
	 * In my application, many of the terms needed by the algorithm
	 * are already computed for other purposes, so I pass these terms to 
	 * the function instead of complete specifications of each segment. 
	 * "T" in the dot products is the vector between Pa and Pb.
	 *
	 * The algorithm is from
	 *
	 * Vladimir J. Lumelsky,
	 * On fast computation of distance between line segments.
	 * In Information Processing Letters, no. 21, pages 55-61, 1985.
	 */
	private static double[] segCoords(double a, double b, double A_dot_B, double A_dot_T, double B_dot_T){  
		double denom = 1 - (A_dot_B)*(A_dot_B);
		double t,u;

		if (denom == 0) t = 0;
		else{
			t = (A_dot_T - B_dot_T*A_dot_B)/denom; 
			t = clipToRange(t,0,a);
		}

		u = t*A_dot_B - B_dot_T;
		if (u < 0) {
			u = 0;
			t = A_dot_T;
			t = clipToRange(t,0,a);
		}else if (u > b){
			u = b;
			t = u*A_dot_B + A_dot_T;
			t = clipToRange(t,0,a);
		}
		return new double[]{t,u};
	}

	/** 
	 * Returns whether the nearest point on rectangle edge 
	 * Pb + B*u, 0 <= u <= b, to the rectangle edge,
	 * Pa + A*t, 0 <= t <= a, is within the half space 
	 * determined by the point Pa and the direction Anorm.
	 *
	 * A,B, and Anorm are unit vectors.
	 * T is the vector between Pa and Pb.
	 */
	private static boolean inVoronoi( double a, double b, double Anorm_dot_B, double Anorm_dot_T, double A_dot_B, double A_dot_T, double B_dot_T){ 
		if( Math.abs(Anorm_dot_B) < 1e-7 ) return false;

		double t, u, v;

		u = -Anorm_dot_T / Anorm_dot_B; 
		u = clipToRange(u,0,b); 

		t = u*A_dot_B + A_dot_T; 
		t = clipToRange(t,0,a); 

		v = t*A_dot_B - B_dot_T; 

		if (Anorm_dot_B > 0) {
			if (v > (u + 1e-7)) return true;
		} else {
			if (v < (u - 1e-7)) return true;
		}
		return false; 
	} 

	private static double[] MTxV(double[][] M1, double[] V1){
		double[] Vr = new double[3];
		Vr[0] = (M1[0][0] * V1[0] +
				M1[1][0] * V1[1] +
				M1[2][0] * V1[2]);
		Vr[1] = (M1[0][1] * V1[0] +
				M1[1][1] * V1[1] +
				M1[2][1] * V1[2]);
		Vr[2] = (M1[0][2] * V1[0] +
				M1[1][2] * V1[1] +
				M1[2][2] * V1[2]);
		return Vr;
	}

	//	private static double[] MTxV(double[][] M1, double[] V1){
	//		double[] Vr = new double[3];
	//		Vr[0] = (M1[0][0] * V1[0] +
	//				 M1[0][1] * V1[1] +
	//				 M1[0][2] * V1[2]);
	//		Vr[1] = (M1[1][0] * V1[0] +
	//				 M1[1][1] * V1[1] +
	//				 M1[1][2] * V1[2]);
	//		Vr[2] = (M1[2][0] * V1[0] +
	//				 M1[2][1] * V1[1] +
	//				 M1[2][2] * V1[2]);
	//		return Vr;
	//	}
	public double distance_Gottschalk(Rectangle b){
		double[][] Rab = new double[3][3];

		// Compute rotation matrix expressing b in a_s coordinate frame
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				Rab[i][j] = normBases[i].dot(b.normBases[j]);	
		//Total: 3*3*3 = 27HOps

		// Compute translation vector t
		Vector tmp = center.subtract(bases[0]).subtractThis(bases[1]).vectorTo(
				b.center.subtract(b.bases[0]).subtractThis(b.bases[1])
		);

		// Bring translation into a_s coordinate frame
		tmp = new Vector(tmp.dot(normBases[0]), tmp.dot(normBases[1]), tmp.dot(normBases[2]));//9HOps
		double[] Tab = new double[]{tmp.x(), tmp.y(), tmp.z()};
		return rectDist(Rab, Tab, extents, b.extents); 
	}

	/** 
	 * Finds the distance between two rectangles A and B.  A is assumed
	 * to have its corner on the origin, one side aligned with
	 * x, the other side aligned with y, and its normal aligned with z.
	 * 
	 * [Rab,Tab] gives the orientation and corner position of rectangle B
	 * 
	 * a[2] are the side lengths of A, b[2] are the side lengths of B
	 */
	private static double rectDist(double[][] Rab, double[] Tab, 
			double[] a, double[] b)	{
		double A0_dot_B0, A0_dot_B1, A1_dot_B0, A1_dot_B1;

		A0_dot_B0 = Rab[0][0];
		A0_dot_B1 = Rab[0][1];
		A1_dot_B0 = Rab[1][0];
		A1_dot_B1 = Rab[1][1];

		double aA0_dot_B0, aA0_dot_B1, aA1_dot_B0, aA1_dot_B1;
		double bA0_dot_B0, bA0_dot_B1, bA1_dot_B0, bA1_dot_B1; 

		aA0_dot_B0 = a[0]*A0_dot_B0;
		aA0_dot_B1 = a[0]*A0_dot_B1;
		aA1_dot_B0 = a[1]*A1_dot_B0;
		aA1_dot_B1 = a[1]*A1_dot_B1;
		bA0_dot_B0 = b[0]*A0_dot_B0;
		bA1_dot_B0 = b[0]*A1_dot_B0;
		bA0_dot_B1 = b[1]*A0_dot_B1;
		bA1_dot_B1 = b[1]*A1_dot_B1;

		double[] Tba = MTxV(Rab,Tab);

		Vector S;
		double t, u;

		// determine if any edge pair contains the closest points

		double ALL_x, ALU_x, AUL_x, AUU_x;
		double BLL_x, BLU_x, BUL_x, BUU_x;
		double LA1_lx, LA1_ux, UA1_lx, UA1_ux, LB1_lx, LB1_ux, UB1_lx, UB1_ux;

		ALL_x = -Tba[0];
		ALU_x = ALL_x + aA1_dot_B0;
		AUL_x = ALL_x + aA0_dot_B0;
		AUU_x = ALU_x + aA0_dot_B0;

		if (ALL_x < ALU_x)
		{ 
			LA1_lx = ALL_x;
			LA1_ux = ALU_x;
			UA1_lx = AUL_x;    
			UA1_ux = AUU_x;
		}
		else
		{ 
			LA1_lx = ALU_x;
			LA1_ux = ALL_x;
			UA1_lx = AUU_x;    
			UA1_ux = AUL_x;
		}

		BLL_x = Tab[0];
		BLU_x = BLL_x + bA0_dot_B1;
		BUL_x = BLL_x + bA0_dot_B0;
		BUU_x = BLU_x + bA0_dot_B0;

		if (BLL_x < BLU_x)
		{ 
			LB1_lx = BLL_x;
			LB1_ux = BLU_x;
			UB1_lx = BUL_x;    
			UB1_ux = BUU_x;
		}
		else
		{ 
			LB1_lx = BLU_x;
			LB1_ux = BLL_x;
			UB1_lx = BUU_x;    
			UB1_ux = BUL_x;
		}

		// UA1, UB1

		if ((UA1_ux > b[0]) && (UB1_ux > a[0]))
		{
			if (((UA1_lx > b[0]) || 
					inVoronoi(b[1],a[1],A1_dot_B0,aA0_dot_B0 - b[0] - Tba[0],
							A1_dot_B1, aA0_dot_B1 - Tba[1], 
							-Tab[1] - bA1_dot_B0))
							&&

							((UB1_lx > a[0]) || 
									inVoronoi(a[1],b[1],A0_dot_B1,Tab[0] + bA0_dot_B0 - a[0],
											A1_dot_B1,Tab[1] + bA1_dot_B0,Tba[1] - aA0_dot_B1)))
			{            
				double[] tu = segCoords(a[1],b[1],A1_dot_B1,Tab[1] + bA1_dot_B0,
						Tba[1] - aA0_dot_B1);
				t = tu[0];
				u = tu[1];

				S = new Vector(
						Tab[0] + Rab[0][0]*b[0] + Rab[0][1]*u - a[0],
						Tab[1] + Rab[1][0]*b[0] + Rab[1][1]*u - t,
						Tab[2] + Rab[2][0]*b[0] + Rab[2][1]*u );
				return S.length();
			}    
		}


		// UA1, LB1

		if ((UA1_lx < 0) && (LB1_ux > a[0]))
		{
			if (((UA1_ux < 0) ||
					inVoronoi(b[1],a[1],-A1_dot_B0,Tba[0] - aA0_dot_B0,
							A1_dot_B1, aA0_dot_B1 - Tba[1], -Tab[1]))
							&&

							((LB1_lx > a[0]) ||
									inVoronoi(a[1],b[1],A0_dot_B1,Tab[0] - a[0],
											A1_dot_B1,Tab[1],Tba[1] - aA0_dot_B1)))
			{
				double[] tu = segCoords(a[1],b[1],A1_dot_B1,Tab[1],Tba[1] - aA0_dot_B1);
				t = tu[0];
				u = tu[1];

				S = new Vector(
						Tab[0] + Rab[0][1]*u - a[0],
						Tab[1] + Rab[1][1]*u - t,
						Tab[2] + Rab[2][1]*u );
				return S.length();
			}
		}

		// LA1, UB1

		if ((LA1_ux > b[0]) && (UB1_lx < 0))
		{
			if (((LA1_lx > b[0]) || 
					inVoronoi(b[1],a[1],A1_dot_B0,-Tba[0] - b[0],
							A1_dot_B1,-Tba[1], -Tab[1] - bA1_dot_B0))
							&&

							((UB1_ux < 0) || 
									inVoronoi(a[1],b[1],-A0_dot_B1, -Tab[0] - bA0_dot_B0,
											A1_dot_B1, Tab[1] + bA1_dot_B0,Tba[1])))
			{

				double[] tu = segCoords(a[1],b[1],A1_dot_B1,Tab[1] + bA1_dot_B0,Tba[1]);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][0]*b[0] + Rab[0][1]*u,
						Tab[1] + Rab[1][0]*b[0] + Rab[1][1]*u - t,
						Tab[2] + Rab[2][0]*b[0] + Rab[2][1]*u );
				return S.length();
			}
		}

		// LA1, LB1

		if ((LA1_lx < 0) && (LB1_lx < 0))
		{   
			if (((LA1_ux < 0) || 
					inVoronoi(b[1],a[1],-A1_dot_B0,Tba[0],A1_dot_B1,
							-Tba[1],-Tab[1]))
							&&

							((LB1_ux < 0) || 
									inVoronoi(a[1],b[1],-A0_dot_B1,-Tab[0],A1_dot_B1,
											Tab[1], Tba[1])))
			{
				double[] tu = segCoords(a[1],b[1],A1_dot_B1,Tab[1],Tba[1]);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][0]*b[0] + Rab[0][1]*u,
						Tab[1] + Rab[1][0]*b[0] + Rab[1][1]*u - t,
						Tab[2] + Rab[2][0]*b[0] + Rab[2][1]*u );
				return S.length();
			}
		}

		double ALL_y, ALU_y, AUL_y, AUU_y;

		ALL_y = -Tba[1];
		ALU_y = ALL_y + aA1_dot_B1;
		AUL_y = ALL_y + aA0_dot_B1;
		AUU_y = ALU_y + aA0_dot_B1;

		double LA1_ly, LA1_uy, UA1_ly, UA1_uy, LB0_lx, LB0_ux, UB0_lx, UB0_ux;

		if (ALL_y < ALU_y)
		{ 
			LA1_ly = ALL_y;
			LA1_uy = ALU_y;
			UA1_ly = AUL_y;    
			UA1_uy = AUU_y;
		}
		else
		{ 
			LA1_ly = ALU_y;
			LA1_uy = ALL_y;
			UA1_ly = AUU_y;    
			UA1_uy = AUL_y;
		}

		if (BLL_x < BUL_x)
		{
			LB0_lx = BLL_x;
			LB0_ux = BUL_x;
			UB0_lx = BLU_x;
			UB0_ux = BUU_x;
		}
		else
		{
			LB0_lx = BUL_x;
			LB0_ux = BLL_x;
			UB0_lx = BUU_x;
			UB0_ux = BLU_x;
		}

		// UA1, UB0

		if ((UA1_uy > b[1]) && (UB0_ux > a[0]))
		{   
			if (((UA1_ly > b[1]) || 
					inVoronoi(b[0],a[1],A1_dot_B1, aA0_dot_B1 - Tba[1] - b[1],
							A1_dot_B0, aA0_dot_B0 - Tba[0], -Tab[1] - bA1_dot_B1))
							&&

							((UB0_lx > a[0]) || 
									inVoronoi(a[1],b[0],A0_dot_B0, Tab[0] - a[0] + bA0_dot_B1,
											A1_dot_B0, Tab[1] + bA1_dot_B1, Tba[0] - aA0_dot_B0)))
			{
				double[] tu = segCoords(a[1],b[0],A1_dot_B0,Tab[1] + bA1_dot_B1,
						Tba[0] - aA0_dot_B0);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][1]*b[1] + Rab[0][0]*u - a[0] ,
						Tab[1] + Rab[1][1]*b[1] + Rab[1][0]*u - t,
						Tab[2] + Rab[2][1]*b[1] + Rab[2][0]*u );
				return S.length();
			}
		}

		// UA1, LB0

		if ((UA1_ly < 0) && (LB0_ux > a[0]))
		{
			if (((UA1_uy < 0) || 
					inVoronoi(b[0],a[1],-A1_dot_B1, Tba[1] - aA0_dot_B1,A1_dot_B0,
							aA0_dot_B0 - Tba[0], -Tab[1]))
							&&

							((LB0_lx > a[0]) || 
									inVoronoi(a[1],b[0],A0_dot_B0,Tab[0] - a[0],
											A1_dot_B0,Tab[1],Tba[0] - aA0_dot_B0)))
			{
				double[] tu = segCoords(a[1],b[0],A1_dot_B0,Tab[1],Tba[0] - aA0_dot_B0);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][0]*u - a[0],
						Tab[1] + Rab[1][0]*u - t,
						Tab[2] + Rab[2][0]*u );
				return S.length();
			}
		}

		// LA1, UB0

		if ((LA1_uy > b[1]) && (UB0_lx < 0))
		{
			if (((LA1_ly > b[1]) || 
					inVoronoi(b[0],a[1],A1_dot_B1,-Tba[1] - b[1],
							A1_dot_B0, -Tba[0], -Tab[1] - bA1_dot_B1))     
							&&

							((UB0_ux < 0) ||             
									inVoronoi(a[1],b[0],-A0_dot_B0, -Tab[0] - bA0_dot_B1,A1_dot_B0,
											Tab[1] + bA1_dot_B1,Tba[0])))
			{
				double[] tu = segCoords(a[1],b[0],A1_dot_B0,Tab[1] + bA1_dot_B1,Tba[0]);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][1]*b[1] + Rab[0][0]*u,
						Tab[1] + Rab[1][1]*b[1] + Rab[1][0]*u - t,
						Tab[2] + Rab[2][1]*b[1] + Rab[2][0]*u );
				return S.length();
			}
		}

		// LA1, LB0

		if ((LA1_ly < 0) && (LB0_lx < 0))
		{
			if (((LA1_uy < 0) || 
					inVoronoi(b[0],a[1],-A1_dot_B1,Tba[1],A1_dot_B0,
							-Tba[0],-Tab[1]))
							&& 

							((LB0_ux < 0) || 
									inVoronoi(a[1],b[0],-A0_dot_B0,-Tab[0],A1_dot_B0,
											Tab[1],Tba[0])))
			{
				double[] tu = segCoords(a[1],b[0],A1_dot_B0,Tab[1],Tba[0]);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][0]*u,
						Tab[1] + Rab[1][0]*u - t,
						Tab[2] + Rab[2][0]*u );
				return S.length();
			}
		}

		double BLL_y, BLU_y, BUL_y, BUU_y;

		BLL_y = Tab[1];
		BLU_y = BLL_y + bA1_dot_B1;
		BUL_y = BLL_y + bA1_dot_B0;
		BUU_y = BLU_y + bA1_dot_B0;

		double LA0_lx, LA0_ux, UA0_lx, UA0_ux, LB1_ly, LB1_uy, UB1_ly, UB1_uy;

		if (ALL_x < AUL_x)
		{
			LA0_lx = ALL_x;
			LA0_ux = AUL_x;
			UA0_lx = ALU_x;
			UA0_ux = AUU_x;
		}
		else
		{
			LA0_lx = AUL_x;
			LA0_ux = ALL_x;
			UA0_lx = AUU_x;
			UA0_ux = ALU_x;
		}

		if (BLL_y < BLU_y)
		{
			LB1_ly = BLL_y;
			LB1_uy = BLU_y;
			UB1_ly = BUL_y;
			UB1_uy = BUU_y;
		}
		else
		{
			LB1_ly = BLU_y;
			LB1_uy = BLL_y;
			UB1_ly = BUU_y;
			UB1_uy = BUL_y;
		}

		// UA0, UB1

		if ((UA0_ux > b[0]) && (UB1_uy > a[1]))
		{
			if (((UA0_lx > b[0]) || 
					inVoronoi(b[1],a[0],A0_dot_B0, aA1_dot_B0 - Tba[0] - b[0],
							A0_dot_B1,aA1_dot_B1 - Tba[1], -Tab[0] - bA0_dot_B0))
							&&

							((UB1_ly > a[1]) || 
									inVoronoi(a[0],b[1],A1_dot_B1, Tab[1] - a[1] + bA1_dot_B0,
											A0_dot_B1,Tab[0] + bA0_dot_B0, Tba[1] - aA1_dot_B1)))
			{
				double[] tu = segCoords(a[0],b[1],A0_dot_B1,Tab[0] + bA0_dot_B0,
						Tba[1] - aA1_dot_B1);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][0]*b[0] + Rab[0][1]*u - t,
						Tab[1] + Rab[1][0]*b[0] + Rab[1][1]*u - a[1],
						Tab[2] + Rab[2][0]*b[0] + Rab[2][1]*u );
				return S.length();
			}
		}

		// UA0, LB1

		if ((UA0_lx < 0) && (LB1_uy > a[1]))
		{
			if (((UA0_ux < 0) || 
					inVoronoi(b[1],a[0],-A0_dot_B0, Tba[0] - aA1_dot_B0,A0_dot_B1,
							aA1_dot_B1 - Tba[1],-Tab[0]))
							&&

							((LB1_ly > a[1]) || 
									inVoronoi(a[0],b[1],A1_dot_B1,Tab[1] - a[1],A0_dot_B1,Tab[0],
											Tba[1] - aA1_dot_B1)))
			{
				double[] tu = segCoords(a[0],b[1],A0_dot_B1,Tab[0],Tba[1] - aA1_dot_B1);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][1]*u - t,
						Tab[1] + Rab[1][1]*u - a[1],
						Tab[2] + Rab[2][1]*u );
				return S.length();
			}
		}

		// LA0, UB1

		if ((LA0_ux > b[0]) && (UB1_ly < 0))
		{
			if (((LA0_lx > b[0]) || 
					inVoronoi(b[1],a[0],A0_dot_B0,-b[0] - Tba[0],A0_dot_B1,-Tba[1],
							-bA0_dot_B0 - Tab[0]))
							&&

							((UB1_uy < 0) || 
									inVoronoi(a[0],b[1],-A1_dot_B1, -Tab[1] - bA1_dot_B0,A0_dot_B1,
											Tab[0] + bA0_dot_B0,Tba[1])))
			{
				double[] tu = segCoords(a[0],b[1],A0_dot_B1,Tab[0] + bA0_dot_B0,Tba[1]);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][0]*b[0] + Rab[0][1]*u - t,
						Tab[1] + Rab[1][0]*b[0] + Rab[1][1]*u,
						Tab[2] + Rab[2][0]*b[0] + Rab[2][1]*u );
				return S.length();
			}
		}

		// LA0, LB1

		if ((LA0_lx < 0) && (LB1_ly < 0))
		{
			if (((LA0_ux < 0) || 
					inVoronoi(b[1],a[0],-A0_dot_B0,Tba[0],A0_dot_B1,-Tba[1],
							-Tab[0]))
							&&

							((LB1_uy < 0) || 
									inVoronoi(a[0],b[1],-A1_dot_B1,-Tab[1],A0_dot_B1,
											Tab[0],Tba[1])))
			{
				double[] tu = segCoords(a[0],b[1],A0_dot_B1,Tab[0],Tba[1]);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][1]*u - t,
						Tab[1] + Rab[1][1]*u,
						Tab[2] + Rab[2][1]*u );
				return S.length();
			}
		}

		double LA0_ly, LA0_uy, UA0_ly, UA0_uy, LB0_ly, LB0_uy, UB0_ly, UB0_uy;

		if (ALL_y < AUL_y)
		{
			LA0_ly = ALL_y;
			LA0_uy = AUL_y;
			UA0_ly = ALU_y;
			UA0_uy = AUU_y;
		}
		else
		{
			LA0_ly = AUL_y;
			LA0_uy = ALL_y;
			UA0_ly = AUU_y;
			UA0_uy = ALU_y;
		}

		if (BLL_y < BUL_y)
		{
			LB0_ly = BLL_y;
			LB0_uy = BUL_y;
			UB0_ly = BLU_y;
			UB0_uy = BUU_y;
		}
		else
		{
			LB0_ly = BUL_y;
			LB0_uy = BLL_y;
			UB0_ly = BUU_y;
			UB0_uy = BLU_y;
		}

		// UA0, UB0

		if ((UA0_uy > b[1]) && (UB0_uy > a[1]))
		{
			if (((UA0_ly > b[1]) || 
					inVoronoi(b[0],a[0],A0_dot_B1, aA1_dot_B1 - Tba[1] - b[1],
							A0_dot_B0, aA1_dot_B0 - Tba[0], -Tab[0] - bA0_dot_B1))
							&&

							((UB0_ly > a[1]) || 
									inVoronoi(a[0],b[0],A1_dot_B0,Tab[1] - a[1] + bA1_dot_B1,A0_dot_B0,
											Tab[0] + bA0_dot_B1, Tba[0] - aA1_dot_B0)))
			{
				double[] tu = segCoords(a[0],b[0],A0_dot_B0,Tab[0] + bA0_dot_B1,
						Tba[0] - aA1_dot_B0);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][1]*b[1] + Rab[0][0]*u - t,
						Tab[1] + Rab[1][1]*b[1] + Rab[1][0]*u - a[1],
						Tab[2] + Rab[2][1]*b[1] + Rab[2][0]*u );
				return S.length();
			}
		}

		// UA0, LB0

		if ((UA0_ly < 0) && (LB0_uy > a[1]))
		{
			if (((UA0_uy < 0) || 
					inVoronoi(b[0],a[0],-A0_dot_B1,Tba[1] - aA1_dot_B1,A0_dot_B0,
							aA1_dot_B0 - Tba[0],-Tab[0]))
							&&      

							((LB0_ly > a[1]) || 
									inVoronoi(a[0],b[0],A1_dot_B0,Tab[1] - a[1],
											A0_dot_B0,Tab[0],Tba[0] - aA1_dot_B0)))
			{
				double[] tu = segCoords(a[0],b[0],A0_dot_B0,Tab[0],Tba[0] - aA1_dot_B0);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][0]*u - t,
						Tab[1] + Rab[1][0]*u - a[1],
						Tab[2] + Rab[2][0]*u );
				return S.length();
			}
		}

		// LA0, UB0

		if ((LA0_uy > b[1]) && (UB0_ly < 0))
		{  
			if (((LA0_ly > b[1]) ||
					inVoronoi(b[0],a[0],A0_dot_B1,-Tba[1] - b[1], A0_dot_B0,-Tba[0],
							-Tab[0] - bA0_dot_B1))
							&&

							((UB0_uy < 0) ||
									inVoronoi(a[0],b[0],-A1_dot_B0, -Tab[1] - bA1_dot_B1, A0_dot_B0,
											Tab[0] + bA0_dot_B1,Tba[0])))
			{
				double[] tu = segCoords(a[0],b[0],A0_dot_B0,Tab[0] + bA0_dot_B1,Tba[0]);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][1]*b[1] + Rab[0][0]*u - t,
						Tab[1] + Rab[1][1]*b[1] + Rab[1][0]*u,
						Tab[2] + Rab[2][1]*b[1] + Rab[2][0]*u );
				return S.length();
			}
		}

		// LA0, LB0

		if ((LA0_ly < 0) && (LB0_ly < 0))
		{   
			if (((LA0_uy < 0) || 
					inVoronoi(b[0],a[0],-A0_dot_B1,Tba[1],A0_dot_B0,
							-Tba[0],-Tab[0]))
							&&

							((LB0_uy < 0) || 
									inVoronoi(a[0],b[0],-A1_dot_B0,-Tab[1],A0_dot_B0,
											Tab[0],Tba[0])))
			{
				double[] tu = segCoords(a[0],b[0],A0_dot_B0,Tab[0],Tba[0]);
				t = tu[0];
				u = tu[1];
				S = new Vector(
						Tab[0] + Rab[0][0]*u - t,
						Tab[1] + Rab[1][0]*u,
						Tab[2] + Rab[2][0]*u );
				return S.length();
			}
		}

		// no edges passed, take max separation along face normals

		double sep1, sep2;

		if (Tab[2] > 0.0)
		{
			sep1 = Tab[2];
			if (Rab[2][0] < 0.0) sep1 += b[0]*Rab[2][0];
			if (Rab[2][1] < 0.0) sep1 += b[1]*Rab[2][1];
		}
		else
		{
			sep1 = -Tab[2];
			if (Rab[2][0] > 0.0) sep1 -= b[0]*Rab[2][0];
			if (Rab[2][1] > 0.0) sep1 -= b[1]*Rab[2][1];
		}

		if (Tba[2] < 0)
		{
			sep2 = -Tba[2];
			if (Rab[0][2] < 0.0) sep2 += a[0]*Rab[0][2];
			if (Rab[1][2] < 0.0) sep2 += a[1]*Rab[1][2];
		}
		else
		{
			sep2 = Tba[2];
			if (Rab[0][2] > 0.0) sep2 -= a[0]*Rab[0][2];
			if (Rab[1][2] > 0.0) sep2 -= a[1]*Rab[1][2];
		}

		double sep = (sep1 > sep2? sep1 : sep2);
		return (sep > 0? sep : 0);
	}
}
