/*
 * JBox2D - A Java Port of Erin Catto's Box2D
 * 
 * JBox2D homepage: http://jbox2d.sourceforge.net/ 
 * Box2D homepage: http://www.box2d.org
 * 
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

package org.jbox2d.collision;

/*
* Copyright (c) 2007 Erin Catto http://www.gphysics.com
*
* This software is provided 'as-is', without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/

import org.jbox2d.common.*;

//updated to rev 108->139 of b2Distance.cpp

/** Implements the GJK algorithm for computing distance between shapes. */
public class Distance{
	public static int g_GJK_Iterations = 0;

	// GJK using Voronoi regions (Christer Ericson) and region selection
	// optimizations (Casey Muratori).

	// The origin is either in the region of points[1] or in the edge region. The origin is
	// not in region of points[0] because that is the old point.
	protected static int ProcessTwo(Vec2 x1, Vec2 x2, Vec2[] p1s, Vec2[] p2s, Vec2[] points) {
		// If in point[1] region
		Vec2 r = new Vec2(-points[1].x,-points[1].y);
		Vec2 d = new Vec2(points[0].x - points[1].x,points[0].y - points[1].y);
		float length = d.normalize();
		float lambda = Vec2.dot(r, d);
		if (lambda <= 0.0f || length < Settings.EPSILON) {
			// The simplex is reduced to a point.
			x1.set(p1s[1]);
			x2.set(p2s[1]);
			p1s[0].set(p1s[1]);
			p2s[0].set(p2s[1]);
			points[0].set(points[1]);
			return 1;
		}

		// Else in edge region
		lambda /= length;
		x1.set( p1s[1].x + lambda * (p1s[0].x - p1s[1].x),
				p1s[1].y + lambda * (p1s[0].y - p1s[1].y));
		x2.set( p2s[1].x + lambda * (p2s[0].x - p2s[1].x),
				p2s[1].y + lambda * (p2s[0].y - p2s[1].y));
		return 2;
	}

	// Possible regions:
	// - points[2]
	// - edge points[0]-points[2]
	// - edge points[1]-points[2]
	// - inside the triangle
	protected static int ProcessThree(Vec2 x1, Vec2 x2, Vec2[] p1s, Vec2[] p2s, Vec2[] points) {
		Vec2 a = points[0];
		Vec2 b = points[1];
		Vec2 c = points[2];

		Vec2 ab = b.sub(a);
		Vec2 ac = c.sub(a);
		Vec2 bc = c.sub(b);

		float sn = -Vec2.dot(a, ab), sd = Vec2.dot(b, ab);
		float tn = -Vec2.dot(a, ac), td = Vec2.dot(c, ac);
		float un = -Vec2.dot(b, bc), ud = Vec2.dot(c, bc);

		// In vertex c region?
		if (td <= 0.0f && ud <= 0.0f) {
			// Single point
			x1.set(p1s[2]);
			x2.set(p2s[2]);
			p1s[0].set(p1s[2]);
			p2s[0].set(p2s[2]);
			points[0].set(points[2]);
			return 1;
		}

		// Should not be in vertex a or b region.
		//B2_NOT_USED(sd);
		//B2_NOT_USED(sn);
		//assert(sn > 0.0f || tn > 0.0f);
		//assert(sd > 0.0f || un > 0.0f);

		float n = Vec2.cross(ab, ac);

		// Should not be in edge ab region.
		float vc = n * Vec2.cross(a, b);
		//assert(vc > 0.0f || sn > 0.0f || sd > 0.0f);

		// In edge bc region?
		float va = n * Vec2.cross(b, c);
		if (va <= 0.0f && un >= 0.0f && ud >= 0.0f && (un+ud) > 0.0f){
			//assert(un + ud > 0.0f);
			float lambda = un / (un + ud);
			x1.set( p1s[1].x + lambda * (p1s[2].x - p1s[1].x),
					p1s[1].y + lambda * (p1s[2].y - p1s[1].y));
			x2.set( p2s[1].x + lambda * (p2s[2].x - p2s[1].x),
					p2s[1].y + lambda * (p2s[2].y - p2s[1].y));
			p1s[0].set(p1s[2]);
			p2s[0].set(p2s[2]);
			points[0].set(points[2]);
			return 2;
		}

		// In edge ac region?
		float vb = n * Vec2.cross(c, a);
		if (vb <= 0.0f && tn >= 0.0f && td >= 0.0f && (tn+td) > 0.0f) {
			//assert(tn + td > 0.0f);
			float lambda = tn / (tn + td);
			x1.set( p1s[0].x + lambda * (p1s[2].x - p1s[0].x),
					p1s[0].y + lambda * (p1s[2].y - p1s[0].y));
			x2.set( p2s[0].x + lambda * (p2s[2].x - p2s[0].x),
					p2s[0].y + lambda * (p2s[2].y - p2s[0].y));
			p1s[1].set(p1s[2]);
			p2s[1].set(p2s[2]);
			points[1].set(points[2]);
			return 2;
		}

		// Inside the triangle, compute barycentric coordinates
		float denom = va + vb + vc;
		//assert(denom > 0.0f);
		denom = 1.0f / denom;
		float u = va * denom;
		float v = vb * denom;
		float w = 1.0f - u - v;
		x1.set( u * p1s[0].x + v * p1s[1].x + w * p1s[2].x,
				u * p1s[0].y + v * p1s[1].y + w * p1s[2].y);
		x2.set( u * p2s[0].x + v * p2s[1].x + w * p2s[2].x,
				u * p2s[0].y + v * p2s[1].y + w * p2s[2].y);
		return 3;
	}

	protected static boolean InPoints(Vec2 w, Vec2[] points, int pointCount) {
		float k_tolerance = 100.0f * Settings.EPSILON;
		Vec2 d=new Vec2(0,0);
		Vec2 m=new Vec2(0,0);
		Vec2 abs_pi = new Vec2(0,0);
		Vec2 wsubp = new Vec2(0,0);
		for (int i = 0; i < pointCount; ++i) {
			// Vec2.abs, Vec2.max, Vec2.sub inlined (midp)
    		//implementation Vec2.abs:
			//    return new Vec2(Math.abs(a.x), Math.abs(a.y));
			Vec2 pi = points[i];
			wsubp.x = w.x-pi.x;
			wsubp.y = w.y-pi.y;
			d.x = wsubp.x > 0f ? d.x : -d.x;
			d.y = wsubp.y > 0f ? d.y : -d.y;
			m.x = w.x > 0 ? w.x : -w.x;
			m.y = w.y > 0 ? w.y : -w.y;
			abs_pi.x = pi.x > 0 ? pi.x : -pi.x;
			abs_pi.y = pi.y > 0 ? pi.y : -pi.y;
			if (abs_pi.x > m.x) m.x = abs_pi.x;
			if (abs_pi.y > m.y) m.y = abs_pi.y;
			// the original code:
			//Vec2 d =Vec2.abs(w.sub(points[i])); 
			//Vec2 m = Vec2.max(Vec2.abs(w), Vec2.abs(points[i]));

//				new Vec2( Math.abs(w.x-points[i].x), Math.abs(w.y-points[i].y));//Vec2.abs(w - points[i]);
			
			if (d.x < k_tolerance * (m.x + 1.0f) &&
				d.y < k_tolerance * (m.y + 1.0f)) {
				return true;
			}
		}

		return false;
	}

	
	/**
	 * Distance between any two objects that implement SupportsGenericDistance.
	 * Note that x1 and x2 are passed so that they may store results - they must
	 * be instantiated before being passed, and the contents will be lost.
	 * 
	 * @param x1 Set to closest point on shape1 (result parameter)
	 * @param x2 Set to closest point on shape2 (result parameter)
	 * @param shape1 Shape to test
	 * @param xf1 Transform of shape1
	 * @param shape2 Shape to test
	 * @param xf2 Transform of shape2
	 * @return
	 */
	public static float DistanceGeneric(Vec2 x1, Vec2 x2,
						  SupportsGenericDistance shape1, XForm xf1,
						  SupportsGenericDistance shape2, XForm xf2) {
		Vec2 p1s[] = new Vec2[3];
		Vec2 p2s[] = new Vec2[3];
		Vec2 points[] = new Vec2[3];
		
		for (int i=0; i<3; ++i) {
			p1s[i] = new Vec2();
			p2s[i] = new Vec2();
			points[i] = new Vec2();
		}
		
		int pointCount = 0;

		x1.set(shape1.getFirstVertex(xf1));
		x2.set(shape2.getFirstVertex(xf2));

		float vSqr = 0.0f;
		int maxIterations = 20;
		for (int iter = 0; iter < maxIterations; ++iter) {
			Vec2 v = x2.sub(x1);
			Vec2 w1 = shape1.support(xf1, v);
			Vec2 w2 = shape2.support(xf2, v.negate());

			vSqr = Vec2.dot(v, v);
			Vec2 w = w2.sub(w1);
			float vw = Vec2.dot(v, w);
			if (vSqr - vw <= 0.01f * vSqr || InPoints(w, points, pointCount)) // or w in points
			{
				if (pointCount == 0) {
					x1.set(w1);
					x2.set(w2);
				}
				g_GJK_Iterations = iter;
				return (float)Math.sqrt(vSqr);
			}

			switch (pointCount) {
			case 0:
				p1s[0].set(w1);
				p2s[0].set(w2);
				points[0].set(w);
				x1.set(p1s[0]);
				x2.set(p2s[0]);
				++pointCount;
				break;

			case 1:
				p1s[1].set(w1);
				p2s[1].set(w2);
				points[1].set(w);
				pointCount = ProcessTwo(x1, x2, p1s, p2s, points);
				break;

			case 2:
				p1s[2].set(w1);
				p2s[2].set(w2);
				points[2].set(w);
				pointCount = ProcessThree(x1, x2, p1s, p2s, points);
				break;
			}

			// If we have three points, then the origin is in the corresponding triangle.
			if (pointCount == 3) {
				g_GJK_Iterations = iter;
				return 0.0f;
				//
			}

			float maxSqr = -Float.MAX_VALUE;// -FLT_MAX;
			for (int i = 0; i < pointCount; ++i) {
				maxSqr = Math.max(maxSqr, Vec2.dot(points[i], points[i]));
			}

			if (pointCount == 3 || vSqr <= 100.0f * Settings.EPSILON * maxSqr) {
				g_GJK_Iterations = iter;
				v.set(x2.x - x1.x, x2.y - x1.y);// x2 - x1
				vSqr = Vec2.dot(v, v);

				return (float)Math.sqrt(vSqr);
				//
			}
		}

		g_GJK_Iterations = maxIterations;
		return (float)Math.sqrt(vSqr);
		//
	}

	protected static float DistanceCC(
		Vec2 x1, Vec2 x2,
		CircleShape circle1, XForm xf1,
		CircleShape circle2, XForm xf2) {
		
		Vec2 p1 = XForm.mul(xf1, circle1.getLocalPosition());
		Vec2 p2 = XForm.mul(xf2, circle2.getLocalPosition());

		Vec2 d = new Vec2(p2.x - p1.x, p2.y - p1.y);
		float dSqr = Vec2.dot(d, d);
		float r1 = circle1.getRadius() - Settings.toiSlop;
		float r2 = circle2.getRadius() - Settings.toiSlop;
		float r = r1 + r2;
		if (dSqr > r * r){
			float dLen = d.normalize();
			float distance = dLen - r;
			x1.set( p1.x + r1 * d.x,
					p1.y + r1 * d.y);
			x2.set( p2.x - r2 * d.x,
					p2.y - r2 * d.y);
			return distance;
		} else if (dSqr > Settings.EPSILON * Settings.EPSILON) {
			d.normalize();
			x1.set( p1.x + r1 * d.x,
					p1.y + r1 * d.y);
			x2.set(x1);
			return 0.0f;
		}

		x1.set(p1);
		x2.set(x1);
		return 0.0f;
	}


	// GJK is more robust with polygon-vs-point than polygon-vs-circle.
	// So we convert polygon-vs-circle to polygon-vs-point.
	protected static float DistancePC(
		Vec2 x1, Vec2 x2,
		PolygonShape polygon, XForm xf1,
		CircleShape circle,   XForm xf2) {
		Point point = new Point(new Vec2(0.0f, 0.0f));
		point.p = XForm.mul(xf2, circle.getLocalPosition());

		float distance = DistanceGeneric(x1, x2, polygon, xf1, point, XForm.identity);

		float r = circle.getRadius() - Settings.toiSlop;

		if (distance > r) {
			distance -= r;
			Vec2 d = x2.sub(x1);
			d.normalize();
			x2.x -= r * d.x;
			x2.y -= r * d.y;
		} else {
			distance = 0.0f;
			x2.set(x1);
		}

		return distance;
	}

	/** 
	 * Find the closest distance between shapes shape1 and shape2, 
	 * and load the closest points into x1 and x2.
	 * Note that x1 and x2 are passed so that they may store results - they must
	 * be instantiated before being passed, and the contents will be lost.
	 * 
	 * @param x1 Closest point on shape1 is put here (result parameter)
	 * @param x2 Closest point on shape2 is put here (result parameter)
	 * @param shape1 First shape to test
	 * @param xf1 Transform of first shape
	 * @param shape2 Second shape to test
	 * @param xf2 Transform of second shape
	 */
	public static float distance(Vec2 x1, Vec2 x2,
					 Shape shape1, XForm xf1,
					 Shape shape2, XForm xf2) {
		
		int type1 = shape1.getType();
		int type2 = shape2.getType();

		if (type1 == ShapeType.CIRCLE_SHAPE && type2 == ShapeType.CIRCLE_SHAPE) {
			return DistanceCC(x1, x2, (CircleShape)shape1, xf1, (CircleShape)shape2, xf2);
		}
		
		if (type1 == ShapeType.POLYGON_SHAPE && type2 == ShapeType.CIRCLE_SHAPE) {
			return DistancePC(x1, x2, (PolygonShape)shape1, xf1, (CircleShape)shape2, xf2);
		}

		if (type1 == ShapeType.CIRCLE_SHAPE && type2 == ShapeType.POLYGON_SHAPE) {
			return DistancePC(x2, x1, (PolygonShape)shape2, xf2, (CircleShape)shape1, xf1);
		}

		if (type1 == ShapeType.POLYGON_SHAPE && type2 == ShapeType.POLYGON_SHAPE) {
			return DistanceGeneric(x1, x2, (PolygonShape)shape1, xf1, (PolygonShape)shape2, xf2);
		}

		return 0.0f;
	}

}

// This is used for polygon-vs-circle distance.
class Point implements SupportsGenericDistance{
	public Vec2 p;
	
	public Point(Vec2 _p) {
		p = _p.clone();
	}
	
	public Vec2 support(XForm xf, Vec2 v) {
		return p;
	}

	public Vec2 getFirstVertex(XForm xf) {
		return p;
	}
	
}
