package com.xith.java3d.geometry.terrain.mesher;

/**
 * This class does not use the utility functions, but completely controls
 * the creation of a triangle strip array, along with normals.
 *
 * @author David Yazel
 */
 
import com.xith.java3d.geometry.terrain.heightscale.*;
import com.xith.java3d.geometry.terrain.heightmap.*;
import com.xith.java3d.geometry.terrain.texturizer.*;
import com.xith.utility.logs.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import javax.media.j3d.*;
import java.util.*;
 
public class TriangleMesher implements MesherInterface {

   TexturizerInterface tex = null;
   HeightMapInterface map = null;
   float raiseY = 0.0f;
   
   // this defines the triangle coordinate offsets for any vertex
      
   int[][][] siblingOffsets = {
      {{-1,-1},{-1,0},{0,0}},
      {{-1,0},{0,1},{0,0}},
      {{0,0},{0,1},{1,1}},
      {{0,0},{1,1},{1,0}},
      {{0,-1},{0,0},{1,0}},
      {{-1,-1},{0,0},{0,-1}}
   };   
    
   public TriangleMesher(TexturizerInterface tex, HeightMapInterface map) {
      super();
      this.tex = tex;
      this.map = map;

      // initialize the siblingOffsetArray
                                 
     
   }

   /**
    * Sets the texturizer class to handle the texture mapping and
    * vertex color assignments.
    */
    
   public void setTexturizer( TexturizerInterface tex ) {
      this.tex = tex;
   }

   /**
    * Sets the heghtmap as the source for all the 3D points in the mesh.
    */   

   public void setHeightmap( HeightMapInterface map ) {
      this.map = map;
   }

   /**
    * I am leaving this method here as a lesson to the reader :)
    * It basically sucks.  The reason is that the normal generated for
    * each triangle will place the entire triangle as flat and completely
    * ignore the fact that each vertex should have a normal that is
    * perpendicular to all the triangles surrounding it.
    *
    * But to do it right you need to know the other triangles.  This information
    * is lost normally when the mesh is contructed, so I am going back to save
    * the information in a matrix of vertices and am going to write the
    * method below called normalizeSmoothMesh
    */
    
   private void normalizeMesh( TriangleStripArray triangles ) {
      
                // Specifically set up Normals
                // This is used for lighting and texture rendering
      
                Vector3f normal = new Vector3f();
                Vector3f v1 = new Vector3f();
                Vector3f v2 = new Vector3f();
                Point3f [] pts = new Point3f[3];
                for (int i = 0; i < 3   ; i++) pts[i] = new Point3f();

                for (int facet = 0; facet < triangles.getVertexCount(); facet = facet+3) {
      
                triangles.getCoordinates(facet, pts);
                v1.sub(pts[1], pts[0]);
                v2.sub(pts[2], pts[0]);
                normal.cross(v1, v2);
                normal.normalize();
                for (int j = 0; j < 3; j++) 
                           triangles.setNormal((facet + j), normal);
         }

            
   }

   // returns a list of triangles that are siblings to a specfied matrix point
   
   private void getSiblingTriangles(Vector siblings, TriangleStripArray triangles,
      int vertexMatrix[][], int row, int col, int width, int depth) {

      siblings.clear();
      
      // get the coordinate of the vertex

      Point3f vertex = new Point3f();
      triangles.getCoordinate(vertexMatrix[col][row],vertex);
      
      // first thing we need to do is build a list of vertices surrounding the
      // vertex we are making.  we need to handle the end cases properly

      int vertices[][] = new int[15][2];
      int numVerts = 0;

      Log.log.println(LogType.EXHAUSTIVE,"Finding all surrounding verticies");
      for (int i=col-1;i<=col+1;i++)
         for (int j=row-1;j<=row+1;j++) { 

            if ((i>=0) && (i<width-1) && (j>=0) && (j<depth-1)) {
               vertices[numVerts][0] = i;
               vertices[numVerts][1] = j;
               numVerts++;
               Log.log.println(LogType.EXHAUSTIVE,"   Found "+i+"x"+j);
            }  else
               Log.log.println(LogType.EXHAUSTIVE,"   Rejecting "+i+"x"+j);
                   
         }            
      Log.log.println(LogType.EXHAUSTIVE,"Found "+numVerts+" surrounding vertices");

      // ok we now have at most 9 vertices.  This we now need to resolve to
      // all non-duplicate triangles which share a point with the vertex in
      // question

      for (int i=0;i<numVerts;i++) {

                Point3f [] pts = new Point3f[3];
                for (int j = 0; j < 3   ; j++) pts[j] = new Point3f();
         int ii = vertices[i][0];
         int jj = vertices[i][1];
         int vertexIndex = vertexMatrix[ii][jj];
                triangles.getCoordinates(vertexIndex, pts);

         // we now have a triangle, check to see if it shares a vertex ours

         boolean matched = false;
         for (int j=0;j<3;j++) 
            if (vertex.distance(pts[j])==0) matched = true;

         // we now have a match, but we have to make sure we are not duplicating
         // a triangle we already have

         if (matched) {

            siblings.add(pts);      
            Log.log.println(LogType.EXHAUSTIVE,"   Matched "+ii+"x"+jj);

         }
                  
         
      }
      
            
   }
   
   /**
    * This method calculates a smooth normal for every vertex, taking into
    * account all the sister triangles.
    *
    * Thanks to Bobby Martin, the math I figured out from his PolyPoint class
    */
   private void normalizeSmoothMesh( TriangleStripArray triangles, 
      int vertexMatrix[][], int width, int depth ) {
   
      // vector of vertices.  The max should be 6 if I am calculating this
      // corectly.  An interior vertex would have 6 sibling triangles from
      // a triangle strip array.

      Vector siblings = new Vector(12);
//      int vertices[];
//      int numVerts;

      // step though every vertix

      for (int i=0; i <depth;i++) 
         for (int j=0; j<width;j++) {

            getSiblingTriangles(siblings,triangles,vertexMatrix,i,j,width,depth);
            Log.log.println(LogType.EXHAUSTIVE,"Siblngs found : "+siblings.size());
            
            // ok we have the siblings, and the triangles for each one.  Now
            // we build the 


         }
            
   }

   // calculate the smoothVertexNormal for the specified vertex.  Take into account any
   // adjoining triangles.  calculate their normals and created a weighted average normal for the
   // vertex.
   
   private void smoothVertexNormal( TriangleStripArray triangles, int count, int col, int row ) {

           Vector3f v1 = new Vector3f();
           Vector3f v2 = new Vector3f();
           Vector3f temp = new Vector3f();
           Vector3f normal = new Vector3f();
      int matched = 0;
       
      // there are 6 possible adjoining triangles which share this vertex. Ignore ones
      // that are off the map.

      for (int i =0;i<6;i++) {

         boolean ok = true;
         for (int j=0;j<3;j++) {
            int x = col + siblingOffsets[i][j][0];
            int y = row + siblingOffsets[i][j][1];
            if ((x<0) || (x>=map.getWidth())) ok = false;
            if ((y<0) || (y>=map.getDepth())) ok = false;            
         }

         // if we are ok then we can process this triangle

         if (ok) {
            matched++;
            Point3f p = map.getMappedCoord(col+siblingOffsets[i][0][0],row+siblingOffsets[i][0][1]);
            Point3f p1 = map.getMappedCoord(col+siblingOffsets[i][1][0],row+siblingOffsets[i][1][1]);
            Point3f p2 = map.getMappedCoord(col+siblingOffsets[i][2][0],row+siblingOffsets[i][2][1]);
         
                 v1.sub(p1, p);
                 v2.sub(p2, p);
                 temp.cross(v1, v2);
                 normal.add(temp);
         }
         
      }
        normal.normalize();
      triangles.setNormal(count,normal);
   

   }
   
   /**
    * This method returns a Shape3D object for the range specified.  Note
    * that the range specified is in matrix coordinates only.  This is because
    * it is very likely large terrains will be made up of many meshes, all generated
    * from the same heightmap.
    */
       
   public Shape3D getMesh( int lowX, int lowZ, int highX, int highZ ) {

      // calculate the width and depth of the mesh being created
      
      int width = highX - lowX + 1;
      int depth = highZ - lowZ + 1;
      int col,row;
                
      // variables for the mesh
      
                TriangleStripArray triangles;	
      
      // calculate the number of strips and the points per strip
      	
                int stripCount = width * 2;
                int perStrip[] = new int[depth-1];

      // set up the array of per strip point counts
      
                for (int i = 0; i < (depth-1); i++)
                                perStrip[i] = stripCount;


      int texCoordSetMap[] = {0, 1, 1};
      int texCoordSetCount = 2;            
      
      // calculate the number of vertices and build the triangle array
            
                int numVertexPoints = ((depth-1)*stripCount);
                triangles = new TriangleStripArray(numVertexPoints,
                        GeometryArray.COORDINATES |
                        GeometryArray.NORMALS | 
                        GeometryArray.TEXTURE_COORDINATE_2 |
                        GeometryArray.COLOR_3,
         perStrip);
         
//         texCoordSetCount, texCoordSetMap,perStrip);

      // step through and define the mesh
         
                int count = 0;
                for (int i = 0; i < (depth-1); i++) {
      
                        for (int j = 0; j < width;j++) {
            
            // Also includes translation to origin
            // may remove this feature later
            // Triangles follow this pattern
            //  0    2   4
            //  |  / |  /|
            //  | /  | / |
            //  1/   3/  etc.
            // Values therefore are i and i+1
   
            col = j+lowX;
            row = i+lowZ;
            Point3f p1 = map.getMappedCoord(col,row);
            p1.y += raiseY;
            triangles.setCoordinate(count,p1);
            tex.texturizeVertex(triangles,p1,count,col,row);
            smoothVertexNormal( triangles, count, col, row );
            count++;

            // now the second coordinate

            col = j+lowX;
            row = i+lowZ+1;
            Point3f p2 = map.getMappedCoord(col,row);
            p2.y += raiseY;
            triangles.setCoordinate(count,p2);
            tex.texturizeVertex(triangles,p2,count,col,row);
            smoothVertexNormal( triangles, count, col, row );
            count++;                   
   
         }
      }

//      normalizeSmoothMesh( triangles, vertexMatrix, width, depth );                        
        
      Shape3D shape = new Shape3D();
      shape.setAppearance(tex.getAppearance());
      shape.setGeometry(triangles);            
      shape.setCapability(shape.ALLOW_APPEARANCE_READ);      
      return shape;
      
   }

   /**
    * Because decals don't seem to be working, we are going to try to
    * lay a decal on top of the terrain by increasing the Y slightly.
    */    

   public void raiseHeight( float deltaY ) {
      raiseY = deltaY;
   }    
}