////////////////////////////////////////////////////////////////////////////////
//																			  //
// 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:14:31 $   
  * $Revision: 1.18 $
  * $Headers$
  * $Id: Rectangle.java,v 1.18 2000/06/08 03:14:31 mtsezgin Exp $     
  * $Name:  $   
  * $Locker:  $
  * $Source: /mit/mtsezgin/CVS/Repository/REC/Rectangle.java,v $
  *  
  **/

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

import java.io.Serializable;


/**
  *
  * This class represents a Rectangle described by its width 
  * and height vectors. This class also implements Rotatable.
  * This means it has a getAngle() method. The angle that is 
  * returned is the angle between the width vector and the 
  * x axis. This angle is always equal to or greater than 0
  * and strictly less than Math.PI/2.
  *
  **/

class      Rectangle
implements GeometricObject,
           Rotatable,
           Serializable
{
    /**
    *
    * The original data points
    *
    **/
    private Polygon points;
    

    /**
    *
    * angle as described by getAngle()
    * This is always between -PI and PI
    *
    **/
    private double angle;
    
    
    /**
    *
    * x component of the width vector.
    *
    **/
    private double width_x;
    
    
    /**
    *
    * y component of the width vector.
    *
    **/
    private double width_y;
    
    
    /**
    *
    * x component of the height vector.
    *
    **/
    private double height_x;
    
    
    /**
    *
    * y component of the height vector.
    *
    **/
    private double height_y;
    
    
    /**
    *
    * width
    *
    **/
    private double width;
    
    
    /**
    *
    * height
    *
    **/
    private double height;
    
    
    /**
    *
    * The x coordinate of the position of width vector.
    *
    **/
    public double x;
    
    
    /**
    *
    * The y coordinate of the position of width vector
    *
    **/
    public double y;
    
    
    /**
    *
    * Time stamp of this object.
    *
    **/
    public long time_stamp;


    /**
    *
    * Graphics context for this Geometric object.
    *
    **/
    public transient Graphics graphics;


    /**
    *
    * The constructor.
    *
    **/    
    Rectangle( java.awt.Rectangle rectangle )
    {
        this( rectangle.x, 
              rectangle.y,
              rectangle.width,
              0.0, // This is not angle.
              rectangle.height );
    }


    /**
    *
    * The constructor.
    *
    **/    
    Rectangle( Point upper_left_point, Dimension dimension )
    {
        this( upper_left_point.x, 
              upper_left_point.y,
              dimension.width, 
              0.0, // This is not angle.
              dimension.height );
    }


    /**
    *
    * The constructor.
    *
    **/    
    Rectangle( Point upper_left_point, Dimension dimension, double angle )
    {
        this.angle    = Math.atan2( Math.sin(angle), Math.cos(angle) );
        this.x        = upper_left_point.x;
        this.y        = upper_left_point.y;
        this.width_x  = dimension.width*Math.cos(angle);
        this.width_y  = dimension.width*Math.sin(angle);
        this.height_x = dimension.height*Math.cos(angle+Math.PI/2);
        this.height_y = dimension.height*Math.sin(angle+Math.PI/2);
        this.width    = dimension.width;
        this.height   = dimension.height;
    }


    /**
    *
    * The constructor.
    *
    **/    
    Rectangle( double x, double y, Point width_vector, double height )
    {
        this( x,
              y,
              width_vector.x,
              width_vector.y,
              height );
    }


    /**
    *
    * The constructor.
    *
    **/    
    Rectangle( double x, 
               double y, 
               double width_x, 
               double width_y, 
               double height )
    {   
        this.x        = x;
        this.y        = y;
        this.width_x  = width_x;
        this.width_y  = width_y;
        this.angle    = getAngle();
        this.height_x = height*Math.cos(angle+Math.PI/2); 
        this.height_y = height*Math.sin(angle+Math.PI/2);
        this.width    = Point.distance( 0, 0, width_x, width_y );
        this.height   = height;
    }


    /**
    *
    * The constructor.
    *
    **/    
    Rectangle( Rectangle r )
    {   
        setRectangle( r );
    }


    /**
    *
    * The constructor.
    *
    **/    
    Rectangle( int x, int y, int width, int height )
    {
        this( x, y, width, 0.0, height );
    }
    

    /**
    *
    * setRectangle
    *
    **/    
    public void
    setRectangle( Rectangle r )
    {   

        this.x        = r.x;
        this.y        = r.y;
        this.width_x  = r.width_x;
        this.width_y  = r.width_y;
        this.angle    = r.angle;
        this.height_x = r.height_x;
        this.height_y = r.height_y;
        this.width    = r.width;
        this.height   = r.height;
    }


    /**
    *
    * scaleAboutCenter
    *
    **/    
    public void
    scaleAboutCenter( double scale )
    {
        this.x         = x - (scale-1)*(width_x+height_x);
        this.y         = y - (scale-1)*(width_y+height_y);
        this.width_x  *= (1+(scale-1)*2);
        this.width_y  *= (1+(scale-1)*2);
        this.height_x *= (1+(scale-1)*2);
        this.height_y *= (1+(scale-1)*2);
        this.width    *= (1+(scale-1)*2);
        this.height   *= (1+(scale-1)*2);
    }
    
    
    /**
    *
    * Implement GeometricObject
    *
    **/    
    public String
    getType()
    {
        if ( width == height )
            return "square";
        else 
            return "rectangle";
    }
    
    
    /**
    *
    * Override toString
    *
    **/    
    public String
    toString()
    {
        return "Rectangle ( " + x + ", " + y + " ) w = " + width + 
               " h = " + height + " angle = " + angle;
    }


    /**
    *
    * Draw the object
    *
    **/
    public void
    paint()
    {
        if ( graphics == null ) // Draw only if g
            return;             // is valid

        graphics.setColor( Color.black );
        paint( graphics );
    }
    
    
    /**
    *
    * This method is used to paint the original data points that
    * forms this GeometricObject
    *
    **/
    public void
    paintOriginal( Graphics g )
    {
        points.paint( g );
    }


    /**
    *
    * Draw the object
    *
    **/
    public void
    paint( Graphics g, int thickness )
    {
        int x1 = (int)width_x;
        int y1 = (int)width_y;
        int x2 = (int)height_x;
        int y2 = (int)height_y;
        
        GraphicsUtil.drawThickLine( thickness,
                                    g,
                                    x,
                                    y,
                                    x+x1, 
                                    y+y1);
        GraphicsUtil.drawThickLine( thickness,
                                    g,
                                    x,
                                    y,
                                    x+x2, 
                                    y+y2);    
        GraphicsUtil.drawThickLine( thickness,
                                    g,
                                    x+x1,
                                    y+y1,
                                    x+x1+x2,
                                    y+y1+y2 );
        GraphicsUtil.drawThickLine( thickness,
                                    g,
                                    x+x2,
                                    y+y2,
                                    x+x1+x2,
                                    y+y1+y2 );
    }


    /**
    *
    * Shade the object
    *
    **/
    public void
    shade( Graphics g, int shades, int thickness, int offset )
    {
        int x1 = (int)width_x;
        int y1 = (int)width_y;
        int x2 = (int)height_x;
        int y2 = (int)height_y;        
                                       
        if ( height > width ) {        
            x1 = (int)height_x;
            y1 = (int)height_y;
            x2 = (int)width_x;
            y2 = (int)width_y;
        }
        
        for ( int i=0; i<shades; i++ ) {
            GraphicsUtil.drawThickLine( thickness,
                                        g,
                                        x+(x1*i/((double)shades)),
                                        y+(y1*i/((double)shades)),
                                        x+(x1*(i+offset)/((double)shades))+x2,
                                        y+(y1*(i+offset)/((double)shades))+y2 );
        }
    }


    /**
    *
    * Draw the object
    *
    **/
    public void
    paint( Graphics g )
    {
        paint( g, 1 );
    }


    /**
    *
    * Shade the object
    *
    **/
    public void
    shade( Graphics g, int number_of_shades )
    {
        shade( g, number_of_shades, 1, 0 );
    }


    /**
    *
    * Returns true if the point is within +-radius distance from
    * the curve defining the object. Returns false o/w.
    *
    **/
    public boolean
    pointIsOn( Point point, int radius )
    {            
        return (getPolygonalBounds()).pointIsOn( point, radius );
    }


    /**
    *
    * Returns true if the point is within +-radius distance from
    * the original curve defining the object. Returns false o/w.
    *
    **/
    public boolean
    pointIsOnOriginal( Point p, int radius )
    {
        if ( points == null ) 
            return false;
        else 
            return points.pointIsOn( p, radius );
    }


       /**
    *
    * Set graphics context for this Geometric object.
    * Must be set at least once before doing any drawing.
    *
    **/
    public void
    setGraphicsContext( Graphics g )
    {
        graphics = g;
    }


    /**
    *
    * This method should return true if the input objects touch.
    * It should be optimized making use of the object type 
    * information.
    *
    **/
    public boolean
    touches( GeometricObject object )
    {
        System.out.println( "Rectangle.touches() called" );
        return getPolygonalBounds().touches( object.getPolygonalBounds() );
    }


    /**
    *
    * Supplied for completeness.
    *
    **/
    public Rectangle
    getRectangularBounds()
    {
        return getPolygonalBounds().getRectangularBounds();
    }


    /**
    *
    * Returns false if the argument is not completely inside 
    * this object. Return true O/W.
    *
    **/
    public boolean
    containsGeometricObject( GeometricObject object )
    {
        return getPolygonalBounds().containsGeometricObject( object );
    }


    /**
    *
    * This method should return a polygon that corresponds to this
    * object. The polygon is explicity closed and the last 
    * point doesn't necessarily have to be the same as the first 
    * (zeroth) point. The returned polygon is a liberal 
    * approximation to the real shape of the object. 
    *
    * Known eksik: This should be refined to return a more 
    * conservative result.
    *
    **/
    public Polygon 
    getPolygonalBounds()
    {
        Polygon result = new Polygon();
        
        result.addPointDouble( x,                      y                      );
        result.addPointDouble( x + width_x,            y + width_y            );
        result.addPointDouble( x + width_x + height_x, y + width_y + height_y );
        result.addPointDouble( x + height_x,           y + height_y           );
        result.addPointDouble( x,                      y                      );
        
        return result;
    }


    /**
    *
    * This method should return the spatial relation of the input
    * parameter with respect to this object. see the SpatialRelation
    * class for a detailed list of possible spatial relations.
    * Another version of this method should be implemented for 
    * handling spatial relations where a rotated coordinate
    * system is to be used. In this method the center square is 
    * adjusted using the scale argument. Note that we are using 
    * the cartesian coordinates for all the operations, but the 
    * returned relationship is for the screen coordinates. In this
    * scheme Y axis is pointing south rather than north as it is 
    * in cartesian coordinates.
    *
    * Known eksik: Does not handle cases when directions are 0.
    *
    * Example for scale:
    * Scale = 1   => 100% 
    * Scale = 1.5 => 150% 
    *
    *  (0,0)
    *      |--------------------------> X
    *      |
    *      |
    *      |       |   |
    *      |       |   |
    *      |    ___|___|___\ horizontal_up
    *      |       |   |   /
    *      |       |   |
    *      |    ___|___|___\ horizontal_down
    *      |       |   |   /
    *      |       |   |
    *      |       |   |
    *      |       V   V
    *      |     left right
    *      |
    *      |
    *      |
    *      |
    *   Y  V
    *
    **/
    public int
    spatialRelation( GeometricObject object, double scale )
    {
        // We only case about the vector which has the smallest
        // abs angle WRT the x axis.
        int origin_index        = 0;
        int diagonal_index        = 0;
        int diagonal_x          = 0;
        int diagonal_y          = 0;
        int origin_x            = 0;        
        int origin_y            = 0;
        int horizontal_vector_x = 0;
        int horizontal_vector_y = 0;
        int vertical_vector_x   = 0;
        int vertical_vector_y   = 0;
        
        Rectangle copy   = new Rectangle( this );
        copy.scaleAboutCenter( scale );
        Polygon   bounds = copy.getPolygonalBounds();
        
        origin_x   = bounds.xpoints[0];
        origin_y   = bounds.ypoints[0];
        diagonal_x = bounds.xpoints[0];
        diagonal_y = bounds.ypoints[0];
        for ( int i=1; i<bounds.npoints; i++ ) {
            if ( bounds.ypoints[i] <= origin_y ) {
                if ( ( bounds.ypoints[i] == origin_y ) &&
                     ( bounds.xpoints[i] >  origin_x ) ){
                } else {
                    origin_x     = bounds.xpoints[i];
                    origin_y     = bounds.ypoints[i];
                    origin_index = i;
                }
            } 
            
            
            if ( bounds.ypoints[i] >= diagonal_y ) {
                if ( ( bounds.ypoints[i] == diagonal_y ) &&
                     ( bounds.xpoints[i] <  diagonal_x ) ){
                } else {
                    diagonal_x     = bounds.xpoints[i];
                    diagonal_y     = bounds.ypoints[i];
                    diagonal_index = i;
                }
            }
        }
        
        for ( int i=0; i<bounds.npoints; i++ ) {
            for ( int j=0; j<bounds.npoints; j++ ) {
                if ( ( i != origin_index )   && 
                     ( i != diagonal_index ) && 
                     ( j != origin_index )   &&
                     ( j != diagonal_index ) &&
                     ( i!= j ) ) {
                    double angle_i = 
                        Math.atan2( Math.abs( bounds.ypoints[i]-origin_y ),
                                    Math.abs( bounds.xpoints[i]-origin_x ) );
                    double angle_j = 
                        Math.atan2( Math.abs( bounds.ypoints[j]-origin_y ),
                                    Math.abs( bounds.xpoints[j]-origin_x ) );
                    System.out.println( "i " + i + " j " + j + " " +
                                        "angle_i " + 
                                        GeometryUtil.radian2degree(angle_i) + 
                                        " " + "angle_j " + 
                                        GeometryUtil.radian2degree(angle_j)  );
                    if ( angle_i > angle_j ) {
                        vertical_vector_x   = bounds.xpoints[i];
                        vertical_vector_y   = bounds.ypoints[i];
                        horizontal_vector_x = bounds.xpoints[j];
                        horizontal_vector_y = bounds.ypoints[j];
                        i = bounds.npoints;
                        break;
                    } else {
                        vertical_vector_x   = bounds.xpoints[j];
                        vertical_vector_y   = bounds.ypoints[j];
                        horizontal_vector_x = bounds.xpoints[i];
                        horizontal_vector_y = bounds.ypoints[i];
                        i = bounds.npoints;
                        break;
                    }
                }
            }
        }
        
        diagonal_x          -= origin_x;
        diagonal_y          -= origin_y;
        vertical_vector_x   -= origin_x;
        vertical_vector_y   -= origin_y;
        horizontal_vector_x -= origin_x;
        horizontal_vector_y -= origin_y;
    
		if ( Tablet.debug ) {
	        System.out.println( "origin_index       = " + origin_index        );
	        System.out.println( "diagonal_index     = " + diagonal_index      );
	        System.out.println( "origin_x           = " + origin_x            );
	        System.out.println( "origin_y           = " + origin_y            );	
	        System.out.println( "diagonal_x         = " + diagonal_x          );
	        System.out.println( "diagonal_y         = " + diagonal_y          );
	        System.out.println( "vertical_vector_x  = " + vertical_vector_x   );
	        System.out.println( "vertical_vector_y  = " + vertical_vector_y   );
	        System.out.println( "horizontal_vector_x= " + horizontal_vector_x );
	        System.out.println( "horizontal_vector_y= " + horizontal_vector_y );
    	}
		    
        Line horizontal_up = new 
            Line( origin_x-horizontal_vector_x,
                  origin_y-horizontal_vector_y,
                  origin_x+horizontal_vector_x*2,
                  origin_y+horizontal_vector_y*2 );
        Line horizontal_down = new 
            Line( origin_x-horizontal_vector_x+vertical_vector_x,
                  origin_y-horizontal_vector_y+vertical_vector_y,
                  origin_x+horizontal_vector_x*2+vertical_vector_x,
                  origin_y+horizontal_vector_y*2+vertical_vector_y);
        Line left = new
             Line( origin_x-vertical_vector_x,
                   origin_y-vertical_vector_y,
                   origin_x+vertical_vector_x*2,
                   origin_y+vertical_vector_y*2 );
        Line right = new 
            Line( origin_x-vertical_vector_x+horizontal_vector_x,
                  origin_y-vertical_vector_y+horizontal_vector_y,
                  origin_x+vertical_vector_x*2+horizontal_vector_x,
                  origin_y+vertical_vector_y*2+horizontal_vector_y );

        
        if ( left.x1 > right.x1    ) {
            Line tmp = left;
            left     = right;
            right    = tmp;
        }
        
        horizontal_up.pointRight();                    
        horizontal_down.pointRight();
        right.pointUp();
        left.pointUp();
        
        Graphics g = Tablet.debug_graphics;
        g.setColor( Color.red );
        horizontal_up.paint( g );
        g.setColor( Color.green );
        horizontal_down.paint( g );
        g.setColor( Color.blue );
        left.paint( g );
        g.setColor( Color.orange );
        right.paint( g );
        
        int     relation   = -1;
        Point   center = object.getRectangularBounds().getCenter();
        System.out.println( center );
        g.setColor( Color.cyan );
        center.paint( g );
        
        int direction1 = horizontal_up.relativeCCW( center.x, center.y );
        int direction2 = horizontal_down.relativeCCW( center.x, center.y );
        int direction3 = left.relativeCCW( center.x, center.y );
        int direction4 = right.relativeCCW( center.x, center.y );
        
        System.out.println("direction1 " + direction1 );
        System.out.println("direction2 " + direction2 );
        System.out.println("direction3 " + direction3 );
        System.out.println("direction4 " + direction4 );
        
        // If the center lies exaclty on the line, make the direction
        // 1 arbitrarily.
        if (direction1 == 0 )
            direction1 = 1;
        if (direction2 == 0 )
            direction3 = 1;
        if (direction3 == 0 )
            direction3 = 1;
        if (direction4 == 0 )
            direction4 = 1;
        
        switch ( direction1 ) {
            case 1 :
                switch ( direction3 ) {
                    case -1 : return SpatialRelation.UPPER_LEFT;
                    case  1 :
                        switch ( direction4 ) {
                            case  1 : return SpatialRelation.UPPER_RIGHT;
                            case -1 : return SpatialRelation.ABOVE;
                            default : return -1;
                        }
                }
                
            case -1 :
                switch ( direction2 ) {

                    case  1 :
                        switch ( direction3 ) {
                            case -1 : return SpatialRelation.LEFT;
                            case  1 : 
                                switch ( direction4 ) {
                                    case -1 : return SpatialRelation.COCENTRIC;
                                    case  1 : return SpatialRelation.RIGHT;
                                    default : return -1;
                                }
                        }

                    case -1 :
                        switch ( direction3 ) {
                            case -1 : return SpatialRelation.LOWER_LEFT;
                            case  1 : 
                                switch ( direction4 ) {
                                    case -1 :return SpatialRelation.BELOW;
                                    case  1 :return SpatialRelation.LOWER_RIGHT;
                                    default : return -1;
                                }
                            default : return -1;
                        }

                    default : return 0;
                }
                
            default : return -1;
        }
    }


    /**
    *
    * This method should return the spatial relation of the input
    * parameter with respect to this object. see the SpatialRelation
    * class for a detailed list of possible spatial relations.
    * This method should hadles spatial relations where a rotated 
    * Rectangle is used
    *
    *
    *  (0,0)
    *      |--------------------------> X
    *      |
    *      |
    *      |       |   |
    *      |       |   |
    *      |    ___|___|___\ horizontal_up
    *      |       |   |   /
    *      |       |   |
    *      |    ___|___|___\ horizontal_down
    *      |       |   |   /
    *      |       |   |
    *      |       |   |
    *      |       V   V
    *      |     left right
    *      |
    *      |
    *      |
    *      |
    *   Y  V
    *
    **/
    public int
    spatialRelation( GeometricObject object )
    {
        return spatialRelation( object, 1.0 );
    }


    /**
    *
    * This method should return true if the input object lies in the
    * major axis direction of this Rectangle. Else return false.
    * This uses the screen coordinate system.
    *
    *  (0,0)
    *      |------------------------> X
    *      |
    *      |
    *      |      /\         /\
    *      |       |          |
    *      |       |          |
    *      |    ___|__________|___\
    *      |       |          |   /
    *      |  *****|          |***** 
    *      |    ___|__________|___\
    *      |       |          |   /
    *      |       |          |
    *      |       |          |
    *      |
    *      |
    *      |
    *      |
    *   Y  V
    *
    **/
    public boolean
    hasOnMajorAxisDirection( GeometricObject object )
    {
        int direction        = getMajorAxisDirection();
        int spatial_relation = spatialRelation( object );
        
        System.out.println( "Direction " + 
            Direction.toString( direction )      );
        System.out.println( "Relation  " + 
            SpatialRelation.toString( spatial_relation ));
        
        switch( direction ) {
            case Direction.LEFT_RIGHT :
                if ( ( spatial_relation == SpatialRelation.LEFT  ) ||
                     ( spatial_relation == SpatialRelation.RIGHT ) )
                    return true;

            case Direction.ABOVE_BELOW :
                if ( ( spatial_relation == SpatialRelation.ABOVE ) ||
                     ( spatial_relation == SpatialRelation.BELOW ) )
                    return true;
                    
            default : return false;
        }
    }


    /**
    *
    * This method should return the spatial relation of the input
    * parameter with respect to this object. see the SpatialRelation
    * class for a detailed list of possible spatial relations.
    * This returns the relationship in the cartesian coordinate
    * system.
    *
    *
    *   Y  /\
    *      |
    *      |
    *      |      /\  /\
    *      |       |   |
    *      |       |   |
    *      |    ___|___|___\ 
    *      |       |   |   /
    *      |       |   |
    *      |    ___|___|___\ 
    *      |       |   |   /
    *      |       |   |
    *      |       |   |
    *      |
    *      |
    *      |
    *      |
    *      |--------------------------> X
    *  (0,0)
    *
    **/
    public int
    spatialRelationCartesian( GeometricObject object )
    {
        return SpatialRelation.screen2cartesian( 
                   spatialRelation( object, 1.0 ) );
    }


    /**
    *
    * Returns the width
    *
    **/
    public double
    getWidth()
    {
        return width;
    }


    /**
    *
    * Returns the height
    *
    **/
    public double
    getHeight()
    {
        return height;
    }


    /**
    *
    * Returns the angle of the object WRT the x axis in radians.
    * This should be made more general by adding an angle field 
    * to the class.
    *
    **/
    public double
    getAngle()
    {
        return Math.atan2( width_y, width_x );
    }


    /**
    *
    * Returns the larger of width and height. 
    *
    **/
    public double
    getMajorAxisLength()
    {
        return ( width > height ) ? width : height;
    }
    

    /**
    *
    * Returns the smaller of width and height. 
    *
    **/
    public double
    getMinorAxisLength()
    {
        return ( width > height ) ? height : width;
    }
    

    /**
    *
    * Rotates the rectangle about the point p theta radians CCW.
    *
    **/
    public void
    rotateAbout( Point p, double theta )
    {
        double dx = x-p.x;
        double dy = y-p.y;
        
        double distance       = p.distance( x, y );
        double relative_angle = Math.atan2( dy, dx );
        angle   += theta; // Below trick for bounding angle -pi<<pi
        angle    = Math.atan2( Math.sin(angle), Math.cos(angle) ); 
        width_x  = width*Math.cos(angle);
        width_y  = width*Math.sin(angle);
        height_x = height*Math.cos(angle+Math.PI/2); 
        height_y = height*Math.sin(angle+Math.PI/2); 
        x        = p.x + (int)( Math.cos(theta+relative_angle)*distance );
        y        = p.y + (int)( Math.sin(theta+relative_angle)*distance );
        p.paint( Tablet.debug_graphics );
    }
    

    /**
    *
    * Returns the center of this rectangle. 
    *
    **/
    public Point
    getCenter()
    {
        return new Point( (int)(x+(width_x+height_x)/2),
                          (int)(y+(width_y+height_y)/2) );
    }
    

    /**
    *
    * Returns true if the input polygon looks like a Rectangle
    * Can be optimized by eliminating Polygon conversions.
    *
    **/
    public static boolean
    isRectangle( Polygon p )
    {
        if ( p.npoints != 5 ) {
            return false;
        }
        
        Rectangle bounds = p.getRectangularBounds();
        Point     center = bounds.getCenter();
        
        double distance;
        double half_diagonal = Point.distance( 0,              
                                               0, 
                                               bounds.width, 
                                               bounds.height )/2;
        
        for ( int i=0; i<4; i++ ) {
            distance = center.distance( p.xpoints[i], p.ypoints[i] );
            
            if ( !GeometryUtil.equalDoubles( half_diagonal, 
                                             distance, 
                                             half_diagonal*.25 ) ) {
                return false;
            }
        }
        
        return true;
    }
    
    
    /**
    *
    * Sets the time stamp of the current Terminal
    *
    **/
    public void
    setTimeStamp( long time_stamp )
    {
        this.time_stamp = time_stamp;
    }
    
    /**
    *
    * Returns the time stamp of the current Terminal
    *
    **/
    public long
    getTimeStamp()
    {
        return time_stamp;
    }
    
    /**
    *
    * Returns the angle between the shorter of width or height 
    * vectors and the x axis.
    *
    **/
    public double
    getMinorAxisAngle()
    {
        if ( width < height )
            return Math.atan2( width_y, width_x );
        else
            return Math.atan2( height_y, height_x );
    }
    
    /**
    *
    * Returns the angle between the longer of width or height 
    * vectors and the x axis.
    *
    **/
    public double
    getMajorAxisAngle()
    {
        if ( width > height )
            return Math.atan2( width_y, width_x );
        else
            return Math.atan2( height_y, height_x );
    }
    
    /**
    *
    * Returns the direction of the major axis of this Rectangle
    *
    * Known eksik: Should be tested.
    *
    **/
    public int
    getMajorAxisDirection()
    {
        if ( width >= height ) {
            if ( Math.abs( width_x ) > Math.abs( width_y ) )
                return Direction.LEFT_RIGHT;
            else 
                return Direction.ABOVE_BELOW;
        } else {
            if ( Math.abs( height_x ) > Math.abs( height_y ) )
                return Direction.LEFT_RIGHT;
            else 
                return Direction.ABOVE_BELOW;
        }
    }
    
    
    /**
    *
    * This method is used to set the original data points that
    * forms this GeometricObject
    *
    **/
    public void
    setDataPoints( Polygon points )
    {
        this.points = points;
    }


    /**
    *
    * This method is used to get the original data points that
    * forms this GeometricObject
    *
    **/
    public Polygon
    getDataPoints()
    {
        return points;
    }
    
    
    /**
    *
    * Return area.
    *
    **/
    public double
    area()
    {
        return width*height;
    }
    
    
    /**
    *
    * Return the rectangularBounds of the union of this rectangle
    * and the argument.
    *
    **/
    public Rectangle
    union( Rectangle rectangle )
    {
        Polygon polygonal_bounds1 = this.getPolygonalBounds();
        Polygon polygonal_bounds2 = rectangle.getPolygonalBounds();
        
        for ( int i=0; i<polygonal_bounds1.npoints; i++ ) {
            polygonal_bounds2.addPoint( polygonal_bounds1.xpoints[i],
                                        polygonal_bounds1.ypoints[i] );
        }
        
        return polygonal_bounds2.getRectangularBounds();
    }
    
    
    /**
    *
    * Add the arguments to the (x, y) position of the object.
    *
    **/
    public void
    translate( double x, double y )
    {
        this.x += x;
        this.y += y;
        if ( points != null )
            points.translate( x, y );
    }
    
    
    /**
    *
    * Returns false if the objects in the input array are
    * completely inside this object. Return true O/W.
    *
    **/
    public boolean
    containsGeometricObjects( GeometricObject objects[] )
    {
        for ( int i=0; i<objects.length; i++ ) {
            if ( !containsGeometricObject( objects[i] ) )
                return false;
        }
        
        return true;
    }
}


/** 
  * 
  * $Log: Rectangle.java,v $
  * Revision 1.18  2000/06/08 03:14:31  mtsezgin
  *
  * Made the class Serializable for supporting saving and loading
  * designs. Both the object attributes, and the original data points
  * are stored and restored.
  *
  * Revision 1.17  2000/06/03 01:52:35  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.16  2000/06/02 21:11:15  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.15  2000/05/03 23:26:47  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.14  2000/04/28 04:45:04  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.13  2000/04/25 22:27:12  mtsezgin
  *
  * The getBounds changed to getRectangularBounds which returns a Rectangle.
  * The spatialRelation method fixed, and another method is supplied for use
  * with cartesian coordinates rather than the screen coordinate system.
  * Orientation related methods added.
  *
  * From this point on, the class no longer entends java.awt.Rectangle
  * because it was not rotatable. The class stores the x and y positions
  * of the Rectangle in addition to the width and height vectors and
  * their magnitudes. The vectors are private and they are only for
  * internal use. Tha angle is also private and methods are supplied
  * for rotation. All other methods were modified to reflect these changes.
  *
  * A method hasOnMajorAxisDirection( GeometricObject object ) is also added
  * which returns true if the argument object lies on the major axis direction
  * of this rectangle. Complementary methods majorAxisLength()
  * minorAxisLength() and getMajorAxisDirection() are also added.
  *
  * Revision 1.12  2000/04/20 03:59:53  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.11  2000/04/18 05:36:34  mtsezgin
  *
  * Now rectangles are fully rotatable.
  *
  * Revision 1.10  2000/04/17 07:02:31  mtsezgin
  *
  * Finally made the Rectangle really rotatable.
  *
  * Revision 1.9  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.8  2000/04/12 04:00:04  mtsezgin
  *
  * Fixed a bug in spatial relation.
  *
  * Revision 1.7  2000/04/11 01:41:47  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.6  2000/04/11 00:41:48  mtsezgin
  *
  * Now the whole package succesfully parses a motor.
  *
  * Revision 1.5  2000/04/07 04:28:55  mtsezgin
  *
  * Added Rotatable interface. Rectangle and Line are Rotatable for now, but
  * Rectangle should be modified to have an angle field. Also other rotatable
  * classes should also implement Rotatable interface.
  *
  * Revision 1.4  2000/04/06 21:33:43  mtsezgin
  *
  * Spatial relation represents 9 basic relations for now. It should be extended.
  *
  * GeometricObject is extended. Implementors extended with empty stubs.
  *
  * Revision 1.3  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.2  2000/04/01 22:51:22  mtsezgin
  *
  * Started implementation. Also from now on using jdk 1.2
  *
  * Revision 1.1.1.1  2000/04/01 03:07:07  mtsezgin
  * Imported sources
  *
  * Revision 1.5  2000/03/31 22:41:04  mtsezgin
  *
  * Started Log tracking.
  *
  *  
  **/
