package org.ntlab.radishforandroidstudio.framework.model3D;

import java.util.ArrayList;

import org.ntlab.radishforandroidstudio.java3d.BoundingPolytope;
import org.ntlab.radishforandroidstudio.java3d.BoundingSphere;
import org.ntlab.radishforandroidstudio.java3d.Matrix4d;
import org.ntlab.radishforandroidstudio.java3d.Point3d;
import org.ntlab.radishforandroidstudio.java3d.Transform3D;
import org.ntlab.radishforandroidstudio.java3d.Vector3d;
import org.ntlab.radishforandroidstudio.java3d.Vector4d;


public class OBB implements Cloneable {
	public ArrayList<Vector3d> vertexList = new ArrayList<Vector3d>();
	public Vector4d[] plane = new Vector4d[6];
	public BoundingPolytope bp = null;
	public static int edges[][] = { { 0, 1 }, { 0, 2 }, { 1, 3 }, { 2, 3 },
			{ 0, 4 }, { 1, 5 }, { 3, 7 }, { 2, 6 }, { 4, 5 }, { 4, 6 },
			{ 5, 7 }, { 6, 7 } };
	static boolean[][] inside = new boolean[8][6];
	static boolean[] inside2 = new boolean[8];

	public OBB() {
	}
	
	public OBB(Vector3d v1, Vector3d v2, Vector3d v3, Vector3d v4, 
			Vector3d v5, Vector3d v6, Vector3d v7, Vector3d v8) {
		vertexList.add(v1);
		vertexList.add(v2);
		vertexList.add(v3);
		vertexList.add(v4);
		vertexList.add(v5);
		vertexList.add(v6);
		vertexList.add(v7);
		vertexList.add(v8);
		createPlanes();
	}

	public void addVertex(Vector3d v) {
		vertexList.add(v);
	}
	
	public Vector3d getVertex(int i) {
		return vertexList.get(i);
	}

	public BoundingSphere getBoundingSphere() {
		double radius = 0.0;
		Point3d p = new Point3d();
		Vector3d cv = new Vector3d();
		for (int i = 0; i < vertexList.size() - 1; i++) {
			for (int j = i + 1; j < vertexList.size(); j++) {
				Vector3d v = new Vector3d();
				v.sub(vertexList.get(i), vertexList.get(j));
				if (radius < v.length()) {
					radius = v.length();
					cv.add(vertexList.get(i), vertexList.get(j));
					cv.scale(0.5);
					p.x = cv.x;
					p.y = cv.y;
					p.z = cv.z;
				}
			}
		}
		BoundingSphere s = new BoundingSphere(p, radius / 2);
		return s;
	}
	
	/**
	 * 頂点情報を元に面および境界多面体を生成する
	 */
	public void createPlanes() {
		// 面の作成
		Vector3d v1 = new Vector3d();
		Vector3d v2 = new Vector3d();
		Vector3d v3 = new Vector3d();
		Vector4d[] plane = new Vector4d[6];
	
		// 0231
		v1 = vertexList.get(0);
		v2.sub(vertexList.get(2), vertexList.get(0));
		v3.sub(vertexList.get(1), vertexList.get(0));
		Vector3d n = new Vector3d();
		n.cross(v2, v3);
		n.normalize();
		plane[0] = new Vector4d();
		plane[0].set(n.x, n.y, n.z, -n.dot(v1));
		
	
		// 0154
		v1 = vertexList.get(0);
		v2.sub(vertexList.get(1), vertexList.get(0));
		v3.sub(vertexList.get(4), vertexList.get(0));
		n = new Vector3d();
		n.cross(v2, v3);
		n.normalize();
		plane[1] = new Vector4d();
		plane[1].set(n.x, n.y, n.z, -n.dot(v1));
	
		// 1375
		v1 = vertexList.get(1);
		v2.sub(vertexList.get(3), vertexList.get(1));
		v3.sub(vertexList.get(5), vertexList.get(1));
		n = new Vector3d();
		n.cross(v2, v3);
		n.normalize();
		plane[2] = new Vector4d();
		plane[2].set(n.x, n.y, n.z, -n.dot(v1));
	
		// 4576
		v1 = vertexList.get(6);
		v2.sub(vertexList.get(4), vertexList.get(6));
		v3.sub(vertexList.get(7), vertexList.get(6));
		n = new Vector3d();
		n.cross(v2, v3);
		n.normalize();
		plane[3] = new Vector4d();
		plane[3].set(n.x, n.y, n.z, -n.dot(v1));
	
		// 0462
		v1 = vertexList.get(6);
		v2.sub(vertexList.get(2), vertexList.get(6));
		v3.sub(vertexList.get(4), vertexList.get(6));
		n = new Vector3d();
		n.cross(v2, v3);
		n.normalize();
		plane[4] = new Vector4d();
		plane[4].set(n.x, n.y, n.z, -n.dot(v1));
	
		// 2673
		v1 = vertexList.get(6);
		v2.sub(vertexList.get(7), vertexList.get(6));
		v3.sub(vertexList.get(2), vertexList.get(6));
		n = new Vector3d();
		n.cross(v2, v3);
		n.normalize();
		plane[5] = new Vector4d();
		plane[5].set(n.x, n.y, n.z, -n.dot(v1));
		
		this.plane = plane;
		
		bp = new BoundingPolytope();
		bp.setPlanes(plane);
	}

	/**
	 * 平面との衝突判定
	 * @param plane 衝突判定の対象となる平面
	 * @return 衝突判定の結果（衝突していない場合はnull）
	 */
	public CollisionResult intersect(Vector4d plane) {
		int i = 0;
		boolean inside = false;
		boolean outside = false;
		int count = 0;
		Vector3d center = new Vector3d(0, 0, 0);
		double l = Math.sqrt(plane.x * plane.x + plane.y * plane.y + plane.z * plane.z);
		double length;
		double deepest = 0.0;
		Vector3d v;
		for (i = 0; i < vertexList.size(); i++) {
			v = vertexList.get(i);
			if (GeometryUtility.inside(v, plane)) {
				inside = true;
				length = -(v.x * plane.x + v.y * plane.y + v.z * plane.z + plane.w) / l;
				if (length > deepest + GeometryUtility.TOLERANCE) {
					center.set(v);
					count = 1;
					deepest = length;
				} else if (length >= deepest - GeometryUtility.TOLERANCE) {
					center.add(v);
					count++;			
				}
			} else {
				outside = true;				
			}
		}

		if (!inside || !outside) {
			// 全頂点が外側か全頂点が内側の場合
			return null;
		}

		center.scale(1.0 / count);

		CollisionResult cr = new CollisionResult();
		cr.length = deepest;
		cr.collisionPoint.setX(center.x);
		cr.collisionPoint.setY(center.y);
		cr.collisionPoint.setZ(center.z);
		cr.normal.setX(plane.x / l);
		cr.normal.setY(plane.y / l);
		cr.normal.setZ(plane.z / l);

		return cr;
	}

	/**
	 * OBBとの衝突判定
	 * @param o 衝突判定の対象となるOBB
	 * @return 衝突判定の結果（衝突していない場合はnull）
	 */
	public CollisionResult intersect(OBB o) {
		// oの各頂点がthisの各面の内側にあるかを調べる
		// i：頂点 j：面
		for (int i = 0; i < o.vertexList.size(); i++) {
			for (int j = 0; j < plane.length; j++) {
				inside[i][j] = GeometryUtility.inside(o.vertexList.get(i), plane[j]);
			}
		}

		boolean collision = false;
		int count = 0;
		Vector3d center = new Vector3d(0, 0, 0);

		// oの各頂点がthisに包含されているか？
		for (int v = 0; v < 8; v++) {
			boolean f1 = false;
			for (int p = 0; p < 6; p++) {
				f1 = inside[v][p];
				if (!f1) {
					break;
				}
			}
			inside2[v] = f1;
			if (f1) {
				collision = true;
				center.add(o.vertexList.get(v));
				count++;
			}
		}

		if (!collision) {
			// 辺が相手の面と交わっているか？
			for (int p = 0; p < 6; p++) {
				Vector3d intersection = null;
				for (int e = 0; e < 12; e++) {
					if (inside[edges[e][0]][p] == !inside[edges[e][1]][p]) {
						intersection = GeometryUtility.intersect(
								plane[p],
								o.vertexList.get(edges[e][0]), 
								o.vertexList.get(edges[e][1]));
						boolean bInside = true;
						for (int p2 = 0; p2 < 6; p2++) {
							if (p != p2
								&& !GeometryUtility.inside(intersection, plane[p2])) {
								bInside = false;
								break;
							}
						}
						if (bInside) {
							collision = true;
							center.add(intersection);
							count++;
						}
					}
				}
			}
		}

		if (!collision)
			return null;

		center.scale(1.0 / count);

		CollisionResult cr = new CollisionResult();

		double lMin = 0;
		int cm = -1;
		Vector3d normal = null;
		for (int m = 0; m < plane.length; m++) {
			Vector3d n = new Vector3d(plane[m].x, plane[m].y, plane[m].z);
			double l = -(center.x * plane[m].x + center.y * plane[m].y + center.z
							* plane[m].z + plane[m].w) / n.length();
			if (lMin > l || cm == -1) {
				lMin = l;
				cm = m;
				normal = n;
			}
		}
		cr.length = lMin;
		cr.collisionPoint.setVector3d(center);
		cr.normal.setX(plane[cm].x / normal.length());
		cr.normal.setY(plane[cm].y / normal.length());
		cr.normal.setZ(plane[cm].z / normal.length());

		return cr;

	}

	public Object clone() {
		OBB obb = new OBB();
		obb.plane = new Vector4d[6];
		for (int i = 0; i < plane.length; i++) {
			obb.plane[i] = (Vector4d) plane[i].clone();
		}
		for (int i = 0; i < vertexList.size(); i++) {
			obb.vertexList.add((Vector3d) vertexList.get(i).clone());
		}
		obb.bp = new BoundingPolytope(obb.plane);

		// System.out.println("veretexListSIZE:"+vertexList.size());
		return obb;
	}

	public void transform(Transform3D t) {
		// TODO Auto-generated method stub
		bp.transform(t);
		bp.getPlanes(plane);
		// for(int i = 0; i<plane.length;i++){
		// System.out.println("plane");
		// System.out.print(plane[i].x + "," + plane[i].y + "," + plane[i].z +
		// "," + plane[i].w);
		// t.transform(plane[i]);
		// System.out.println("-->" + plane[i].x + "," + plane[i].y + "," +
		// plane[i].z + "," + plane[i].w);
		// }
		for (int i = 0; i < vertexList.size(); i++) {
			// System.out.println("vertex");
			// System.out.print(vertexList.get(i).x + "," + vertexList.get(i).y
			// + "," + vertexList.get(i).z);
			Matrix4d mat4d = new Matrix4d();
			t.get(mat4d);
			double x = mat4d.m00 * vertexList.get(i).x + mat4d.m01
					* vertexList.get(i).y + mat4d.m02 * vertexList.get(i).z
					+ mat4d.m03;
			double y = mat4d.m10 * vertexList.get(i).x + mat4d.m11
					* vertexList.get(i).y + mat4d.m12 * vertexList.get(i).z
					+ mat4d.m13;
			double z = mat4d.m20 * vertexList.get(i).x + mat4d.m21
					* vertexList.get(i).y + mat4d.m22 * vertexList.get(i).z
					+ mat4d.m23;
			vertexList.get(i).x = x;
			vertexList.get(i).y = y;
			vertexList.get(i).z = z;
			// t.transform(vertexList.get(i));
			// System.out.println("-->" + vertexList.get(i).x + "," +
			// vertexList.get(i).y + "," + vertexList.get(i).z);
		}
	}
}
