﻿/*!
 * ITAdapter Java Script Library v2.0.0 
 * Various geometry-related functions and data structures such as: Point, PolarPoint, Rectangle etc....
 *
 * License: Unrestricted use/modification with mandatory reference to IT Adapter Corp. Inc. as the original author  
 *
 * (c) 2002-2011 IT Adapter Corp. Inc.
 * http://www.itadapter.com/
 * Author: Dmitriy Khmaladze
 * Date:  Jan 8, 2011
 */
function GeometryNamespace() 
{
   var geometry = this;
   
   var PI = 3.14159265;
   var PI2 = 6.28318531;
   
   this.PI = PI;
   this.PI2 = PI2;
   
   this.MapDirection = {
                      North:      {Name: "North"},
                      NorthEast:  {Name: "NorthEast"},
                      East:       {Name: "East"},
                      SouthEast:  {Name: "SouthEast"},
                      South:      {Name: "South"},
                      SouthWest:  {Name: "SouthWest"},
                      West:       {Name: "West"},
                      NorthWest:  {Name: "NorthWest"}
                    };   
   
   
   
   /** 
    * Returns pixel distance between two points
    */
   this.distance = function(x1, y1, x2, y2)
   {
      return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
   }
   
    /** 
    * Returns pixel distance between two points
    */
   this.distancePoints = function(p1, p2)
   {
      return this.distance(p1.getX(), p1.getY(),
                           p2.getX(), p2.getY());
   }

   
   /**
    * Returns azimuth angle (theta) in radians  
    */
   this.azimuthRad = function(xc, yc, xd, yd)
   {
      var angle = Math.acos( (yc - yd)/getDistance(xc, yc, xd, yd) );
      if (xd < xc) angle = PI + (PI - angle);
      return angle;
   }
  
  /**
   * Returns azimuth angle (theta) in degrees  
   */
  this.azimuthDeg = function(xc, yc, xd, yd)
  {
     return (getAzimuthRad(xc, yc, xd, yd) / PI2) * 360;
  }


  /**
   * Returns azimuth angle (theta) in radix units  
   */
  this.azimuthOfRadix = function(xc, yc, xd, yd, radix)
  {
      var a = getAzimuthRad(xc, yc, xd, yd);
      var half = PI / radix;
      var z = PI2 - half;
      if (a >= z) a = a - z; else a = a + half;
      return Math.round((a / PI2) * radix);
  }


  /**
   * Returns rectangle from coordinate pairs  
   */
  this.toRectXY = function(x1,y1, x2,y2)
  {
    return new geometry.Rectangle(new geometry.Point(x1, y1), 
                                  new geometry.Point(x2, y2)); 
  
  }
  
  /**
   * Returns rectangle from coordinats and dimensions  
   */
  this.toRectWH = function(x1,y1, w,h)
  {
    return new geometry.Rectangle(new geometry.Point(x1, y1), 
                                  new geometry.Point(x1 + w, y1 + h)); 
  
  }
  
 
  /**
    * Returns area of overlap between two rectangles   
    */
  this.overlapAreaRect = function(rect1, rect2)
  {
    var tl1 = rect1.getTopLeft();
    var tl2 = rect2.getTopLeft();
    
    return this.overlapAreaWH(tl1.getX(), tl1.getY(), rect1.getWidth, rect1.getHeight(),
                              tl2.getX(), tl2.getY(), rect2.getWidth, rect2.getHeight());
  }
 
 
   
   
   /**
    * Returns area of overlap between two rectangles expressed as top-left/widht-height pairs  
    */
   this.overlapAreaWH = function(x1,y1, w1,h1, x2,y2, w2,h2)
    {
      var ix;
      var iy;
      
      if (w2>=w1)
      {
        if (x1<=x2) ix = (x1+w1) - x2;
        else
        if ((x1+w1)>=(x2+w2)) ix = (x2+w2)-x1;  
        else ix = w1;  
      }
      else
      {
        if (x2<=x1) ix = (x2+w2) - x1;
        else
        if ((x2+w2)>=(x1+w1)) ix = (x1+w1)-x2;  
        else ix = w2;
      }
      
      if (h2>=h1)
      {
        if (y1<=y2) iy = (y1+h1) - y2;
        else
        if ((y1+h1)>=(y2+h2)) iy = (y2+h2)-y1;  
        else iy = h1;  
      }
      else
      {
        if (y2<=y1) iy = (y2+h2) - y1;
        else
        if ((y2+h2)>=(y1+h1)) iy = (y1+h1)-y2;  
        else iy = h2;
      }
      
      if (ix<0) ix = 0;
      if (iy<0) iy = 0;
      
     
      return ix*iy;
    }


   /**
    * Returns area of overlap between two rectangles expressed as top-left/bottom-right pairs  
    */
    this.overlapAreaXY = function(left1, top1, right1, bott1, left2, top2, right2, bott2)
    {
      return this.overlapAreaWH(left1, top1, right1-left1, bott1-top1, left2, top2, right2-left2, bott2-top2);
    }



    /** 
    * Modifies an angle by delta value ensuring that resulting angle is always between 0 and 2Pi 
    */ 
    this.wrapAngle = function(angle, delta)
    {
      delta = delta % PI2;
      
      if (delta<0) 
        delta =  PI2 + delta;
      
      var result = angle + delta;
      
      return result % PI2;
    }


    /**
     * Converts map direction to angular coordinate in radians
     */
    this.mapDirectionToAngle = function(direction)
    {
       switch (direction)
       {
         case geometry.MapDirection.North:  return 4/16 * PI2;
         case geometry.MapDirection.South:  return 12/16 * PI2;
         case geometry.MapDirection.East:   return 0.0;
         case geometry.MapDirection.West:   return 8/16 * PI2;

         case geometry.MapDirection.NorthEast: return 2/16 * PI2;
         case geometry.MapDirection.NorthWest: return 6/16 * PI2;
         case geometry.MapDirection.SouthEast: return 14/16 * PI2;
         case geometry.MapDirection.SouthWest: return 10/16 * PI2;
         
         default: return 0.0;
       }
    }
    
    
    /**
     * Converts a radian angular coordinate into map direction
     */
    this.angleToMapDirection = function(angle)
    {
       angle = geometry.wrapAngle(angle, 0);
       
       if ((angle >= 0.0) && (angle < PI2 * 1/16)) return geometry.MapDirection.East;
       else
       if ((angle >= PI2 * 1/16) && (angle < PI2 * 3/16)) return geometry.MapDirection.NorthEast;
       else
       if ((angle >= PI2 * 3 / 16) && (angle < PI2 * 5 / 16)) return geometry.MapDirection.North;
       else
       if ((angle >= PI2 * 5 / 16) && (angle < PI2 * 7 / 16)) return geometry.MapDirection.NorthWest;
       else
       if ((angle >= PI2 * 7 / 16) && (angle < PI2 * 9 / 16)) return geometry.MapDirection.West;
       else
       if ((angle >= PI2 * 9 / 16) && (angle < PI2 * 11 / 16)) return geometry.MapDirection.SouthWest;
       else
       if ((angle >= PI2 * 11 / 16) && (angle < PI2 * 13 / 16)) return geometry.MapDirection.South;
       else
       if ((angle >= PI2 * 13 / 16) && (angle < PI2 * 15 / 16)) return geometry.MapDirection.SouthEast;
       else 
         return geometry.MapDirection.East;
       
    }
    
    
    /**
     * Calculates an area of an inner rectangle that violates outside perimeter
     */
    this.calculatePerimeterViolationArea = function(perimeter, inner)
    {
      var ix = 0;
      var iy = 0;

      if (inner.getLeft()<perimeter.getLeft()) ix = perimeter.getLeft() - inner.getLeft();
       else
        if (inner.getRight()>perimeter.getRight()) ix = inner.getRight() - perimeter.getRight();

      if (inner.getTop() < perimeter.getTop()) iy = perimeter.getTop() - inner.getTop();
       else
        if (inner.getBottom() > perimeter.getBottom()) iy = inner.getBottom() - perimeter.getBottom();
      
      
      return (ix * inner.getHeight()) + (iy * inner.getWidth());
    }



    /**
     * Returns a point of intersection between a ray cast from the center of a rectangle 
     *  under certain polar coordinate angle and a rectangle side
     */
    this.findRayFromRectangleCenterSideIntersection = function(rect, theta)
    {
        var center = new geometry.Point(rect.getLeft() + rect.getWidth() / 2, rect.getTop() + rect.getHeight() / 2);

        var rayLength = rect.getWidth() > rect.getHeight() ? rect.getWidth() : rect.getHeight(); //make ray "infinite" in comparison to rect
        
        if (rayLength < 100) rayLength = 100; //safeguard 

        var rayEnd = new geometry.PolarPoint(rayLength, theta);//create ray "end" point
        var rayEndPoint = rayEnd.toPoint();

        var k = (rayEndPoint.getX() != 0)? (rayEndPoint.getY()) / (rayEndPoint.getX()) : 0; //get line incline  aka. y = kx

        var x = 0;
        var y = 0;
        
        var lst = [];
        
        //north
        x = center.getX() + ((k != 0) ? ((rect.getTop() - center.getY()) / k) : 0);
        y = rect.getTop();
        if ((x >= rect.getLeft()) && 
            (x <= rect.getRight()))
           lst.push(new geometry.Point(x, y));
       
        //south
        x = center.getX() + ((k != 0) ? ((rect.getBottom() - center.getY()) / k) : 0);
        y = rect.getBottom();
        if ((x >= rect.getLeft()) &&
            (x <= rect.getRight()))
          lst.push(new geometry.Point(x, y));
       
        //east
        x = rect.getRight();
        y = center.getY() + k * (rect.getRight() - center.getX());
        if ((y >= rect.getTop()) &&
            (y <= rect.getBottom()))
          lst.push(new geometry.Point(x, y));
       
        //west
        x = rect.getLeft();
        y = center.getY() + k * (rect.getLeft() - center.getX());
        if ((y >= rect.getTop()) &&
            (y <= rect.getBottom()))
          lst.push(new geometry.Point(x, y));
        
        var minPoint = new geometry.Point(1000000, 1000000);
        var minDistance = 1000000;

        var re = rayEnd.toPoint(); //rayEnd is relative to absolute 0,0 
        re.offset(center.getX(), center.getY()); // need to make relative to rectangle center

        for(var i in lst) //find closest point
        {
           var p = lst[i];
           
           var dst = geometry.distancePoints(p, re);
           
           if (dst < minDistance)
           {
             minPoint = p;
             minDistance = dst;
           }
        } 
        
        return minPoint;
      }

     
     
     



    /**
    * Point class represents x,y pair on a cartesian plane 
    */
    this.Point = function(x, y)
    {
      var fX = x;   this.getX = function() { return fX; }
                    this.setX = function(newx) { fX = newx; }
      
      var fY = y;   this.getY = function() { return fY; }
                    this.setY = function(newy) { fY = newy; }
                            
      /**
      * Changes point coordinates
      */
      this.offset = function(dx, dy)
        {
          fX += dx;
          fY += dy;
        }         
        
     /**
      * Returns point as polar point relative to the specified center
      */
      this.toPolarPoint = function(center)
      {
         var dist = geometry.distancePoints(center, this);
         var angle = Math.atan2(fY - center.getY(), fX - center.getX());

         if (angle < 0)  angle = PI2 + angle;

         return new geometry.PolarPoint(dist, angle); 
      }   
      
      
      /**
      * Determines whether the two points contain equivalent coordinates
      */
      this.isEqual = function(point)
      {
         return (fX == point.getX()) && (fY == point.getY());
      }
      
                     
    }//Point
    
    this.Point.prototype.toString = function()
    {
      return "(" + this.getX().toString() + " , " + this.getY().toString() + ")"; 
    }
   
    
    
    
    
    
    

    /**
    * Polar Point class represents point with polar coordinates  
    */
    this.PolarPoint = function(radius, theta)
    {
      var fRadius = radius;   this.getRadius = function() { return fRadius; } 
                              this.setRadius = function(newradius) { fRadius = newradius; }  
      
      var fTheta = checkangle(theta);  this.getTheta = function() { return fTheta; }
                                       this.setTheta = function(newtheta) { fTheta = checkangle(newtheta); }  
      
     /**
      * Returns polar point as simple x,y point
      */
      this.toPoint = function()
      {
          var x = fRadius * Math.cos(fTheta);
          var y = fRadius * Math.sin(fTheta);
          return new geometry.Point(x, y);      
      }
      
      /**
      * Determines whether the two points contain equivalent coordinates
      */
      this.isEqual = function(point)
      {
         return (fRadius == point.getRadius()) && (fTheta == point.getTheta());
      }
      
      
                                       
      function checkangle(angle)
      {
       if ((angle < 0) || (angle > PI2))
            throw "Invalid polar coordinates angle";
       return angle;     
      }      
      
      
    }//PolarPoint
    
    this.PolarPoint.prototype.toString = function()
    {
      return "(" + this.getRadius().toString() + " , " + this.getTheta().toString() + ")"; 
    }






   
    /**
    * Rectangle Point class represents a set of points  
    */
    this.Rectangle = function(corner1, corner2)
    {
       var fCorner1 = corner1;  this.getCorner1 = function(){ return fCorner1; }
                                this.setCorner1 = function(newval) { fCorner1 = newval;}
                                  
       var fCorner2 = corner2;  this.getCorner2 = function(){ return fCorner2; }
                                this.setCorner2 = function(newval) { fCorner2 = newval;}
                                
       /**
       * Returns top left corner point per natural axis orientation when x increases from left to right, and y increases from top to bottom
       */
       this.getTopLeft = function()
       {
         var lx = fCorner1.getX();
         var other = fCorner2.getX();
         
         if (other < lx) lx = other;
         
         var ty = fCorner1.getY();
         other = fCorner2.getY();
      
         if (other < ty) ty = other;
          
         return new geometry.Point(lx, ty);
       } 
       
       /**
       * Returns bottom right corner point per natural axis orientation when x increases from left to right, and y increases from top to bottom
       */
       this.getBottomRight = function()
       {
         var rx = fCorner1.getX();
         var other = fCorner2.getX();
         
         if (other > rx) rx = other;
         
         var by = fCorner1.getY();
         other = fCorner2.getY();
      
         if (other > by) by = other;
          
         return new geometry.Point(rx, by);
       }     
       
       /*
       * Return rectangle width
       */
       this.getWidth = function()
       {
         return Math.abs(fCorner1.getX() - fCorner2.getX());
       }  
       
       
       /*
       * Return rectangle height
       */
       this.getHeight = function()
       {
         return Math.abs(fCorner1.getY() - fCorner2.getY());
       }     
       
       
       /**
       * Returns left-most edge coordinate
       */
       this.getLeft = function()
       {
         var lx = fCorner1.getX();
         var other = fCorner2.getX();
         
         if (other < lx) lx = other;
          
         return lx;
       }             
       
       /**
       * Returns right-most edge coordinate
       */
       this.getRight = function()
       {
         var rx = fCorner1.getX();
         var other = fCorner2.getX();
         
         if (other > rx) rx = other;
          
         return rx;
       } 
       
       
       /**
       * Returns top-most edge coordinate
       */
       this.getTop = function()
       {
         var ty = fCorner1.getY();
         var other = fCorner2.getY();
         
         if (other < ty) ty = other;
          
         return ty;
       }             
       
       /**
       * Returns bottom-most edge coordinate
       */
       this.getBottom = function()
       {
         var by = fCorner1.getY();
         var other = fCorner2.getY();
         
         if (other > by) by = other;
          
         return by;
       } 
       
       
                                
    
    
    }//Rectangle

    this.Rectangle.prototype.toString = function()
    {
      return "(TopLeft: " + this.getTopLeft().toString() + " , BottomRight: " + this.getBottomRight().toString() + ")"; 
    }



}


//namespace
if (!ITA.Geometry)
 ITA.Geometry = new GeometryNamespace();
