Source: main.js

/*
CoordGeom.js - main.js
A JAVASCRIPT FRAMEWORK FOR COORDINATE GEOMETRY + VECTOR

Creator:    Chloe Lo
Created at: 24 JAN 2018

RELEASES
v0.0-alpha: 30 JAN 2018
v0.3-alpha:  6 FEB 2018
v0.5-alpha: 18 APR 2018
v1.0:       25 MAY 2018
v1.2-alpha: 27 NOV 2018
*/

// # ALBEGRA COMPONENT

/**
 *  class Point
 *  @param {Number} x x-coordinate of the point in 2D Euclidean plane
 *  @param {Number} y y-coordinate of the point in 2D Euclidean plane
 */
class Point {
  constructor(x,y) {
    this.x = x;
    this.y = y;
  }

  // all below calculated properties are relative to origin
  /**
   * angle of the point made with the positive x-axis from the origin
   * @return {Number} the angle in radian, return NaN if the point is at the origin
   */
  get angle() {
    if (!(this.y == 0 && this.x == 0)) {
      return Math.atan2(this.y, this.x);
    } else {
      return NaN;
    }
  }
  /**
   * slope of the point made with the positive x-axis from the origin
   * @return {Number} the slope, return NaN if the point is at the origin
   */
  get slope() {
    if (!(this.y == 0 && this.x == 0)) {
      return this.y/this.x;
    } else {
      return NaN;
    }
  }
  /**
   * distance of the point from the origin
   * @return {Number} the distance
   */
  get distance() {
    return Math.hypot(this.x, this.y);
  }

  // methods
  /**
   * translate the point by the given amount in x- and y-direction
   * @param  {Number} x amount to displace in the x-direction
   * @param  {Number} y amount to displace in the y-direction
   * @return {Point}   the translated point
   */
  translate(x, y) {
    this.x = this.x + x;
    this.y = this.y + y;
    return this;
  }
  /**
   * translate the point by the given vector
   * @param  {Vector} vector the displacement of the point
   * @return {Point}        the translated point
   */
  translateByVector(vector) {
    this.x = this.x + vector.x;
    this.y = this.y + vector.y;
    return this;
  }
  // all below methods are relative to origin or the axes
  /**
   * rotate the point about the origin for the given angle in radian
   * @param  {Number} angle angle to rotate in radian
   * @return {Point}       the rotated point
   */
  rotate(angle) {
    var x = this.x;
    var y = this.y;
    this.x = x*Math.cos(angle) - y*Math.sin(angle);
    this.y = x*Math.sin(angle) + y*Math.cos(angle);
    return this;
  }
  /**
   * flip the point horizontally
   * @return {Point} the reflected point
   */
  flipX() {
    this.x = -this.x;
    return this;
  }
  /**
   * flip the point vertically
   * @return {Point} the reflected point
   */
  flipY() {
    this.y = -this.y;
    return this;
  }
  /**
   * update the point to the given coordinates, i.e. change the point to somewhere else
   * @param  {Number} x new x-coordinate of the point
   * @param  {Number} y new y-coordinate of the point
   * @return {Point}   the updated point
   */
  update(x,y) {
    this.x = x;
    this.y = y;
    return this;
  }
  /**
   * clone the current point and return a new point object, so that changes made to the original will not affect the clone
   * @return {Point} the cloned point
   */
  clone() {
    return new Point(this.x, this.y);
  }
}

// - MARK: define class Line
/**
 * class Line, a line that extends infinitely in the 2D Euclidean plane
 * @param {Point} point1 the first point that defines the line
 * @param {Point} point2 the second point that defines the line
 */
class Line {
  // init
  constructor(point1, point2) {
    this.point1 = point1.clone();
    this.point2 = point2.clone();
  }

  // calculated properties
  /**
   * the slope of the line
   * @return {Number} the slope of the line as in m in the expression y = mx + c, return NaN if the line is vertical
   */
  get m() {
    if (this.point1.x != this.point2.x) {
      var dx = this.point1.x - this.point2.x;
      var dy = this.point1.y - this.point2.y;
      return dy/dx;
    } else {
      return NaN;
    }
  }
  /**
   * the y-intercept of the line
   * @return {Point} the coordinates of the y-intercept, return NaN if the line is vertical
   */
  get yIntercept() {
    if (!isNaN(this.m)) {
      return new Point(0, this.point1.y - this.m*this.point1.x);
    } else {
      return NaN;
    }
  }
  /**
   * the y-coordinate of the y-intercept of the line
   * @return {Number} the number c as in the expression y = mx + c of the line, return NaN if the line is vertical
   */
  get c() {
    if (!isNaN(this.m)) {
      return this.point1.y - this.m*this.point1.x;
    } else {
      return NaN;
    }
  }
  /**
   * the x-intercept of the line
   * @return {Point} the coordinates of the x-intercept, return NaN if the line is horizontal
   */
  get xIntercept() {
    if (this.m != 0 && !isNaN(this.m)) {
      return new Point(this.point1.x - this.point1.y/this.m, 0);
    } else if (this.m == 0) {
      return NaN;
    } else {
      return new Point(this.point1.x, 0);
    }
  }
  /**
   * whether the line is vertical or not
   * @return {Boolean} whether the line is vertical or not
   */
  get isVertical() {
    return isNaN(this.m);
  }
  /**
   * whether the line is horizontal or not
   * @return {Boolean} whether the line is horizontal or not
   */
  get isHorizontal() {
    return (this.m == 0);
  }

  // methods
  /**
   * get the y-coordinate of a given x-coordinate on the line
   * @param  {Number} x the input x-coordinate
   * @return {Number}   the output y-coordinate, return NaN if the line is vertical
   */
  calY(x) {
    if (!isNaN(this.m)) {
      return this.m*x + this.c;
    } else {
      return NaN;
    }
  }
  /**
   * get the x-coordinate of the given y-coordinate on the line
   * @param  {Number} y the input y-coodinate
   * @return {Number}   the output x-coordinate, return NaN if the line is horizontal
   */
  calX(y) {
    if (!isNaN(this.m)) {
      if (this.m != 0) {
        return (y - this.c)/this.m;
      } else {
        return NaN;
      }
    } else {
      return this.xIntercept.x;
    }
  }
}

// - MARK: define class lineSegment
/**
 * class LineSegment, a line with two end points.
 * @param {Point} point1 the first end point of the line segment
 * @param {Point} point2 the second send point of the line segment
 */
class LineSegment {
  // init
  constructor(point1, point2) {
    this.point1 = point1.clone();
    this.point2 = point2.clone();
  }

  // calculated properties
  /**
   * the mid-point of the line segment
   * @return {Point} coordinates of the mid-point
   */
  get midpoint() {
    var x = (parseFloat(this.point1.x) + parseFloat(this.point2.x))/2;
    var y = (parseFloat(this.point1.y) + parseFloat(this.point2.y))/2
    return new Point(x,y);
  }
  /**
   * the horizontal run of the line segment
   * @return {Number} change in x
   */
  get dx() {
    return this.point2.x - this.point1.x;
  }
  /**
   * the vertical rise of the line segment
   * @return {Number} change in y
   */
  get dy() {
    return this.point2.y - this.point1.y;
  }
  /**
   * the angle made by the line segment with the horizontal
   * @return {Number} angle in radian, return NaN if the line segment is of length zero
   */
  get angle() {
    if (!(this.dx == 0 && this.dy == 0)) {
      return Math.atan2(this.dy, this.dx);
    } else {
      return NaN;
    }
  }
  /**
   * the length of the line segment
   * @return {Number} length
   */
  get length() {
    return Math.hypot(this.dx, this.dy);
  }
  /**
   * the slope of the line segment, as in rise over run, i.e. dy/dx
   * @return {Number} slope, return NaN if the line segment is vertical
   */
  get slope() {
    if (this.dx != 0) {
      return this.dy/this.dx;
    } else {
      return NaN;
    }
  }
}

// - MARK: define class Vector
/**
 * the class Vector
 * @param {Number} x the x component of the vector
 * @param {Number} y the y component of the vector
 */
class Vector {

  // init properties
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  // calculated properties
  /**
   * whether the vector is a zero vector or not
   * @return {Boolean} whether it is a zero vector
   */
  get isZeroVector() {
    return (this.x == 0 && this.y == 0);
  }
  /**
   * the angle of the vector
   * @return {Number} the angle in radian, return NaN if it is a zero vector
   */
  get angle() {
    if (!this.isZeroVector) {
      return Math.atan2(this.y, this.x);
    } else {
      return NaN;
    }
  }
  /**
   * the slope of the vector, calculated by y/x
   * @return {Number} the slope, return NaN if the vector is either zero or vertical
   */
  get slope() {
    if (!this.isZeroVector && (this.x != 0)) {
      return this.y/this.x;
    } else {
      return NaN;
    }
  }
  /**
   * the magnitude of the vector
   * @return {Number} the magnitude
   */
  get magnitude() {
    return Math.hypot(this.x, this.y);
  }
  /**
   * the unit vector of the given vector
   * @return {Vector} the unit vector, return NaN if the vector is zero
   */
  get unitVector() {
    if (!this.isZeroVector) {
      return new Vector(this.x/this.magnitude, this.y/this.magnitude);
    // cannot use scale due to float point error
    } else {
      return NaN;
    }
  }

  // methods
  /**
   * rotate the vector by the given degree, update is made to the original vector
   * @param  {Number} angle the angle to rotate in radian
   * @return {Vector}       the rotated vector
   */
  rotate(angle) {
    var x = this.x;
    var y = this.y;
    this.x = x*Math.cos(angle) - y*Math.sin(angle);
    this.y = x*Math.sin(angle) + y*Math.cos(angle);
    return this;
  }
  /**
   * flip the vector horizontally, update is made to the original vector
   * @return {Vector} the flipped vector
   */
  flipX() {
    this.x = -this.x;
    return this;
  }
  /**
   * flip the vector horizontally, update is made to the original vector
   * @return {Vector} the flipped vector
   */
  flipY() {
    this.y = -this.y;
    return this;
  }
  /**
   * the negative of the vector
   * @return {Vector} the flipped vector
   */
  negative() {
    this.flipX().flipY();
    return this;
  }
  /**
   * scale the vector by the given scale factor
   * @param  {Number} factor the scale factor
   * @return {Vector}        the scaled vector
   */
  scale(factor) {
    this.x = this.x*factor;
    this.y = this.y*factor;
    return this;
  }
  /**
   * scale the vector in the x-direction only by the given scale factor
   * @param  {Number} factor the scale factor
   * @return {Vector}        the scaled vector
   */
  scaleX(factor) {
    this.x = this.x*factor;
    return this;
  }
  /**
   * scale the vector in the y-direction only by the given scale factor
   * @param  {Number} factor the scale factor
   * @return {Vector}        the scaled vector
   */
  scaleY(factor) {
    this.y = this.y*factor;
    return this;
  }
}

/**
 * The class Polygon
 * @param {Point[]} points the array of points which form the polygon
 */
class Polygon {

  // init
  constructor(points) {
    var pointsClone = [];
    var edges = [];
    for (var i=0; i<points.length; i++) {
      pointsClone.push(points[i].clone());
      if (i < points.length - 1) {
        var edge = new LineSegment(points[i], points[i+1]);
      } else {
        var edge = new LineSegment(points[i], points[0]);
      }
      edges.push(edge);
    }
    /**
     * containing the vertices of the polygon
     * @type {Point[]}
     */
    this.vertices = pointsClone;
    /**
     * containing the edges of the polygon
     * @type {LineSegment[]}
     */
    this.edges = edges;
  }

  // calculated properties
  /**
   * the degree or order of the polygon, i.e. the number of vertices or edges
   * @return {Number} the degree or order of the polygon; an integer
   */
  get n() {
    return this.vertices.length;
  }
  /**
   * the area of the polygon
   * @return {Number} the area
   */
  get area() {
    var total1 = 0;
    var total2 = 0;
    var n = this.n;
    for (var i = 0; i < n; i++) {
      if (i == 0) {
        // handle first case
        total1 += this.vertices[0].x*this.vertices[1].y;
        total2 += this.vertices[0].x*this.vertices[n-1].y;
      } else if (i < n-1) {
        // handle anything in between
        total1 += this.vertices[i].x*this.vertices[i+1].y;
        total2 += this.vertices[i].x*this.vertices[i-1].y;
      } else {
        // handle last case
        total1 += this.vertices[n-1].x*this.vertices[0].y;
        total2 += this.vertices[n-1].x*this.vertices[n-2].y;
      }
    }
    return Math.abs(total1 - total2)/2;
  }


  // methods
  /**
   * translate the polygon by the given displacement in both x- and y-directions; this is not a clone and the original polygon is affected
   * @param  {Number} x the translation in the x-direction
   * @param  {Number} y the translation in the y-direction
   * @return {Polygon}   the translated polygon
   */
  translate(x,y) {
    for (var i=0; i<this.n; i++) {
      this.vertices[i].translate(x,y);
      this.edges[i].point1.translate(x,y);
      this.edges[i].point2.translate(x,y);
    }
    this.minimumCorner.translate(x,y);
    this.maximumCorner.translate(x,y);
    return this;
  }

}

/**
 * the class Circle
 * @param {Point} center the coordinates of the circle's center
 * @param {Number} radius the length of the radius of the circle
 */
class Circle {

  // init
  constructor(center, radius) {
    this.center = center.clone();
    this.radius = radius;
  }

  // calculated properties
  /**
   * the diameter of the circle
   * @return {Number} the diameter
   */
  get diameter() {
    return this.radius*2;
  }
  /**
   * the area of the circle
   * @return {Number} the area
   */
  get area() {
    return Math.PI*this.radius*this.radius;
  }
  /**
   * the length of the circumference of the circle
   * @return {Number} the circumference
   */
  get circumference() {
    return Math.PI*2*this.radius;
  }

  // methods
  /**
   * translate the circle by the given translation in x- and y-direction
   * @param  {Number} x the translation in the x-direction
   * @param  {Number} y the translation in the y-direction
   * @return {Circle}   the translated circle
   */
  translate(x,y) {
    this.center.translate(x,y);
    return this;
  }
  /**
   * set a new radius for the circle
   * @param {Number} r the new radius
   * @return {Circle} the circle with the new radius
   */
  setRadius(r) {
    this.radius = r;
    return this;
  }
}


// MARK: - geometrical functions

// - point and line interaction
/**
 * Find the intersection between two lines
 * @param  {Line} line1 the first line
 * @param  {Line} line2 the second line
 * @return {Point}       the intersection; return NaN if the two lines are parallel or identical
 */
function interceptOfLines(line1, line2) {
  if ((line1.m != line2.m) && !isNaN(line1.m) && !isNaN(line2.m)) {
    var x = (line2.c-line1.c)/(line1.m-line2.m);
    return new Point(x,line1.m*x+line1.c);
  } else if ((isNaN(line1.m) && isNaN(line2.m)) || (line1.m == line2.m)) {
    return NaN
  } else {
    if (isNaN(line1.m)) {
      var x = line1.xIntercept.x;
      return new Point(x, line2.m*x + line2.c);
    } else {
      var x = line2.xIntercept.x;
      return new Point(x, line1.m*x + line1.c);
    }
  }
}
/**
 * Create a new line from a given point and slope
 * @param  {Point} point the point which the line passes through
 * @param  {Number} m     the slope of the line
 * @return {Line}       the line
 */
function newLineFromPointSlope(point, m) {
  var point2;
  if (!isNaN(m)) {
    point2 = newPointTranslatedByVector(point, new Vector(1,m));

  } else {
    point2 = newPointTranslatedByVector(point, new Vector(0,1));
  }
  return new Line(point, point2);
}
/**
 * Create a new line that extends to infinity from a line segment
 * @param  {LineSegment} lineSegment the line segment
 * @return {Line}             the resultant line
 */
function newLineFromLineSegment(lineSegment) {
  return new Line(lineSegment.point1, lineSegment.point2);
}
/**
 * The perpandicular bisector of a line segment
 * @param  {LineSegment} lineSegment the given line segment
 * @return {Line}             the perpendicular bisector
 */
function perpendicularBisector(lineSegment) {
  var m;
  if (lineSegment.slope == 0) {
    m = NaN;
  } else if (isNaN(lineSegment.slope)) {
    m = 0
  } else {
    m = -1/lineSegment.slope;
  }
  return newLineFromPointSlope(lineSegment.midpoint, m);
}
/**
 * get a new point with the given displacement in vector from a given point
 * @param  {Point} point  the original point
 * @param  {Vector} vector the displacement from the original point
 * @return {Point}        the new point
 */
function newPointTranslatedByVector(point, vector) {
  return new Point(point.x+vector.x, point.y+vector.y);
}
/**
 * Project a point onto the line so that the projected point is the closest to the point on the line
 * @param  {Point} point the point to be projected
 * @param  {Line} line  the line on which the point will be projected
 * @return {Point}       the projected point
 */
function projectedPointOnLine(point, line) {
  if (pointIsOnLine(point, line)) {
    return point.clone();
  } else if (!isNaN(line.m) && (line.m != 0)) {
    var m2 = -1/line.m;
    var line2 = newLineFromPointSlope(point, m2);
    var intercept = interceptOfLines(line, line2);
    return intercept;
  } else if (line.m == 0) {
    return new Point(point.x, line.c);
  } else {
    return new Point(line.xIntercept.x, point.y);
  }
}
/**
 * Reflect a point about a given straight line
 * @param  {Point} point the point to be reflected
 * @param  {Line} line  the line about which the point will be reflected
 * @return {Point}       the reflected point
 */
function newPointReflectedInLine(point, line) {
  var intercept = projectedPointOnLine(point, line);
  var change = vectorFromPoints(point, intercept);
  return newPointTranslatedByVector(intercept, change);
}
/**
 * Obtain the shortest distance of a point from a line
 * @param  {Point} point the point
 * @param  {Line} line  the line
 * @return {Number}       the shortest distance
 */
function distanceOfPointFromLine(point, line) {
  var projection = projectedPointOnLine(point, line);
  return new LineSegment(point, projection).length;
}
/**
 * Construct a vector that points from the first point to the second point
 * @param  {Point} point1 the first point
 * @param  {Point} point2 the second point
 * @return {Vector}        the resultant vector
 */
function vectorFromPoints(point1, point2) {
  return new Vector(point2.x - point1.x, point2.y - point1.y);
}
/**
 * the dot product of two vectors, i.e. x1*x2 + y1*y2
 * @param  {Vector} vector1 the first vector
 * @param  {Vector} vector2 the second vector
 * @return {Number}         the dot product
 */
function dotProduct(vector1, vector2) {
  return vector1.x*vector2.x + vector1.y*vector2.y;
}
/**
 * the angle made between two vectors joining at the tail
 * @param  {Vector} vector1 the first vector
 * @param  {Vector} vector2 the second vector
 * @return {Number}         the angle in radian; return NaN if either one of the vectors is a zero vector
 */
function angleBetweenVectors(vector1, vector2) {
  if (!vector1.isZeroVector && !vector2.isZeroVector) {
    return Math.acos(dotProduct(vector1, vector2)/(vector1.magnitude*vector2.magnitude));
  } else {
    return NaN;
  }
}
/**
 * sum the two vectors together
 * @param {Vector} vector1 the first vector
 * @param {Vector} vector2 the second vector
 * @return {Vector} the resultant vector
 */
function addVectors(vector1, vector2) {
  return new Vector(vector1.x+vector2.x, vector1.y+vector2.y);
}
/**
 * subtract the two vectors, i.e. return vector 1 - vector 2
 * @param  {Vector} vector1 the first vector
 * @param  {Vector} vector2 the second vector
 * @return {Vector}         the resultant vector
 */
function subtractVectors(vector1, vector2) {
  return new Vector(vector1.x-vector2.x, vector1.y-vector2.y);
}
/**
 * the intersection between a circle and a line
 * @param  {Circle} circle the circle
 * @param  {Line} line   the line
 * @return {Point[]}        an array containing the intersection points; the array will be empty if there is no intersection found
 */
function intersectionOfCircleAndLine(circle, line) {
  var r = circle.radius;
  var a = circle.center.x;
  var b = circle.center.y;
  if (!isNaN(line.m)) {
    var m = line.m;
    var c = line.c;
    var qa = m*m + 1;
    var qb = 2*(m*(c-b)-a);
    var qc = a*a + (c-b)*(c-b) - r*r;
    var delta = qb*qb - 4*qa*qc;
    if (delta < 0) {
      return [];
    } else if (delta == 0) {
      var x = -qb/(2*qa);
      return [new Point(x, x*m + c)];
    } else {
      var x1 = (-qb + Math.sqrt(delta))/(2*qa);
      var x2 = (-qb - Math.sqrt(delta))/(2*qa);
      return [new Point(x1, x1*m + c), new Point(x2, x2*m + c)];
    }
  } else {
    // update when point in/out/on circle is implemented?
    var x = line.xIntercept.x;
    var d = Math.abs(x - a);
    if (d < r) {
      return [new Point(x,b+Math.sqrt(r*r-d*d)), new Point(x,b-Math.sqrt(r*r-d*d))];
    } else if (d == r) {
      return [new Point(x, b)];
    } else {
      return [];
    }
  }
}
/**
 * the intersection between two circles
 * @param  {Circle} circle1 the first circle
 * @param  {Circle} circle2 the second circle
 * @return {Point[]}         an array containing the intersection points; the array will be empty if there is no intersection found
 */
function intersectionOfCircles(circle1, circle2) {
  // check whether the two circles overlapped
  var center1ToCenter2 = vectorFromPoints(circle1.center, circle2.center);
  var d = center1ToCenter2.magnitude;
  var r1 = circle1.radius;
  var r2 = circle2.radius;
  var angle = center1ToCenter2.angle;
  if (d < r1 + r2 && d > 0) {
    // obtain the solution where origin at center of c1
    // and c2 rotated to have same y as c1
    var solX = (r1*r1 - r2*r2 + d*d)/(2*d);
    var solY1 = Math.sqrt((r1*r1) - (solX*solX));
    var solY2 = -solY1;
    // transform the solutions to the correct coordinates
    var sol1 = new Point(solX, solY1).rotate(angle).translateByVector(circle1.center);
    var sol2 = new Point(solX, solY2).rotate(angle).translateByVector(circle1.center);
    return [sol1, sol2];
  } else if (d == r1 + r2) {
    var sol = new Point(r1, 0).rotate(angle).translateByVector(circle1.center);
    return [sol];
  } else {
    return [];
  }
}
/**
 * check that whether the first circle is inside the second circle
 * @param  {Circle}  circle1 the first circle
 * @param  {Circle}  circle2 the second circle
 * @return {Boolean}         whether the first circle is inside the second circle
 */
function isCircleInCircle(circle1, circle2) {
  if (intersectionOfCircles(circle1, circle2).length > 0) {
    return false;
  } else {
    return ((pointIsStrictlyInCircle(circle1.center, circle2)) || (pointIsStrictlyInCircle(circle2.center, circle1)));
  }
}
// - on line or line segment
/**
 * check whether a point is on a line or not
 * @param  {Point} point the point
 * @param  {Line} line  the line
 * @return {Boolean}       whether the point is on the line or not
 */
function pointIsOnLine(point, line) {
  return (point.y == line.m*point.x + line.c);
}
/**
 * check whether a point is on a line segment or not
 * @param  {Point} point       the point
 * @param  {LineSegment} lineSegment the line segment
 * @return {Boolean}             whether the point is on the line segment or not
 */
function pointIsOnLineSegment(point, lineSegment) {
  var point1 = lineSegment.point1;
  var point2 = lineSegment.point2;
  var line = new Line(point1, point2);
  var lowerBound = Math.min(point1.x, point2.x);
  var upperBound = Math.max(point1.x, point2.x);
  return (pointIsOnLine(point, line) && (point.x >= lowerBound) && (point.x <= upperBound));
}
/**
 * check whether a point is strictly inside a circle or not
 * @param  {Point} point  the point
 * @param  {Circle} circle the circle
 * @return {Boolean}        whether the point is inside the circle or not
 */
function pointIsStrictlyInCircle(point, circle) {
  var pointFromCenter = vectorFromPoints(point, circle.center);
  return (pointFromCenter.magnitude < circle.radius);
}
/**
 * check whether a point is on the circumference of the circle or not
 * @param  {Point} point  the point
 * @param  {Circle} circle the circle
 * @return {Boolean}        whether the point is on the circumference or not
 */
function pointIsOnCircleCircumference(point, circle) {
  var pointFromCenter = vectorFromPoints(point, circle.center);
  return (pointFromCenter.magnitude == circle.radius);
}
/**
 * check whether a point is strictly outside of a circle or not
 * @param  {Point} point  the point
 * @param  {Circle} circle the circle
 * @return {Boolean}        whether the point is outside the circle or not
 */
function pointIsStrictlyOutOfCircle(point, circle) {
  var pointFromCenter = vectorFromPoints(point, circle.center);
  return (pointFromCenter.magnitude > circle.radius);
}
// - polygon
/**
 * check whether a point is on one of the edges of the polygon or not
 * @param  {Point} point   the point
 * @param  {Polygon} polygon the polygon
 * @return {Boolean}         whether the point is on one of the edges or not
 */
function pointIsOnPolygonEdge(point, polygon) {
  var onEdge = false;
  polygon.edges.forEach(function(edge) {
    if (pointIsOnLineSegment(point, edge)) {
      onEdge = true;
    }
  });
  return onEdge;
}
/**
 * check whether a point is strictly inside the polygon or not; the winding number algorithm is used
 * @param  {Point} point   the point
 * @param  {Polygon} polygon the polygon
 * @return {Boolean}         whether the point is enclosed by the polygon or not
 */
function pointIsStrictlyInPolygon(point, polygon) {
  var windingNumber = 0;
  polygon.edges.forEach(function(edge) {
    var thisVertex = edge.point1;
    var nextVertex = edge.point2;
    var lineFromEdge = newLineFromLineSegment(edge);
    if (thisVertex.y <= point.y) {
      if (nextVertex.y > point.y) {
        if (lineIsLeftOfPoint(lineFromEdge, point)) {
          windingNumber++;
        }
      }
    } else {
      if (nextVertex.y <= point.y) {
        if (lineIsLeftOfPoint(lineFromEdge, point)) {
          windingNumber--;
        }
      }
    }
  });
  return (Math.abs(windingNumber) == 1);
}
/**
 * check whether a point is strictly outside the polygon or not; the winding number algorithm is used
 * @param  {Point} point   the point
 * @param  {Polygon} polygon the polygon
 * @return {Boolean}         whether the point is outside the polygon or not
 */
function pointIsStrictlyOutOfPolygon(point, polygon) {
  return (!pointIsOnPolygonEdge(point, polygon) && !pointIsStrictlyInPolygon(point, polygon));
}


// MARK: - helper functions
/**
 * @private to check whether a line is on the left of a point or not
 * @param  {Line} line  the line
 * @param  {Point} point the point
 * @return {Boolean}       whether the line is at the left of the point or not
 */
function lineIsLeftOfPoint(line, point) {
  if (!isNaN(line.m)) {
    if (line.m == 0) {
      return false;
    } else {
      if (pointIsOnLine(point, line)) {
        return false;
      } else {
        return (line.calX(point.y) < point.x);
      }
    }
  } else {
    return (line.xIntercept.x < point.x);
  }
}