/* Copyright (C) 2000-2004 by Peter Eastman

   This program is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT ANY 
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
   PARTICULAR PURPOSE.  See the GNU General Public License for more details. */

package artofillusion.raytracer;

import artofillusion.*;
import artofillusion.material.*;
import artofillusion.math.*;
import artofillusion.texture.*;

/** RTDisplacedTriangle represents a displacement mapped triangle to be raytraced.  It is 
    defined by specifying a
    RenderingMesh, the index of the RenderingTriangle within the mesh, and a matrix which 
    gives the transformation from the mesh's local coordinates to world coordinates.  To
    save time and memory, the constructor is also passed two arrays which contain the
    vertices and normals for the mesh, transformed into world coordinates. */

public class RTDisplacedTriangle extends RTObject
{
  RenderingTriangle tri;
  Vec3 trueNorm;
  double minheight, maxheight;
  private double t, u, v, w, tol, mint, maxt, time;
  private boolean bumpMapped;
  private BoundingBox bounds;
  private Mat4 toLocal, fromLocal;
  private ExtraInfo extra;
  
  private static final Vec3 temp1 = new Vec3();
  private static final Vec3 temp2 = new Vec3();
  private static final Vec3 temp3 = new Vec3();
  private static final Vec3 temp4 = new Vec3();
  private static final Vec3 orig = new Vec3();
  private static final Vec3 dir = new Vec3();
  
  /**
   * This inner class holds many internal fields that are only used after the triangle is initialized.
   * Putting them in a separate object saves memory on triangles that never get hit by a ray (and hence
   * never get initialized).
   */
  
  private static class ExtraInfo
  {
    Mat4 trans;
    BoundingBox bounds2;
    double v1x, v1y, v2y;
    double n1x, n1y, n1z;
    double n2x, n2y, n2z;
    double n3x, n3y, n3z;
    double dn1x, dn1y, dn2x, dn2y;
    double minscale, maxscale, intersection[];
    double tint[], uint[], vint[];
    Vec3 interp, rint[];
    short intersections;
    Ray ray;
  }
  
  public RTDisplacedTriangle(RenderingMesh mesh, int which, Mat4 fromLocal, Mat4 toLocal, double tol, double time)
  {
    tri = mesh.triangle[which];
    Vec3 vert1 = mesh.vert[tri.v1];
    Vec3 vert2 = mesh.vert[tri.v2];
    Vec3 vert3 = mesh.vert[tri.v3];
    trueNorm = mesh.faceNorm[which];
    this.fromLocal = fromLocal;
    this.toLocal = toLocal;
    this.tol = tol;
    this.time = time;
    Vec3 norm1 = mesh.norm[tri.n1];
    Vec3 norm2 = mesh.norm[tri.n2];
    Vec3 norm3 = mesh.norm[tri.n3];

    // Make sure trueNorm points in the same direction as the vertex normals.

    int i = 0;
    if (trueNorm.dot(norm1) < 0.0) i++;
    if (trueNorm.dot(norm2) < 0.0) i++;
    if (trueNorm.dot(norm3) < 0.0) i++;
    if (i > 1)
      trueNorm.scale(-1.0);
    
    // Evaluate the displacement at many points over the triangle.  Use this to determine
    // the maximum and minimum displacements, as well as a bounding box for the object.

    minheight = Double.MAX_VALUE;
    maxheight = -Double.MAX_VALUE;
    bounds = new BoundingBox(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
    bounds.minx = bounds.miny = bounds.minz = Double.MAX_VALUE;
    bounds.maxx = bounds.maxy = bounds.maxz = -Double.MAX_VALUE;
    double d = 2.0*Math.max(Math.max(vert1.minus(vert2).length2(), vert1.minus(vert3).length2()), 
	vert2.minus(vert3).length2());
    int j, divisions = (int) Math.ceil(Math.sqrt(d)/tol);
    Vec3 temp = new Vec3();
    d = 1.0/divisions;
    for (i = 0; i <= divisions; i++)
      for (j = 0; j <= divisions-i; j++)
	{
	  u = i*d;
	  v = j*d;
	  w = (divisions-i-j)*d;
	  double disp = tri.getDisplacement(u, v, w, tol, time);
	  temp.set(u*norm1.x+v*norm2.x+w*norm3.x, u*norm1.y+v*norm2.y+w*norm3.y, u*norm1.z+v*norm2.z+w*norm3.z);
	  temp.normalize();
	  double h = disp/temp.dot(trueNorm);
	  if (h < minheight)
	    minheight = h;
	  if (h > maxheight)
	    maxheight = h;
	  temp.set(u*vert1.x+v*vert2.x+w*vert3.x+disp*temp.x, 
	      u*vert1.y+v*vert2.y+w*vert3.y+disp*temp.y, 
	      u*vert1.z+v*vert2.z+w*vert3.z+disp*temp.z);
	  if (temp.x < bounds.minx)
	    bounds.minx = temp.x;
	  if (temp.x > bounds.maxx)
	    bounds.maxx = temp.x;
	  if (temp.y < bounds.miny)
	    bounds.miny = temp.y;
	  if (temp.y > bounds.maxy)
	    bounds.maxy = temp.y;
	  if (temp.z < bounds.minz)
	    bounds.minz = temp.z;
	  if (temp.z > bounds.maxz)
	    bounds.maxz = temp.z;
	}
    bounds.outset(0.5*tol);
    bumpMapped = tri.theMesh.mapping.getTexture().hasComponent(Texture.BUMP_COMPONENT);
    lastRay = -1;
  }

  /** Determine whether this triangle is really displaced.  If the displacement map is completely flat
      over it, return false.  Otherwise, return true. */
  
  public boolean isReallyDisplaced()
  {
    return (minheight != 0.0 || maxheight != 0.0);
  }
  
  /** Set the tolerance which should be used for evaluating the surface. */
  
  public void setTolerance(double tol)
  {
    this.tol = tol;
  }
  
  /** Part of the initialization is done lazily to save time and memory. */

  private void init()
  {
    extra = new ExtraInfo();
    
    // Determine a coordinate transformation which places the triangle in the xy plane, with
    // vertex 3 at the origin and vertex 2 on the y axis.
    
    Vec3 vert[] = tri.theMesh.vert;
    Vec3 vert1 = vert[tri.v1];
    Vec3 vert2 = vert[tri.v2];
    Vec3 vert3 = vert[tri.v3];
    extra.trans = Mat4.viewTransform(vert3, trueNorm, vert2.minus(vert3));
    
    // Find the vertex positions and normals in the coordinate system described above.
    
    Vec3 v1 = extra.trans.times(vert1);
    Vec3 v2 = extra.trans.times(vert2);
    extra.v1x = v1.x;
    extra.v1y = v1.y;
    extra.v2y = v2.y;
    Vec3 norm[] = tri.theMesh.norm;
    Vec3 n1 = extra.trans.timesDirection(norm[tri.n1]);
    Vec3 n2 = extra.trans.timesDirection(norm[tri.n2]);
    Vec3 n3 = extra.trans.timesDirection(norm[tri.n3]);
    extra.n1x = n1.x;
    extra.n1y = n1.y;
    extra.n1z = n1.z;
    extra.n2x = n2.x;
    extra.n2y = n2.y;
    extra.n2z = n2.z;
    extra.n3x = n3.x;
    extra.n3y = n3.y;
    extra.n3z = n3.z;

    // Find the bounding box of the volume swept out by the triangle, in the coordinate
    // system described above.
    
    Vec3 pos[] = new Vec3 [6];
    pos[0] = new Vec3(v1.x+n1.x*minheight, v1.y+n1.y*minheight, n1.z*minheight);
    pos[1] = new Vec3(v1.x+n1.x*maxheight, v1.y+n1.y*maxheight, n1.z*maxheight);
    pos[2] = new Vec3(n2.x*minheight, v2.y+n2.y*minheight, n2.z*minheight);
    pos[3] = new Vec3(n2.x*maxheight, v2.y+n2.y*maxheight, n2.z*maxheight);
    pos[4] = new Vec3(n3.x*minheight, n3.y*minheight, n3.z*minheight);
    pos[5] = new Vec3(n3.x*maxheight, n3.y*maxheight, n3.z*maxheight);
    BoundingBox bounds2 = new BoundingBox(pos[0].x, pos[0].x, pos[0].y, pos[0].y, pos[0].z, pos[0].z);
    for (int i = 1; i < 6; i++)
      {
	if (pos[i].x < bounds2.minx)
	  bounds2.minx = pos[i].x;
	if (pos[i].x > bounds2.maxx)
	  bounds2.maxx = pos[i].x;
	if (pos[i].y < bounds2.miny)
	  bounds2.miny = pos[i].y;
	if (pos[i].y > bounds2.maxy)
	  bounds2.maxy = pos[i].y;
	if (pos[i].z < bounds2.minz)
	  bounds2.minz = pos[i].z;
	if (pos[i].z > bounds2.maxz)
	  bounds2.maxz = pos[i].z;
      }
    bounds2.outset(0.5*tol);
    extra.bounds2 = bounds2;

    // Create various other objects which will be needed.
    
    extra.dn1x = n1.x-n3.x;
    extra.dn1y = n1.y-n3.y;
    extra.dn2x = n2.x-n3.x;
    extra.dn2y = n2.y-n3.y;
    extra.minscale = 1.0/Math.max(Math.max(n1.z, n2.z), n3.z);
    extra.maxscale = 1.0/Math.min(Math.min(n1.z, n2.z), n3.z);
    extra.intersection = new double [4];
    extra.interp = new Vec3();
    extra.rint = new Vec3 [] {new Vec3()};
    extra.tint = new double [1];
    extra.uint = new double [1];
    extra.vint = new double [1];
  }

  /** Get the TextureMapping for this object. */
  
  public final TextureMapping getTextureMapping()
  {
    return tri.getTextureMapping();
  }

  /** Get the MaterialMapping for this object. */
  
  public final MaterialMapping getMaterialMapping()
  {
    return tri.theMesh.matMapping;
  }

  /** Determine whether the given ray intersects this triangle. */

  protected boolean checkIntersection(Ray r)
  {
    lastRay = r.getID();

    if (!rayIntersectsBounds(r.origin, r.direction, bounds))
      return (lastRayResult = false);
    double mint1 = mint, maxt1 = maxt;
    if (extra == null)
      init();

    // Transform the ray to the local coordinate system, and check it against the bounding box.
    
    orig.set(r.origin);
    dir.set(r.direction);
    extra.trans.transform(orig);
    extra.trans.transformDirection(dir);

    if (!rayIntersectsBounds(orig, dir, extra.bounds2))
      return (lastRayResult = false);
    if (mint1 > mint)
      mint = mint1;
    if (maxt1 < maxt)
      maxt = maxt1;
    
    // See if the ray is entirely outside the triangle.
    
    if (mint < 0.0)
      mint = 0.0;
    double x, y, z;
    x = orig.x+maxt*dir.x;
    y = orig.y+maxt*dir.y;
    z = orig.z+maxt*dir.z;
    calcCoords(x, y, z);
    double lastu = u, lastv = v, lastw = w;
    x = orig.x+mint*dir.x;
    y = orig.y+mint*dir.y;
    z = orig.z+mint*dir.z;
    calcCoords(x, y, z);
    if ((u < 0.0 && lastu < 0.0) || (u > 1.0 && lastu > 1.0) ||
        (v < 0.0 && lastv < 0.0) || (v > 1.0 && lastv > 1.0) ||
        (w < 0.0 && lastw < 0.0) || (w > 1.0 && lastw > 1.0))
      return (lastRayResult = false);
    temp1.set(u*extra.n1x+v*extra.n2x+w*extra.n3x, u*extra.n1y+v*extra.n2y+w*extra.n3y, u*extra.n1z+v*extra.n2z+w*extra.n3z);

    // The ray intersects the volume, so step along it and check for intersections.  Determine
    // how many steps to use.
    
    int i;
    double disp = tri.getDisplacement(u, v, w, tol, time), height = disp*temp1.z/temp1.length();
    double prevDelta = z-height, prevt;
    boolean above = (z > height);
    boolean wasOutsideU = (u < 0.0 || u > 1.0), wasOutsideV = (v < 0.0 || v > 1.0), wasOutsideW = (w < 0.0 || w > 1.0);
    boolean outsideU, outsideV, outsideW;
    
    t = prevt = mint;
    extra.intersections = -1;
    extra.tint[0] = Double.MAX_VALUE;
    while (t < maxt)
      {
	t += tol;
	if (t >= maxt)
	  {
	    t = maxt;
	    x = orig.x+t*dir.x;
	    y = orig.y+t*dir.y;
	    z = orig.z+t*dir.z;
	    u = lastu;
	    v = lastv;
	    w = lastw;
	  }
	else
	  {
	    x = orig.x+t*dir.x;
	    y = orig.y+t*dir.y;
	    z = orig.z+t*dir.z;
	    calcCoords(x, y, z);
	  }
	disp = tri.getDisplacement(u, v, w, tol, time);
	temp1.set(u*extra.n1x+v*extra.n2x+w*extra.n3x, u*extra.n1y+v*extra.n2y+w*extra.n3y, u*extra.n1z+v*extra.n2z+w*extra.n3z);
	height = disp*temp1.z/temp1.length();

	outsideU = (u < 0.0 || u > 1.0);
	outsideV = (v < 0.0 || v > 1.0);
	outsideW = (w < 0.0 || w > 1.0);
	if ((outsideU && wasOutsideU) || (outsideV && wasOutsideV) || (outsideW && wasOutsideW))
	  {
	    // The ray is currently outside the triangle.
	    
	    above = (z > height);
	    prevDelta = z-height;
	    prevt = t;
	    wasOutsideU = outsideU;
	    wasOutsideV = outsideV;
	    wasOutsideW = outsideW;
	    continue;
	  }
	if ((above && z <= height) || (!above && z >= height))
	  {
	    // The ray intersects the surface.
	    
	    double truet = t-(t-prevt)*(z-height)/(z-height-prevDelta);
	    if (truet <= tol)
	      {
		// Ignore intersections too close to the ray origin to prevent the surface from
		// shadowing itself.
		
		above = (z > height);
		prevDelta = z-height;
		prevt = t;
		wasOutsideU = outsideU;
		wasOutsideV = outsideV;
		wasOutsideW = outsideW;
		continue;
	      }
	    x = orig.x+truet*dir.x;
	    y = orig.y+truet*dir.y;
	    z = orig.z+truet*dir.z;
	    calcCoords(x, y, z);
	    extra.tint[0] = truet;
	    extra.uint[0] = u;
	    extra.vint[0] = v;
	    break;
	  }
	prevDelta = z-height;
	above = (z > height);
	prevt = t;
	wasOutsideU = outsideU;
	wasOutsideV = outsideV;
	wasOutsideW = outsideW;
      }
    if (extra.tint[0] == Double.MAX_VALUE)
      return (lastRayResult = false);
    
    // Find the point of intersection.
    
    Vec3 ri = extra.rint[0];
    disp = tri.getDisplacement(u, v, w, tol, time);
    ri.set(r.origin.x+extra.tint[0]*r.direction.x, r.origin.y+extra.tint[0]*r.direction.y, r.origin.z+extra.tint[0]*r.direction.z);

    // Calculate the derivates of the displacement function.
    
    double dhdu = (tri.getDisplacement(u+(1e-5), v, w-(1e-5), tol, time)-disp)*1e5;
    double dhdv = (tri.getDisplacement(u, v+(1e-5), w-(1e-5), tol, time)-disp)*1e5;

    // Use them to find the local normal vector.

    Vec3 norm[] = tri.theMesh.norm;
    Vec3 norm1 = norm[tri.n1];
    Vec3 norm2 = norm[tri.n2];
    Vec3 norm3 = norm[tri.n3];
    extra.interp.set(u*norm1.x+v*norm2.x+w*norm3.x, u*norm1.y+v*norm2.y+w*norm3.y, u*norm1.z+v*norm2.z+w*norm3.z);
    extra.interp.normalize();
    Vec3 vert[] = tri.theMesh.vert;
    Vec3 vert1 = vert[tri.v1];
    Vec3 vert2 = vert[tri.v2];
    Vec3 vert3 = vert[tri.v3];
    temp1.set(vert1.x+disp*norm1.x, vert1.y+disp*norm1.y, vert1.z+disp*norm1.z);
    temp2.set(vert2.x+disp*norm2.x, vert2.y+disp*norm2.y, vert2.z+disp*norm2.z);
    temp3.set(vert3.x+disp*norm3.x, vert3.y+disp*norm3.y, vert3.z+disp*norm3.z);
    temp1.set(temp1.x-temp3.x, temp1.y-temp3.y, temp1.z-temp3.z);
    temp2.set(temp3.x-temp2.x, temp3.y-temp2.y, temp3.z-temp2.z);
    temp3.set(temp1.y*extra.interp.z-temp1.z*extra.interp.y, temp1.z*extra.interp.x-temp1.x*extra.interp.z, temp1.x*extra.interp.y-temp1.y*extra.interp.x);
    temp4.set(temp2.y*extra.interp.z-temp2.z*extra.interp.y, temp2.z*extra.interp.x-temp2.x*extra.interp.z, temp2.x*extra.interp.y-temp2.y*extra.interp.x);
    temp3.scale(-1.0/temp3.dot(temp2));
    temp4.scale(1.0/temp4.dot(temp1));
    temp1.set(dhdu*temp4.x+dhdv*temp3.x, dhdu*temp4.y+dhdv*temp3.y, dhdu*temp4.z+dhdv*temp3.z);
    extra.interp.scale(temp1.dot(extra.interp)+1.0);
    extra.interp.subtract(temp1);
    extra.interp.normalize();
    extra.ray = r;
    return (lastRayResult = true);
  }

  /** Given the coordinates of a point along the ray, calculate the barycentric coordinates
      (u, v, w) cooresponding to it.  Also calculate the displacement h of that point on the
      triangle, and return the height (the distance along the non-interpolated normal).  If
      u, v, or w is outside the range [0, 1], the return value is meaningless. */

  private final void calcCoords(double x, double y, double z)
  {
    double dmax = z*extra.maxscale, dmin = z*extra.minscale, d, zmax, zmin, fract, fract2;
    
    guessCoords(x, y, dmax);
    double ua = u, va = v, wa = w;
    zmax = dmax*(u*extra.n1z + v*extra.n2z + w*extra.n3z);
    guessCoords(x, y, dmin);
    double ub = u, vb = v, wb = w;
    zmin = dmin*(u*extra.n1z + v*extra.n2z + w*extra.n3z);
    if (zmax == zmin)
      return;
    fract = (z-zmin)/(zmax-zmin);
    fract2 = 1.0-fract;
    u = fract*ua + fract2*u;
    v = fract*va + fract2*v;
    w = fract*wa + fract2*w;
  }
  
  /** Given the coordinates of a point along the ray, and a guess about the displacement h,
      find the corresponding guesses about u, v, and w. */
  
  private final void guessCoords(double x, double y, double disp)
  {
    double a, b, c, e, f, g, m;
    
    a = extra.v1x+disp*extra.dn1x;
    b = disp*extra.dn2x;
    c = x-disp*extra.n3x;
    e = extra.v1y+disp*extra.dn1y;
    f = extra.v2y+disp*extra.dn2y;
    g = y-disp*extra.n3y;
    m = 1.0/(a*f-b*e);
    u = (c*f-b*g)*m;
    v = (a*g-c*e)*m;
    w = 1.0-u-v;
  }


  /** Find all intersection points with the ray.  This is called in response to a call to 
      numIntersections(), since that indicates that all intersections will be needed, not
      just the first. */

  private final void findAllIntersections()
  {
    double x, y, z;
    x = orig.x+t*dir.x;
    y = orig.y+t*dir.y;
    z = orig.z+t*dir.z;
    temp1.set(u*extra.n1x+v*extra.n2x+w*extra.n3x, u*extra.n1y+v*extra.n2y+w*extra.n3y, u*extra.n1z+v*extra.n2z+w*extra.n3z);
    double disp = tri.getDisplacement(u, v, w, tol, time), height = disp*temp1.z/temp1.length();
    double prevDelta = z-height, prevt = t;
    boolean above = (z > height);
    boolean wasOutsideU = (u < 0.0 || u > 1.0), wasOutsideV = (v < 0.0 || v > 1.0), wasOutsideW = (w < 0.0 || w > 1.0);
    boolean outsideU, outsideV, outsideW;
    int i;
    
    extra.intersections = 1;
    while (t < maxt)
      {
	t += tol;
	if (t >= maxt)
	  t = maxt;
	x = orig.x+t*dir.x;
	y = orig.y+t*dir.y;
	z = orig.z+t*dir.z;
	calcCoords(x, y, z);
	disp = tri.getDisplacement(u, v, w, tol, time);
	temp1.set(u*extra.n1x+v*extra.n2x+w*extra.n3x, u*extra.n1y+v*extra.n2y+w*extra.n3y, u*extra.n1z+v*extra.n2z+w*extra.n3z);
	height = disp*temp1.z/temp1.length();

	outsideU = (u < 0.0 || u > 1.0);
	outsideV = (v < 0.0 || v > 1.0);
	outsideW = (w < 0.0 || w > 1.0);
	if ((outsideU && wasOutsideU) || (outsideV && wasOutsideV) || (outsideW && wasOutsideW))
	  {
	    // The ray is currently outside the triangle.
	    
	    above = (z > height);
	    prevDelta = z-height;
	    prevt = t;
	    wasOutsideU = outsideU;
	    wasOutsideV = outsideV;
	    wasOutsideW = outsideW;
	    continue;
	  }
	if ((above && z <= height) || (!above && z >= height))
	  {
	    // The ray intersects the surface.
	    
	    if (extra.intersections == extra.tint.length)
	      {
		double newt[] = new double [extra.intersections*2];
		double newu[] = new double [extra.intersections*2];
		double newv[] = new double [extra.intersections*2];
		Vec3 newr[] = new Vec3 [extra.intersections*2];
		for (int j = 0; j < extra.tint.length; j++)
		  {
		    newt[j] = extra.tint[j];
		    newu[j] = extra.uint[j];
		    newv[j] = extra.vint[j];
		    newr[j] = extra.rint[j];
		  }
		for (int j = extra.tint.length; j < newt.length; j++)
		  newr[j] = new Vec3();
		extra.tint = newt;
		extra.uint = newu;
		extra.vint = newv;
		extra.rint = newr;
	      }
	    double oldz = z;
	    double truet = t-(t-prevt)*(z-height)/(z-height-prevDelta);
	    x = orig.x+truet*dir.x;
	    y = orig.y+truet*dir.y;
	    z = orig.z+truet*dir.z;
	    calcCoords(x, y, z);
	    extra.tint[extra.intersections] = truet;
	    extra.uint[extra.intersections] = u;
	    extra.vint[extra.intersections] = v;
	    extra.rint[extra.intersections].set(extra.ray.origin.x+truet*extra.ray.direction.x, 
	      extra.ray.origin.y+truet*extra.ray.direction.y, 
	      extra.ray.origin.z+truet*extra.ray.direction.z);
	    extra.intersections++;
	    z = oldz;
	  }
	prevDelta = z-height;
	above = (z > height);
	prevt = t;
	wasOutsideU = outsideU;
	wasOutsideV = outsideV;
	wasOutsideW = outsideW;
      }
  }

  /* The following methods should be called after intersects() has returned true.  They 
     return information about the point of intersection between the ray and the triangle. */

  /** Find the full list of intersections, and return how many there are. */

  public int numIntersections()
  {
    if (extra.intersections == -1)
      findAllIntersections();
    return extra.intersections;
  }

  /** Get the point of intersection. */
  
  public final void intersectionPoint(int n, Vec3 p)
  {
    p.set(extra.rint[n]);
  }

  /** Get the distance from the ray origin to the point of intersection. */

  public final double intersectionDist(int n)
  {
    return extra.tint[n];
  }

  /** Get the true normal of the triangle. */
  
  public void trueNormal(Vec3 n)
  {
    n.set(extra.interp);
  }

  /** Get the surface properties at the point of intersection.
      @param spec        the texture properties will be stored in this
      @param n           the surface normal will be stored in this
      @param viewDir     the direction from which the surface is being viewed
      @param size        the width of the region over which the texture should be averaged
  */
  
  public void intersectionProperties(TextureSpec spec, Vec3 n, Vec3 viewDir, double size)
  {
    n.set(extra.interp);
    tri.getTextureSpec(spec, -n.dot(viewDir), extra.uint[0], extra.vint[0], 1.0-extra.uint[0]-extra.vint[0], size, time);
    if (bumpMapped)
      {
	fromLocal.transformDirection(spec.bumpGrad);
	n.scale(spec.bumpGrad.dot(n)+1.0);
	n.subtract(spec.bumpGrad);
	n.normalize();
      }
  }

  /** Get the surface transparency at the point of intersection. */
  
  public void intersectionTransparency(int n, RGBColor trans, double angle, double size)
  {
    tri.getTransparency(trans, angle, extra.uint[n], extra.vint[n], 1.0-extra.uint[0]-extra.vint[0], size, time);
  }

  /** Get a bounding box for this triangle. */
  
  public BoundingBox getBounds()
  {
    return bounds;
  }

  /** Determine whether any part of the triangle lies within a bounding box. */

  public boolean intersectsBox(BoundingBox bb)
  {
    if (!bb.intersects(bounds))
      return false;
    double dot = tri.theMesh.vert[tri.v1].dot(trueNorm);
    double mindot = dot+minheight, maxdot = dot+maxheight;
    boolean anyBelow = false, anyAbove = false;
    dot = trueNorm.x*bb.minx + trueNorm.y*bb.miny + trueNorm.z*bb.minz;
    if (dot < mindot)
      anyBelow = true;
    else if (dot > maxdot)
      anyAbove = true;
    else
      return true;
    Vec3 corners[] = bb.getCorners();
    dot = trueNorm.x*bb.minx + trueNorm.y*bb.miny + trueNorm.z*bb.maxz;
    if (dot < mindot)
    {
      if (anyAbove)
        return true;
      anyBelow = true;
    }
    else if (dot > maxdot)
    {
      if (anyBelow)
        return true;
      anyAbove = true;
    }
    else
      return true;
    dot = trueNorm.x*bb.minx + trueNorm.y*bb.maxy + trueNorm.z*bb.minz;
    if (dot < mindot)
    {
      if (anyAbove)
        return true;
      anyBelow = true;
    }
    else if (dot > maxdot)
    {
      if (anyBelow)
        return true;
      anyAbove = true;
    }
    else
      return true;
    dot = trueNorm.x*bb.minx + trueNorm.y*bb.maxy + trueNorm.z*bb.maxz;
    if (dot < mindot)
    {
      if (anyAbove)
        return true;
      anyBelow = true;
    }
    else if (dot > maxdot)
    {
      if (anyBelow)
        return true;
      anyAbove = true;
    }
    else
      return true;
    dot = trueNorm.x*bb.maxx + trueNorm.y*bb.miny + trueNorm.z*bb.minz;
    if (dot < mindot)
    {
      if (anyAbove)
        return true;
      anyBelow = true;
    }
    else if (dot > maxdot)
    {
      if (anyBelow)
        return true;
      anyAbove = true;
    }
    else
      return true;
    dot = trueNorm.x*bb.maxx + trueNorm.y*bb.miny + trueNorm.z*bb.maxz;
    if (dot < mindot)
    {
      if (anyAbove)
        return true;
      anyBelow = true;
    }
    else if (dot > maxdot)
    {
      if (anyBelow)
        return true;
      anyAbove = true;
    }
    else
      return true;
    dot = trueNorm.x*bb.maxx + trueNorm.y*bb.maxy + trueNorm.z*bb.minz;
    if (dot < mindot)
    {
      if (anyAbove)
        return true;
      anyBelow = true;
    }
    else if (dot > maxdot)
    {
      if (anyBelow)
        return true;
      anyAbove = true;
    }
    else
      return true;
    dot = trueNorm.x*bb.maxx + trueNorm.y*bb.maxy + trueNorm.z*bb.maxz;
    if (dot < mindot)
    {
      if (anyAbove)
        return true;
    }
    else if (dot > maxdot)
    {
      if (anyBelow)
        return true;
    }
    else
      return true;
    return false;
  }
  
  /** Determine whether a ray intersects the bounding box.  If so, return true and set mint and
      maxt to the points where the ray enters and exits the box. */
  
  boolean rayIntersectsBounds(Vec3 origin, Vec3 direction, BoundingBox bb)
  {
    double t1, t2;
    
    mint = -Double.MAX_VALUE;
    maxt = Double.MAX_VALUE;
    if (direction.x == 0.0)
      {
	if (origin.x < bb.minx || origin.x > bb.maxx)
	  return false;
      }
    else
      {
	t1 = (bb.minx-origin.x)/direction.x;
	t2 = (bb.maxx-origin.x)/direction.x;
	if (t1 < t2)
	  {
	    if (t1 > mint)
	      mint = t1;
	    if (t2 < maxt)
	      maxt = t2;
	  }
	else
	  {
	    if (t2 > mint)
	      mint = t2;
	    if (t1 < maxt)
	      maxt = t1;
	  }
	if (mint > maxt || maxt < 0.0)
	  return false;
      }
    if (direction.y == 0.0)
      {
	if (origin.y < bb.miny || origin.y > bb.maxy)
	  return false;
      }
    else
      {
	t1 = (bb.miny-origin.y)/direction.y;
	t2 = (bb.maxy-origin.y)/direction.y;
	if (t1 < t2)
	  {
	    if (t1 > mint)
	      mint = t1;
	    if (t2 < maxt)
	      maxt = t2;
	  }
	else
	  {
	    if (t2 > mint)
	      mint = t2;
	    if (t1 < maxt)
	      maxt = t1;
	  }
	if (mint > maxt || maxt < 0.0)
	  return false;
      }
    if (direction.z == 0.0)
      {
	if (origin.z < bb.minz || origin.z > bb.maxz)
	  return false;
      }
    else
      {
	t1 = (bb.minz-origin.z)/direction.z;
	t2 = (bb.maxz-origin.z)/direction.z;
	if (t1 < t2)
	  {
	    if (t1 > mint)
	      mint = t1;
	    if (t2 < maxt)
	      maxt = t2;
	  }
	else
	  {
	    if (t2 > mint)
	      mint = t2;
	    if (t1 < maxt)
	      maxt = t1;
	  }
	if (mint > maxt || maxt < 0.0)
	  return false;
      }
    return true;
  }
  
  /** Get the transformation from world coordinates to the object's local coordinates. */
  
  public Mat4 toLocal()
  {
    return toLocal;
  }
}