/*
 * Copyright (c) 2003-2007 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme.bounding;

import java.io.Serializable;
import java.util.ArrayList;

import com.jme.intersection.Intersection;
import com.jme.math.Quaternion;
import com.jme.math.Ray;
import com.jme.math.Vector3f;
import com.jme.scene.TriMesh;
import com.jme.scene.batch.TriangleBatch;
import com.jme.util.SortUtil;

/**
 * CollisionTree defines a well balanced red black tree used for triangle 
 * accurate collision detection. The CollisionTree supports three types: 
 * Oriented Bounding Box, Axis-Aligned Bounding Box and Sphere. The tree 
 * is composed of a heirarchy of nodes, all but leaf nodes have two children, 
 * a left and a right, where the children contain half of the triangles of
 * the parent. This "half split" is executed down the tree until the node is
 * maintaining a set maximum of triangles. This node is called the leaf node. 
 * 
 * Intersection checks are handled as follows:<br>
 * 1. The bounds of the node is checked for intersection. If no intersection
 * occurs here, no further processing is needed, the children (nodes or triangles)
 * do not intersect.<br>
 * 2a. If an intersection occurs and we have children left/right nodes, pass
 * the intersection information to the children.<br>
 * 2b. If an intersection occurs and we are a leaf node, pass each triangle 
 * individually for intersection checking.<br>
 * 
 * Optionally, during creation of the collision tree, sorting can be applied. 
 * Sorting will attempt to optimize the order of the triangles in such a way
 * as to best split for left and right sub-trees. This function can lead to faster
 * intersection tests, but increases the creation time for the tree.
 * 
 * The number of triangles a leaf node is responsible for is defined in 
 * CollisionTreeManager. It is actually recommended to allow CollisionTreeManager
 * to maintain the collision trees for a scene.
 * 
 * @author Mark Powell
 * @see com.jme.bounding.CollisionTreeManager
 */
public class CollisionTree implements Serializable {

	private static final long serialVersionUID = 1L;
	
	/**
	 * defines a CollisionTree as using Oriented Bounding Boxes.
	 */
	public static final int OBB_TREE = 0;

	/**
	 * defines a CollisionTree as using Axis-Aligned Bounding Boxes.
	 */
	public static final int AABB_TREE = 1;

	/**
	 * defines a CollisionTree as using Bounding Spheres.
	 */
	public static final int SPHERE_TREE = 2;
	
	//Default tree is axis-aligned
	private int type = AABB_TREE;

	//children trees
	private CollisionTree left;
	private CollisionTree right;

	//bounding volumes that contain the triangles that the node is 
	//handling
	private BoundingVolume bounds;
	private BoundingVolume worldBounds;

	//the list of triangle indices that compose the tree. This list
	//contains all the triangles of the batch and is shared between
	//all nodes of this tree.
	private int[] triIndex;

	//Defines the pointers into the triIndex array that this node is
	//directly responsible for.
	private int start, end;

	//Required Spatial information
	private TriMesh parent;
	private TriangleBatch batch;

	// static variables to contain information for ray intersection
	static private final Vector3f tempVa = new Vector3f();
	static private final Vector3f tempVb = new Vector3f();
	static private final Vector3f tempVc = new Vector3f();
	static private final Vector3f tempVd = new Vector3f();
	static private final Vector3f tempVe = new Vector3f();
	static private final Vector3f tempVf = new Vector3f();

	static private Vector3f[] verts = new Vector3f[3];
	static private Vector3f[] target = new Vector3f[3];

	//Comparator used to sort triangle indices
	protected static TreeComparator comparator = new TreeComparator();
	
	/**
	 * Constructor creates a new instance of CollisionTree. The type of tree
	 * is provided as a parameter with valid options being:
	 * AABB_TREE, OBB_TREE, SPHERE_TREE.
	 * @param type the type of collision tree to make
	 */
	public CollisionTree(int type) {
		this.type = type;
	}

	/**
	 * Recreate this Collision Tree for the given TriMesh and batch
	 * index.
	 * 
	 * @param batchIndex the index of the batch to generate the tree for.
	 * @param parent
	 *            The trimesh that this OBBTree should represent.
	 * @param doSort true to sort triangles during creation, false otherwise
	 */
	public void construct(int batchIndex, TriMesh parent, boolean doSort) {
		this.parent = parent;
		this.batch = parent.getBatch(batchIndex);
		triIndex = parent.getBatch(batchIndex).getTriangleIndices(triIndex);
		createTree(0, triIndex.length, doSort);
	}

	/**
	 * Recreate this Collision Tree for the given TriMesh and batch.
	 * 
	 * @param batch the batch to generate the tree for.
	 * @param parent
	 *            The trimesh that this OBBTree should represent.
	 * @param doSort true to sort triangles during creation, false otherwise
	 */
	public void construct(TriangleBatch batch, TriMesh parent, boolean doSort) {
		this.parent = parent;
		this.batch = batch;
		triIndex = batch.getTriangleIndices(triIndex);
		createTree(0, triIndex.length, doSort);
	}

	/**
	 * Creates a Collision Tree by recursively creating children nodes, splitting
	 * the triangles this node is responsible for in half until the desired
	 * triangle count is reached.
	 * 
	 * @param start
	 *            The start index of the tris array, inclusive.
	 * @param end
	 *            The end index of the tris array, exclusive.
	 * @param doSort
	 * 			  True if the triangles should be sorted at each level,
	 * 			false otherwise.
	 */
	public void createTree(int start, int end, boolean doSort) {
		this.start = start;
		this.end = end;
		
		if (parent == null || triIndex == null) {
			return;
		}

		createBounds();

		// the bounds at this level should contain all the triangles this level
		// is reponsible for.
		bounds.computeFromTris(triIndex, batch, start, end);

		// check to see if we are a leaf, if the number of triangles we
		// reference is less than or equal to the maximum defined by the
		// CollisionTreeManager we are done.
		if (end - start + 1 <= CollisionTreeManager.getInstance()
				.getMaxTrisPerLeaf()) {
			return;
		}

		// if doSort is set we need to attempt to optimize the referenced
		// triangles.
		// optimizing the sorting of the triangles will help group them
		// spatially
		// in the left/right children better.
		if (doSort) {
			sortTris();
		}

		// create the left child, it will reference many of our values (index,
		// parent, batch)
		if (left == null) {
			left = new CollisionTree(type);
		}

		left.triIndex = this.triIndex;
		left.parent = this.parent;
		left.batch = this.batch;
		left.createTree(start, (start + end) / 2, doSort);

		// create the right child, it will reference many of our values (index,
		// parent, batch)
		if (right == null) {
			right = new CollisionTree(type);
		}
		right.triIndex = this.triIndex;
		right.parent = this.parent;
		right.batch = this.batch;
		right.createTree((start + end) / 2, end, doSort);
	}
	
	/**
	 * Tests if the world bounds of the node at this level interesects a 
	 * provided bounding volume. If an intersection occurs, true is 
	 * returned, otherwise false is returned. If the provided volume is
	 * invalid, false is returned.
	 * @param volume the volume to intersect with.
	 * @return true if there is an intersect, false otherwise.
	 */
	public boolean intersectsBounding(BoundingVolume volume) {
		switch(volume.getType()) {
		case BoundingVolume.BOUNDING_BOX:
			return worldBounds.intersectsBoundingBox((BoundingBox)volume);
		case BoundingVolume.BOUNDING_OBB:
			return worldBounds.intersectsOrientedBoundingBox((OrientedBoundingBox)volume);
		case BoundingVolume.BOUNDING_SPHERE:
			return worldBounds.intersectsSphere((BoundingSphere)volume);
		default:
			return false;
		}
		
	}
	
	/**
	 * Determines if this Collision Tree intersects the given CollisionTree. If
	 * a collision occurs, true is returned, otherwise false is returned. If
	 * the provided collisionTree is invalid, false is returned.
	 * 
	 * @param collisionTree
	 *            The Tree to test.
	 * @return True if they intersect, false otherwise.
	 */
	public boolean intersect(CollisionTree collisionTree) {
		if (collisionTree == null) {
			return false;
		}

		collisionTree.bounds
				.transform(collisionTree.parent.getWorldRotation(),
						collisionTree.parent.getWorldTranslation(),
						collisionTree.parent.getWorldScale(),
						collisionTree.worldBounds);

		// our two collision bounds do not intersect, therefore, our triangles must
		// not intersect. Return false.
		if (!intersectsBounding(collisionTree.worldBounds)) {
			return false;
		}

		// check children
		if (left != null) { // This is not a leaf
			if (collisionTree.intersect(left)) {
				return true;
			}
			if (collisionTree.intersect(right)) {
				return true;
			}
			return false;
		}

		// This is a leaf
		if (collisionTree.left != null) { // but collision isn't
			if (intersect(collisionTree.left)) {
				return true;
			}
			if (intersect(collisionTree.right)) {
				return true;
			}
			return false;
		}

		// both are leaves
		Quaternion roti = parent.getWorldRotation();
		Vector3f scalei = parent.getWorldScale();
		Vector3f transi = parent.getWorldTranslation();

		Quaternion rotj = collisionTree.parent.getWorldRotation();
		Vector3f scalej = collisionTree.parent.getWorldScale();
		Vector3f transj = collisionTree.parent.getWorldTranslation();

		//for every triangle to compare, put them into world space and check
		//for intersections
		for (int i = start; i < end; i++) {
			batch.getTriangle(triIndex[i], verts);
			roti.mult(verts[0], tempVa).multLocal(scalei).addLocal(transi);
			roti.mult(verts[1], tempVb).multLocal(scalei).addLocal(transi);
			roti.mult(verts[2], tempVc).multLocal(scalei).addLocal(transi);
			for (int j = collisionTree.start; j < collisionTree.end; j++) {
				collisionTree.batch.getTriangle(collisionTree.triIndex[j],
						target);
				rotj.mult(target[0], tempVd).multLocal(scalej).addLocal(transj);
				rotj.mult(target[1], tempVe).multLocal(scalej).addLocal(transj);
				rotj.mult(target[2], tempVf).multLocal(scalej).addLocal(transj);
				if (Intersection.intersection(tempVa, tempVb, tempVc, tempVd,
						tempVe, tempVf))
					return true;
			}
		}
		return false;
	}

	/**
	 * Determines if this Collision Tree intersects the given CollisionTree. If
	 * a collision occurs, true is returned, otherwise false is returned. If
	 * the provided collisionTree is invalid, false is returned. All collisions
	 * that occur are stored in lists as an integer index into the mesh's 
	 * triangle buffer. where aList is the triangles for this mesh and bList 
	 * is the triangles for the test tree.
	 * 
	 * @param collisionTree
	 *            The Tree to test.
	 * @param aList a list to contain the colliding triangles of this mesh.
	 * @param bList a list to contain the colliding triangles of the testing mesh.
	 * @return True if they intersect, false otherwise.
	 */
	public boolean intersect(CollisionTree collisionTree, ArrayList<Integer> aList,
			ArrayList<Integer> bList) {
		
		if (collisionTree == null) {
			return false;
		}
		
		// our two collision bounds do not intersect, therefore, our triangles must
		// not intersect. Return false.
		collisionTree.bounds
				.transform(collisionTree.parent.getWorldRotation(),
						collisionTree.parent.getWorldTranslation(),
						collisionTree.parent.getWorldScale(),
						collisionTree.worldBounds);
		
		if (!intersectsBounding(collisionTree.worldBounds)) {
			return false;
		}
		
		//if our node is not a leaf send the children (both left and right) to
		// the test tree.
		if (left != null) { // This is not a leaf
			boolean test = collisionTree.intersect(left, bList, aList);
			test = collisionTree.intersect(right, bList, aList) || test;
			return test;
		}

		// This node is a leaf, but the testing tree node is not. Therefore,
		// continue processing the testing tree until we find its leaves.
		if (collisionTree.left != null) {
			boolean test = intersect(collisionTree.left, aList, bList);
			test = intersect(collisionTree.right, aList, bList) || test;
			return test;
		}

		// both this node and the testing node are leaves. Therefore, we can
		// switch to checking the contained triangles with each other. Any 
		// that are found to intersect are placed in the appropriate list.
		Quaternion roti = parent.getWorldRotation();
		Vector3f scalei = parent.getWorldScale();
		Vector3f transi = parent.getWorldTranslation();

		Quaternion rotj = collisionTree.parent.getWorldRotation();
		Vector3f scalej = collisionTree.parent.getWorldScale();
		Vector3f transj = collisionTree.parent.getWorldTranslation();
		
		boolean test = false;
		
		for (int i = start; i < end; i++) {
			batch.getTriangle(triIndex[i], verts);
			roti.mult(verts[0], tempVa).multLocal(scalei).addLocal(transi);
			roti.mult(verts[1], tempVb).multLocal(scalei).addLocal(transi);
			roti.mult(verts[2], tempVc).multLocal(scalei).addLocal(transi);
			for (int j = collisionTree.start; j < collisionTree.end; j++) {
				collisionTree.batch.getTriangle(collisionTree.triIndex[j],
						target);
				rotj.mult(target[0], tempVd).multLocal(scalej).addLocal(transj);
				rotj.mult(target[1], tempVe).multLocal(scalej).addLocal(transj);
				rotj.mult(target[2], tempVf).multLocal(scalej).addLocal(transj);
				if (Intersection.intersection(tempVa, tempVb, tempVc, tempVd,
						tempVe, tempVf)) {
					test = true;
					aList.add(triIndex[i]);
					bList.add(collisionTree.triIndex[j]);
				}
			}
		}
		return test;

	}

	/**
	 * intersect checks for collisions between this collision tree and a 
	 * provided Ray. Any collisions are stored in a provided list. The ray
	 * is assumed to have a normalized direction for accurate calculations.
	 * @param ray the ray to test for intersections.
	 * @param triList the list to store instersections with.
	 */
	public void intersect(Ray ray, ArrayList<Integer> triList) {

		// if our ray doesn't hit the bounds, then it must not hit a triangle.
		if (!worldBounds.intersects(ray)) {
			return;
		}

		//This is not a leaf node, therefore, check each child (left/right) for
		//intersection with the ray.
		if (left != null) {
			left.bounds.transform(parent.getWorldRotation(), parent
					.getWorldTranslation(), parent.getWorldScale(),
					left.worldBounds);
			left.intersect(ray, triList);
		}

		if (right != null) {
			right.bounds.transform(parent.getWorldRotation(), parent
					.getWorldTranslation(), parent.getWorldScale(),
					right.worldBounds);
			right.intersect(ray, triList);
		} else if (left == null) {
			//This is a leaf node. We can therfore, check each triangle this
			//node contains. If an intersection occurs, place it in the 
			//list.
			Quaternion roti = parent.getWorldRotation();
			Vector3f scalei = parent.getWorldScale();
			Vector3f transi = parent.getWorldTranslation();
			
			for (int i = start; i < end; i++) {
				batch.getTriangle(this.triIndex[i], verts);
				roti.mult(verts[0], tempVa).multLocal(scalei).addLocal(transi);
				roti.mult(verts[1], tempVb).multLocal(scalei).addLocal(transi);
				roti.mult(verts[2], tempVc).multLocal(scalei).addLocal(transi);
				if (ray.intersect(tempVa, tempVb, tempVc)) {
					triList.add(triIndex[i]);
				}
			}
		}
	}

	/**
	 * Returns the bounding volume for this tree node in local space.
	 * @return the bounding volume for this tree node in local space.
	 */
	public BoundingVolume getBounds() {
		return bounds;
	}

	/**
	 * Returns the bounding volume for this tree node in world space.
	 * @return the bounding volume for this tree node in world space.
	 */
	public BoundingVolume getWorldBounds() {
		return worldBounds;
	}

	/**
	 * recursively sets the parent of the tree. 
	 * @param parent the parent to set.
	 */
	public void setParent(TriMesh parent) {
		this.parent = parent;
		if (left != null) {
			left.setParent(parent);
		}

		if (right != null) {
			right.setParent(parent);
		}
	}

	
	/**
	 * creates the appropriate bounding volume based on the type set
	 * during construction.
	 */
	private void createBounds() {
		switch(type) {
		case AABB_TREE:
			bounds = new BoundingBox();
			worldBounds = new BoundingBox();
			break;
		case OBB_TREE:
			bounds = new OrientedBoundingBox();
			worldBounds = new OrientedBoundingBox();
			break;
		case SPHERE_TREE:
			bounds = new BoundingSphere();
			worldBounds = new BoundingSphere();
			break;
		default:
			break;
		}
	}
	
	/**
	 * sortTris attempts to optimize the ordering of the subsection of the array
	 * of triangles this node is responsible for. The sorting is based on the
	 * most efficient method along an axis. Using the TreeComparator and quick
	 * sort, the subsection of the array is sorted.
	 */
	public void sortTris() {
		switch (type) {
		case AABB_TREE:
			//determine the longest length of the box, this axis will be best
			//for sorting.
			if (((BoundingBox) bounds).xExtent > ((BoundingBox) bounds).yExtent) {
				if (((BoundingBox) bounds).xExtent > ((BoundingBox) bounds).zExtent) {
					comparator.setAxis(TreeComparator.X_AXIS);
				} else {
					comparator.setAxis(TreeComparator.Z_AXIS);
				}
			} else {
				if (((BoundingBox) bounds).yExtent > ((BoundingBox) bounds).zExtent) {
					comparator.setAxis(TreeComparator.Y_AXIS);
				} else {
					comparator.setAxis(TreeComparator.Z_AXIS);
				}
			}
			break;
		case OBB_TREE:
			//determine the longest length of the box, this axis will be best
			//for sorting.
			if (((OrientedBoundingBox) bounds).extent.x > ((OrientedBoundingBox) bounds).extent.y) {
				if (((OrientedBoundingBox) bounds).extent.x > ((OrientedBoundingBox) bounds).extent.z) {
					comparator.setAxis(TreeComparator.X_AXIS);
				} else {
					comparator.setAxis(TreeComparator.Z_AXIS);
				}
			} else {
				if (((OrientedBoundingBox) bounds).extent.y > ((OrientedBoundingBox) bounds).extent.z) {
					comparator.setAxis(TreeComparator.Y_AXIS);
				} else {
					comparator.setAxis(TreeComparator.Z_AXIS);
				}
			}
			break;
		case SPHERE_TREE:
			//sort any axis, X is fine.
			comparator.setAxis(TreeComparator.X_AXIS);
			break;
		default:
			break;
		}

		comparator.setCenter(bounds.center);
		comparator.setBatch(batch);
		SortUtil.qsort(triIndex, start, end - 1, comparator);
	}
}
