package framework.model3D;

import java.util.ArrayList;
import java.util.Enumeration;

import java3d.BoundingPolytope;
import java3d.Box;
import java3d.Cone;
import java3d.Cylinder;
import java3d.IndexedTriangleArray;
import java3d.IndexedTriangleFanArray;
import java3d.IndexedTriangleStripArray;
import java3d.Node;
import java3d.TriangleStripArray;
import java3d.Vector3d;
import java3d.Vector4d;

import java3d.Appearance;
import java3d.Geometry;
import java3d.Shape3D;
import java3d.Sphere;
import java3d.TransformGroup;
import java3d.TriangleArray;
import java3d.TriangleFanArray;

public class BaseObject3D implements Placeable {
	public TransformGroup center;
	
	protected BoundingSurface[] boundingSurfaces = null;
	
	public BaseObject3D() {
		center = new TransformGroup();
	}
	
	public BaseObject3D(Geometry g, Appearance a) {
		center = new TransformGroup();
		Shape3D shape = new Shape3D(g, a);
		center.addChild(shape);
	}

	@Override
	public TransformGroup getTransformGroupToPlace() {
		return getBody().center;
	}
	
	public BaseObject3D getBody() {
		return this;
	}

	public Node getPrimitiveNode() {
		return (Node)center.getChild(0);
	}
	
	public Enumeration<Node> getPrimitiveNodes() {
		return (Enumeration<Node>)center.getAllChildren();
	}
	
	/**
	 * 衝突判定用のボリューム（ポリゴンと粗い判定用の多角柱）を取得する
	 * @return　衝突判定用のボリューム列
	 */
	public BoundingSurface[] getBoundingSurfaces() {
		if (boundingSurfaces == null) {
			Node node = getPrimitiveNode();
			if (node == null) return null;
			
			ArrayList<Vector3d> vertex3DList = null;
			if (node instanceof Box) {
				// Boxの場合
				Box box = ((Box)node);
				// 頂点列を取得する
				vertex3DList = getVertexList(box.getShape(Box.BACK).getGeometry());
				vertex3DList.addAll(getVertexList(box.getShape(Box.BOTTOM).getGeometry()));
				vertex3DList.addAll(getVertexList(box.getShape(Box.FRONT).getGeometry()));
				vertex3DList.addAll(getVertexList(box.getShape(Box.LEFT).getGeometry()));
				vertex3DList.addAll(getVertexList(box.getShape(Box.RIGHT).getGeometry()));
				vertex3DList.addAll(getVertexList(box.getShape(Box.TOP).getGeometry()));
			} else if (node instanceof Cylinder) {
				// Cylinderの場合
				Cylinder cylinder = ((Cylinder)node);
				// 頂点列を取得する
				vertex3DList = getVertexList(cylinder.getShape(Cylinder.BODY).getGeometry());
				vertex3DList.addAll(getVertexList(cylinder.getShape(Cylinder.BOTTOM).getGeometry()));
				vertex3DList.addAll(getVertexList(cylinder.getShape(Cylinder.TOP).getGeometry()));
			} else if (node instanceof Cone) {
				// Coneの場合
				Cone cone = ((Cone)node);
				// 頂点列を取得する
				vertex3DList = getVertexList(cone.getShape(Cone.BODY).getGeometry());
				vertex3DList.addAll(getVertexList(cone.getShape(Cone.CAP).getGeometry()));
			} else if (node instanceof Sphere) {
				// Sphereの場合
				Sphere sphere = ((Sphere)node);
				// 頂点列を取得する
				vertex3DList = getVertexList(sphere.getShape(Sphere.BODY).getGeometry());
			} else if (node instanceof Shape3D) {
				// Shape3Dの場合
				Shape3D shape = (Shape3D)node;				
				// 頂点列を取得する
				vertex3DList = getVertexList(shape.getGeometry());
			}
			if (vertex3DList == null) return null;

			BoundingSurface[] surfaces = new BoundingSurface[vertex3DList.size() / 3];

			for (int i = 0; i < vertex3DList.size(); i += 3) {
				Vector3d v1 = vertex3DList.get(i);
				Vector3d v2 = vertex3DList.get(i + 1);
				Vector3d v3 = vertex3DList.get(i + 2);
				BoundingSurface bSurface = new BoundingSurface();
				bSurface.addVertex((Vector3d)v1.clone());
				bSurface.addVertex((Vector3d)v2.clone());
				bSurface.addVertex((Vector3d)v3.clone());
				bSurface.setBounds(createBoundingPolytope(v1, v2, v3));
				surfaces[i / 3] = bSurface;
			}
			boundingSurfaces = surfaces;
		}
		return boundingSurfaces;
	}

	private ArrayList<Vector3d> getVertexList(Geometry g) {
		ArrayList<Vector3d> vertex3DList = new ArrayList<Vector3d>();
		double coordinate1[] = new double[3];
		if (g instanceof IndexedTriangleArray) {
			// IndexedTriangleArray の場合
			IndexedTriangleArray triArray = (IndexedTriangleArray)g;

			// 全頂点を3D上の頂点をvertex3DListに入れていく。
			for (int i = 0; i < triArray.getIndexCount(); i++) {
				triArray.getCoordinates(triArray.getCoordinateIndex(i), coordinate1);
				vertex3DList.add(new Vector3d(coordinate1));
			}
		} else if (g instanceof TriangleArray) {
			// TriangleArray の場合
			TriangleArray triArray = (TriangleArray)g;

			// 全頂点を3D上の頂点をvertex3DListに入れていく。
			for (int i = 0; i < triArray.getVertexCount(); i++) {
				triArray.getCoordinates(i, coordinate1);
				vertex3DList.add(new Vector3d(coordinate1));
			}
		} else if (g instanceof IndexedTriangleStripArray) {
			// IndexedTriangleStripArray の場合
			IndexedTriangleStripArray triStripAttay = (IndexedTriangleStripArray)g;				
			int stripVertexCounts[] = new int[triStripAttay.getNumStrips()];
			triStripAttay.getStripIndexCounts(stripVertexCounts);
			// 全頂点を3D上の頂点をvertex3DListに入れていく
			int index = 0;
			double coordinate2[] = new double[3];
			double coordinate3[] = new double[3];
			double coordinate4[] = new double[3];
			for (int i = 0; i < triStripAttay.getNumStrips(); i++) {
				for (int j = 0; j < stripVertexCounts[i]; j += 2) {						
					triStripAttay.getCoordinates(triStripAttay.getCoordinateIndex(index), coordinate1);
					triStripAttay.getCoordinates(triStripAttay.getCoordinateIndex(index+1), coordinate2);
					triStripAttay.getCoordinates(triStripAttay.getCoordinateIndex(index+2), coordinate3);
					triStripAttay.getCoordinates(triStripAttay.getCoordinateIndex(index+3), coordinate4);
					vertex3DList.add(new Vector3d(coordinate1));	//1つめの三角形
					vertex3DList.add(new Vector3d(coordinate2));
					vertex3DList.add(new Vector3d(coordinate3));
					vertex3DList.add(new Vector3d(coordinate2));	//2つめの三角形
					vertex3DList.add(new Vector3d(coordinate4));
					vertex3DList.add(new Vector3d(coordinate3));
					index += 2;
				}
			}
		} else if (g instanceof TriangleStripArray) {
			// TriangleStripArray の場合
			TriangleStripArray triStripAttay = (TriangleStripArray)g;				
			int stripVertexCounts[] = new int[triStripAttay.getNumStrips()];
			triStripAttay.getStripVertexCounts(stripVertexCounts);
			// 全頂点を3D上の頂点をvertex3DListに入れていく
			int index = 0;
			double coordinate2[] = new double[3];
			double coordinate3[] = new double[3];
			double coordinate4[] = new double[3];
			for (int i = 0; i < triStripAttay.getNumStrips(); i++) {
				for (int j = 0; j < stripVertexCounts[i]; j += 2) {						
					triStripAttay.getCoordinates(index, coordinate1);
					triStripAttay.getCoordinates(index+1, coordinate2);
					triStripAttay.getCoordinates(index+2, coordinate3);
					triStripAttay.getCoordinates(index+3, coordinate4);
					vertex3DList.add(new Vector3d(coordinate1));	//1つめの三角形
					vertex3DList.add(new Vector3d(coordinate2));
					vertex3DList.add(new Vector3d(coordinate3));
					vertex3DList.add(new Vector3d(coordinate2));	//2つめの三角形
					vertex3DList.add(new Vector3d(coordinate4));
					vertex3DList.add(new Vector3d(coordinate3));
					index += 2;
				}
			}
		} else if (g instanceof IndexedTriangleFanArray) {
			// IndexedTriangleFanArray の場合
			IndexedTriangleFanArray triFanAttay = (IndexedTriangleFanArray)g;				
			int stripVertexCounts[] = new int[triFanAttay.getNumStrips()];
			triFanAttay.getStripIndexCounts(stripVertexCounts);
			// 全頂点を3D上の頂点をvertex3DListに入れていく
			int index = 0;
			double coordinate2[] = new double[3];
			double coordinate3[] = new double[3];
			double coordinate4[] = null;
			for (int i = 0; i < triFanAttay.getNumStrips(); i++) {
				triFanAttay.getCoordinates(triFanAttay.getCoordinateIndex(index), coordinate1);			// 中心点
				triFanAttay.getCoordinates(triFanAttay.getCoordinateIndex(index+1), coordinate2);
				index += 2;
				for (int j = 2; j < stripVertexCounts[i]; j++) {
					triFanAttay.getCoordinates(triFanAttay.getCoordinateIndex(index), coordinate3);
					vertex3DList.add(new Vector3d(coordinate1));
					vertex3DList.add(new Vector3d(coordinate2));
					vertex3DList.add(new Vector3d(coordinate3));
					coordinate4 = coordinate2;
					coordinate2 = coordinate3;
					coordinate3 = coordinate4;
					index++;
				}
			}
		} else if (g instanceof TriangleFanArray) {
			// TriangleFanArray の場合
			TriangleFanArray triFanAttay = (TriangleFanArray)g;				
			int stripVertexCounts[] = new int[triFanAttay.getNumStrips()];
			triFanAttay.getStripVertexCounts(stripVertexCounts);
			// 全頂点を3D上の頂点をvertex3DListに入れていく
			int index = 0;
			double coordinate2[] = new double[3];
			double coordinate3[] = new double[3];
			double coordinate4[] = null;
			for (int i = 0; i < triFanAttay.getNumStrips(); i++) {
				triFanAttay.getCoordinates(index, coordinate1);			// 中心点
				triFanAttay.getCoordinates(index + 1, coordinate2);
				index += 2;
				for (int j = 2; j < stripVertexCounts[i]; j++) {
					triFanAttay.getCoordinates(index, coordinate3);
					vertex3DList.add(new Vector3d(coordinate1));
					vertex3DList.add(new Vector3d(coordinate2));
					vertex3DList.add(new Vector3d(coordinate3));
					coordinate4 = coordinate2;
					coordinate2 = coordinate3;
					coordinate3 = coordinate4;
					index++;
				}
			}
		} else {
			return null;
		}
		return vertex3DList;
	}

	protected BoundingPolytope createBoundingPolytope(Vector3d vertex1,
			Vector3d vertex2, Vector3d vertex3) {
		Vector3d v1 = new Vector3d();
		Vector3d v2 = new Vector3d();
		Vector3d v3 = new Vector3d();
		Vector3d v4 = new Vector3d();
		Vector3d v5 = new Vector3d();
		Vector3d v6 = new Vector3d();
		Vector3d cv1 = new Vector3d();
		Vector3d cv2 = new Vector3d();
		cv1.sub(vertex3, vertex1);
		cv2.sub(vertex2, vertex1);
		Vector3d cv = new Vector3d();
		cv.cross(cv1, cv2);
		cv.normalize();
		cv.scale(0.01);
		v1.set(vertex1);
		v2.set(vertex2);
		v3.set(vertex3);
		v4.set(vertex1);
		v4.add(cv);
		v5.set(vertex2);
		v5.add(cv);
		v6.set(vertex3);
		v6.add(cv);

		Vector3d pv1 = new Vector3d();
		Vector3d pv2 = new Vector3d();
		Vector3d pv3 = new Vector3d();
		Vector3d pn = new Vector3d();
		Vector4d[] plane = new Vector4d[5];

		// 0
		pv1 = v1;
		pv2.sub(v2, v1);
		pv3.sub(v3, v1);

		pn.cross(pv2, pv3);
		pn.normalize();
		plane[0] = new Vector4d(pn.x, pn.y, pn.z, -pn.dot(pv1));

		// 1
		pv1 = v1;
		pv2.sub(v4, v1);
		pv3.sub(v2, v1);

		pn.cross(pv2, pv3);
		pn.normalize();
		plane[1] = new Vector4d(pn.x, pn.y, pn.z, -pn.dot(pv1));

		// 2
		pv1 = v1;
		pv2.sub(v3, v1);
		pv3.sub(v4, v1);

		pn.cross(pv2, pv3);
		pn.normalize();
		plane[2] = new Vector4d(pn.x, pn.y, pn.z, -pn.dot(pv1));

		// 3
		pv1 = v6;
		pv2.sub(v3, v6);
		pv3.sub(v5, v6);

		pn.cross(pv2, pv3);
		pn.normalize();
		plane[3] = new Vector4d(pn.x, pn.y, pn.z, -pn.dot(pv1));

		// 4
		pv1 = v6;
		pv2.sub(v5, v6);
		pv3.sub(v4, v6);

		pn.cross(pv2, pv3);
		pn.normalize();
		plane[4] = new Vector4d(pn.x, pn.y, pn.z, -pn.dot(pv1));

		return new BoundingPolytope(plane);
	}
	
	public boolean hasAppearancePrepared() {
		return true;
	}
}