/*
 * $RCSfile: BoundingBox.java,v $
 *
 * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
 *
 * Use is subject to license terms.
 *
 * $Revision: 1.8 $
 * $Date: 2007/04/12 17:34:04 $
 * $State: Exp $
 */

package javax.media.j3d;

import javax.vecmath.*;
import com.sun.j3d.internal.HashCodeUtil;

/**
 *  This class defines an axis aligned bounding box which is used for
 *  bounding regions.
 *
 */

public class BoundingBox extends Bounds {

    /**
     * The corner of the bounding box with the numerically smallest
     * values.
     */
    Point3d       lower;

    /**
     * The corner of the bounding box with the numerically largest
     * values.
     */
    Point3d       upper;

    private Point3d centroid = null;
    private static final double EPS = 1.0E-8;
    
    // reusable temp objects
    private BoundingSphere tmpSphere = null;
    private BoundingBox tmpBox = null;
    private BoundingPolytope tmpPolytope = null;    
    private Point3d tmpP3d = new Point3d();


    /**
     * Constructs and initializes a BoundingBox given min,max in x,y,z.
     * @param lower the "small" corner
     * @param upper the "large" corner
     */
    public BoundingBox(Point3d lower, Point3d upper) {
	boundId = BOUNDING_BOX;
	this.lower = new Point3d(lower);
	this.upper = new Point3d(upper);
	updateBoundsStates();
    }
    
    /**
     * Constructs and initializes a 2X bounding box about the
     * origin. The lower corner is initialized to (-1.0d, -1.0d, -1.0d)
     * and the opper corner is initialized to (1.0d, 1.0d, 1.0d).
     */
    public BoundingBox() {
	boundId = BOUNDING_BOX;
	lower = new Point3d(-1.0d, -1.0d, -1.0d);
	upper = new Point3d( 1.0d,  1.0d,  1.0d);
	updateBoundsStates();
    }
    
    /**
     * Constructs a BoundingBox from a  bounding object. 
     * @param boundsObject  a bounds object 
     */
    public BoundingBox(Bounds boundsObject) {
	int i;
	boundId = BOUNDING_BOX;
	if( boundsObject == null ) {
	    // Negative volume.
	    lower = new Point3d( 1.0d,  1.0d,  1.0d);
	    upper = new Point3d(-1.0d, -1.0d, -1.0d);
	}
	else if( boundsObject.boundsIsInfinite ) {
	    lower = new Point3d( Double.NEGATIVE_INFINITY,
				 Double.NEGATIVE_INFINITY,
				 Double.NEGATIVE_INFINITY);
	    upper = new Point3d(Double.POSITIVE_INFINITY,
				Double.POSITIVE_INFINITY,
				Double.POSITIVE_INFINITY);
	}
	else if( boundsObject.boundId == BOUNDING_BOX){
	    BoundingBox box = (BoundingBox)boundsObject;
	    
	    lower = new Point3d(box.lower.x, box.lower.y, box.lower.z);
	    upper = new Point3d(box.upper.x, box.upper.y, box.upper.z);

	}
	else if( boundsObject.boundId == BOUNDING_SPHERE ) {
	    BoundingSphere sphere = (BoundingSphere)boundsObject;
	    
	    lower = new Point3d(sphere.center.x-sphere.radius, 
				sphere.center.y-sphere.radius,
				sphere.center.z-sphere.radius);
	    
	    upper = new Point3d(sphere.center.x+sphere.radius, 
				sphere.center.y+sphere.radius,
				sphere.center.z+sphere.radius);
	    
	}
	else if(boundsObject.boundId == BOUNDING_POLYTOPE) {
	    BoundingPolytope polytope = (BoundingPolytope)boundsObject;
	    if( polytope.nVerts < 1 ) { // handle degenerate case
		lower = new Point3d(-1.0d, -1.0d, -1.0d);
		upper = new Point3d( 1.0d,  1.0d,  1.0d);
	    } else {
		lower = new Point3d( polytope.verts[0].x, polytope.verts[0].y,
				     polytope.verts[0].z);
		upper = new Point3d( polytope.verts[0].x, polytope.verts[0].y,
				     polytope.verts[0].z);
		
		for(i=1;i<polytope.nVerts;i++) {
		    if( polytope.verts[i].x < lower.x )
			lower.x = polytope.verts[i].x;
		    if( polytope.verts[i].y < lower.y )
			lower.y = polytope.verts[i].y;
		    if( polytope.verts[i].z < lower.z )
			lower.z = polytope.verts[i].z;
		    if( polytope.verts[i].x > upper.x )
			upper.x = polytope.verts[i].x;
		    if( polytope.verts[i].y > upper.y )
			upper.y = polytope.verts[i].y;
		    if( polytope.verts[i].z > upper.z )
			upper.z = polytope.verts[i].z;
		}
	    }
	    
	} else {
	    throw new IllegalArgumentException(J3dI18N.getString("BoundingBox0"));
	}

        updateBoundsStates();
    }

    /**
     * Constructs a BoundingBox from an array of bounding objects. 
     * @param bounds an array of bounding objects
     */
    public BoundingBox(Bounds[] bounds) {
      int i=0;

       upper = new Point3d();
       lower = new Point3d();
       boundId = BOUNDING_BOX;

       if( bounds == null || bounds.length <= 0  ) {
	   // Negative volume.
	   lower = new Point3d( 1.0d,  1.0d,  1.0d);
	   upper = new Point3d(-1.0d, -1.0d, -1.0d);
	   updateBoundsStates();
	   return;
       }

       // find first non empty bounds object
       while( bounds[i] == null && i < bounds.length) {
	   i++;
       }
 
       if( i >= bounds.length ) { // all bounds objects were empty
	   // Negative volume.
	   lower = new Point3d( 1.0d,  1.0d,  1.0d);
	   upper = new Point3d(-1.0d, -1.0d, -1.0d);
	   updateBoundsStates();
	   return;
       } 
 
       this.set(bounds[i++]);
       if(boundsIsInfinite)
	   return;
       
       for(;i<bounds.length;i++) {
           if( bounds[i] == null );   // do nothing
           else if( bounds[i].boundsIsEmpty); // do nothing
	   else if( bounds[i].boundsIsInfinite ) {
	       lower.x = lower.y = lower.z = Double.NEGATIVE_INFINITY;
	       upper.x = upper.y = upper.z = Double.POSITIVE_INFINITY;
	       break;  // We're done.
	   }
	   else if(bounds[i].boundId == BOUNDING_BOX){
	       BoundingBox box = (BoundingBox)bounds[i];

	       if( lower.x > box.lower.x) lower.x = box.lower.x;
	       if( lower.y > box.lower.y) lower.y = box.lower.y;
	       if( lower.z > box.lower.z) lower.z = box.lower.z;
	       if( upper.x < box.upper.x) upper.x = box.upper.x;
	       if( upper.y < box.upper.y) upper.y = box.upper.y;
	       if( upper.z < box.upper.z) upper.z = box.upper.z;

	   }
	   else if(bounds[i].boundId == BOUNDING_SPHERE) {
	       BoundingSphere sphere = (BoundingSphere)bounds[i];
	       if( lower.x > (sphere.center.x - sphere.radius))
		   lower.x = sphere.center.x - sphere.radius;
	       if( lower.y > (sphere.center.y - sphere.radius))
		   lower.y = sphere.center.y - sphere.radius;
	       if( lower.z > (sphere.center.z - sphere.radius))
		   lower.z = sphere.center.z - sphere.radius;
	       if( upper.x < (sphere.center.x + sphere.radius))
		   upper.x = sphere.center.x + sphere.radius;
	       if( upper.y < (sphere.center.y + sphere.radius))
		   upper.y = sphere.center.y + sphere.radius;
	       if( upper.z < (sphere.center.z + sphere.radius))
		   upper.z = sphere.center.z + sphere.radius;
	   }
	   else if(bounds[i].boundId == BOUNDING_POLYTOPE) {
	       BoundingPolytope polytope = (BoundingPolytope)bounds[i];

	       for(i=0;i<polytope.nVerts;i++) { // XXXX: handle polytope with no verts
		   if( polytope.verts[i].x < lower.x )
		       lower.x = polytope.verts[i].x;
		   if( polytope.verts[i].y < lower.y )
		       lower.y = polytope.verts[i].y;
		   if( polytope.verts[i].z < lower.z )
		       lower.z = polytope.verts[i].z;
		   if( polytope.verts[i].x > upper.x )
		       upper.x = polytope.verts[i].x;
		   if( polytope.verts[i].y > upper.y )
		       upper.y = polytope.verts[i].y;
		   if( polytope.verts[i].z > upper.z )
		       upper.z = polytope.verts[i].z;
	       }
	   }
	   else {
	       throw new IllegalArgumentException(J3dI18N.getString("BoundingBox1"));
	   }
       }
       updateBoundsStates();
    }

    /**
     * Gets the lower corner of this bounding box.
     * @param p1 a Point to receive the lower corner of the bounding box
     */
    public void getLower(Point3d p1) {
        p1.x = lower.x;
        p1.y = lower.y;
        p1.z = lower.z;
    }
    
    /**
     * Sets the lower corner of this bounding box.
     * @param xmin minimum x value of boundining box
     * @param ymin minimum y value of boundining box
     * @param zmin minimum z value of boundining box
     */
    public void setLower(double xmin, double ymin, double zmin ) {
        lower.x = xmin;
        lower.y = ymin;
        lower.z = zmin;

        updateBoundsStates();
    }
    
    /**
     * Sets the lower corner of this bounding box.
     * @param p1 a Point defining the new lower corner of the bounding box
     */
    public void setLower(Point3d p1) {

        lower.x = p1.x;
        lower.y = p1.y;
        lower.z = p1.z;

        updateBoundsStates();
    }

    /**
     * Gets the upper corner of this bounding box.
     * @param p1 a Point to receive the upper corner of the bounding box
     */
    public void getUpper(Point3d p1) {
        p1.x = upper.x;
        p1.y = upper.y;
        p1.z = upper.z;
    }

    /**
     * Sets the upper corner of this bounding box.
     * @param xmax max x value of boundining box
     * @param ymax max y value of boundining box
     * @param zmax max z value of boundining box
     */
    public void setUpper(double xmax, double ymax, double zmax ) {
        upper.x = xmax;
        upper.y = ymax;
        upper.z = zmax;

        updateBoundsStates();
    }

    /**
     * Sets the upper corner of this bounding box.
     * @param p1 a Point defining the new upper corner of the bounding box
     */
    public void setUpper(Point3d p1) {
        upper.x = p1.x;
        upper.y = p1.y;
        upper.z = p1.z;

        updateBoundsStates();
    }

    /**
     * Sets the the value of this BoundingBox
     * @param boundsObject another bounds object
     */
    public void set(Bounds  boundsObject) {
      int i;

      if(( boundsObject == null ) ||( boundsObject.boundsIsEmpty)) {
	  // Negative volume.
	  lower.x = lower.y = lower.z = 1.0d;
	  upper.x = upper.y = upper.z = -1.0d;
	  
      } else if( boundsObject.boundsIsInfinite ) {
	  lower.x = lower.y = lower.z = Double.NEGATIVE_INFINITY;
	  upper.x = upper.y = upper.z = Double.POSITIVE_INFINITY;
	  
      } else if( boundsObject.boundId == BOUNDING_BOX){
	  BoundingBox box = (BoundingBox)boundsObject;
	  
	  lower.x = box.lower.x;
	  lower.y = box.lower.y;
	  lower.z = box.lower.z;
	  upper.x = box.upper.x;
	  upper.y = box.upper.y;
	  upper.z = box.upper.z;
	  
      } else if( boundsObject.boundId == BOUNDING_SPHERE ) {
	  BoundingSphere sphere = (BoundingSphere)boundsObject;
	  lower.x = sphere.center.x - sphere.radius;
	  lower.y = sphere.center.y - sphere.radius;
	  lower.z = sphere.center.z - sphere.radius;
	  upper.x = sphere.center.x + sphere.radius;
	  upper.y = sphere.center.y + sphere.radius;
	  upper.z = sphere.center.z + sphere.radius;
	  
      } else if(boundsObject.boundId == BOUNDING_POLYTOPE) {
	  BoundingPolytope polytope = (BoundingPolytope)boundsObject;
	  lower.x = upper.x = polytope.verts[0].x;
	  lower.y = upper.y = polytope.verts[0].y;
	  lower.z = upper.z = polytope.verts[0].z;
	  
	  for(i=1;i<polytope.nVerts;i++) {
	      if( polytope.verts[i].x < lower.x ) lower.x = polytope.verts[i].x;
	      if( polytope.verts[i].y < lower.y ) lower.y = polytope.verts[i].y;
	      if( polytope.verts[i].z < lower.z ) lower.z = polytope.verts[i].z;
	      if( polytope.verts[i].x > upper.x ) upper.x = polytope.verts[i].x;
	      if( polytope.verts[i].y > upper.y ) upper.y = polytope.verts[i].y;
	      if( polytope.verts[i].z > upper.z ) upper.z = polytope.verts[i].z;
	  }
	  
      } else {
	  throw new IllegalArgumentException(J3dI18N.getString("BoundingBox0"));
      }

      updateBoundsStates();
    }


    /**
     * Creates a copy of this bounding box.
     * @return a new bounding box 
     */
    public Object clone() {
	return new BoundingBox(this.lower, this.upper);
    }


    /**
     * Indicates whether the specified <code>bounds</code> object is
     * equal to this BoundingBox object.  They are equal if the
     * specified <code>bounds</code> object is an instance of
     * BoundingBox and all of the data
     * members of <code>bounds</code> are equal to the corresponding
     * data members in this BoundingBox.
     * @param bounds the object with which the comparison is made.
     * @return true if this BoundingBox is equal to <code>bounds</code>;
     * otherwise false
     *
     * @since Java 3D 1.2
     */
    public boolean equals(Object bounds) {
	try {
	    BoundingBox box = (BoundingBox)bounds;
	    return (lower.equals(box.lower) &&
		    upper.equals(box.upper));
	}
	catch (NullPointerException e) {
	    return false;
	}
        catch (ClassCastException e) {
	    return false;
	}
    }


    /**
     * Returns a hash code value for this BoundingBox object
     * based on the data values in this object.  Two different
     * BoundingBox objects with identical data values (i.e.,
     * BoundingBox.equals returns true) will return the same hash
     * code value.  Two BoundingBox objects with different data
     * members may return the same hash code value, although this is
     * not likely.
     * @return a hash code value for this BoundingBox object.
     *
     * @since Java 3D 1.2
     */
    public int hashCode() {
	long bits = 1L;
	bits = 31L * bits + HashCodeUtil.doubleToLongBits(lower.x);
	bits = 31L * bits + HashCodeUtil.doubleToLongBits(lower.y);
	bits = 31L * bits + HashCodeUtil.doubleToLongBits(lower.z);
	bits = 31L * bits + HashCodeUtil.doubleToLongBits(upper.x);
	bits = 31L * bits + HashCodeUtil.doubleToLongBits(upper.y);
	bits = 31L * bits + HashCodeUtil.doubleToLongBits(upper.z);
	return (int) (bits ^ (bits >> 32));
    }


    /** 
     * Combines this bounding box with a bounding object   so that the
     * resulting bounding box encloses the original bounding box and the
     * specified bounds object.
     * @param boundsObject another bounds object
     */
    public void combine(Bounds boundsObject) {
	
	if((boundsObject == null) || (boundsObject.boundsIsEmpty)
	   || (boundsIsInfinite)) 
	    return;
	
	if((boundsIsEmpty) || (boundsObject.boundsIsInfinite)) {
	    this.set(boundsObject);
	    return;
	}
	
	if( boundsObject.boundId == BOUNDING_BOX){
	    BoundingBox box = (BoundingBox)boundsObject;
	    
	    if( lower.x > box.lower.x) lower.x = box.lower.x;
	    if( lower.y > box.lower.y) lower.y = box.lower.y;
	    if( lower.z > box.lower.z) lower.z = box.lower.z;
	    if( upper.x < box.upper.x) upper.x = box.upper.x;
	    if( upper.y < box.upper.y) upper.y = box.upper.y;
	    if( upper.z < box.upper.z) upper.z = box.upper.z;
	    
	}
	else if( boundsObject.boundId == BOUNDING_SPHERE ) {
	    BoundingSphere sphere = (BoundingSphere)boundsObject;
	    if( lower.x > (sphere.center.x - sphere.radius))
		lower.x = sphere.center.x - sphere.radius;
	    if( lower.y > (sphere.center.y - sphere.radius))
		lower.y = sphere.center.y - sphere.radius;
	    if( lower.z > (sphere.center.z - sphere.radius))
		lower.z = sphere.center.z - sphere.radius;
	    if( upper.x < (sphere.center.x + sphere.radius))
		upper.x = sphere.center.x + sphere.radius;
	    if( upper.y < (sphere.center.y + sphere.radius))
		upper.y = sphere.center.y + sphere.radius;
	    if( upper.z < (sphere.center.z + sphere.radius))
		upper.z = sphere.center.z + sphere.radius;
	    
	}
	else if(boundsObject.boundId == BOUNDING_POLYTOPE) {
	    BoundingPolytope polytope = (BoundingPolytope)boundsObject;
	    int i;
	    for(i=1;i<polytope.nVerts;i++) {
                if( polytope.verts[i].x < lower.x ) lower.x = polytope.verts[i].x;
                if( polytope.verts[i].y < lower.y ) lower.y = polytope.verts[i].y;
                if( polytope.verts[i].z < lower.z ) lower.z = polytope.verts[i].z;
                if( polytope.verts[i].x > upper.x ) upper.x = polytope.verts[i].x;
                if( polytope.verts[i].y > upper.y ) upper.y = polytope.verts[i].y;
                if( polytope.verts[i].z > upper.z ) upper.z = polytope.verts[i].z;
	    }
	} else {
	    throw new IllegalArgumentException(J3dI18N.getString("BoundingBox3"));
	}
	
        updateBoundsStates();
    }

    /** 
     * Combines this bounding box with an array of bounding objects  
     * so that the resulting bounding box encloses the original bounding 
     * box and the array of bounding objects.
     * @param bounds an array of bounds objects
     */
    public void combine(Bounds[] bounds) {
       int i=0;

       if( (bounds == null) || (bounds.length <= 0)
	   || (boundsIsInfinite))
	   return;
       
       // find first non empty bounds object
       while( (i<bounds.length) && ((bounds[i]==null) || bounds[i].boundsIsEmpty)) {
	   i++;
       }
       if( i >= bounds.length)
	   return;   // no non empty bounds so do not modify current bounds
       
       if(boundsIsEmpty)
	   this.set(bounds[i++]);

       if(boundsIsInfinite)
	   return;
       
       for(;i<bounds.length;i++) {
          if( bounds[i] == null );  // do nothing
          else if( bounds[i].boundsIsEmpty); // do nothing
	  else if( bounds[i].boundsIsInfinite ) {
	      lower.x = lower.y = lower.z = Double.NEGATIVE_INFINITY;
	      upper.x = upper.y = upper.z = Double.POSITIVE_INFINITY;
	      break;  // We're done.
	  }
	  else if( bounds[i].boundId == BOUNDING_BOX){
 	      BoundingBox box = (BoundingBox)bounds[i];
	      
              if( lower.x > box.lower.x) lower.x = box.lower.x;
              if( lower.y > box.lower.y) lower.y = box.lower.y;
              if( lower.z > box.lower.z) lower.z = box.lower.z;
              if( upper.x < box.upper.x) upper.x = box.upper.x;
              if( upper.y < box.upper.y) upper.y = box.upper.y;
              if( upper.z < box.upper.z) upper.z = box.upper.z;
	  }
	  else if( bounds[i].boundId == BOUNDING_SPHERE ) {
	      BoundingSphere sphere = (BoundingSphere)bounds[i];
              if( lower.x > (sphere.center.x - sphere.radius))
		  lower.x = sphere.center.x - sphere.radius;
              if( lower.y > (sphere.center.y - sphere.radius))
		  lower.y = sphere.center.y - sphere.radius;
              if( lower.z > (sphere.center.z - sphere.radius))
		  lower.z = sphere.center.z - sphere.radius;
              if( upper.x < (sphere.center.x + sphere.radius))
		  upper.x = sphere.center.x + sphere.radius;
              if( upper.y < (sphere.center.y + sphere.radius))
		  upper.y = sphere.center.y + sphere.radius;
              if( upper.z < (sphere.center.z + sphere.radius))
		  upper.z = sphere.center.z + sphere.radius;
	  }
	  else if(bounds[i].boundId == BOUNDING_POLYTOPE) {
 	      BoundingPolytope polytope = (BoundingPolytope)bounds[i];
              for(i=1;i<polytope.nVerts;i++) {
                if( polytope.verts[i].x < lower.x ) lower.x = polytope.verts[i].x;
                if( polytope.verts[i].y < lower.y ) lower.y = polytope.verts[i].y;
                if( polytope.verts[i].z < lower.z ) lower.z = polytope.verts[i].z;
                if( polytope.verts[i].x > upper.x ) upper.x = polytope.verts[i].x;
                if( polytope.verts[i].y > upper.y ) upper.y = polytope.verts[i].y;
                if( polytope.verts[i].z > upper.z ) upper.z = polytope.verts[i].z;
              }
	  } else {
	      throw new IllegalArgumentException(J3dI18N.getString("BoundingBox4"));
 	  }
       }
       
       updateBoundsStates();
    }

    /** 
     * Combines this bounding box with a point so that the resulting
     * bounding box encloses the original bounding box and the point.
     * @param point a 3d point in space 
     */
    public void combine(Point3d point) {

	if( boundsIsInfinite) {
	    return;
	}
	
	if( boundsIsEmpty) {
            upper.x = lower.x = point.x;
            upper.y = lower.y = point.y;
            upper.z = lower.z = point.z;
	} else {
	    if( point.x > upper.x) upper.x = point.x;
   	    if( point.y > upper.y) upper.y = point.y;
	    if( point.z > upper.z) upper.z = point.z;
	    
	    if( point.x < lower.x) lower.x = point.x;
	    if( point.y < lower.y) lower.y = point.y;
	    if( point.z < lower.z) lower.z = point.z;
	}
	
         updateBoundsStates();
    }

    /** 
     * Combines this bounding box with an array of points so that the
     * resulting bounding box encloses the original bounding box and the
     * array of points.
     * @param points an array of 3d points in space 
     */
    public void combine(Point3d[] points) {

	int i;

	if( boundsIsInfinite) {
	    return;
	}
	
	if( boundsIsEmpty) {
	    this.setUpper(points[0]);
	    this.setLower(points[0]);
	}
	
	for(i=0;i<points.length;i++) {
	    if( points[i].x > upper.x) upper.x = points[i].x;
	    if( points[i].y > upper.y) upper.y = points[i].y;
	    if( points[i].z > upper.z) upper.z = points[i].z;
	    
	    if( points[i].x < lower.x) lower.x = points[i].x;
	    if( points[i].y < lower.y) lower.y = points[i].y;
	    if( points[i].z < lower.z) lower.z = points[i].z;
	}
	
        updateBoundsStates();
    }

    /**
     * Modifies the bounding box so that it bounds the volume
     * generated by transforming the given bounding object.
     * @param boundsObject the bounding object to be transformed 
     * @param matrix a transformation matrix
     */
    public void transform( Bounds boundsObject, Transform3D matrix) {
	
	if( boundsObject == null || boundsObject.boundsIsEmpty)  {
	  // Negative volume.
	  lower.x = lower.y = lower.z = 1.0d;
	  upper.x = upper.y = upper.z = -1.0d;
	  updateBoundsStates();
	  return;
	}

	if(boundsObject.boundsIsInfinite) {
	    lower.x = lower.y = lower.z = Double.NEGATIVE_INFINITY;
	    upper.x = upper.y = upper.z = Double.POSITIVE_INFINITY;
	    updateBoundsStates();
	    return;
	}

	if(boundsObject.boundId == BOUNDING_BOX){
	    if (tmpBox == null) {
		tmpBox = new BoundingBox( (BoundingBox)boundsObject);
	    } else {
		tmpBox.set((BoundingBox)boundsObject);
	    }
	    tmpBox.transform(matrix);
	    this.set(tmpBox);
	}
	else if(boundsObject.boundId == BOUNDING_SPHERE) {
	    if (tmpSphere == null) {
	        tmpSphere = new BoundingSphere( (BoundingSphere)boundsObject);
	    } else {
	        tmpSphere.set((BoundingSphere)boundsObject);
	    }
	    tmpSphere.transform(matrix);
	    this.set(tmpSphere);   
	}
	else if(boundsObject.boundId == BOUNDING_POLYTOPE) {
	    if (tmpPolytope == null) {
	        tmpPolytope =
		    new BoundingPolytope((BoundingPolytope) boundsObject);
	    } else {
	        tmpPolytope.set((BoundingPolytope)boundsObject);
	    }
	    tmpPolytope.transform(matrix);
	    this.set(tmpPolytope);
	    
	}
	else {
	    throw new IllegalArgumentException(J3dI18N.getString("BoundingBox5"));
	}
    }

    /** 
     * Transforms this bounding box by the given matrix.
     * @param matrix a transformation matrix
     */
    public void transform(Transform3D matrix) {

	if(boundsIsInfinite)
	    return;
	
	double ux, uy, uz, lx, ly, lz;
	ux = upper.x; uy = upper.y; uz = upper.z;
	lx = lower.x; ly = lower.y; lz = lower.z;
	
	tmpP3d.set(ux, uy, uz);
	matrix.transform( tmpP3d );
	upper.x = tmpP3d.x;
	upper.y = tmpP3d.y;
	upper.z = tmpP3d.z;
	lower.x = tmpP3d.x;
	lower.y = tmpP3d.y;
	lower.z = tmpP3d.z;
	
	tmpP3d.set(lx, uy, uz);
	matrix.transform( tmpP3d );	
	if ( tmpP3d.x  > upper.x ) upper.x = tmpP3d.x;
	if ( tmpP3d.y  > upper.y ) upper.y = tmpP3d.y;
	if ( tmpP3d.z  > upper.z ) upper.z = tmpP3d.z;
	if ( tmpP3d.x  < lower.x ) lower.x = tmpP3d.x;
	if ( tmpP3d.y  < lower.y ) lower.y = tmpP3d.y;
	if ( tmpP3d.z  < lower.z ) lower.z = tmpP3d.z;
	
	tmpP3d.set(lx, ly, uz);
	matrix.transform( tmpP3d );
	if ( tmpP3d.x  > upper.x ) upper.x = tmpP3d.x;
	if ( tmpP3d.y  > upper.y ) upper.y = tmpP3d.y;
	if ( tmpP3d.z  > upper.z ) upper.z = tmpP3d.z;
	if ( tmpP3d.x  < lower.x ) lower.x = tmpP3d.x;
	if ( tmpP3d.y  < lower.y ) lower.y = tmpP3d.y;
	if ( tmpP3d.z  < lower.z ) lower.z = tmpP3d.z;
	
	tmpP3d.set(ux, ly, uz);
	matrix.transform( tmpP3d );
	if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x;
	if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y;
	if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z;
	if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x;
	if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y;
	if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z;

	tmpP3d.set(lx, uy, lz);
	matrix.transform( tmpP3d );
	if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x;
	if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y;
	if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z;
	if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x;
	if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y;
	if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z;

	tmpP3d.set(ux, uy, lz);
	matrix.transform( tmpP3d );
	if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x;
	if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y;
	if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z;
	if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x;
	if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y;
	if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z;
	
	tmpP3d.set(lx, ly, lz);
	matrix.transform( tmpP3d );
	if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x;
	if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y;
	if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z;
	if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x;
	if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y;
	if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z;
	
	tmpP3d.set(ux, ly, lz);
	matrix.transform( tmpP3d );
	if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x;
	if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y;
	if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z;
	if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x;
	if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y;
	if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z;

    }
    
    /** 
     * Test for intersection with a ray.
     * @param origin the starting point of the ray   
     * @param direction the direction of the ray
     * @param position3 a point defining the location of the pick w= distance to pick
     * @return true or false indicating if an intersection occured 
     */
    boolean intersect(Point3d origin, Vector3d direction, Point4d position ) {
        double t1,t2,tmp,tnear,tfar,invDir,invMag;
	double dirx, diry, dirz;
	
	/*
	  System.err.println("BoundingBox.intersect(p,d,p) called\n");
	  System.err.println("bounds = " + lower + " -> " + upper);
	  */
	
	if( boundsIsEmpty ) {
	    return false;
	}

	if( boundsIsInfinite ) {
	    position.x = origin.x;
	    position.y = origin.y;
	    position.z = origin.z;
	    position.w = 0.0;
	    return true;
	}
       
	double dirLen = direction.x*direction.x + direction.y*direction.y +
	    direction.z*direction.z;
       
	// Handle zero length direction vector.
	if(dirLen == 0.0)
	    return intersect(origin, position);
       
	invMag = 1.0/Math.sqrt(dirLen);
	dirx = direction.x*invMag;
	diry = direction.y*invMag;
	dirz = direction.z*invMag;

	/*
	  System.err.println("dir = " + dirx + ", " + diry + ", " + dirz);
	  System.err.println("origin = " + origin);
	  */
	
	// initialize tnear and tfar to handle dir.? == 0 cases
	tnear = -Double.MAX_VALUE;
	tfar = Double.MAX_VALUE;
       
	if(dirx == 0.0) {
	    //System.err.println("dirx == 0.0");
	    if (origin.x < lower.x || origin.x > upper.x ) {
		//System.err.println( "parallel to x plane and outside");
		return false;
	    }
	} else {
	    invDir = 1.0/dirx;
	    t1 = (lower.x-origin.x)*invDir;
	    t2 = (upper.x-origin.x)*invDir;
	   
	    //System.err.println("x t1 = " + t1 + " t2 = " + t2);
	    if( t1 > t2) {
		tnear = t2;
		tfar = t1;
	    }else {
		tnear = t1;
		tfar = t2;
	    }
	    if( tfar < 0.0 ) {
		//System.err.println( "x failed: tnear="+tnear+"  tfar="+tfar);
		return false;
	    }
	    //System.err.println("x tnear = " + tnear + " tfar = " + tfar);
	}
	// y
	if (diry == 0.0) {
	    //System.err.println("diry == 0.0");
	    if( origin.y < lower.y || origin.y > upper.y ){
		//System.err.println( "parallel to y plane and outside");
		return false;
            }
	} else {
	    invDir = 1.0/diry;
	    //System.err.println("invDir = " + invDir);
	    t1 = (lower.y-origin.y)*invDir;
	    t2 = (upper.y-origin.y)*invDir;
	   
	    if( t1 > t2) {
		tmp = t1;
		t1 = t2;
		t2 = tmp;
	    }
	    //System.err.println("y t1 = " + t1 + " t2 = " + t2);
	    if( t1 > tnear) tnear = t1;
	    if( t2 < tfar ) tfar  = t2;
	   
	    if( (tfar < 0.0) ||  (tnear > tfar)){
		//System.err.println( "y failed: tnear="+tnear+"  tfar="+tfar);
		return false;
	    }
	    //System.err.println("y tnear = " + tnear + " tfar = " + tfar);
	}
       
	// z
	if (dirz == 0.0) {
	    //System.err.println("dirz == 0.0");
	    if( origin.z < lower.z || origin.z > upper.z ) {
		//System.err.println( "parallel to z plane and outside");
		return false;
	    }
	}  else {
	    invDir = 1.0/dirz;
	    t1 = (lower.z-origin.z)*invDir;
	    t2 = (upper.z-origin.z)*invDir;
	   
	    if( t1 > t2) {
		tmp = t1;
		t1 = t2;
		t2 = tmp;
	    }
	    //System.err.println("z t1 = " + t1 + " t2 = " + t2);
	    if( t1 > tnear) tnear = t1;
	    if( t2 < tfar ) tfar  = t2;
	   
	    if( (tfar < 0.0) ||  (tnear > tfar)){
		//System.err.println( "z failed: tnear="+tnear+"  tfar="+tfar);
		return false;
	    }
	    //System.err.println("z tnear = " + tnear + " tfar = " + tfar);
	}
       
	if((tnear < 0.0) && (tfar >= 0.0)) {
	    // origin is inside the BBox.
	    position.x = origin.x + dirx*tfar;
	    position.y = origin.y + diry*tfar;
	    position.z = origin.z + dirz*tfar;
	    position.w = tfar;
	}
	else {
	    position.x = origin.x + dirx*tnear;
	    position.y = origin.y + diry*tnear;
	    position.z = origin.z + dirz*tnear;
	    position.w = tnear;
	}
       
	return true;
       
    }
    

    /** 
     * Test for intersection with a point.
     * @param point the pick point   
     * @param position a point defining the location  of the pick w= distance to pick
     * @return true or false indicating if an intersection occured 
     */
    boolean intersect(Point3d point,  Point4d position ) {

        if( boundsIsEmpty ) {
	   return false;
        }

       if( boundsIsInfinite ) {
	   position.x = point.x;
	   position.y = point.y;
	   position.z = point.z;
	   position.w = 0.0;
	   return true;
       }

       if( point.x <= upper.x && point.x >= lower.x &&
	   point.y <= upper.y && point.y >= lower.y &&
	   point.z <= upper.z && point.z >= lower.z)  {
	   position.x = point.x;
	   position.y = point.y;
	   position.z = point.z;
	   position.w = 0.0;
	   return true;
       } else
	   return false;
       
    }
    
   /**
    * Test for intersection with a segment.
    * @param start a point defining  the start of the line segment 
    * @param end a point defining the end of the line segment 
    * @param position a point defining the location  of the pick w= distance to pick
    * @return true or false indicating if an intersection occured
    */
  boolean intersect( Point3d start, Point3d end, Point4d position ) {
      double t1,t2,tmp,tnear,tfar,invDir,invMag;
      double dirx, diry, dirz;
      
      if( boundsIsEmpty ) {
	  return false;
      }

      if( boundsIsInfinite ) {
	  position.x = start.x;
	  position.y = start.y;
	  position.z = start.z;
	  position.w = 0.0;
	  return true;
      }
      
      dirx = end.x - start.x;
      diry = end.y - start.y;
      dirz = end.z - start.z;
      
      double dirLen = dirx*dirx + diry*diry + dirz*dirz;
      
      // Optimization : Handle zero length direction vector.
      if(dirLen == 0.0)
	  return intersect(start, position);
       
      dirLen = Math.sqrt(dirLen);
      // System.err.println("dirLen is " + dirLen);
      invMag = 1.0/dirLen;
      dirx = dirx*invMag;
      diry = diry*invMag;
      dirz = dirz*invMag;
      
      /*
	System.err.println("dir = " + dir);
	System.err.println("start = " + start);
	System.err.println("lower = " + lower);
	System.err.println("upper = " + upper);
	*/
      
      // initialize tnear and tfar to handle dir.? == 0 cases
      tnear = -Double.MAX_VALUE;
      tfar = Double.MAX_VALUE;
      
        if(dirx == 0.0) {
            //System.err.println("dirx == 0.0");
            if (start.x < lower.x || start.x > upper.x ) {
		//System.err.println( "parallel to x plane and outside");
		return false;
            }
	} else {
            invDir = 1.0/dirx;
            t1 = (lower.x-start.x)*invDir;
            t2 = (upper.x-start.x)*invDir;
	    
            //System.err.println("x t1 = " + t1 + " t2 = " + t2);
	    if( t1 > t2) {
		tnear = t2;
		tfar = t1;
	    }else {
		tnear = t1;
		tfar = t2;
	    }
	    if( tfar < 0.0 ) {
		//System.err.println( "x failed: tnear="+tnear+"  tfar="+tfar);
		return false;
	    }
            //System.err.println("x tnear = " + tnear + " tfar = " + tfar);
        }
	// y
        if (diry == 0.0) {
            //System.err.println("diry == 0.0");
            if( start.y < lower.y || start.y > upper.y ){
		//System.err.println( "parallel to y plane and outside");
		return false;
            }
        } else {
            invDir = 1.0/diry;
            //System.err.println("invDir = " + invDir);
            t1 = (lower.y-start.y)*invDir;
            t2 = (upper.y-start.y)*invDir;
	    
            if( t1 > t2) {
		tmp = t1;
		t1 = t2;
		t2 = tmp;
            }
            //System.err.println("y t1 = " + t1 + " t2 = " + t2);
            if( t1 > tnear) tnear = t1;
            if( t2 < tfar ) tfar  = t2;
	    
            if( (tfar < 0.0) ||  (tnear > tfar)){
		//System.err.println( "y failed: tnear="+tnear+"  tfar="+tfar);
		return false;
            }
            //System.err.println("y tnear = " + tnear + " tfar = " + tfar);
        }
	
	// z
        if (dirz == 0.0) {
            //System.err.println("dirz == 0.0");
            if( start.z < lower.z || start.z > upper.z ) {
		//System.err.println( "parallel to z plane and outside");
		return false;
            }
        }  else {
            invDir = 1.0/dirz;
            t1 = (lower.z-start.z)*invDir;
            t2 = (upper.z-start.z)*invDir;
	    
            if( t1 > t2) {
		tmp = t1;
		t1 = t2;
		t2 = tmp;
            }
            //System.err.println("z t1 = " + t1 + " t2 = " + t2);
            if( t1 > tnear) tnear = t1;
            if( t2 < tfar ) tfar  = t2;
	    
            if( (tfar < 0.0) ||  (tnear > tfar)){
		//System.err.println( "z failed: tnear="+tnear+"  tfar="+tfar);
		return false;
            }
            //System.err.println("z tnear = " + tnear + " tfar = " + tfar);
        }
	
	if((tnear < 0.0) && (tfar >= 0.0)) {
	    // origin is inside the BBox.
 	    position.x = start.x + dirx*tfar;
	    position.y = start.y + diry*tfar;
	    position.z = start.z + dirz*tfar;
	    position.w = tfar;
	}
	else {
	    if(tnear>dirLen) {
		// Segment is behind BBox.
		/*
		  System.err.println("PickSegment : intersected postion : " + position
		  + " tnear " + tnear + " tfar " + tfar );
		  */
		return false;
	    }
	    position.x = start.x + dirx*tnear;
            position.y = start.y + diry*tnear;
            position.z = start.z + dirz*tnear;

            position.w = tnear;
        }
	
	/*  
	    System.err.println("tnear = " + tnear + " tfar = " + tfar + " w " +
	    position.w);
	    System.err.println("lower = " + lower);
	    System.err.println("upper = " + upper + "\n");
	*/
        return true;
	
  }

    /** 
     * Test for intersection with a ray.
     * @param origin the starting point of the ray   
     * @param direction the direction of the ray
     * @return true or false indicating if an intersection occured 
     */
    public boolean intersect(Point3d origin, Vector3d direction ) {
	
        if( boundsIsEmpty ) {
	    return false;
        }
	
	if( boundsIsInfinite ) {
	    return true;
	}

	Point3d p=new Point3d();
	return intersect( origin, direction, p );
    }
    
    /**
     * A protected intersect method that returns the point of intersection.
     * Used by Picking methods to sort or return closest picked item.
     */
    boolean intersect(Point3d origin, Vector3d direction, Point3d intersect ) {
	double theta=0.0;

        if( boundsIsEmpty ) {
	   return false;
        }

	if( boundsIsInfinite ) {
	  intersect.x = origin.x;
	  intersect.y = origin.y;
	  intersect.z = origin.z;
	  return true;
	}

	if (direction.x > 0.0 )
	    theta = Math.max( theta, (lower.x - origin.x)/direction.x );
	if (direction.x < 0.0 )
	    theta = Math.max( theta, (upper.x - origin.x)/direction.x );
	if (direction.y > 0.0 )
	    theta = Math.max( theta, (lower.y - origin.y)/direction.y );
	if (direction.y < 0.0 )
	    theta = Math.max( theta, (upper.y - origin.y)/direction.y );
	if (direction.z > 0.0 )
	    theta = Math.max( theta, (lower.z - origin.z)/direction.z );
	if (direction.z < 0.0 )
	    theta = Math.max( theta, (upper.z - origin.z)/direction.z );

	intersect.x = origin.x + theta*direction.x;
	intersect.y = origin.y + theta*direction.y;
	intersect.z = origin.z + theta*direction.z;

	if (intersect.x < (lower.x-EPS)) return false;
	if (intersect.x > (upper.x+EPS)) return false;
	if (intersect.y < (lower.y-EPS)) return false;
	if (intersect.y > (upper.y+EPS)) return false;
	if (intersect.z < (lower.z-EPS)) return false;
	if (intersect.z > (upper.z+EPS)) return false;

        return true;

    }

    /** 
     * Test for intersection with a point.
     * @param point a point defining a position in 3-space 
     * @return true or false indicating if an intersection occured 
     */
    public boolean intersect(Point3d point ) {

        if( boundsIsEmpty ) {
	   return false;
        }
	if( boundsIsInfinite ) {
	  return true;
	}

	if( point.x <= upper.x && point.x >= lower.x &&
	    point.y <= upper.y && point.y >= lower.y &&
	    point.z <= upper.z && point.z >= lower.z) 
	    return true;
        else
	    return false;
    }
    /**
     * Tests whether the bounding box is empty.  A bounding box is
     * empty if it is null (either by construction or as the result of
     * a null intersection) or if its volume is negative.  A bounding box
     * with a volume of zero is <i>not</i> empty.
     * @return true if the bounding box is empty; otherwise, it returns false
     */
    public boolean isEmpty() {

	 return boundsIsEmpty;
    }

   /** 
     * Test for intersection with another bounds object.
     * @param boundsObject another bounds object 
     * @return true or false indicating if an intersection occured 
     */
    boolean intersect(Bounds boundsObject, Point4d position) {
	return intersect(boundsObject);
    }

    /** 
     * Test for intersection with another bounds object. 
     * @param boundsObject another bounds object 
     * @return true or false indicating if an intersection occured 
     */
    public boolean intersect(Bounds boundsObject) {
            
	if( boundsObject == null ) {
	    return false;
	}

        if( boundsIsEmpty || boundsObject.boundsIsEmpty ) {
	    return false;
        }

	if( boundsIsInfinite || boundsObject.boundsIsInfinite ) {
	    return true;
	}

	if( boundsObject.boundId == BOUNDING_BOX){
	    BoundingBox box = (BoundingBox)boundsObject;
	    // both boxes are axis aligned
	    if( upper.x > box.lower.x && box.upper.x > lower.x &&
		upper.y > box.lower.y && box.upper.y > lower.y &&
		upper.z > box.lower.z && box.upper.z > lower.z )
		return true;
	    else
		return false;
	} else if( boundsObject.boundId == BOUNDING_SPHERE) {
	    BoundingSphere sphere = (BoundingSphere)boundsObject;
	    double rad_sq = sphere.radius*sphere.radius;
	    double dis = 0.0;
	      
	    if( sphere.center.x < lower.x )
		dis = (sphere.center.x-lower.x)*(sphere.center.x-lower.x);
	    else
		if( sphere.center.x > upper.x )
		    dis = (sphere.center.x-upper.x)*(sphere.center.x-upper.x);

	    if( sphere.center.y < lower.y )
		dis += (sphere.center.y-lower.y)*(sphere.center.y-lower.y);
	    else
		if( sphere.center.y > upper.y )
		    dis += (sphere.center.y-upper.y)*(sphere.center.y-upper.y);

	    if( sphere.center.z < lower.z )
		dis += (sphere.center.z-lower.z)*(sphere.center.z-lower.z);
	    else
		if( sphere.center.z > upper.z )
		    dis += (sphere.center.z-upper.z)*(sphere.center.z-upper.z);
                      
	    if( dis <= rad_sq ) 
		return true;
	    else 
		return false; 
        } else if(boundsObject.boundId == BOUNDING_POLYTOPE) {
	    // intersect an axis aligned box with a polytope
	    return intersect_ptope_abox ( (BoundingPolytope)boundsObject, this );
        } else {
            throw new IllegalArgumentException(J3dI18N.getString("BoundingBox6"));    
        }    

    }

    /** 
     * Test for intersection with an array of bounds objects.
     * @param boundsObjects an array of bounding objects 
     * @return true or false indicating if an intersection occured 
     */
    public boolean intersect(Bounds[] boundsObjects) {

	double distsq, radsq;
	int i;
	
	if( boundsObjects == null || boundsObjects.length <= 0  )  {
	    return false;
	}
	
	if( boundsIsEmpty ) {
	    return false;
	}
	
	for(i = 0; i < boundsObjects.length; i++){
	    if( boundsObjects[i] == null || boundsObjects[i].boundsIsEmpty) ;	    
	    else if( boundsIsInfinite || boundsObjects[i].boundsIsInfinite ) {
		return true; // We're done here.
	    }
	    else if( boundsObjects[i].boundId == BOUNDING_BOX){
		BoundingBox box = (BoundingBox)boundsObjects[i];
		// both boxes are axis aligned
		if( upper.x > box.lower.x && box.upper.x > lower.x &&
		    upper.y > box.lower.y && box.upper.y > lower.y &&
		    upper.z > box.lower.z && box.upper.z > lower.z )
		    return true;
	    }
	    else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) {
		BoundingSphere sphere = (BoundingSphere)boundsObjects[i];
		double rad_sq = sphere.radius*sphere.radius;
		double dis = 0.0;
	       
		if( sphere.center.x < lower.x )
		    dis = (sphere.center.x-lower.x)*(sphere.center.x-lower.x);
		else
		    if( sphere.center.x > upper.x )
			dis = (sphere.center.x-upper.x)*(sphere.center.x-upper.x);
	       
		if( sphere.center.y < lower.y )
		    dis += (sphere.center.y-lower.y)*(sphere.center.y-lower.y);
		else
		    if( sphere.center.y > upper.y )
			dis += (sphere.center.y-upper.y)*(sphere.center.y-upper.y);
		
		if( sphere.center.z < lower.z )
		    dis += (sphere.center.z-lower.z)*(sphere.center.z-lower.z);
		else
		    if( sphere.center.z > upper.z )
			dis += (sphere.center.z-upper.z)*(sphere.center.z-upper.z);
                      
		if( dis <= rad_sq ) 
		    return true;

	    }
	    else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) {
		if( intersect_ptope_abox ( (BoundingPolytope)boundsObjects[i], this ))
		    return true;
	    }
	    else {
		//	       System.err.println("intersect ?? ");
	    }
	}
	
	return false;
    }
    
    /** 
     * Test for intersection with another bounding box.
     * @param boundsObject another bounding object 
     * @param newBoundBox the new bounding box which is the intersection of
     *        the boundsObject and this BoundingBox
     * @return true or false indicating if an intersection occured 
     */
    public boolean intersect(Bounds boundsObject, BoundingBox newBoundBox) {

	if((boundsObject == null) || boundsIsEmpty || boundsObject.boundsIsEmpty ) {
	    // Negative volume.
	    newBoundBox.setLower( 1.0d,  1.0d,  1.0d);
	    newBoundBox.setUpper(-1.0d, -1.0d, -1.0d);
	    return false;
        }


	if(boundsIsInfinite && (!boundsObject.boundsIsInfinite)) {
	    newBoundBox.set(boundsObject);
	    return true;
	}
	else if((!boundsIsInfinite) && boundsObject.boundsIsInfinite) {
	    newBoundBox.set(this);
	    return true;
	}
	else if(boundsIsInfinite && boundsObject.boundsIsInfinite) {
	    newBoundBox.set(this);
	    return true;
	}
	else if( boundsObject.boundId == BOUNDING_BOX){
	    BoundingBox box = (BoundingBox)boundsObject;
	    // both boxes are axis aligned
	    if( upper.x > box.lower.x && box.upper.x > lower.x &&
		upper.y > box.lower.y && box.upper.y > lower.y &&
		upper.z > box.lower.z && box.upper.z > lower.z ){

               
		if(upper.x > box.upper.x) 
		    newBoundBox.upper.x = box.upper.x; 
		else
		    newBoundBox.upper.x = upper.x; 

		if(upper.y > box.upper.y) 
		    newBoundBox.upper.y = box.upper.y; 
		else
		    newBoundBox.upper.y = upper.y; 

		if(upper.z > box.upper.z) 
		    newBoundBox.upper.z = box.upper.z; 
		else
		    newBoundBox.upper.z = upper.z; 

		if(lower.x < box.lower.x) 
		    newBoundBox.lower.x = box.lower.x; 
		else
		    newBoundBox.lower.x = lower.x; 

		if(lower.y < box.lower.y) 
		    newBoundBox.lower.y = box.lower.y; 
		else
		    newBoundBox.lower.y = lower.y; 

		if(lower.z < box.lower.z) 
		    newBoundBox.lower.z = box.lower.z; 
		else
		    newBoundBox.lower.z = lower.z; 

		newBoundBox.updateBoundsStates();
		return true;
	    } else {
		// Negative volume.
		newBoundBox.setLower( 1.0d,  1.0d,  1.0d);
		newBoundBox.setUpper(-1.0d, -1.0d, -1.0d);
		return false;
	    }
        }
	else if( boundsObject.boundId == BOUNDING_SPHERE) {
	    BoundingSphere sphere = (BoundingSphere)boundsObject;
	    if( this.intersect( sphere) ) {
		BoundingBox sbox = new BoundingBox( sphere );
		this.intersect( sbox, newBoundBox );
		return true;
	    } else {
		// Negative volume.
		newBoundBox.setLower( 1.0d,  1.0d,  1.0d);
		newBoundBox.setUpper(-1.0d, -1.0d, -1.0d);
		return false;
	    }
	    
            //      System.err.println("intersect Sphere ");
        }
	else if(boundsObject.boundId == BOUNDING_POLYTOPE) {
	    BoundingPolytope polytope = (BoundingPolytope)boundsObject;
	    if( this.intersect( polytope)) {
		BoundingBox pbox = new BoundingBox( polytope); // convert polytope to box
		this.intersect( pbox, newBoundBox );
		return true;
	    } else {
		// Negative volume.
		newBoundBox.setLower( 1.0d,  1.0d,  1.0d);
		newBoundBox.setUpper(-1.0d, -1.0d, -1.0d);
		return false;
	    }
        } else {
            throw new IllegalArgumentException(J3dI18N.getString("BoundingBox7"));    
        }    
    }

    /** 
     * Test for intersection with an array of  bounds objects.
     * @param boundsObjects an array of  bounds objects 
     * @param newBoundBox the new bounding box which is the intersection of
     *	      the boundsObject and this BoundingBox
     * @return true or false indicating if an intersection occured 
     */
    public boolean intersect(Bounds[] boundsObjects, BoundingBox newBoundBox) {

       if( boundsObjects == null || boundsObjects.length <= 0 ||  boundsIsEmpty ) {
	    // Negative volume.
	    newBoundBox.setLower( 1.0d,  1.0d,  1.0d);
	    newBoundBox.setUpper(-1.0d, -1.0d, -1.0d);
	    return false;
       }

       int i=0;
       // find first non null bounds object
       while( boundsObjects[i] == null && i < boundsObjects.length) {
	   i++;
       }
 
       if( i >= boundsObjects.length ) { // all bounds objects were empty
	   // Negative volume.
	   newBoundBox.setLower( 1.0d,  1.0d,  1.0d);
	   newBoundBox.setUpper(-1.0d, -1.0d, -1.0d);
	   return false;
       } 
       
       
       boolean status = false;
       BoundingBox tbox = new BoundingBox();

       for(;i<boundsObjects.length;i++) {
	   if( boundsObjects[i] == null || boundsObjects[i].boundsIsEmpty) ;
	   else if( boundsObjects[i].boundId == BOUNDING_BOX){
	       BoundingBox box = (BoundingBox)boundsObjects[i];
	       // both boxes are axis aligned
	       if( upper.x > box.lower.x && box.upper.x > lower.x &&
		   upper.y > box.lower.y && box.upper.y > lower.y &&
		   upper.z > box.lower.z && box.upper.z > lower.z ){
               
		   if(upper.x > box.upper.x) 
		       newBoundBox.upper.x = box.upper.x; 
		   else
		       newBoundBox.upper.x = upper.x; 

		   if(upper.y > box.upper.y) 
		       newBoundBox.upper.y = box.upper.y; 
		   else
		       newBoundBox.upper.y = upper.y; 

		   if(upper.z > box.upper.z) 
		       newBoundBox.upper.z = box.upper.z; 
		   else
		       newBoundBox.upper.z = upper.z; 

		   if(lower.x < box.lower.x) 
		       newBoundBox.lower.x = box.lower.x; 
		   else
		       newBoundBox.lower.x = lower.x; 

		   if(lower.y < box.lower.y) 
		       newBoundBox.lower.y = box.lower.y; 
		   else
		       newBoundBox.lower.y = lower.y; 

		   if(lower.z < box.lower.z) 
		       newBoundBox.lower.z = box.lower.z; 
		   else
		       newBoundBox.lower.z = lower.z; 
		   status = true;
		   newBoundBox.updateBoundsStates();
	       } 
	   }
	   else if( boundsObjects[i].boundId == BOUNDING_SPHERE) {
	       BoundingSphere sphere = (BoundingSphere)boundsObjects[i];
	       if( this.intersect(sphere)) {
		   BoundingBox sbox = new BoundingBox( sphere ); // convert sphere to box 
		   this.intersect(sbox,tbox); // insersect two boxes
		   if( status ) {
		       newBoundBox.combine( tbox );
		   } else {
		       newBoundBox.set( tbox );
		       status = true;
		   }
	       }

	   }
	   else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) {
	       BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i];
	       if( this.intersect( polytope)) {
		   BoundingBox pbox = new BoundingBox( polytope ); // convert polytope to box 
		   this.intersect(pbox,tbox); // insersect two boxes
		   if ( status ) {
		       newBoundBox.combine( tbox );                          
		   } else {
		       newBoundBox.set( tbox );                              
		       status = true;
		   }
	       }
	   } else {
	       throw new IllegalArgumentException(J3dI18N.getString("BoundingBox6"));    
	   }
	   
	   if(newBoundBox.boundsIsInfinite)
	       break; // We're done. 
       }
       if( status == false ) {
	   // Negative volume.
	   newBoundBox.setLower( 1.0d,  1.0d,  1.0d);
	   newBoundBox.setUpper(-1.0d, -1.0d, -1.0d);
       } 
       return status;
    }


    /** 
     * Finds closest bounding object that intersects this bounding box.
     * @param boundsObjects an array of bounds objects 
     * @return closest bounding object 
     */
    public Bounds closestIntersection( Bounds[] boundsObjects) {

	if( boundsObjects == null || boundsObjects.length <= 0  ) {
	    return null;
        }

        if( boundsIsEmpty ) {
	    return null;
        }

	getCenter();
	
	double dis,far_dis,pdist,x,y,z,rad_sq;
	double cenX = 0.0, cenY = 0.0, cenZ = 0.0;
	boolean contains = false;
	boolean inside;
	boolean intersect = false;
	double smallest_distance = Double.MAX_VALUE; 
	int i,j,index=0;

	for(i = 0; i < boundsObjects.length; i++){
	    if( boundsObjects[i] == null ) ;

	    else if( this.intersect( boundsObjects[i])) {
		intersect = true;
		if( boundsObjects[i].boundId == BOUNDING_BOX){
		    BoundingBox box = (BoundingBox)boundsObjects[i];
		    cenX = (box.upper.x+box.lower.x)/2.0;
		    cenY = (box.upper.y+box.lower.y)/2.0;
		    cenZ = (box.upper.z+box.lower.z)/2.0;
		    dis = Math.sqrt( (centroid.x-cenX)*(centroid.x-cenX) +
				     (centroid.y-cenY)*(centroid.y-cenY) +
				     (centroid.z-cenZ)*(centroid.z-cenZ) );
		    inside = false;
		    
		    if( lower.x <= box.lower.x &&
			lower.y <= box.lower.y &&
			lower.z <= box.lower.z &&
			upper.x >= box.upper.x &&
			upper.y >= box.upper.y &&
			upper.z >= box.upper.z ) { // box is contained 
			inside = true;
		    }
		    if( inside ) {
			if( !contains ){ // initialize smallest_distance for the first containment
			    index = i;
			    smallest_distance = dis;
			    contains = true;
			} else{
			    if( dis < smallest_distance){
				index = i;
				smallest_distance = dis;
			    }
			}
		    } else if (!contains) {
			if( dis < smallest_distance){
			    index = i;
			    smallest_distance = dis;
			}
		    }
		    
		}
		else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) {
		    BoundingSphere sphere = (BoundingSphere)boundsObjects[i];
		    dis = Math.sqrt( (centroid.x-sphere.center.x)*
				     (centroid.x-sphere.center.x) +
				     (centroid.y-sphere.center.y)*
				     (centroid.y-sphere.center.y) +
				     (centroid.z-sphere.center.z)*
				     (centroid.z-sphere.center.z) );
		    
		    inside = false;
		    
		    // sphere sphere.center is inside box
		    if(sphere.center.x <= upper.x && sphere.center.x >= lower.x &&
		       sphere.center.y <= upper.y && sphere.center.y >= lower.y &&
		       sphere.center.z <= upper.z && sphere.center.z >= lower.z ) { 
			// check if sphere intersects any side 
			if (sphere.center.x - lower.x >= sphere.radius && 
			    upper.x - sphere.center.x >= sphere.radius &&
			    sphere.center.y - lower.y >= sphere.radius &&
			    upper.y - sphere.center.y >= sphere.radius &&
			    sphere.center.z - lower.z >= sphere.radius &&
			    upper.z - sphere.center.z >= sphere.radius  ) {
			    // contains the sphere
			    inside = true;
			}
		    }
		    if (inside ) {
			// initialize smallest_distance for the first containment
			if( !contains ){
			    index = i;
			    smallest_distance = dis;
			    contains = true;
			} else{
			    if( dis < smallest_distance){
				index = i;
				smallest_distance = dis;
			    }
			}
		    } else if (!contains) { 
			if( dis < smallest_distance){
			    index = i;
			    smallest_distance = dis;
			}
		    }
		}
		else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) {
		    BoundingPolytope polytope =
			(BoundingPolytope)boundsObjects[i];
		    dis = Math.sqrt( (centroid.x-polytope.centroid.x)*
				     (centroid.x-polytope.centroid.x) +
				     (centroid.y-polytope.centroid.y)*
				     (centroid.y-polytope.centroid.y) +
				     (centroid.z-polytope.centroid.z)*
				     (centroid.z-polytope.centroid.z) );
		    inside = true;
		    for(j=0;j<polytope.nVerts;j++) {
			if( polytope.verts[j].x < lower.x ||
			    polytope.verts[j].y < lower.y ||
			    polytope.verts[j].z < lower.z ||
			    polytope.verts[j].x > upper.x ||
			    polytope.verts[j].y > upper.y ||
			    polytope.verts[j].z > upper.z ) { // box contains polytope
			    inside = false;
			    
			}
			
		    }
		    if( inside ) { 
			if( !contains ){ // initialize smallest_distance for the first containment
			    index = i;
			    smallest_distance = dis;
			    contains = true;
			} else{
			    if( dis < smallest_distance){
				index = i;
				smallest_distance = dis;
			    }
			}
		    } else if (!contains) {
			if( dis < smallest_distance){
			    index = i;
			    smallest_distance = dis;
			}
		    }
		    
		} else {
		    throw new IllegalArgumentException(J3dI18N.getString("BoundingBox9"));    
		}
	    }
	}
	
	if ( intersect ) 
            return boundsObjects[index];
	else 
	    return null;
    }
    
    /** 
     * Tests for intersection of box and frustum.
     * @param frustum  
     * @return true if they intersect 
     */
    boolean intersect(CachedFrustum frustum ) {

	if (boundsIsEmpty)
	    return false;

	if(boundsIsInfinite)
	    return true;
	
	// System.err.println("intersect frustum with box="+this.toString()); 
	// System.err.println("frustum "+frustum.toString()); 
	// check if box and bounding box  of frustum intersect 
        if ((upper.x < frustum.lower.x) || 
	    (lower.x > frustum.upper.x) ||
            (upper.y < frustum.lower.y) || 
	    (lower.y > frustum.upper.y) ||
            (upper.z < frustum.lower.z) ||
	    (lower.z > frustum.upper.z) ) {
	    
	    // System.err.println("*** box and bounding box of frustum do not intersect");
	    return false;
	}

	// check if all box points out any frustum plane  
	int i = 5;
	while (i>=0){
	    Vector4d vc = frustum.clipPlanes[i--];
	    if ((( upper.x*vc.x + upper.y*vc.y + 
		   upper.z*vc.z + vc.w ) < 0.0 ) &&
		(( upper.x*vc.x + lower.y*vc.y + 
		   upper.z*vc.z + vc.w ) < 0.0 ) &&
		(( upper.x*vc.x + lower.y*vc.y + 
		   lower.z*vc.z + vc.w ) < 0.0 ) &&
		(( upper.x*vc.x + upper.y*vc.y + 
		   lower.z*vc.z + vc.w ) < 0.0 ) &&
		(( lower.x*vc.x + upper.y*vc.y + 
		   upper.z*vc.z + vc.w ) < 0.0 ) &&
		(( lower.x*vc.x + lower.y*vc.y + 
		   upper.z*vc.z + vc.w ) < 0.0 ) &&
		(( lower.x*vc.x + lower.y*vc.y + 
		   lower.z*vc.z + vc.w ) < 0.0 ) &&
		(( lower.x*vc.x +  upper.y*vc.y + 
		   lower.z*vc.z + vc.w ) < 0.0 )) {
		// all corners outside this frustum plane
		// System.err.println("*** all corners outside this frustum plane");
		return false;
	    }
	}
	    
	return true;
    }

    /** 
     * Returns a string representation of this class.
     */ 
    public String toString() { 
	return new String( "Bounding box: Lower="+lower.x+" "+
			   lower.y+" "+lower.z+" Upper="+upper.x+" "+
			   upper.y+" "+upper.z  ); 
    } 

    private void updateBoundsStates() {
	if((lower.x == Double.NEGATIVE_INFINITY) &&
	   (lower.y == Double.NEGATIVE_INFINITY) &&
	   (lower.z == Double.NEGATIVE_INFINITY) &&
	   (upper.x == Double.POSITIVE_INFINITY) &&
	   (upper.y == Double.POSITIVE_INFINITY) &&
	   (upper.z == Double.POSITIVE_INFINITY)) {
	    boundsIsEmpty = false;
	    boundsIsInfinite = true;
	    return;
	}

	if (checkBoundsIsNaN()) {
	     boundsIsEmpty = true;
	     boundsIsInfinite = false;	     
	     return;
	}
	else {
	    boundsIsInfinite = false;
	    if( lower.x > upper.x ||
		lower.y > upper.y ||
		lower.z > upper.z ) {
		boundsIsEmpty = true;
	    } else {
		boundsIsEmpty = false;
	    }   
	}
    }
    
    // For a infinite bounds. What is the centroid ?    
     Point3d getCenter() {
	 if(centroid == null) {
	     centroid = new Point3d();
	 }
	 
         centroid.x = (upper.x+lower.x)*0.5;
         centroid.y = (upper.y+lower.y)*0.5;
         centroid.z = (upper.z+lower.z)*0.5;

	 return centroid;
     }

    void translate(BoundingBox bbox, Vector3d value) {
	if (bbox == null || bbox.boundsIsEmpty) {
	    // Negative volume.
	    setLower( 1.0d,  1.0d,  1.0d);
	    setUpper(-1.0d, -1.0d, -1.0d);
	    return;
	}
	if(bbox.boundsIsInfinite) {
	    this.set(bbox);
	    return;
	}
	
	lower.x = bbox.lower.x + value.x;
	lower.y = bbox.lower.y + value.y;
	lower.z = bbox.lower.z + value.z;
	upper.x = bbox.upper.x + value.x;
	upper.y = bbox.upper.y + value.y;
	upper.z = bbox.upper.z + value.z;
    }


    /**
     * if the passed the "region" is same type as this object
     * then do a copy, otherwise clone the Bounds  and
     * return
     */
    Bounds copy(Bounds r) {
	if (r != null && this.boundId == r.boundId) {
	    BoundingBox region = (BoundingBox) r;
	    region.lower.x = lower.x;
	    region.lower.y = lower.y;
	    region.lower.z = lower.z;
	    region.upper.x = upper.x;
	    region.upper.y = upper.y;
	    region.upper.z = upper.z;
	    region.boundsIsEmpty = boundsIsEmpty;
	    region.boundsIsInfinite = boundsIsInfinite;
	    return region;
	}
	else {
	    return (Bounds) this.clone();
	}
    }

    // Check is any of the bounds is a NaN, if yes, then
    // set it an empty bounds
    boolean checkBoundsIsNaN() {
	if (Double.isNaN(lower.x+lower.y+lower.z+upper.x+upper.y+upper.z)) {
	     return true;
	}

	return false;
    }

    int getPickType() {
	return PickShape.PICKBOUNDINGBOX;
    }
}      

