package graph;

import java.awt.*;
import java.util.*;
import java.lang.*;

/*
**************************************************************************
**
**    Class  IsoCurve            
**
**************************************************************************
**    Copyright (C) 1996 Leigh Brookshaw
**
**    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.
**
**    You should have received a copy of the GNU General Public License
**    along with this program; if not, write to the Free Software
**    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**************************************************************************
**
**    This class will calculate the curve of a given value passing
**    through a grid of values
**
*************************************************************************/


/**
 * This class will calculate the constant curve of a given value passing
 * through a grid of values.
 *
 * @version $Revision: 1.6 $, $Date: 1996/07/30 04:52:40 $.
 * @author Leigh Brookshaw
 */

public class IsoCurve extends Object {

/*
*****************
**
** Constants
**
****************/

  /**
   *  Flag a cell face as a terminal face ie the curve terminates here.
   */
     static final int TERMINAL  = 0;

  /**
   *  Flag a cell as having the curve coming through its Left face
   */
     static final int LEFT      = 1;

  /**
   *  Flag a cell as having the curve coming through its Right face
   */
     static final int RIGHT     = 2;

  /**
   *  Flag a cell as having the curve coming through its Top face
   */
     static final int TOP       = 3;

  /**
   *  Flag a cell as having the curve coming through its Bottom face
   */
     static final int BOTTOM    = 4;
  /**
   *   Initial size of the array that will hold a contour.
   */
     static final int ARRAYSIZE    = 100;
  /**
   *   Maximum size of the array that will hold a contour.
   */
     static final int MAXARRAYSIZE = 2000;
     
/*
***************
**
** Variables
**
**************/

  /**
   *   Vector of cells that the contour passes through.
   */
     protected Vector cells;
  /**
   *   Array holding the data grid.
   */
     protected double grid[];
  /**
   *   X Dimension of data grid
   */
     protected int nx;
  /**
   *   Y Dimension of data grid
   */
     protected int ny;
  /**
   *   Array that holds the points of the contour
   */
     protected double curve[];
  /**
   *   Number of points in the contour
   */
     protected int  size;
  /**
   *   Contour value to be found
   */
     protected double value;
     
/*
*****************
**
** Constructors
**
****************/

  /**
   * Instantiate the class and initialize all the variables
   */
     public IsoCurve() {
            cells = null;
            grid = null;
            nx = 0;
            ny = 0;
            curve = null;
            size = 0;
            value = 0.0;
     }
     
  /**
   * Instantiate the class and initialize all the variables.
   * @param grid An nx by ny Array containing the grid to contour
   * @param nx   X dimension of the grid.
   * @param ny   Y dimension of the grid.
   */
     public IsoCurve(double grid[], int nx, int ny) {
        this();
     	setGrid(grid,nx,ny);
     }
     
/*
******************
**
** Public Methods
**
*****************/
  /**
   * Set the grid to be contoured
   * @param nx   X dimension of the grid.
   * @param ny   Y dimension of the grid.
   */
     public void setGrid(double grid[], int nx, int ny) {
           this.grid = grid;
           this.nx   = nx;
           this.ny   = ny;
     }
  /**
   * Set the value to contour
   * @param value the contour level
   */
      public void setValue(double value) {
            this.value = value;
            
            if( grid == null) return;

            //System.out.println("setValue called:" + value);
            
            createCells();
     }
  /**
   * Return a contour curve. If null
   * is returned it means that all the contour curves have been found.
   * @return The array containing the (x,y) pairs of the contour curve.
   */
     public double[] getCurve() {
           size = 0;

           getcurve();
           
           if( size == 0 || curve == null ) {
                    //System.out.println("getCurve: Nothing found");
                    return null;
		  }
           
           double tmp[] = new double[size];
           
           System.arraycopy(curve,0,tmp,0,size);

           return tmp;
     }
     
/*
**********************
**
** Protected Methods
**
*********************/
  /**
   * Create the vector of all cells that contain the contour.
   */    
     protected void createCells() {
            double bl, br, tl, tr;
            boolean bottom, top, right, left;
            Cell cell;
            int jcell, icell;            
            int i, j, count;
     
            if(cells == null)    cells = new Vector();
            else                 cells.removeAllElements();
            
            
            for(j=0; j<ny-1; j++) {
               
                 jcell = j*nx;
                 
                 for(i=0; i<nx-1; i++) {
             
                   icell = i + jcell;
                   
                   bl = grid[icell];
                   br = grid[icell+1];
                   tl = grid[icell+nx];
                   tr = grid[icell+nx+1];
                   
                   bottom = false;
                   top    = false;
                   left   = false;
                   right  = false;
                   
                   if( (bl-value)*(br-value) <= 0.0 )  bottom = true;
                   if( (br-value)*(tr-value) <= 0.0 )  right  = true;
                   if( (tr-value)*(tl-value) <= 0.0 )  top    = true;
                   if( (tl-value)*(bl-value) <= 0.0 )  left   = true;
                   
                   if( top && bottom && left && right ) {
                   
                      if( tr >= tl && bl >= br ) {
                      
                            cell = new Cell();
                            cell.i = i;
                            cell.j = j;
                            cell.face[0] = TOP;
                            cell.face[1] = LEFT;
                            cells.addElement(cell);
                       
                            cell = new Cell();
                            cell.i = i;
                            cell.j = j;
                            cell.face[0] = BOTTOM;
                            cell.face[1] = RIGHT;
                            cells.addElement(cell);
                          
                      } else
                      if ( tl >= tr && br >= bl ) {
                   
                            cell = new Cell();
                            cell.i = i;
                            cell.j = j;
                            cell.face[0] = TOP;
                            cell.face[1] = LEFT;
                            cells.addElement(cell);
                       
                            cell = new Cell();
                            cell.i = i;
                            cell.j = j;
                            cell.face[0] = BOTTOM;
                            cell.face[1] = RIGHT;
                            

                      }
                   } else 
                   if( top || bottom || left || right ) {
                            cell = new Cell();
                            cells.addElement(cell);
                            cell.i = i;
                            cell.j = j;

                            if( bl == value && br == value) bottom = false;
                            if( bl == value && tl == value) left   = false;
                            if( tr == value && tl == value) top    = false;
                            if( tr == value && br == value) right  = false;
/*
**                          Check for pathological cell and to avoid out of bounds
**                          errors!
*/

                            count = 0;
                            if( bottom ) count++;
                            if( top )    count++;
                            if( left )   count++;
                            if( right )  count++;

                            if( count > 2 ) {
                                System.out.println("IsoCurve: Pathological Cell");
                                System.out.println("          value="+value);
                                System.out.println("          i="+i+", j="+j);
                                System.out.println("          tl="+tl+", tr="+tr);
                                System.out.println("          bl="+bl+", br="+br);
		            } else {
                                count = 0;
                                if( bottom ) { cell.face[count] = BOTTOM; count++; }
                                if( top    ) { cell.face[count] = TOP;    count++; }
                                if( left   ) { cell.face[count] = LEFT;   count++; }
                                if( right  ) { cell.face[count] = RIGHT;  count++; }
                            } 
                   }
             
             
                
                 }
            }
	  }
            
  /**
   * Traverse the cells and find One connect countour.
   */ 
     protected void getcurve() {
            Cell current;
            int face = TERMINAL;
            int ifcell = -1;
            int jfcell = -1;
            int icell,jcell;
            double bl, br, tl, tr;
            int index;
            int i;
            double d[] = new double[2];
            
     
            size = 0;

            //System.out.println("getcurve: Number of cells "+cells.size());

            if( cells == null || cells.isEmpty() ) return;
            
            current = (Cell)(cells.firstElement());

            if(current.face[0] == TERMINAL && current.face[1] == TERMINAL) {
                cells.removeElement(current);
                System.out.println("getcurve: Terminal cell removed!");
                return;
            }
/*
**          Calculate the first point in the new curve
*/

            icell = current.i;
            jcell = current.j;
            
            ifcell = -1;
            jfcell = -1;
/*
**          Check to see if we can loop back on ourselves. ie we have partially
**          resolved this curve or we are against a wall.
**          Use the terminating wall to start the curve.
*/
            if( search(icell-1,jcell) == null && 
                ( current.face[0] == LEFT || current.face[1] == LEFT ) ) {
                    
                	 addDataPoint(LEFT,icell,jcell);
                     face = LEFT;
                     
            } else
            if( search(icell+1,jcell) == null && 
                ( current.face[0] == RIGHT || current.face[1] == RIGHT ) ) {
                    
                     addDataPoint(RIGHT,icell,jcell);
                     face = RIGHT;

            } else
            if( search(icell,jcell-nx) == null && 
                ( current.face[0] == BOTTOM || current.face[1] == BOTTOM ) ) {
                    
                     addDataPoint(BOTTOM,icell,jcell);
                     face = BOTTOM;
                     
            } else
            if( search(icell,jcell+nx) == null && 
                ( current.face[0] == TOP || current.face[1] == TOP ) ) {

                     addDataPoint(TOP,icell,jcell);
                     face = TOP;

            } else {
            	
/*
**          No terminating wall so it does not matter where we start as
**          long as it is not a terminal face. Remember the position
**          of the cell behind us just in case we loop back as the curve
**          must be closed.
*/
                index = 0;
                if( current.face[0] == TERMINAL ) index = 1;
                
                addDataPoint(current.face[index],icell,jcell);
                face = current.face[index];
                

                if( face == TOP ) {
                     ifcell = current.i;
                     jfcell = current.j+1;
                } else 
                if( face == BOTTOM ) {
                     ifcell = current.i;
                     jfcell = current.j-1;
                } else 
                if( face == LEFT ) {
                     ifcell = current.i-1;
                     jfcell = current.j;
                } else 
                if( face == RIGHT ) {
                     ifcell = current.i+1;
                     jfcell = current.j;
               }
            }
            


            while( current != null ) {
                
               icell = current.i;
               jcell = current.j;
               
               if( current.face[0] == face )  face = current.face[1];
               else                           face = current.face[0];
                          
               if(face != TERMINAL) addDataPoint(face,icell,jcell);
               
               if(face == TOP ) {
                     jcell++;
                     face = BOTTOM;
                } else
               if(face == BOTTOM ) {
                     jcell--;
                     face = TOP;
                } else
               if(face == LEFT ) {
                     icell--;
                     face = RIGHT;
                } else
               if(face == RIGHT ) {
                     icell++;
                     face = LEFT;
                }
                
                cells.removeElement(current);

                if( icell == ifcell && jcell == jfcell ) {
                     addDataPoint(curve[0], curve[1]);
                     current = null;
                } else {
                     current = search(icell,jcell);
                }
            }

     }
  /**
   * Return the (x,y) position where the contour cross the cell wall.
   * @param wall The cell wall the contour crosses.
   * @param icell The x index of the cell position.
   * @param jcell The y index of the cell position.
   */

     protected double[] getPoint(int wall, int icell, int jcell)  {
     	double d[] = new double[2];
        double bl, br, tl, tr;
        int index = icell + jcell*nx;
        
        if(wall == TOP ) {
                     tl = grid[index+nx];
                     tr = grid[index+nx+1];
                     d[1] = (double)(jcell+1);
                     d[0] = (double)icell + (value-tl)/(tr-tl);
         } else
         if(wall == BOTTOM ) {
                     bl = grid[index];
                     br = grid[index+1];
                     d[1] = (double)jcell;
                     d[0] = (double)icell + (value-bl)/(br-bl);
         } else
         if(wall == LEFT ) {
                     bl = grid[index];
                     tl = grid[index+nx];
                     d[1] = (double)jcell + (value-bl)/(tl-bl);
                     d[0] = (double)icell;
         } else
         if(wall == RIGHT ) {
                     br = grid[index+1];
                     tr = grid[index+1+nx];
                     d[1] = (double)jcell + (value-br)/(tr-br);
                     d[0] = (double)(icell + 1);
         }
     	
         return  d;
         }
         
  /**
   * Add a data point to the contour curve.
   * @param wall The cell wall the contour crosses.
   * @param icell The x index of the cell position.
   * @param jcell The y index of the cell position.
   */ 
     protected void addDataPoint(int wall, int icell, int jcell)  {
     	  double d[];
     	  
     	  d = getPoint(wall,icell,jcell);
     	  
     	  addDataPoint(d[0], d[1]);
     }
     
         
  /**
   * Add a data point to the contour curve.
   * @param x The x position of the point.
   * @param y The y position of the point.
   */ 
     protected void addDataPoint(double x,double y) {

            if(size >= MAXARRAYSIZE-2) return;

            if( curve == null)  {
            	curve = new double[ARRAYSIZE];
            } else
            if(size == curve.length-2 ) {
               double tmp[] = new double[2*size];
               System.arraycopy(curve,0,tmp,0,size);
               curve = tmp;
            }
            
            curve[size] = x;
            size++;
            curve[size] = y;
            size++;
     }

  /**
   * Search for a cell in the contour cell list
   * @param icell the x index of the cell
   * @param jcell the y index of the cell
   */
     protected Cell search(int icell, int jcell) {
           int i;
           Cell current = null;
           
           if( cells.isEmpty() ) return null;
           
           for(i=0; i<cells.size(); i++) {
               current = (Cell)(cells.elementAt(i));
               
               if(current.i == icell && current.j == jcell) return current;
           }
           
           return null;
     
     }


}

/**
* This class is a structure class holding information about the
* cells that the contour line passes through. Used only with IsoCurve class
*/
class Cell extends Object {
        int i;
        int j;
        int face[] = new int[2];
        boolean visited;

        public Cell() {
                       i = -1;
                       j = -1;
                       face[0] = IsoCurve.TERMINAL;
                       face[1] = IsoCurve.TERMINAL;
		     }

}
