cristiingineru/rabbit

View on GitHub
src/geometry.js

Summary

Maintainability
F
6 days
Test Coverage
"use strict";


export function Geometry() {

  var EPSILON = Number.EPSILON || 2.220446049250313e-16,
      PI = Math.PI,
      sin = Math.sin,
      cos = Math.cos;


  var createNewCanvasCallState = () => {
    return {
      box: {x: NaN, y: NaN, width: NaN, height: NaN},
      transforms: [[]],
      shapesInPath: [],
      moveToLocation: {x: NaN, y: NaN},
      lineWidths: [1]
    };
  },

  pathFillShapeHandlers = {
    rect: (state, shape) => {
      var x = shape.x,
        y = shape.y,
        width = shape.width,
        height = shape.height,
        newBox = {x: x, y: y, width: width, height: height};
      state.box = union(state.box, newBox);
      return state;
    },
    arc: (state, shape) => {
      var cx = shape.cx,
          cy = shape.cy,
          r = shape.r,
          sx = shape.sx,
          sy = shape.sy,
          sAngle = shape.sAngle,
          eAngle = shape.eAngle,
          counterclockwise = shape.counterclockwise,
          arcAngles = relevantArcAngles(r, sAngle, eAngle, counterclockwise),
          scaledArcPoints = arcAngles.map((a) => {
            var sr = scaledRadius(r, sx, sy, a);
            return {x: cx + sr*cos(a), y: cy + sr*sin(a)};
          }),
          newBox = boxPoints(scaledArcPoints);
      if (!isNaN(cx) && !isNaN(cy) && arcAngles.length > 1) {
        state.box = union(state.box, newBox);
      }
      return state;
    }
  },

  pathStrokeShapeHandlers = {
    rect: (state, shape) => {
      var x = shape.x,
        y = shape.y,
        width = shape.width,
        height = shape.height,
        scaledLineWidth = state.lineWidth !== 1 ? state.lineWidth : 0,
        xScaledLineWidth = scaledLineWidth * state.transform.scale.x,
        yScaledLineWidth = scaledLineWidth * state.transform.scale.y,
        newBox = {x: x - xScaledLineWidth  / 2, y: y - yScaledLineWidth / 2, width: width + xScaledLineWidth, height: height + yScaledLineWidth};
      state.box = union(state.box, newBox);
      return state;
    },
    arc: (state, shape) => {
      var cx = shape.cx,
          cy = shape.cy,
          r = shape.r,
          sx = shape.sx,
          sy = shape.sy,
          sAngle = shape.sAngle,
          eAngle = shape.eAngle,
          counterclockwise = shape.counterclockwise,
          arcAngles = relevantArcAngles(sAngle, eAngle, counterclockwise),
          scaledArcPoints = flatten(arcAngles.map((a) => {
            var w = scaledRadius(state.lineWidth, state.transform.scale.x, state.transform.scale.y, a),
                sir = scaledRadius(r, sx, sy, a) - w/2, // inner radius
                sr = scaledRadius(r, sx, sy, a),    // radius
                sor = scaledRadius(r, sx, sy, a) + w/2, // outer radius
                points = [];
            if (w === 1) {
              points.push({x: cx + sr*cos(a), y: cy + sr*sin(a)});
            } else {
              points.push({x: cx + sir*cos(a), y: cy + sir*sin(a)});
              points.push({x: cx + sor*cos(a), y: cy + sor*sin(a)});
            }
            return points;
          })),
          newBox = boxPoints(scaledArcPoints);
      if (!isNaN(cx) && !isNaN(cy) && arcAngles.length > 1) {
        state.box = union(state.box, newBox);
      }
      return state;
    },
    lineTo: (state, shape) => {
      var x1 = shape.x1,
        y1 = shape.y1,
        x2 = shape.x2,
        y2 = shape.y2,
        scaledLineWidth = getScaledWidthOfLine(x1, y1, x2, y2, state.transform.scale.x, state.transform.scale.y, state.lineWidth),
        rect = getRectAroundLine(x1, y1, x2, y2, scaledLineWidth !== 1 ? scaledLineWidth : 0),
        newBox = {
          x: Math.min(rect.x1, rect.x2, rect.x3, rect.x4),
          y: Math.min(rect.y1, rect.y2, rect.y3, rect.y4),
          width: Math.max(rect.x1, rect.x2, rect.x3, rect.x4) - Math.min(rect.x1, rect.x2, rect.x3, rect.x4),
          height: Math.max(rect.y1, rect.y2, rect.y3, rect.y4) - Math.min(rect.y1, rect.y2, rect.y3, rect.y4)
        };
      state.box = union(state.box, newBox);
      return state;
    }
  },

  canvasCallHandlers = {
    lineWidth: (state, call) => {
      state.lineWidths[state.lineWidths.length - 1] = call.val;
      return state;
    },
    fillRect: (state, call) => {
      var x = call.arguments[0] * state.transform.scale.x + state.transform.translate.x,
        y = call.arguments[1] * state.transform.scale.y + state.transform.translate.y,
        width = call.arguments[2] * state.transform.scale.x,
        height = call.arguments[3] * state.transform.scale.y,
        newBox = {x: x, y: y, width: width, height: height};
      state.box = union(state.box, newBox);
      return state;
    },
    strokeRect: (state, call) => {
      var x = call.arguments[0] * state.transform.scale.x + state.transform.translate.x,
        y = call.arguments[1] * state.transform.scale.y + state.transform.translate.y,
        width = call.arguments[2] * state.transform.scale.x,
        height = call.arguments[3] * state.transform.scale.y,
        scaledLineWidth = state.lineWidth !== 1 ? state.lineWidth : 0,
        xScaledLineWidth = scaledLineWidth * state.transform.scale.x,
        yScaledLineWidth = scaledLineWidth * state.transform.scale.y,
        newBox = {x: x - xScaledLineWidth / 2, y: y - yScaledLineWidth / 2, width: width + xScaledLineWidth, height: height + yScaledLineWidth};
      state.box = union(state.box, newBox);
      return state;
    },
    rect: (state, call) => {
      var x = call.arguments[0] * state.transform.scale.x + state.transform.translate.x,
        y = call.arguments[1] * state.transform.scale.y + state.transform.translate.y,
        width = call.arguments[2] * state.transform.scale.x,
        height = call.arguments[3] * state.transform.scale.y;
      state.shapesInPath.push({type: 'rect', x: x, y: y, width: width, height: height});
      return state;
    },
    arc: (state, call) => {
      var cx = call.arguments[0] * state.transform.scale.x + state.transform.translate.x,
        cy = call.arguments[1] * state.transform.scale.y + state.transform.translate.y,
        r = call.arguments[2],
        sx = state.transform.scale.x,
        sy = state.transform.scale.y,
        sAngle = call.arguments[3],
        eAngle = call.arguments[4],
        counterclockwise = call.arguments[5] || false;
      state.shapesInPath.push({type: 'arc', cx: cx, cy: cy, r: r, sx: sx, sy: sy, sAngle: sAngle, eAngle: eAngle, counterclockwise: counterclockwise});
      return state;
    },
    moveTo: (state, call) => {
      var x1 = call.arguments[0] * state.transform.scale.x + state.transform.translate.x,
        y1 = call.arguments[1] * state.transform.scale.y + state.transform.translate.y;
      state.moveToLocation = {x: x1, y: y1};
      return state;
    },
    lineTo: (state, call) => {
      var x1 = state.moveToLocation.x,
        y1 = state.moveToLocation.y,
        x2 = call.arguments[0] * state.transform.scale.x + state.transform.translate.x,
        y2 = call.arguments[1] * state.transform.scale.y + state.transform.translate.y;
      state.shapesInPath.push({type: 'lineTo', x1: x1, y1: y1, x2: x2, y2: y2});
      return state;
    },
    arcTo: (state, call) => {
      var x0 = state.moveToLocation.x,
          y0 = state.moveToLocation.y,
          x1 = call.arguments[0] * state.transform.scale.x + state.transform.translate.x,
          y1 = call.arguments[1] * state.transform.scale.y + state.transform.translate.y,
          x2 = call.arguments[2] * state.transform.scale.x + state.transform.translate.x,
          y2 = call.arguments[3] * state.transform.scale.y + state.transform.translate.y,
          r = call.arguments[4],
          sx = state.transform.scale.x,
          sy = state.transform.scale.y,
          decomposition = decomposeArcTo(x0, y0, x1, y1, x2, y2, r, sx, sy);
      if (decomposition.line) {
        state.shapesInPath.push({type: 'lineTo', x1: decomposition.line.x1, y1: decomposition.line.y1, x2: decomposition.line.x2, y2: decomposition.line.y2});
      }
      if (decomposition.arc) {
        state.shapesInPath.push({type: 'arc', cx: decomposition.arc.x, cy: decomposition.arc.y, r: r, sx: sx, sy: sy, sAngle: decomposition.arc.sAngle, eAngle: decomposition.arc.eAngle, counterclockwise: decomposition.arc.counterclockwise});
      }
      state.moveToLocation = {x: decomposition.point.x, y: decomposition.point.y};
      return state;
    },
    save: (state, call) => {
      state.transforms.push([]);
      state.lineWidths.push(lastElement(state.lineWidths));
      return state;
    },
    restore: (state, call) => {
      state.transforms.pop();
      state.lineWidths.pop();
      return state;
    },
    translate: (state, call) => {
      lastElement(state.transforms)
        .push({translate: {x: call.arguments[0], y: call.arguments[1]}});
      return state;
    },
    scale: (state, call) => {
      lastElement(state.transforms)
        .push({scale: {x: call.arguments[0], y: call.arguments[1]}});
      return state;
    },
    beginPath: (state, call) => {
      state.shapesInPath = [];
      return state;
    },
    fill: (state, call) => {
      return state.shapesInPath.reduce((state, shape) => {
        var handler = getPathFillShapeHandler(shape);
        return handler(state, shape);
      }, state);
    },
    stroke: (state, call) => {
      for(var i = 0; i < state.shapesInPath.length; i++) {
        var shape = state.shapesInPath[i],
            handler = getPathStrokeShapeHandler(shape);
        state = handler(state, shape);
      }
      return state;
    }
  },

  nullCanvasCallHandler = (state, call) => {
    return state;
  },

  getCanvasCallHandler = (call) => {
    return canvasCallHandlers[call.method] || canvasCallHandlers[call.attr] || nullCanvasCallHandler;
  },

  getPathFillShapeHandler = (shape) => {
    return pathFillShapeHandlers[shape.type];
  },

  getPathStrokeShapeHandler = (shape) => {
    return pathStrokeShapeHandlers[shape.type];
  },

  preCanvasCallHandler = (state) => {
    state.transform = totalTransform(flatten(state.transforms));
    state.lineWidth = lastElement(state.lineWidths);
    return state;
  },

  getBBox = (shape) => {
    var state = createNewCanvasCallState();
    state = shape.reduce((state, call) => {
      var handler = getCanvasCallHandler(call);
      return handler(preCanvasCallHandler(state), call);
    }, createNewCanvasCallState());
    return state.box;
  },

  flatten = (array) => {
    return array
      .reduce((previousArray, currentArray) => {
        return previousArray.concat(currentArray);
      }, []);
  },

  lastElement = (array) => {
    return array[array.length - 1];
  },

  firstTruthyOrZero = (val1, val2) =>{
    if (val1 || val1 === 0) {
      return val1;
    }
    return val2;
  },

  union = (box1, box2) => {
    box1 = {
      x: firstTruthyOrZero(box1.x, box2.x),
      y: firstTruthyOrZero(box1.y, box2.y),
      width: firstTruthyOrZero(box1.width, box2.width),
      height: firstTruthyOrZero(box1.height, box2.height)
    };
    box2 = {
      x: firstTruthyOrZero(box2.x, box1.x),
      y: firstTruthyOrZero(box2.y, box1.y),
      width: firstTruthyOrZero(box2.width, box1.width),
      height: firstTruthyOrZero(box2.height, box1.height)
    };
    var result = {
      x: Math.min(box1.x, box2.x),
      y: Math.min(box1.y, box2.y),
      width: Math.max(box1.width, box2.width, box1.x < box2.x
        ? box1.width + box2.width + (box2.x - (box1.x + box1.width))
        : box1.width + box2.width + (box1.x - (box2.x + box2.width))),
      height: Math.max(box1.height, box2.height, box1.y < box2.y
        ? box1.height + box2.height + (box2.y - (box1.y + box1.height))
        : box1.height + box2.height + (box1.y - (box2.y + box2.height)))
    };
    return result;
  },

  boxPoints = (points) => {
    var xes = points.map((p) => p.x),
        yes = points.map((p) => p.y),
        minX = Math.min.apply(null, xes),
        maxX = Math.max.apply(null, xes),
        minY = Math.min.apply(null, yes),
        maxY = Math.max.apply(null, yes),
        box = {x: NaN, y: NaN, width: NaN, height: NaN};
    if (minX !== +Infinity && maxX !== -Infinity && minY !== +Infinity && maxY !== -Infinity) {
      box = {
        x: minX,
        y: minY,
        width: maxX - minX,
        height: maxY - minY
      };
    }
    return box;
  },

  totalTransform = (transforms) => {
    return transforms
      .map((value) => {
        return {
          translate: value.translate || {x: 0, y: 0},
          scale: value.scale || {x: 1, y: 1}
        };
      })
      .reduce((previousValue, currentValue) => {
        return {
          translate: {
            x: previousValue.translate.x + currentValue.translate.x * previousValue.scale.x,
            y: previousValue.translate.y + currentValue.translate.y * previousValue.scale.y
          },
          scale: {
            x: previousValue.scale.x * currentValue.scale.x,
            y: previousValue.scale.y * currentValue.scale.y
          }
        };
      }, {translate: {x: 0, y: 0}, scale: {x: 1, y: 1}});
  },

  getRectAroundLine = (x1, y1, x2, y2, width) => {
    var rect;
    if (x1 === y1 && x1 === x2 && x1 === y2) {
      rect = {
        x1: x1, y1: x1,  x2: x1, y2: x1,
        x4: x1, y4: x1,  x3: x1, y3: x1
      };
    } else {
      rect = getRectAroundLongLine(x1, y1, x2, y2, width);
    }
    return rect;
  },

  getRectAroundLongLine = (x1, y1, x2, y2, width) => {
    //  r = the radius or the given distance from a given point to the nearest corners of the rect
    //  a = the angle between the line and the horizontal axis
    //  b1, b2 = the angle between half the hight of the rectangle and the horizontal axis
    //
    //  In the following example the given line is horizontal, so a = 0.
    //  The given line is between the two @ symbols.
    //  The + symbols are the corners of rectangle to be determined.
    //  In order to find the b1 and b2 angles we have to add PI/2 and respectivly subtract PI/2.
    //  b1 is vertical and pointing upwords and b2 is also vertical but pointing downwords.
    //  Each corner is r or width / 2 far away from its corespondent line ending.
    //  So we know the distance (r), the starting points (x1, y1) and (x2, y2) and the (b1, b2) directions.
    //
    //  (x1,y1)                    (x2,y2)
    //      +------------------------+
    //      ^                        ^
    //      |                        |
    //      | b1                     | b1
    //      @========================@
    //      | b2                     | b2
    //      |                        |
    //      v                        v
    //      +------------------------+
    //  (x4,y4)                    (x3,y3)
    //

    var r = width / 2,
      a = Math.atan((y2 - y1) / (x2 - x1)),
      b1 = a + Math.PI/2,
      b2 = a - Math.PI/2,
      rx1 = r * Math.cos(b1) + x1,
      ry1 = r * Math.sin(b1) + y1,
      rx2 = r * Math.cos(b1) + x2,
      ry2 = r * Math.sin(b1) + y2,
      rx3 = r * Math.cos(b2) + x2,
      ry3 = r * Math.sin(b2) + y2,
      rx4 = r * Math.cos(b2) + x1,
      ry4 = r * Math.sin(b2) + y1;
    return {
      x1: rx1, y1: ry1,  x2: rx2, y2: ry2,
      x4: rx4, y4: ry4,  x3: rx3, y3: ry3
    };
  },

  getScaledWidthOfLine = (x1, y1, x2, y2, sx, sy, width) => {
    //  The original points are not moved. Only the width will be scaled.
    //  The width of an horizontal line will be scaled with the sy ratio only.
    //  The width of a vertival line will be scaled with the sx ratio only.
    //  The width of an oblique line will be scaled with both the sx and sy
    //but proportional with the angle between the line and the x and y axes.
    //
    //                                                    .\
    //               .\  (x2,y2)                         ...\  (x2,y2)
    //              ...@                                .....@
    //             .../.\                              ...../.\
    //            .../...              sx             ...../...\
    //           .../...            +--->            ...../.....
    //          .../...             |               ...../.....
    //         .../...              |               \.../.....
    //         \./...               |                \./.....
    //          @...             sy v                 @.....
    //  (x1,y1)  \.                           (x1,y1)  \...
    //                                                  \.
    //
    var a = Math.atan((y2 - y1) / (x2 - x1)),
      sina = Math.sin(a), cosa = Math.cos(a),
      scaledWidth = width * Math.sqrt(sx*sx * sina*sina + sy*sy * cosa*cosa);
    return scaledWidth;
  },

  getParallelsAroundSegment = (x1, y1, x2, y2, distance) => {
    var rect = getRectAroundLongLine(x1, y1, x2, y2, 2 * distance);
    return [
      {x1: rect.x1, y1: rect.y1, x2: rect.x2, y2: rect.y2},
      {x1: rect.x4, y1: rect.y4, x2: rect.x3, y2: rect.y3}
    ];
  },

  getIntersectionOfTwoLines = (l1, l2) => {
    var a1 = l1.y2 - l1.y1, b1 = l1.x1 - l1.x2, c1 = l1.x2*l1.y1 - l1.x1*l1.y2,
        a2 = l2.y2 - l2.y1, b2 = l2.x1 - l2.x2, c2 = l2.x2*l2.y1 - l2.x1*l2.y2,
        x = (c2*b1 - c1*b2) / (a1*b2 - a2*b1),
        y = l2.y1 === l2.y2 ? l2.y1 : (-c1 - a1*x) / b1;
    return {x: x, y: y};
  },

  getDistanceBetweenTwoPoints = (x1, y1, x2, y2) => {
    return Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
  },

  getAngleBetweenThreePoints = (x1, y1, x2, y2, x3, y3) => {
    var a = getDistanceBetweenTwoPoints(x1, y1, x2, y2),
        b = getDistanceBetweenTwoPoints(x2, y2, x3, y3),
        c = getDistanceBetweenTwoPoints(x3, y3, x1, y1),
        cosC = (a*a + b*b - c*c) / (2*a*b),
        C = Math.acos(cosC);
    return C;
  },

  permuteLines = (alphaLines, betaLines) => {
    var permutations = [];
    alphaLines.forEach((alphaLine) => {
      betaLines.forEach((betaLine) => {
        permutations.push({alpha: alphaLine, beta: betaLine});
      });
    })
    return permutations;
  },

  almostEqual = (a, b) => {
    // gross approximation to cover the flot and trigonometric precision
    return a === b || Math.abs(a - b) < 20 * EPSILON;
  },

  isCenterInBetween = (cx, cy, x0, y0, x1, y1, x2, y2) => {
    //
    //  True is returned in situations like this one:
    //
    //                             '   /
    //                            '   /
    //                           '   /
    //                          '   /
    //              ===P0==========P1=============
    //                        '   /
    //              ---------C---/----------------
    //                      '   /
    //                     '   /
    //                    '   P2
    //                   '   /
    //
    var a = getAngleBetweenThreePoints(x2, y2, x1, y1, x0, y0),
        a1 = getAngleBetweenThreePoints(cx, cy, x1, y1, x0, y0),
        a2 = getAngleBetweenThreePoints(cx, cy, x1, y1, x2, y2);
    return almostEqual(a, a1 + a2) && (a1 + a2 <= Math.PI);
  },

  getTheCenterOfTheCorner = (x0, y0, x1, y1, x2, y2, distance, sx, sy) => {
    //
    //                                    d  d
    //                                  '  /  '
    //                                 '  /  '
    //   alpha line 0    -------------'--/--'---------
    //                               '  /  '             d
    //     given line    ===P==========P==============
    //                             '  /  '               d
    //   alpha line 1    ---------C--/--'-------------
    //                           '  /  '
    //                          '  /  '
    //                         '  P  '
    //                        '  /  '
    //
    //     beta lines 0 & 1 with one of the given line inbetween
    //
    //
    //  P = the given P0, P1, P2 points
    //
    //  d = the given distance / radius of the circle
    //
    //  C = the center of the circle/corner to be determined

    var d1 = getScaledWidthOfLine(x0, y0, x1, y1, sx, sy, distance),
        d2 = getScaledWidthOfLine(x1, y1, x2, y2, sx, sy, distance),
        alphaLines = getParallelsAroundSegment(x0, y0, x1, y1, d1),
        betaLines = getParallelsAroundSegment(x1, y1, x2, y2, d2),
        permutations = permuteLines(alphaLines, betaLines),
        intersections = permutations.map((p) => getIntersectionOfTwoLines(p.alpha, p.beta)),
        center = intersections.filter((i) => isCenterInBetween(i.x, i.y, x0, y0, x1, y1, x2, y2))[0];

    return center || {x: NaN, y: NaN};
  },

  getTheFootOfThePerpendicular = (x1, y1, x2, y2, cx, cy) => {
    var m = (y2 - y1) / (x2 - x1),
        cm = -1 / m,
        C = y1*(x2 - x1) - x1*(y2 - y1),
        x = (C - (x2 - x1)*(cy - cm*cx)) / (cm*(x2 - x1) + y1 - y2),
        y = cm*(x - cx) + cy;
    return m === 0 // horizontal
      ? {x: cx, y: y1}
      : (m === Infinity // vertical
        ? {x: x1, y: cy}
        : {x: x, y: y});
  },

  xyToArcAngle = (cx, cy, x, y) => {
    var horizontalX = cx + 1,
        horizontalY = cy,
        a = Math.abs(getAngleBetweenThreePoints(x, y, cx, cy, horizontalX, horizontalY));
    if(y < cy) {
      //third & forth quadrants
      a = Math.PI + Math.PI - a;
    }
    return a;
  },

  scaledRadius = (r, sx, sy, a) => {
    //
    //  The sx and sy scalings can be different so the circle looks more like an
    //ellipse. This function is returning the radius corrsponding to the specified angle
    //and taking into account the sx and sy values.
    //
    //            *   *                                  *        *
    //         *         *                         *                   *
    //       *             *           sx       *                        *
    //                             +------>    *                          *
    //       *             *       |
    //         *         *      sy v           *                          *
    //            *   *                         *                        *
    //                                            *                    *
    //                                                  *         *
    //
    var aa, //adjusted angle
        na = a % (2*PI); //normalized angle
    if (sx === sy) {
      return r * sx;
    } else if (almostEqual(na, 0) || almostEqual(na, PI)) {
      return r * sx;
    } else if (almostEqual(na, PI/2) || almostEqual(na, 3*PI/2)) {
      return r * sy;
    } else if (na < 1*PI/2) {
      aa = na;
      return r * (sx * (PI/2-aa)/(PI/2) + sy * (aa)/(PI/2));
    } else if (na < 2*PI/2) {
      aa = na - 1*PI/2;
      return r * (sx * (aa)/(PI/2) + sy * (PI/2-aa)/(PI/2));
    } else if (na < 3*PI/2) {
      aa = na - 2*PI/2;
      return r * (sx * (PI/2-aa)/(PI/2) + sy * (aa)/(PI/2));
    } else if (na < 4*PI/2) {
      aa = na - 3*PI/2;
      return r * (sx * (aa)/(PI/2) + sy * (PI/2-aa)/(PI/2));
    }
  },

  collinear = (x0, y0, x1, y1, x2, y2) => {
    var m1 = (y1 - y0) / (x1 - x0),
        m2 = (y2 - y1) / (x2 - x1);
    return almostEqual(m1, m2);
  },

  decomposeArcTo = (x0, y0, x1, y1, x2, y2, r, sx, sy) => {
    //
    //  The sx and sy is used to scale the radius (r) only.
    //All other coordinates have to be already scaled.
    //
    var decomposition = {
      point: {x: x1, y: y1}
    };
    if(collinear(x0, y0, x1, y1, x2, y2)) {
      decomposition.line = {x1: x0, y1: y0, x2: x1, y2: y1};
    } else if (!isNaN(x0) && !isNaN(y0)) {
      var center = getTheCenterOfTheCorner(x0, y0, x1, y1, x2, y2, r, sx, sy),
          foot1 = getTheFootOfThePerpendicular(x0, y0, x1, y1, center.x, center.y),
          foot2 = getTheFootOfThePerpendicular(x1, y1, x2, y2, center.x, center.y),
          angleFoot1 = xyToArcAngle(center.x, center.y, foot1.x, foot1.y),
          angleFoot2 = xyToArcAngle(center.x, center.y, foot2.x, foot2.y),
          sAngle = Math.abs(angleFoot2 - angleFoot1) < Math.PI ? angleFoot2 : angleFoot1,
          eAngle = Math.abs(angleFoot2 - angleFoot1) < Math.PI ? angleFoot1 : angleFoot2;
      if (sAngle > eAngle) {
        var temp = sAngle;
        sAngle = eAngle;
        eAngle = temp + 2*PI;
      }
      if (!isNaN(center.x) && !isNaN(center.y)) {
        if (!almostEqual(getDistanceBetweenTwoPoints(x0, y0, foot1.x, foot1.y), 0)) {
          decomposition.line = {x1: x0, y1: y0, x2: foot1.x, y2: foot1.y};
        }
        decomposition.arc = {x: center.x, y: center.y, r: r, sAngle: sAngle, eAngle: eAngle, counterclockwise: false};
        decomposition.point = {x: foot2.x, y: foot2.y};
      }
    }
    return decomposition;
  },

  relevantArcAngles = (sAngle, eAngle, counterclockwise) => {
    //
    //  The function is returning the specified sAngle and eAngle and
    //all the multiple of PI/2. The result doesn't contain duplications.
    //  Example: For sAngle = PI/6 and eAngle = 7*PI/6,
    // When counterclockwise = false the result is: [PI/6, 7*PI/6, PI/2, 2*PI/2]
    // When counterclockwise = true the result is: [PI/6, 7*PI/6, 3*PI/2, 4*PI/2]
    //
    var angles = [], uniqueAngles = [];
    angles.push(sAngle);
    angles.push(eAngle);
    if (counterclockwise) {
      var temp = sAngle;
          sAngle = eAngle;
          eAngle = sAngle + 2*PI;
    }
    [1*PI/2, 2*PI/2, 3*PI/2, 4*PI/2].forEach((a) => {
      if(eAngle > a && a > sAngle) {
        angles.push(a);
      }
    });

    //removing the duplicated points
    uniqueAngles.push(angles.pop());
    while(angles.length > 0) {
      var angle = angles.pop(),
          found = uniqueAngles.find((a) =>
            almostEqual(angle, a) ||
            almostEqual(angle - 2*PI, a) ||
            almostEqual(angle, a - 2*PI));
      if (found === undefined) {
        uniqueAngles.push(angle);
      }
    }

    return uniqueAngles;
  },

  // http://stackoverflow.com/questions/2752725/finding-whether-a-point-lies-inside-a-rectangle-or-not
  isPointInsideRectangle = (point, rectangle) => {
    var segments = [{
      x1: rectangle.x,
      y1: rectangle.y,
      x2: rectangle.x + rectangle.width,
      y2: rectangle.y }, {
      x1: rectangle.x + rectangle.width,
      y1: rectangle.y,
      x2: rectangle.x + rectangle.width,
      y2: rectangle.y + rectangle.height}, {
      x1: rectangle.x + rectangle.width,
      y1: rectangle.y + rectangle.height,
      x2: rectangle.x,
      y2: rectangle.y + rectangle.height}, {
      x1: rectangle.x,
      y1: rectangle.y + rectangle.height,
      x2: rectangle.x,
      y2: rectangle.y
    }];

    var isInside = segments.map((segment) => {
      var A = -(segment.y2 - segment.y1),
        B = segment.x2 - segment.x1,
        C = -(A * segment.x1 + B * segment.y1),
        D = A * point.x + B * point.y + C;
        return D;
    }).every((D) => {
      return D >= 0;
    });

    return isInside;
  };


  this.getBBox = getBBox;
  this.union = union;
  this.totalTransform = totalTransform;
  this.getRectAroundLine = getRectAroundLine;
  this.getParallelsAroundSegment = getParallelsAroundSegment;
  this.getIntersectionOfTwoLines = getIntersectionOfTwoLines;
  this.getAngleBetweenThreePoints = getAngleBetweenThreePoints;
  this.getTheCenterOfTheCorner = getTheCenterOfTheCorner;
  this.getTheFootOfThePerpendicular = getTheFootOfThePerpendicular;
  this.xyToArcAngle = xyToArcAngle;
  this.scaledRadius = scaledRadius;
  this.decomposeArcTo = decomposeArcTo;
  this.isPointInsideRectangle = isPointInsideRectangle;

}