////////////////////////////////////////////////////////////////////////////////
//																			  //
// AUTHOR:      Tevfik Metin Sezgin	                        				  //
//				Massachusetts Institute of Technology						  //
//				Department of Electrical Engineering and Computer Science	  //
//				Artificial Intelligence Laboratory							  //
//																			  //
// E-MAIL:		mtsezgin@ai.mit.edu, mtsezgin@mit.edu						  //
//																			  //
// COPYRIGHT:   Tevfik Metin Sezgin                    						  //
//              All rights reserved. This code can not be copied, modified,   //
//              or distributed in whole or partially without the written 	  //
//				permission of the author. Also see the COPYRIGHT file.		  //
//																			  //
////////////////////////////////////////////////////////////////////////////////

  /**
  *
  * See the end of the file for the log of changes.
  *
  * $Author: mtsezgin $
  * $Date: 2000/06/08 03:20:07 $
  * $Revision: 1.18 $
  * $Headers$
  * $Id: Tablet.java,v 1.18 2000/06/08 03:20:07 mtsezgin Exp $
  * $Name:  $
  * $Locker:  $
  * $Source: /mit/mtsezgin/CVS/Repository/REC/Tablet.java,v $
  *
  **/

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;

import java.util.Vector;

import java.io.Serializable;

import java.awt.geom.AffineTransform;

/**
  *
  * This class stores all the data from a single stroke.
  * Time array has the same length as points.
  *
  **/

class      StrokeData
implements Serializable
{
	/**
	 * The vertices in this data set.
	 **/
    public Vertex vertices[];


	/**
	 * The absolute time stamps for the vertices.
	 **/
    public long time[];
	
	/**
	 * Time derivative of position
	 **/
    public transient double speed[];


	/**
	 * Time derivative of speed
	 **/
    public transient double acceleration[];
	
	
	/**
	 * Direction array. Length of this array is the same as the
	 * length of the vertices array. Basically the direction (ie.
	 * the angle of the tangent) at the last point in vertices is 
	 * just the same as that of the previous point. 
	 **/
    public transient double d[];
	
	
	/**
	 * Derivative of the direction array (d). The length of this 
	 * array is one less than that of d.
	 **/
    public transient double dd_dt[];


	/**
	 * The vertices mapped to polar coordinates.
	 **/
	public PolarPoint polar_points[];


	/**
	 * Parameters and constants.
	 **/
    public int        direction_window_width = 5;
	public int        fit_method             = LinearFit.SIMPLE_TANGENTS_METHOD;
    public double     test_line_scale        = 1.2;

    public Point      center;
	public Rectangle  bounding_box;
    public Dimension  radius;

    public boolean  v_is_valid            = true;
    public boolean  a_is_valid            = true;
    public boolean  d_is_valid            = true;
    public boolean  dd_dt_is_valid        = true;
    public boolean  polar_points_is_valid = true;


    /**
    *
    * The constructor.
    *
    **/
    public StrokeData( Point points[] )
    {
		int length = points.length;
		
        time     = new long[length];
        vertices = new Vertex[length];
		
		for ( int i=0; i<length; i++ ) {
			time[i]     = points[i].time_stamp - points[0].time_stamp;
			vertices[i] = new Vertex( points[i] );
			vertices[i].setIndex( i );
		}
    }
    
    
    /**
     *
     * Derive speed, and fill in the speed array.
     * This method should cache data in order to save computation.
     *
     **/
    public void
    deriveProperties()
    {
		System.out.println( "Deriving properties : " );
		invalidateCaches();
		System.out.println( "Deriving speed... " );
		deriveSpeed();
		Util.printArray( speed, "\tspeed" );
		
		System.out.println( "Deriving acceleration... " );
		deriveAcceleration();
		Util.printArray( acceleration, "\tacceleration" );
		
		System.out.println( "Deriving direction... " );
		derive_d( fit_method );
		Util.printArray( d, "direction" );
		
		System.out.println( "Deriving change in direction... " );
		derive_dd_dt();
		Util.printArray( dd_dt, "\tdd_dt" );
		
		System.out.println( "Deriving properties done " );
		Util.printArray( time, "\ttime" );
	}
    

    /**
    *
    * Invalidates caches 
    *
    **/
    public void
    invalidateCaches() 
    {
        v_is_valid            = false;
        a_is_valid            = false;
        d_is_valid            = false;
        dd_dt_is_valid        = false;
        polar_points_is_valid = false;
    }
    
    
    /**
     *
     * Return the vertices array.
     *
     **/
    public Vertex[]
    getVertices()
    {
		return vertices;
    }
    
    
    /**
     *
     * Derive the polar coordinates of the vertices with respect to
     * the center. Also determine the upper_right and lower 
     * right points (for surrounding rectangle).
     *
     **/
    public void
    derivePolarCoordinates()
    {
        if ( polar_points_is_valid )     // Don't do anything if the 
            return;                        // cached value is valid
        
        int x;
        int y;
        
        deriveBoundingBox();

        Point lower_left  = new Point( (int)bounding_box.x, 
									   (int)bounding_box.y );
        Point upper_right = new Point( (int)(bounding_box.x + 
									   bounding_box.getWidth()),
									   (int)(bounding_box.y + 
									   bounding_box.getHeight()) );
        
        radius = new Dimension( (upper_right.x - lower_left.x)/2,
                                (upper_right.y - lower_left.y)/2 );
        
        center = new Point( (upper_right.x + lower_left.x)/2,
                            (upper_right.y + lower_left.y)/2 );

        polar_points = new PolarPoint[vertices.length];
        
        

        for ( int i=0; i<polar_points.length; i++ ) {
            x = ( vertices[i].x - center.x );
            y = ( vertices[i].y - center.y );
            polar_points[i] = new PolarPoint( Math.sqrt( x*x + y*y ),
                                              Math.atan2( y, x ) );
        }
        
        for ( int i=1; i<polar_points.length; i++ ) {
            if ( Math.abs( polar_points[i].theta - 
                           polar_points[i-1].theta ) > Math.PI ) {
                for ( int j=-10; j<11; j++ ) {
                    if ( Math.abs( polar_points[i].theta - 
                                   polar_points[i-1].theta   + 
                                   j*2*Math.PI) < Math.PI ) {
                        polar_points[i].theta += j*2*Math.PI;
                        break;
                    }
                }
            }
        }
        
	    if ( Tablet.debug ) {
	        for ( int i=0; i<polar_points.length; i++ ) {
	        System.out.println( i + " " + polar_points[i] );
	        }
	    }

        polar_points_is_valid = true;
    }
  
    
    /**
     *
     * Derive the bounding box for the vertices.
     *
     **/
    public void
    deriveBoundingBox()
    {
        Point lower_left  = new Point( vertices[0] );
        Point upper_right = new Point( vertices[0] );
        
        for ( int i=0; i<vertices.length; i++ ) {
            if ( vertices[i].x < lower_left.x )
                lower_left.x = vertices[i].x;
                
            if ( vertices[i].y < lower_left.y )
                lower_left.y = vertices[i].y;
                
            if ( vertices[i].x > upper_right.x )
                upper_right.x = vertices[i].x;
                
            if ( vertices[i].y > upper_right.y )
                upper_right.y = vertices[i].y;
        }
		
		bounding_box = new Rectangle( lower_left.x,  
									  lower_left.y,
									  upper_right.x - lower_left.x,
									  upper_right.y - lower_left.y );
    }


    /**
     *
     * Derive speed, and fill in the speed array.
     * This method should cache data in order to save computation.
     *
     **/
    public void
    deriveSpeed()
    {
        if ( v_is_valid )     // Don't do anything if the 
            return;           // cached value is valid
    
        double  dx;
        double  dy;
        double  dt;
        
        speed = new double[vertices.length-1];
        for ( int i=0; i<vertices.length-1; i++ ) { 
            dx = vertices[i+1].x - vertices[i].x;
            dy = vertices[i+1].y - vertices[i].y;
            dt = time[i+1]       - time[i];
            speed[i] = Math.sqrt( dx*dx + dy*dy )/dt;
        }

        v_is_valid = true;
    }
    
    
    /**
     *
     * Derive acceleration, and fill in the acceleration array.
     *
     **/
    public void
    deriveAcceleration()
    {
        if ( a_is_valid )     // Don't do anything if the 
            return;           // cached value is valid

        deriveSpeed();
        acceleration = new double[speed.length-1];
        
        for ( int i=0; i<acceleration.length; i++ ) {
            acceleration[i] = (speed[i]-speed[i+1])/(( time[i+2] - time[i] )/2);
        }

        a_is_valid = true;
    }
    
    
    /**
     *
     * Derive direction, and fill in the d array.
     *
     **/
    public void
    derive_d( int method )
    {
        if ( d_is_valid )     // Don't do anything if the 
            return;           // cached value is valid
        
		System.out.println( "Using "+LinearFit.fitMethodToString(fit_method) );
		switch( method ) {
			case LinearFit.SIMPLE_TANGENTS_METHOD :
				double tmp_d[] = GeometryUtil.getIntermediateAngles( vertices );
				d = new double[tmp_d.length+1];
				for ( int i=0; i<tmp_d.length; i++ ) {
					d[i] = tmp_d[i];
				}
				d[d.length-1] = tmp_d[tmp_d.length-1];
				break;
			
			case LinearFit.SWODR_METHOD :
				if ( Tablet.platform_is_windowsNT ) {
					d = deriveDirectionViaSWODR( vertices,
												 direction_window_width );
				} else {
					System.out.println( "Fit method not available..." );
					throw( new NullPointerException() );
				}
				break;
				
			case LinearFit.ROTATION_METHOD :
				d = deriveDirectionUsingRotationalSWODR(vertices, 
														direction_window_width);
				break;

			default :
				System.out.println( "Error: unknown fit method " + method );
		}
		
		d_is_valid = true;
		return;
    }
    
    
    /**
     *
     * Find time derivative of direction, and fill in the dd_dt 
	 * array.
     *
     **/
    public void
    derive_dd_dt()
    {
        if ( dd_dt_is_valid )     // Don't do anything if the 
            return;               // cached value is valid

		dd_dt = differantiateDirection( d );
        
        dd_dt_is_valid = true;
    }
    
    
    /**
     *
     * Derive direction, and return it.
     * This method is different than the previous one in that here
	 * we look at a collection of vertices rather than two vertices to
	 * come up with the direction. Also use the rotation method.
     *
     **/
    public double[]
    deriveDirectionUsingRotationalSWODR( Point input[],
										 int window_span )
    {
		double direction[] =
			OrthogonalDistanceRegression.deriveDirectionUsingRotationalSWODR(
				input,
				window_span );

		GeometryUtil.continualizeDirection( direction );
		
		return direction;
    }
    
    
    /**
     *
     * Derive direction, and fill in the d array.
     * This method is different than the previous one in that here
	 * we look at a collection of vertices rather than two vertices to
	 * find the direction. Also this is supposed to work faster. 
	 * Returns the direction array.
     *
     **/
    public double[]
    deriveDirectionViaSWODR( Point input[], int twice_window_size )
    {
		double x[] = new double[input.length];
		double y[] = new double[input.length];

		double direction[];
		double swodr_result[][];
		
		// Add some noise
		for ( int i=0; i<input.length; i++ ) {
			x[i] = input[i].x + (1-Math.random())*1.0;
			y[i] = input[i].y + (1-Math.random())*1.0;
		}
		swodr_result = OrthogonalDistanceRegression.doSlidingWindowODR( 
						  "input",
						  "output",
						   x,
						   y,
						   twice_window_size );
						   
		// In this scheme each point has a direction[i]
        direction = new double[swodr_result.length];
		for ( int i=0; i<direction.length; i++ ) {
				direction[i] = Math.atan2( swodr_result[i][0], 1 );
        }
		
		GeometryUtil.continualizeDirection( direction );
		
		return direction;
	}
    
    
    /**
     *
     * Differantiate the direction array using the current timing
	 * data.
     *
     **/
    public double[]
    differantiateDirection( double direction[] )
    {
		double derivative[] = new double[direction.length-1];
        
		double dt;
		double delta_theta;
		
        for ( int i=0; i<derivative.length; i++ ) {
			delta_theta   = direction[i+1] - direction[i];
            dt            = time[i+1] - time[i];
            derivative[i] = delta_theta/dt;
		}
		
		return derivative;
    }

    
    /**
     *
     * Average speed
     *
     **/
    public double
    averageSpeed()
    {
        double  dx;
        double  dy;
        double  dt;
        double  average = 0.0;
    
        for ( int i=0; i<vertices.length-1; i++ ) {
            dx = vertices[i+1].x - vertices[i].x;
            dy = vertices[i+1].y - vertices[i].y;
            average += Math.sqrt( dx*dx + dy*dy );
        }
        average /= (time[time.length-1] - time[0]);
    
        return average;
    }

    
    /**
     *
     * Average 
     *
     **/
    public double
    averageFinG( double f[], double g[] )
    {
        double  average = 0.0;
    
        for ( int i=1; ( i<f.length ) && ( i<g.length ); i++ ) {
			average += ( f[i] + f[i-1] )/2.0*( g[i] - g[i-1] );
        }
        average /= ( Math.abs( g[g.length-1] ) - Math.abs( g[0]) );
    
        return average;
    }

    
    /**
     *
     * Average 
     *
     **/
    public double
    averageFinG( double f[], long g[] )
    {
		double g_copy[] = new double[g.length];
        for ( int i=0; i<g.length; i++ ) {
			g_copy[i] = g[i];
        }
    
        return averageFinG( f, g_copy );
    }
    
    
    /**
     *
     * Put the valid points in the points array in a Polygon and
     * return it.
     *
     **/
    public Polygon
    getDataPoints()
    {
        Polygon result = new Polygon();
                
        for ( int i=0; i<vertices.length; i++ ) {
            result.addPoint( vertices[i].x, vertices[i].y );
        }
        
        return result;
    }


    /**
    *
    * See if the input is a polygon.
    *
    **/ 
    public Vertex[]
    getSpeedFit( double average_scale )
    {
        int    edges = 0;
        double dx;
        double dy;
        double dt;
        double average = 0.0;
        double scaled_average;
		
        deriveSpeed();
        average = averageSpeed();
		
        scaled_average = average*average_scale;       
		
        if ( speed[0] > scaled_average ) {              
            speed[0] = scaled_average/2;                
        }                                               
        if ( speed[speed.length-1] > scaled_average ) { 
            speed[speed.length-1] = scaled_average/2;   
        }

        boolean below = ( speed[0] < scaled_average );

        for ( int i=0; i<speed.length; i++ ) { 
            if ( ( speed[i] > scaled_average ) && ( below == true ) ) {
                below = false;
                edges++;
            }
            if ( ( speed[i] < scaled_average ) && ( below == false ) ) {
                below = true;
                edges++;
            }
        }
        
        edges /= 2;
        System.out.println( edges + " edges detected." );

        if ( edges < 2 )
            return null;
            
        // Now lets find the the minimums of the v graph
        
        int    mins      = 0;
        int    indices[] = new int[edges+1];
        double min       = scaled_average;
        below            = (speed[0] < scaled_average);
        
        for ( int i=0; i<speed.length; i++ ) { 
            if ( ( speed[i] > scaled_average ) && ( below == true ) ) {
                mins++;
                min   = scaled_average;
                below = false;
            }
            if ( speed[i] < scaled_average ) {
                if ( speed[i] < min ) {
                    min           = speed[i];
                    indices[mins] = i;
                }
                below = true;
            }
        }

        Vertex result[]                   = new Vertex[edges+1];
		double output_polygon_certainty[] = new double[edges+1];
        
		double maximum_speed = speed[indices[1]];
		
		for ( int i=1; i<edges; i++ ) {
			if ( speed[indices[i]] > maximum_speed ) { 
				maximum_speed = speed[indices[i]];     
			}
		}
		for ( int i=1; i<edges; i++ ) {
			output_polygon_certainty[i] = 1 - speed[indices[i]]/maximum_speed;
		}

		output_polygon_certainty[0]                                 = 1.0;
		output_polygon_certainty[output_polygon_certainty.length-1] = 1.0;
        for ( int i=0; i<=edges; i++ ) {
            result[i] = new Vertex( vertices[ indices[i] ],
				                    output_polygon_certainty[i] );
        }
        
        System.out.println( "Final output polygon via speed" ); 
        for ( int i=0; i<result.length; i++ ) {
            System.out.println( "Speed certainty -> " + result[i] );
        }
        
        return result;
    }


    /**
    * 
    * Find the corners of the polygon using the change in the 
	* direction. This is basically done by finding the peaks of the 
	* dd_dt array.
	*
    **/
	public Vertex[]
	getDirectionFit( double dd_dt_scale )
    {
		double sum     = 0.0;
		double dt      = 0.0;

		for ( int i=0; i<dd_dt.length-1; i++ ) {
            dt = ( time[i+1] - time[i])/2;
			sum += Math.abs( dd_dt[i] * dt );
		}
		double dd_dt_average = sum/( time[time.length-1] - time[0] );

		int    index            = 0;
		double max              = 0.0;
		double overall_max      = 0.0;
		double treshold         = dd_dt_average*dd_dt_scale;
		Vector result_vector    = new Vector();
		result_vector.addElement( new Vertex( vertices[0], 1.0 ) );
		
		for ( int i=0; i<dd_dt.length-1; i++ ) {
			if ( max < Math.abs(dd_dt[i]) ) {
				max = Math.abs(dd_dt[i]);
				result_vector.set( index, new Vertex( vertices[i], max ) );
				overall_max = ( max > overall_max ) ? max : overall_max;
			}
			if ( ( Math.abs( dd_dt[i]   ) <  treshold ) &&
			     ( Math.abs( dd_dt[i+1] ) >= treshold ) ) {
				index++;
				max = Math.abs(dd_dt[i+1]);
				result_vector.addElement( new Vertex( vertices[i+1], max ) );
				overall_max = ( max > overall_max ) ? max : overall_max;
			}
		}
		result_vector.addElement( new Vertex(vertices[vertices.length-1], 1.0));
		result_vector.insertElementAt( new Vertex( vertices[0], 1.0 ), 0 );
		
		Vertex result[] = new Vertex[result_vector.size()];

		for ( int i=0; i<result.length; i++ ) {
			result[i] = (Vertex)result_vector.elementAt( i );
			result[i].certainty /= overall_max;
		}

		result = removeRedundantEndPoints( result );
		
		result[0].certainty               = 1.0;
		result[result.length-1].certainty = 1.0;
		
		System.out.println( "Got " + index + " points using direction info" );
		for ( int i=0; i<result.length; i++ ) {
			System.out.println( "dd_dt certainty -> " + result[i] );
		}
		
		return result;
	}
	
	
    /**
    *
    * See if the input is a line.
    *
    **/
    public boolean
    testLine( double test_line_scale )
    {
        double  dx;
        double  dy;
        double  length   = 0.0;
        double  distance = 0.0;
        
        for ( int i=0; i<vertices.length-1; i++ ) {
            dx = vertices[i+1].x - vertices[i].x;
            dy = vertices[i+1].y - vertices[i].y;

            length += Math.sqrt( dx*dx + dy*dy );
        }
        
        distance = vertices[0].distance( vertices[vertices.length-1] );
        
        if ( length < distance*test_line_scale ) {
            System.out.println("That was definitely a line");
			return true;
        } else {
            System.out.println("Doesn't look like a line");
			return false;
        }
    }
    
    
    /**
    *
    * Return the ellipse fully containing the data points.
    *
    **/
    public Ellipse
    getEllipse()
    {
		deriveBoundingBox();
		return new Ellipse( center.x - radius.width,
						    center.y - radius.height,
						    radius.width*2,
						    radius.height*2 );
	}
    
    
    /**
    *
    * calculate the lsq error.
    *
    **/
    public double
    leastSquaresForCircle()
    {
        derivePolarCoordinates();
        double rotation_angle = 0.0;
        Point  p = new Point();

        
        double a = radius.width;
        double b = radius.height;
        if ( a < b ) {
            double tmp = a;
            a = b;
            b = tmp;
            rotation_angle = Math.PI/2;
        }
        
        // Now a > b
        double c     = Math.sqrt( a*a-b*b );
        double e     = Math.sqrt( 1 - (b*b)/(a*a) ); // Eccentricity
        double r     = 0;
        double theta = 0;
                       
        Graphics g = Tablet.debug_graphics;
        double ls_error   = 0.0;                         
        if ( g != null ) {
            g.setColor( Color.green );
        
            for ( int i=0; i<polar_points.length; i++ ) {
                theta = polar_points[i].theta + rotation_angle;
                r = a*Math.sqrt( (1-e*e) /
                                 (1-e*e*Math.cos(theta)*Math.cos(theta)) );
                p.x = center.x + (int)(r*Math.cos(theta - rotation_angle));
                p.y = center.y + (int)(r*Math.sin(theta - rotation_angle));
                
                g.drawLine( vertices[i].x, 
							vertices[i].y, 
                            p.x, 
							p.y );
                
                ls_error += vertices[i].distanceSq( p );
            }
        	g.setColor( Color.blue );
        	g.fillOval( center.x, center.y, 4, 4 );
        	g.drawRect( center.x-radius.width, 
						center.y-radius.height,
						radius.width*2,
						radius.height*2 );
        }
        
        return (ls_error/polar_points.length-1);
    }

	
    /**
    * 
    * Remove the redundant at the beginning and the end.
    *
    **/
    public Vertex[]
    removeRedundantEndPoints( Vertex input_points[] )
    {
		boolean removed_redundant_point_at_the_beginning = false;
		boolean removed_redundant_point_at_the_end       = false;

		int     length     = input_points.length;
		int     new_length = input_points.length;
		
		if ( input_points[0].equals( input_points[1] ) ) {
			removed_redundant_point_at_the_beginning = true;
			new_length--;
		}
		if ( input_points[length-1].equals( input_points[length-2] ) ) {
			removed_redundant_point_at_the_end = true;
			new_length--;
		}
				
		Vertex output[] = new Vertex[new_length];
		
		if ( removed_redundant_point_at_the_beginning ) {
			for ( int i=0; i<output.length; i++ ) {
				output[i] = input_points[i+1];
			}
		} else {
			for ( int i=0; i<output.length; i++ ) {
				output[i] = input_points[i];
			}
		}
		
		return output;
	}

	
    /**
    * 
    * Set fit method
    *
    **/
    public void
    setFitMethod( int method )
    {
		fit_method     = method;
		d_is_valid     = false;
		dd_dt_is_valid = false;
		deriveProperties();
	}

	
    /**
    * 
    * Do convolution.
    *
    **/
    public void
    convolveDirection( double filter[] )
    {
		d = Util.convolve( d, filter );
		dd_dt_is_valid = false;
		derive_dd_dt();
	}

	
    /**
    * 
    * Do convolution.
    *
    **/
    public void
    convolveChangeInDirection( double filter[] )
    {
		dd_dt = Util.convolve( dd_dt, filter );
	}
}

/**
  *
  * $Log: Tablet.java,v $
  * Revision 1.18  2000/06/08 03:20:07  mtsezgin
  *
  * Now the user can save the design, and read back the designs that
  * were saved earlied. This is accomplished through Serialization.
  *
  * Revision 1.16  2000/06/02 21:11:16  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.15  2000/05/26 20:43:35  mtsezgin
  *
  * Modified to make use of the ControlsModule that lets the user play
  * with parameters used in the recognition process.
  *
  * Revision 1.13  2000/05/22 02:42:34  mtsezgin
  *
  * The current version enables polygons to be sketched in pieces.
  *
  * Revision 1.12  2000/05/07 17:27:58  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.11  2000/04/28 04:45:05  mtsezgin
  *
  * Now each GeometricObject keeps the mouse input that was previously
  * discarded. User can switch between seeing the recognized mode and
  * the raw mode. setDataPoints( Polygon points ) and getDataPoints()
  * are added to GeometricObject, and all the implementors are modified
  * accordingly.
  *
  * Revision 1.10  2000/04/20 04:29:51  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.8  2000/04/17 07:02:32  mtsezgin
  *
  * Finally made the Rectangle really rotatable.
  *
  * Revision 1.7  2000/04/13 06:24:09  mtsezgin
  *
  * The current version of the program recognized Crosses, and Shades.
  * Implementors of Terminal and their descendants were modified to
  * implement the changes in GeometricObject.
  *
  * Revision 1.6  2000/04/12 04:00:17  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.5  2000/04/11 00:41:48  mtsezgin
  *
  * Now the whole package succesfully parses a motor.
  *
  * Revision 1.4  2000/04/06 19:16:24  mtsezgin
  *
  * Modified all the classes to use my Point class which extends java.awt.Point
  * instead of directly using java.awt.Point
  *
  * Revision 1.3  2000/04/01 20:34:04  mtsezgin
  *
  * Renamed Oval.java to Ellipse.java
  *
  * Revision 1.2  2000/04/01 04:11:32  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.1.1.1  2000/04/01 03:07:07  mtsezgin
  * Imported sources
  *
  * Revision 1.3  2000/03/31 22:41:05  mtsezgin
  *
  * Started Log tracking.
  *
  * Revision 1.2  2000/03/31 22:30:31  mtsezgin
  *
  *
  * Starting the log management.
  *
  *
  **/
