package SModeling;

import java.awt.*;
import java.util.Vector;
import FDATI.*;

/** 
 *  Class which contains methods for displaying an object of type model.
 *  Includes methods for handling model transformations, backface culling,
 *  hiddenline removal and fixture point selection.<p>
 *
 * Portions of this class based on code by James Gosling, relevant code
 * is marked.<p>
 *
 * <hr>
 * Copyright (c) 1995, H. Robert Frost, Stanford University.
 * All rights reserved.<p>
 * Copyright (c) 1996, H. Robert Frost, Enterprise Integration Technologies,
 * Inc. All rights reserved.<p>
 *
 * RESTRICTED RIGHTS LEGEND: Use, duplication or disclosure by the 
 * Government is subject to restrictions as set forth in 
 * subparagraph(c)(1)(ii) of the Rights in Technical Data and Computer
 * Software clause at DFARS 252.227-7013 and in similar clauses in the
 * FAR and NASA FAR supplement.<p>
 * 
 * This software is bound by the terms and conditions listed in the
 * attached <a href="../LICENSE">LICENSE file</A>.
 * <hr>
 * @author <A HREF="http://cdr.stanford.edu/html/people/frost-bio.html"> Rob Frost</A>
 * @version 5/21/96 for Java(tm) Language Version 1.0.2
 */


public class ModelDisplay {

  static final int CAP = 20, INCR = 5; /* capacity and increment
					 for Vectors */

  static final hpoint north_light = new hpoint(0,1,1,1);
  static final hpoint south_light = new hpoint(0,-1,1,1);
  static final hpoint east_light = new hpoint(-1,0,1,1);
  static final hpoint west_light = new hpoint(1,0,1,1);
  boolean north = true;
  boolean south = false;
  boolean east = false;
  boolean west = false;

  hpoint direction_vector = new hpoint();
  boolean directional = false;

  /* reference frame triad */
  hpoint origin = new hpoint(0,0,0,1);
  hpoint z_axis = new hpoint(0,0,1,1);
  hpoint y_axis = new hpoint(0,1,0,1);
  hpoint x_axis = new hpoint(1,0,0,1);
 
  /**
   *  Canvas to which the model is painted.
   */
  public ModelViewer parent;
  model Mod;
  Vector tverticies; /* set of transformed verticies, type hpoint */
 
  /**
   * Vector of transformed fixture points, type fix_point.
   */
  public Vector tfix_points; /* list of transformed fixture points,
				type fix_point */
  Vector vis_segments;/* holds visible segments from partially 
				blocked lines, each segment has 3 entries,
				two endpoints and a line index*/

 /**
  * Transformation matrix which describes position and
  * orientation of the model wrt to center of the
  * display canvas.
  */
  public matrix Mat;
  
  /**
   * Transformation matrix for the reference frame triad.
   */
  public matrix Mat_triad;

  boolean transformed = false;
  p_vline intersect_vector = null; /* current interesection vector */ 
  int num_lines; /* number of unique edges */
  int num_verticies; /* number of verticies */
  int num_faces; /* number of faces on the solid */		

  /**
   * Dimensions of display canvas
   */
  public int width, height;
 
  /**
   * Additional displacement x and y specified by user
   */
  public int deltax, deltay;
  
  /**
   * Bounding box for the model
   */
  public float xmin, xmax, ymin, ymax, zmin, zmax;
  
  int closest_vertex = 0;

  /**
   * Creates a ModelDisplay object for the specified model.
   * Gives the model an initial rotation (30 deg. about x, then
   * 30 deg. about y)
   * @param m Model to be displayed
   */

  public ModelDisplay(model m) {

    Mod = m;
    tverticies = new Vector(CAP, INCR);
    vis_segments = new Vector(CAP, INCR);
    tfix_points = new Vector(CAP, INCR);	
    for(int i = 0; i < Mod.verticies.size(); i++){
      hpoint temp = (hpoint)Mod.verticies.elementAt(i);
      tverticies.addElement((hpoint)temp.clone());
    }
    num_faces = Mod.num_faces;
    num_verticies = Mod.num_verticies;
    num_lines = Mod.num_lines;
    findBB();

    Mat = new matrix(true); /* initialize orientation to identity, create an
			     inverse */
    Mat_triad = new matrix(false);

    /* give object initial rotation */
    Mat.rotate(30,1);
    Mat.rotate(30,2); 
    Mat_triad.rotate(30,1);
    Mat_triad.rotate(30,2);

    north_light.normalize();
    south_light.normalize();
    east_light.normalize();
    west_light.normalize();

    deltax = 0;
    deltay = 0;
  }
  
  /** 
   *  Find the bounding box of this model.
   *  Based on code by James Gosling.
   */
  
  public void findBB() {
    if (num_verticies <= 0) return;

    hpoint temp = (hpoint)tverticies.elementAt(0);
    float xmin = temp.getx(), xmax = xmin;
    float ymin = temp.gety() , ymax = ymin;
    float zmin = temp.getz() , zmax = zmin;
    
    for (int i = 1; i < num_verticies; i++) {
      temp = (hpoint)tverticies.elementAt(i);
      float x = temp.getx();
      if (x < xmin)
	xmin = x;
      if (x > xmax)
	xmax = x;
      float y = temp.gety();
      if (y < ymin)
	ymin = y;
      if (y > ymax)
	ymax = y;
      float z = temp.getz();
      if (z < zmin)
	zmin = z;
      if (z > zmax)
	zmax = z;
    }
    this.xmax = xmax;
    this.xmin = xmin;
    this.ymax = ymax;
    this.ymin = ymin;
    this.zmax = zmax;
    this.zmin = zmin;
  }

  /**
   * Finds the vertex which is closest to the viewing plane.
   */

  int find_closest_vertex() {
    
    int closest_index = 0;
    hpoint temp = (hpoint)tverticies.elementAt(0);
    float closest_z = temp.getz();
    for(int i = 1; i < num_verticies; i++) {
      temp = (hpoint)tverticies.elementAt(i);
      if(temp.getz() < closest_z) {
	closest_z = temp.getz();
	closest_index = i;
      }
    }
    closest_vertex = closest_index + 1;
    return (closest_index + 1);

  }

  /**
   * Transform all the points in this model by the current transformation
   * matrix M
   */
  
  void transform() {
    if (transformed || num_verticies <= 0)
      return;
    /* transform verticies */
    for(int i = 0; i < num_verticies; i++) {
      Mat.multiply((hpoint)tverticies.elementAt(i));
    }
    /* transform fixture points */
    int num_fixpts = tfix_points.size();
    for(int i = 0; i < num_fixpts; i++) {
      fix_point temp = (fix_point)tfix_points.elementAt(i);
      Mat.multiply(temp.point);
    }
    /* transform visible segments */
    int num_seg = vis_segments.size();
    for(int i = 0; i < num_seg; i++) {
      if( i%3 == 0 || i%3 == 1) { 
	hpoint temp = (hpoint)vis_segments.elementAt(i);
	Mat.multiply(temp);
      }
    }
    Mat.unit();
    transformed = true;
  }

  /**
   * Transform the reference frame triad.
   */

  void transform_triad(){
    if(transformed) return;
    Mat_triad.multiply(origin);
    Mat_triad.multiply(x_axis);
    Mat_triad.multiply(y_axis);
    Mat_triad.multiply(z_axis);
    Mat_triad.multiply(direction_vector);
    Mat_triad.unit();
    transformed = true;
  }

  /**
   * Find visible faces and lines, uses the dot product between the 
   * face normal vector and the viewing direction to determine if a
   * face is visible. For concave object this will result in some
   * blocked faces being displayed.
   */
  
  void find_visible () {
    
    int num_visible = 0;
    hpoint view = new hpoint(0,0,1,1);

    /* get two lines for each face to find normals with */
    try {
 
      for(int i = 0; i < num_faces; i++) {
	
	face temp = (face)Mod.faces.elementAt(i);
		
	hpoint p1 = (hpoint)tverticies.elementAt(temp.getV(0)-1);
	hpoint p2 = (hpoint)tverticies.elementAt(temp.getV(1)-1);
	hpoint p3 = (hpoint)tverticies.elementAt(temp.getV(2)-1);
		
	hpoint e1 = p2.diff(p1);
	hpoint e2 = p3.diff(p2);
	
	e2.cross(e1);  
        temp.in_norm = e2;
	temp.in_norm.normalize();
	
	if(temp.in_norm.dot(view) > 0) {
	  temp.visible = true;
	  num_visible++;
	} else {
	  temp.visible = false;
	} 

      }
    } catch(Exception e) {
      parent.action(this, e.toString());
    }
      
    /* sort through lines and only keep those with valid faces */
    try {
       
      for(int i = 0; i < num_lines; i++) {
	line temp = (line)Mod.lines.elementAt(i);
	face f1 = (face)Mod.faces.elementAt(temp.f1);
	face f2 = (face)Mod.faces.elementAt(temp.f2);
	if(f1.visible || f2.visible) {
	  temp.visible = true;
	} else {
	  temp.visible = false;
	}
      } 
    } catch (Exception e) {
      parent.action(this, e.toString());
    }
      
  }

  /**
   *  Determine if a given x,y screen point intersects with a
   *  given face. If it does, return a p_vline represent the inward
   *  surface normal at the point of intersection.
   */
 
  p_vline face_intersect(int x, int y, int face) {
  
    face temp = (face)Mod.faces.elementAt(face);

    boolean intersects = true;

    hpoint v1 = (hpoint)tverticies.elementAt(temp.getV(0)-1);

    /* find z coordinate of intersection of line with plane of
       face, will always intersect with plane */

    float z =  (-temp.in_norm.getx()*((float)x - v1.getx()) +
		-temp.in_norm.gety()*((float)y - v1.gety())
		)/temp.in_norm.getz() + v1.getz();

    hpoint intersect = new hpoint((float)x,(float)y,z,1);
    temp.intersect = intersect; /* set face intersect variable */

    int num_edges = temp.verticies.size();

    /* For each edge bounding the face, determine if the intersection
       is on the correct side of the edge, only if it is on the correct
       side of every edge is the intersection on the face */

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

      int p1, p2;
      p1 = temp.getV(i);
      if(i >= num_edges -1) { /* last vertex */
	p2 = temp.getV(0);
      } else {
	p2 = temp.getV(i+1);
      }
      hpoint v_to_p = intersect.diff((hpoint)tverticies.elementAt(p1-1));
      hpoint begin = (hpoint)tverticies.elementAt(p1-1);
      hpoint end = (hpoint)tverticies.elementAt(p2-1);

      hpoint edge = end.diff(begin);
      v_to_p.cross(edge);
      v_to_p.normalize();
        
      if ( temp.in_norm.dot(v_to_p) <= 0) {
	intersects = false;
      }
    }
    
    if (intersects) {
      p_vline answer = new p_vline(intersect, temp.in_norm);
      return answer;
    } else {
      return null;
    }
  }
  

  /**
   * Test a given x,y screen point for intersection with all of
   * the visible faces in a model. If the point intersects with more
   * than one face, pick the face closer to the viewer ( this allows for
   * correct intersection determination with concave models ).
   */
  
  public boolean all_intersect(int x, int y) {
    
    p_vline intersect = null;
    int face_index = 0;

    try {
      
      for(int i = 0; i < num_faces; i++){
	face temp = (face)Mod.faces.elementAt(i);
	if(temp.visible) {
	  if(intersect == null) {
	    intersect = face_intersect(x,y,i);
	    if(intersect != null) {
	      face_index = i;
	    }
	  } else {
	    p_vline next_intersect = face_intersect(x,y,i);
	    if(next_intersect != null) {
	      float z1 = intersect.point.getz();
	      float z2 = next_intersect.point.getz();
	      if(z2 <= z1) {
		intersect = next_intersect;
		face_index = i;
	      }
	    }
	  }
	}
      }
    } catch (Exception e) {
      parent.action(this, "Can't find intersection.\n" + e.toString());
    }
   
    intersect_vector = intersect;
       
    if(intersect_vector != null) {
      hpoint point = intersect.point;
      fix_point fp = new fix_point(point, face_index);
      tfix_points.addElement(fp);
      fix_point abs_fp = new fix_point((hpoint)point.clone(), face_index);
      Mat.inverse.multiply(abs_fp.point);
      Mod.fix_points.addElement(abs_fp);
      Mod.current_point = Mod.fix_points.size();
    }
  
    From_Middle();
    parent.repaint();

    if(intersect != null) {
      return true;
    } else {
      return false;
    }
  }

  /*
   * This method finds the edges on forward faces (those not removed by
   * back culling) which are intersected by edges on faces closer to the
   * screen (i.e. blocked edges).
   */
	
  void HiddenLineRemoval() {

    float current_pos; /* index of current face in Vector faces */
    float temp_pos;  /* index of current face in Vector faces */
    line current_edge; /* edge being tested */
    line temp_edge; /* edge which might be blocking */
    hpoint v1, v2, v3, v4; /* end points of current and temp edges */

    /* testing variables */
    int num_vis_face = 0;
    int blocking_faces;
    int num_intersections = 0;

    /* get position of closest vertex for each face */
    float edge_pos[] = GetEdgePosition();
   
    /* for every edge on the object */
    for(int k = 0;k < num_lines;k++) {
      current_edge = (line)Mod.lines.elementAt(k);
      /* the edge is visible */
      if(current_edge.visible) {
	current_pos = edge_pos[k];
	v1 = (hpoint)tverticies.elementAt(current_edge.p1-1);
	v2 = (hpoint)tverticies.elementAt(current_edge.p2-1);
	/* for every other line on the object */
	for(int j = 0; j < num_lines; j++) {
	  temp_edge = (line)Mod.lines.elementAt(j);
	  /* edge is visible and not current edge */
	  if(temp_edge.visible && j != k) {
	    temp_pos = edge_pos[j];
	    /* it is closer to the screen */
	    if(temp_pos <= current_pos) {
	      v3 = (hpoint)tverticies.elementAt(temp_edge.p1-1);
	      v4 = (hpoint)tverticies.elementAt(temp_edge.p2-1);
	      /* edges don't share an end point */
	      if( !( v1.equals(v3) || v1.equals(v4) || v2.equals(v3) ||
		     v2.equals(v4) ) ) {		   
		/* test to see if the projected edges intersect on the 
		   screen */
		try {
		  hpoint intersection =line_intersection(v1,v2,v3,v4);
		  if( intersection != null) {
		    current_edge.blocked = true;
		    current_edge.intersections.addElement(intersection);
		    num_intersections++;
		  }
		} catch (Exception e) {
		  parent.action(this,"line intersection failed, " + e.toString());
		}
	      }
	    }
	  }
	}
      }
    }

    /* for intersected edges, find those segments which are visible */
    SetFaceBlocked();
    try {
      FindVisibleSegments();
    } catch(Exception e) {
      parent.action(this, "error finding visible segments, " + e.toString());
    }
    
  }
  

  /**
   *  Sort the list of intersection points for each edge according to the
   *  distance from the line.p1 vertex to the intersection point.
   */

  void OrderIntersects(line l) {
    float inter_dist[] = new float[l.intersections.size()];
    hpoint v1, p, diff, temp1;
    float min;
    int min_index;

    /* get distances */
    v1 = (hpoint)tverticies.elementAt(l.p1-1);
    for(int i = 0; i < l.intersections.size(); i++) {
      p = (hpoint)l.intersections.elementAt(i);
      diff = p.diff(v1);
      inter_dist[i] = diff.magnitude();
    }

    /* do a bubble sort (I know, I know, but I am assuming that there are never
       many intersections, and I didn't feel like implementing quick sort!) */
    for(int i = 0; i < l.intersections.size(); i++) {
      min = inter_dist[i];
      min_index =i;
      for(int j = i+1; j < l.intersections.size(); j++) {
	if(inter_dist[j] < min) {
	  min_index = j;
	  min = inter_dist[j];
	}
      }
      if(i != min_index) {
	temp1 = (hpoint)l.intersections.elementAt(i);
	l.intersections.setElementAt((hpoint)l.intersections.elementAt(min_index),i);
	l.intersections.setElementAt(temp1,min_index);

      }
    }

  }

  /**
   *  Find the visible segments of intersected edges.
   */

  void FindVisibleSegments() {
    int k;
    /* for all the edges on the object */
    for(int i = 0; i< num_lines; i++) {
      line l = (line)Mod.lines.elementAt(i);
      /* if the edge is blocked */
      if(l.blocked) {
	OrderIntersects(l); /* order the intersections */
	if(l.intersections.size() > 0) {
	  /* for segment from vertex 1 to first intersection*/
	  DetermineSegmentVisibility((hpoint)tverticies.elementAt(l.p1-1),
				     (hpoint)l.intersections.elementAt(0),i);
	  /* between all intersections */
	  if(l.intersections.size() > 1) {
	    for(k=0;k<l.intersections.size()-1;k++) {
	      DetermineSegmentVisibility((hpoint)l.intersections.elementAt(k),
					 (hpoint)l.intersections.elementAt(k+1),i);
	    }
	  }
	  /* between the last intersection and vertex 2 */
	  DetermineSegmentVisibility((hpoint)l.intersections.lastElement(),
				     (hpoint)tverticies.elementAt(l.p2-1),i);
	}
      }
    }
    
    for(int i = 0; i < num_faces; i++){
      face temp = (face)Mod.faces.elementAt(i);
      if(temp.blocked) {
	for(k = 0; k < temp.lines.size(); k ++) {
	  line l = (line)Mod.lines.elementAt(temp.getE(k)-1);
	  if(!l.blocked) {
	    DetermineSegmentVisibility((hpoint)tverticies.elementAt(l.p1-1),
				       (hpoint)tverticies.elementAt(l.p2-1),
				       temp.getE(k)-1);
	    l.blocked = true;
	  }
	}
      }
    }


  }


  /** 
   * Set the blocked flag for every face in the model, if a face has
   * a blocked edge then the face is also blocked.
   */

  void SetFaceBlocked() {
    
    for(int i = 0; i < num_faces; i++) {
      face temp = (face)Mod.faces.elementAt(i);
      if(temp.visible) {
	for(int j = 0; j < temp.lines.size(); j ++) {
	  line edge = (line)Mod.lines.elementAt(temp.getE(j) -1);
	  if(edge.blocked) {
	    temp.blocked = true;
	  }
	}
      }
    }
  }
   
 /**
   * Determine if a given point is visible. Check each visible face to see if
   * it is blocked.
   * @param p point whose visibility we want to check
   * @param f index of face which point is located on, -1 if not on a face
   * @param e index if edge which point is on, -1 if not on an edge
   */

  boolean DeterminePointVisibility(hpoint p, int f, int e) {
    line on_line = null;
    boolean is_visible = true;

    if(e > 0) {
      on_line = (line)Mod.lines.elementAt(e);
    }      

    /* for all faces in the model */

    for(int i = 0; i < num_faces; i++) {
      /* if it is not an adjacent face(for point on edge), or the on_face (for
         point on face)*/
      if( ((e > 0) && (i != on_line.f1) && (i != on_line.f2))
	  || ((f > 0) && (i != f)) ) {
	face temp = (face)Mod.faces.elementAt(i);
	/* if it is visible */
	if(temp.visible) {
	  try {
	    if(face_intersect((int)p.getx(),(int)p.gety(),i) != null) {
	      is_visible = false;
	    }
	  } catch(Exception excep) {
	    if(is_visible) {
	      parent.action(this, "point is visible");
	    }
	  }
	}
      }
    }
    
    return is_visible;
  }

  /**
   *  For a given segment, determine if the segment is visible .
   *  p1 and p2 are the endpoints of the segment, l is the edge index
   */

  void DetermineSegmentVisibility(hpoint p1, hpoint p2, int l) {
    float x,y; /* mid point coordinates */
    hpoint mid_point; /* point whose visibility is tested */
    line current_line = (line)Mod.lines.elementAt(l);
    boolean is_visible = true;

    x = (p1.getx() - p2.getx())/2 + p2.getx();    
    y = (p1.gety() - p2.gety())/2 + p2.gety();
   
    mid_point = new hpoint(x,y,1,1);

    if(DeterminePointVisibility(mid_point,-1,l)) {
      vis_segments.addElement((hpoint)p1.clone());
      vis_segments.addElement((hpoint)p2.clone());
      vis_segments.addElement(new Integer(l));
    }

  }
  
  /**
   * Returns an array of floats which contain the z-coordinate of the closest
   * vertex for each edge in the model.
   */

  float[] GetEdgePosition() {
    float vertex_pos[] = new float[num_lines];
    line temp_edge;
    hpoint v1, v2;
     
    for(int i = 0; i < num_lines; i++) {
      temp_edge = (line)Mod.lines.elementAt(i);
      if(temp_edge.visible) {
	v1 = (hpoint)tverticies.elementAt(temp_edge.p1 -1);
	v2 = (hpoint)tverticies.elementAt(temp_edge.p2 -1);
	
	if(v1.getz() < v2.getz()) {
	  vertex_pos[i] = v1.getz();
	} else {
	  vertex_pos[i] = v2.getz();
	}
      }
    }
	    
    return vertex_pos;  
  }


  /**
   * Determines if the projections of two lines on the screen interesect,
   * returns the intersection point if they do.
   */

  hpoint line_intersection(hpoint p1, hpoint p2, hpoint p3, hpoint p4) {
    
    hpoint answer = null;
    
    float x1 = p1.getx();
    float y1 = p1.gety();
    float x2 = p2.getx();
    float y2 = p2.gety();
    float x3 = p3.getx();
    float y3 = p3.gety();
    float x4 = p4.getx();
    float y4 = p4.gety();

    float s1 = (y2 - y1)/(x2 - x1);
    float s2 = (y4 - y3)/(x4 - x3);
    
    /* solve for x,y point of intersection between lines defined by
       four points */
    float x = (x3*s2 - x1*s1 + y1 - y3)/(s2-s1);
    float y = s1*(x-x1) + y1;

    /* check to see if point is on line segments */
    if( ((x <= x1 && x >= x2) || (x <= x2 && x >= x1)) &&
	((x <= x3 && x >= x4) || (x <= x4 && x >= x3)) &&
	((y <= y1 && y >= y2) || (y <= y2 && y >= y1)) &&
	((y <= y3 && y >= y4) || (y <= y4 && y >= y3)) ) {
      answer = new hpoint(x,y,1,1);;
    } 
    
    return answer;
  }
 
  /** 
   * Resets the blocked flags for both faces and lines and clears the
   * respective vectors. Called when the model is rotated (hidden lines
   * change).
   */
  
  void HiddenLineReset () {
    for(int j = 0; j < num_lines;j++) {
      line temp = (line)Mod.lines.elementAt(j);
      if(temp.blocked) {
	temp.intersections.removeAllElements();
      }  
      temp.blocked = false;
    }
    vis_segments.removeAllElements();    
    for(int i = 0; i < num_faces; i++) {
      face temp = (face)Mod.faces.elementAt(i);
      temp.blocked = false;
    }
  }

  /** 
   * Resets the visible flags for all lines in the model. Called to cancel
   * effects of backface culling & hiddenline removal.
   */

  void VisibleLineReset () {
    for(int i = 0; i < num_lines; i++) {
      line temp = (line)Mod.lines.elementAt(i);
      temp.visible = true;
    } 
  } 
 

  /**
   * Move model from origin to center of screen, done after rotations and
   * scaling
   */
  
  public void To_Middle() {
    
    Mat.translate(width/2 + deltax, height/2 + deltay, 500);
    transformed = false;
    transform(); /* apply translation */

    Mat_triad.translate((float)(.15*height), (float)(.85*height), 500);
    transformed = false;
    transform_triad(); /* apply translation */
    
  }
  
  /**
   * Move model from center of screen to origin, done before rotations
   * and scaling
   */

  public void From_Middle() {
   
    Mat.translate(-width/2 - deltax, -height/2 - deltay, -500);
    transformed = false;
    transform(); /* apply translation */

    Mat_triad.translate((float)(-.15*height), (float)(-.85*height), -500);
    transformed = false;
    transform_triad(); /* apply translation */


  }


  /**
   * Sets the value of the transformed flag.
   * @param value New value for the transformed flag.
   */

  public void setTransformed (boolean value) {
    transformed = value;
  }
  
  /** 
   * Paint the model in the specified Graphics context.
   * @param g Graphics context into which the model should be painted.
   * @param reset Resets all lines to visible.
   * @param backfacecull Resets all lines and then culls.
   * @param hiddenline Resets all lines then checks for hiddenlines.
   * @param render Fill the face polygons according to current light vector.
   * @param edges Should edges of the model be drawn?
   */

  public void paint(Graphics g, boolean reset, boolean hiddenline,
		    boolean backfacecull, boolean render, boolean edges) {
    p_vline intersect = null; /* current mouse intersection point and
			         normal for intersected face */

    if (num_verticies <= 0) /* don't draw anything if no verticies */
      return;

    transform(); /* apply outstanding rotations and scalings */
    transformed = false;
    Mat_triad.scale((float).125*height,(float).125*height,
		    (float).125*height,false);
    transform_triad();
    
    if(reset || backfacecull || hiddenline) {
      VisibleLineReset(); /*reset for backface culling, sets all lines to
			    visible*/
      HiddenLineReset(); /* reset for hiddenline removal, sets all lines to
			    non-intersected */
    }
    
    /* remove faces whose outward normals are in the view direction */
    if(backfacecull || hiddenline) {
      try {
	find_visible();  
      } catch(Exception e) {
	parent.action(this, " find_visible failed, " + e.toString());
      }
    }

    /* determine if any non-culled faces are blocked by other faces */     
    
    if(hiddenline && edges) {
      try {
	HiddenLineRemoval();
      } catch(Exception e) {
	parent.action(this, "error finding blocked edges, " + e.toString());
      }
    }
    
    To_Middle(); /* move to middle of display area */
    
    /* if render set then render the visible faces of the model */    
    if(render){
      render_faces(g);
    }

    /* draw the visible edges of the model if they need to be redrawn */  
    if(edges){
      draw_lines(g);
    }
    
    /* draw visible segments of blocked lines */
    if(edges){
      draw_segments(g);
    }

    /* Draw added fixture points on visible faces */
    draw_fixels(g);
   
    /* Draw the ref frame triad */
    draw_triad(g, direction_vector);

    From_Middle(); /* return model to origin */   
    x_axis.normalize();
    y_axis.normalize();
    z_axis.normalize();
    direction_vector.normalize();

  }

  /**
   * Sets the direction vector for directional analysis.
   * @param dir Are we doing directional analysis?
   * @param dir_vec Directional vector
   */

  public void setDirection(boolean dir, hpoint dir_vec){

    directional = dir;
   if(dir){
     direction_vector = dir_vec;
     direction_vector = constructDirectionVector();
   }
  }

  /**
   * Sets the light vector for rendering.
   * @param n North component?
   * @param s South component?
   * @param e East component?
   * @param w West component?
   */
  
  public void set_light(boolean n, boolean s, boolean e, boolean w){
    north = n;
    south = s;
    east = e;
    west = w;
  }

  hpoint constructDirectionVector(){
    hpoint vector = new hpoint();
    if(directional){
      direction_vector.normalize();
      float x = direction_vector.getx()*x_axis.getx() +
	direction_vector.gety()*y_axis.getx() +
	  direction_vector.getz()*z_axis.getx();
      float y = direction_vector.getx()*x_axis.gety() +
	direction_vector.gety()*y_axis.gety() +
	  direction_vector.getz()*z_axis.gety();
      float z = direction_vector.getx()*x_axis.getz() +
	direction_vector.gety()*y_axis.getz() +
	  direction_vector.getz()*z_axis.getz();
      vector = new hpoint(x,y,z,1);
    }
    return vector;
  }

  void draw_triad(Graphics g, hpoint vector){

    g.setColor(Color.red);
    draw_two_points(g, origin,x_axis);
    g.drawString("i",(int)x_axis.getx() + 3,(int)x_axis.gety());
    draw_two_points(g, origin,y_axis);
    g.drawString("j",(int)y_axis.getx() + 3,(int)y_axis.gety());
    draw_two_points(g, origin,z_axis);
    g.drawString("k",(int)z_axis.getx() + 3,(int)z_axis.gety());

    if(directional){
      g.setColor(Color.yellow);
      draw_two_points(g,origin,vector);
    }
  }
    

  void render_faces(Graphics g){
    
    /* for every blocked face on the model */
    for(int face_index = 0;face_index<num_faces;face_index++){
      face temp = (face)Mod.faces.elementAt(face_index);
      /* if the face is visible and not blocked */
      if(temp.visible && temp.blocked){
	render_face(g,temp);
      }
    }
    
    /* for every visible and not blocked face on the model */
    for(int face_index = 0;face_index<num_faces;face_index++){
      face temp = (face)Mod.faces.elementAt(face_index);
      /* if the face is visible and not blocked */
      if(temp.visible && !temp.blocked){
	  render_face(g,temp);
	}
    }
    
  }

  void render_face(Graphics g, face f){
    
    Polygon poly = new Polygon();
    /* construct a polygon for the face */
    for( int vert_index = 0; vert_index < f.size();vert_index++){
      
      hpoint vert = (hpoint)tverticies.elementAt(f.getV(vert_index)-1);
      
      poly.addPoint((int)vert.getx(),(int)vert.gety());
      
    }
    float orient = 0;
    int num_lights = 0;
    float temp = 0;

    if(north){
      temp = f.in_norm.dot(north_light);
      if(temp > 0) orient += temp;
      num_lights++;
    }
    if(south) {
      temp = f.in_norm.dot(south_light);
      if(temp > 0) orient += temp;
      num_lights++;
    }
    if(east) {
      temp = f.in_norm.dot(east_light);
      if(temp > 0) orient += temp;
      num_lights++;
    }
    if(west){
      temp = f.in_norm.dot(west_light);
      if(temp > 0) orient += temp;
      num_lights++;
    }

    orient *= (1 - (num_lights-1)*.15);
    if(orient > 1) orient = 1;
    
    g.setColor(new Color(orient,orient,orient));
    g.fillPolygon(poly);
  }

  void draw_lines(Graphics g){
    for(int i=0; i< num_lines; i++) {
      line temp = (line)Mod.lines.elementAt(i);
      
      if(temp.visible && !temp.blocked) {
     	if(Mod.fix_points.size() > 0){ 
	  fix_point current_fixel =
	    (fix_point)tfix_points.elementAt(Mod.current_point -1);
	  int current_face = current_fixel.face_index;    
	  if(temp.f1 == current_face || temp.f2 == current_face) {
	    g.setColor(Color.green);
	  } else {
	    g.setColor(Color.black);
	  }
	} else {
	  g.setColor(Color.black);
	}
	draw_edge(g, temp);
      }
    }
  }

  void draw_segments(Graphics g){
    hpoint p1, p2;

    if(vis_segments.size() > 0) {
      Integer line_index;
      line temp;
      for(int i =0; i < vis_segments.size();i++) {
	p1 = (hpoint)vis_segments.elementAt(i);
	p2 = (hpoint)vis_segments.elementAt(i+ 1);
	line_index = (Integer)vis_segments.elementAt(i+2);
	temp = (line)Mod.lines.elementAt(line_index.intValue());
	if(Mod.fix_points.size() > 0){ 
	  fix_point current_fixel =
	    (fix_point)tfix_points.elementAt(Mod.current_point -1);
	  int current_face = current_fixel.face_index;    
	  if(temp.f1 == current_face || temp.f2 == current_face) {
	    g.setColor(Color.green);
	  } else {
	    g.setColor(Color.black);
	  }
	} else {
	  g.setColor(Color.black);
	}
	draw_two_points(g,p1,p2);
	i += 2;
      }
    }
  }

  void draw_fixels(Graphics g){
    hpoint p1, p2;

    if(tfix_points.size() > 0) { /* if we have fix_points */
      int num_fixs = tfix_points.size();
      for(int i = 0;i<num_fixs;i++) {
	boolean is_visible = false;
	fix_point fp = (fix_point)tfix_points.elementAt(i); 
	face temp = (face)Mod.faces.elementAt(fp.face_index);
	/* if the face is visible and not blocked the fixel is visible, if
	   the face is visible and blocked must test to if the fixel point
	   is visible */
	if( temp.visible){
	  if( !temp.blocked) {
	    is_visible = true;
	  } else {
	    is_visible = DeterminePointVisibility(fp.point,fp.face_index,-1);
	  }
	}
	    
	/* if the fix_point is visible draw it */
	if(is_visible) {
	  p1 = fp.point;
	  hpoint vect = (hpoint)temp.in_norm.clone();
	  vect.normalize();
	  vect.scale(10);
	  p2 = p1.diff(vect);

	  hpoint v1 = (hpoint)tverticies.elementAt(temp.getV(0)-1);
	  hpoint v2 = (hpoint)tverticies.elementAt(temp.getV(1)-1);
	  hpoint tang = v2.diff(v1);
	  tang.normalize();
	  tang.scale(5);
	  hpoint tang_perp = (hpoint)tang.clone();
	  tang_perp.cross(vect);
	  tang_perp.normalize();
	  tang_perp.scale(5);
	  hpoint pyr1 = p2.add(tang);
	  hpoint pyr2 = p2.diff(tang);
	  hpoint pyr3 = p2.add(tang_perp);
	  hpoint pyr4 = p2.diff(tang_perp);
	  
	  /* determine color for pyramid */

	  if(Mod.current_point-1 == i) {
	    g.setColor(Color.green);
	  } else  {
	    g.setColor(Color.blue);
	  }

	  /* draw the pyramid representing each fixture point */

	  draw_two_points(g,p1,pyr1);
	  draw_two_points(g,p1,pyr2);
	  draw_two_points(g,p1,pyr3);
	  draw_two_points(g,p1,pyr4);
	  draw_two_points(g,pyr1,pyr3);
	  draw_two_points(g,pyr3,pyr2);
	  draw_two_points(g,pyr2,pyr4);
	  draw_two_points(g,pyr4,pyr1);

	}
      }
    }
  }

  void draw_edge (Graphics g, line l) {
    hpoint p1 = (hpoint)tverticies.elementAt(l.p1-1);
    hpoint p2 = (hpoint)tverticies.elementAt(l.p2-1);
    draw_two_points(g, p1, p2);
  }

  void draw_two_points (Graphics g, hpoint p1, hpoint p2) {
    g.drawLine((int)p1.getx(), (int)p1.gety(),
	       (int)p2.getx(),(int)p2.gety());
  }	

  void draw_face_edges (Graphics g, face f) {
    int num_edges = f.verticies.size();
    for(int j = 0;j < num_edges;j++) {
      int vert1, vert2;
      vert1 = f.getV(j);
      if(j >= num_edges -1) { /* last vertex */
	vert2 = f.getV(0);
      } else {
	vert2 = f.getV(j+1);
      }
      hpoint begin = (hpoint)tverticies.elementAt(vert1-1);
      hpoint end = (hpoint)tverticies.elementAt(vert2-1);
      g.drawLine((int)begin.getx(), (int)begin.gety(),
		 (int)end.getx(),(int)end.gety());  
    }
  } 

} /* end class ModelDisplay */







