// (c) MIT 2003.  All rights reserved.

////////////////////////////////////////////////////////////////////////////////
//
// FILE:	Estimator.java
//
// AUTHOR:	Manoj D. Muzumdar
//		archon@ai.mit.edu
//
// DATE:	Feb 99
//
// COPYRIGHT:	Massachusetts Institute of Technology
//		All rights reserved.
//
// PURPOSE:	Mechanical engineering drawing recognition estimator
//
// HISTORY:     Feb 99  archon  created
//
////////////////////////////////////////////////////////////////////////////////

package rec.meche;

import rec.util.*;
import rec.geo.*;
import rec.loc.*;
import rec.sys.*;
import rec.core.widgets.*;

public final class Estimator
{
  public static GPoint connects( GPoint p1, GPoint p2 )
  {
    if( Geometry.distance( p1, p2 ) <= 20.0 )
      return( new GPoint( (p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0 ) );

    return( null );
  }

  public static boolean connects( GPoint p1, GPoint p2, double distance )
  {
    return( Geometry.distance( p1, p2 ) <= distance );
  }

  // Returns true if s connects p1 and p2.
  public static boolean connects( GPoint p1, GPoint p2, GSegment s,
				  double distance )
  {
    return( (connects( p1, s.getHeadPoint(), distance )
	     && connects( p2, s.getTailPoint(), distance ))
	    ||
	    (connects( p1, s.getTailPoint(), distance )
	     && connects( p2, s.getHeadPoint(), distance )) );
  }

  // Returns the point the s2 connects to on s1.
  public static GPoint connects( GSegment s1, GSegment s2, double distance )
  {
    double d1 = Geometry.distance( s1.getHeadPoint(), s2.getHeadPoint() );
    double d2 = Geometry.distance( s1.getHeadPoint(), s2.getTailPoint() );
    double d3 = Geometry.distance( s1.getTailPoint(), s2.getHeadPoint() );
    double d4 = Geometry.distance( s1.getTailPoint(), s2.getTailPoint() );

    // Calculate the minimum distance.

    double min = d1;
    if( d2 < min )
      min = d2;
    if( d3 < min )
      min = d3;
    if( d4 < min )
      min = d4;

    if( min <= distance ) {

      if( min == d1 || min == d2 )
	return( s1.getHeadPoint() );

      if( min == d3 || min == d4 )
	return( s1.getTailPoint() );
    }

    return( null );
  }

  //////////////////////////////////////////////////////////////////////////////

  public static boolean isNear( GShape shape, GSegment segment,
				double tolerance )
  {
    return( shape.includes( segment.getHeadPoint(), tolerance ) ||
	    shape.includes( segment.getTailPoint(), tolerance ) );
  }
  

  public static boolean isNear( GShape shape, GSegment segment )
  {
    return isNear( shape, segment, 20.0 );
  }

  //////////////////////////////////////////////////////////////////////////////

  /**
   * I know this is stupid but angles are usually calculated in
   * radians but it is easier to type and think in degrees.
   * @param angleRads the angle being tested (in radians)
   * @param tolerDegrees the tolerance (in degrees).
   **/
  public static boolean isPerpendicular( double angleRads,
					 double tolerDegrees )
  {
    return( (angleRads >= Radian.scaleFromDegrees( 90.0 - tolerDegrees ) &&
	     angleRads <= Radian.scaleFromDegrees( 90.0 + tolerDegrees ))
            ||
            (angleRads >= Radian.scaleFromDegrees( 270.0 - tolerDegrees ) &&
	     angleRads <= Radian.scaleFromDegrees( 270.0 + tolerDegrees )));
  }    

  /**
   * Default to a tolerance of 20 degrees
   * @param angle in radians
   **/
  public static boolean isPerpendicular( double angle )
  {
    return isPerpendicular( angle, 20.0 );
  }

  public static boolean arePerpendicular( GSegment s1, GSegment s2 )
  {
    return arePerpendicular( s1, s2, 20 );
  }

  public static boolean arePerpendicular( GSegment s1, GSegment s2,
					  double tolerDegrees )
  {
    GPoint intersection;

    if( (intersection = s1.getIntersection( s2 )) == null )
      return( false );

    GPoint p1 = s1.getHeadPoint();
    GPoint p2 = s2.getHeadPoint();

    if( intersection.equals( p1 ) )
      p1 = s1.getTailPoint();

    if( intersection.equals( p2 ) )
      p2 = s2.getTailPoint();

    double angle = Geometry.angleBetween( p1, intersection, p2 );

    return( isPerpendicular( angle, tolerDegrees ) );
  }

  public static boolean isAngle45( double angle )
  {
    return( (angle >= Radian.scaleFromDegrees( 25.0 )
             && angle <= Radian.scaleFromDegrees( 65.0 ))
            ||
            (angle >= Radian.scaleFromDegrees( 295.0 )
             && angle <= Radian.scaleFromDegrees( 335.0 )) );
  }

  public static boolean areAngle45( GSegment s1, GSegment s2 )
  {
    GPoint intersection;

    if( (intersection = s1.getIntersection( s2 )) == null )
      return( false );

    GPoint p1 = s1.getHeadPoint();
    GPoint p2 = s2.getHeadPoint();

    if( intersection.equals( p1 ) )
      p1 = s1.getTailPoint();

    if( intersection.equals( p2 ) )
      p2 = s2.getTailPoint();

    double angle = Geometry.angleBetween( p1, intersection, p2 );

    return( isAngle45( angle ) );
  }

  public static boolean isOblong( double width, double height )
  {
    return isOblong( width / height );
  }
  
  public static boolean isOblong( double ratio ) 
  {
    return (ratio < 0.66) || (ratio > 1.5);
  }
  

  //////////////////////////////////////////////////////////////////////////////

  public static boolean isSameSize( double s1, double s2 )
  {
    double ratio = s1 / s2;

    // Have a 33% tolerance.
    return( ratio >= 3.0 / 4.0 && ratio <= 4.0 / 3.0 );
  }

  //////////////////////////////////////////////////////////////////////////////

  public static boolean isSameAngle( GPoint head1, GPoint tail1,
				     GPoint head2, GPoint tail2 )
  {
    double angle1 = Geometry.angle( head1, tail1 );
    double angle2 = Geometry.angle( head2, tail2 );

    double rad = -1.0;

    if( angle1 <= angle2 )
      rad = angle2 - angle1;
    else
      rad = 2.0 * Math.PI + angle2 - angle1;

    // Get the acute angle.
    if( rad > Math.PI )
      rad = 2.0 * Math.PI - rad;

    // Have a 22.5 degree tolerance.
    return( rad >= Math.PI / -8.0 && rad <= Math.PI / 8.0 );
  }

  public static boolean isSameAngle( double angle1, double angle2 )
  {
    double rad = -1.0;

    if( angle1 <= angle2 )
      rad = angle2 - angle1;
    else
      rad = 2.0 * Math.PI + angle2 - angle1;

    // Get the acute angle.
    if( rad > Math.PI )
      rad = 2.0 * Math.PI - rad;

    // Have a 22.5 degree tolerance.
    return( rad >= Math.PI / -8.0 && rad <= Math.PI / 8.0 );
  }

  //////////////////////////////////////////////////////////////////////////////

  // Checks if there is a segment overlap on a minor axis side and
  //   returns the point on that side if there is an overlap.
  public static GPoint rectOverlap( WRectangle r, GSegment s )
  {
    if( r instanceof WSquare )
      return( null );

    GSegment maj = r.getMajorAxis();

    GPoint min1 = maj.getHeadPoint();
    GPoint min2 = maj.getTailPoint();
    double len = maj.getLength();

    double len11 = Geometry.distance( min1, s.getHeadPoint() );
    double len12 = Geometry.distance( min2, s.getHeadPoint() );

    boolean insideHead = len11 < len && len12 < len;

    double len21 = Geometry.distance( min1, s.getTailPoint() );
    double len22 = Geometry.distance( min2, s.getTailPoint() );

    boolean insideTail = len21 < len && len22 < len;

    GSegment min = r.getMinorAxis();
    GPoint maj1 = min.getHeadPoint();
    GPoint maj2 = min.getTailPoint();
    len = min.getLength();

    len11 = Geometry.distance( maj1, s.getHeadPoint() );
    len12 = Geometry.distance( maj2, s.getHeadPoint() );

    insideHead = insideHead && len11 < len && len12 < len;

    len21 = Geometry.distance( maj1, s.getTailPoint() );
    len22 = Geometry.distance( maj2, s.getTailPoint() );

    insideTail = insideTail && len21 < len && len22 < len;

    if( insideHead && !insideTail ) {
      if( isSameAngle( s.getHeadPoint(), s.getTailPoint(),
		       min1, min2 ) )
	return( min2 );
      return( min1 );
    }

    if( !insideHead && insideTail ) {
      if( isSameAngle( s.getTailPoint(), s.getHeadPoint(),
		       min1, min2 ) )
	return( min2 );
      return( min1 );
    }

    return( null );
  }

  // Returns the inside point.
  public static GPoint rectInside( WRectangle r, GSegment s )
  {
    if( r instanceof WSquare )
      return( null );

    GSegment maj = r.getMajorAxis();

    GPoint min1 = maj.getHeadPoint();
    GPoint min2 = maj.getTailPoint();
    double len = maj.getLength();

    double len11 = Geometry.distance( min1, s.getHeadPoint() );
    double len12 = Geometry.distance( min2, s.getHeadPoint() );

    boolean insideHead = len11 < len && len12 < len;

    double len21 = Geometry.distance( min1, s.getTailPoint() );
    double len22 = Geometry.distance( min2, s.getTailPoint() );

    boolean insideTail = len21 < len && len22 < len;

    GSegment min = r.getMinorAxis();

    GPoint maj1 = min.getHeadPoint();
    GPoint maj2 = min.getTailPoint();
    len = min.getLength();

    len11 = Geometry.distance( maj1, s.getHeadPoint() );
    len12 = Geometry.distance( maj2, s.getHeadPoint() );

    insideHead = insideHead && len11 < len && len12 < len;

    len21 = Geometry.distance( maj1, s.getTailPoint() );
    len22 = Geometry.distance( maj2, s.getTailPoint() );

    insideTail = insideTail && len21 < len && len22 < len;

    if( insideHead && !insideTail )
      return( s.getHeadPoint() );

    if( !insideHead && insideTail )
      return( s.getTailPoint() );

    return( null );
  }
}

////////////////////////////////////////////////////////////////////////////////
