/**
   * Calculates the area of a polygon according to formula from John Russ. Note that sub-areas are
   * subtracted if polygon has the shape of an "8".
   *
   * @return polygon area
   */
  public double calcArea() {
    int size = this.size();
    if (size <= 2) {
      return 0;
    }
    double sumAreas = 0.0;
    double sumVolumesX = 0.0;
    double sumVolumesY = 0.0;
    double sumX;
    double sumY;
    double deltaX;
    double deltaY;
    LocationOJ prevLoc = (LocationOJ) this.get(size - 1);
    for (int i = 0; i < size; i++) {
      sumX = ((LocationOJ) this.get(i)).getX() + prevLoc.getX();
      sumY = ((LocationOJ) this.get(i)).getY() + prevLoc.getY();
      deltaX = ((LocationOJ) this.get(i)).getX() - prevLoc.getX();
      deltaY = ((LocationOJ) this.get(i)).getY() - prevLoc.getY();

      sumVolumesX = sumVolumesX + sumX * sumX * deltaY; // Eq 3. p 489
      sumVolumesY = sumVolumesY + sumY * sumY * deltaX; // Eq 3. p 489
      sumAreas = sumAreas + sumX * deltaY; // Eq 4. p 490}
      prevLoc = (LocationOJ) this.get(i);
    }
    return Math.abs(sumAreas / 2);
  }
 /** No operation so far */
 public void clearZ() {
   if (true) {
     return;
   }
   for (int index = 0; index < this.size(); index++) {
     LocationOJ loc = (LocationOJ) get(index);
     loc.z = 0;
     this.set(index, loc);
   }
 }
  /**
   * Four 2D points are used to define two lines p1-p2 and p3-p4. Returns the crosspoint of the two
   * lines, or NaN if they are parallel. Result is a 3D point, with z being set to NaN.
   */
  public LocationOJ calcCrossPoint(
      double p1h,
      double p1v,
      double p2h,
      double p2v,
      double p3h,
      double p3v,
      double p4h,
      double p4v) {

    double a1 = 0;
    double a2 = 0.0;
    double b1 = 0;
    double b2 = 0;
    double dx1;
    double dx2;

    LocationOJ result = new LocationOJ(nan, nan, nan);
    dx1 = (p2h - p1h);
    dx2 = p4h - p3h;
    if (dx1 == 0 && dx2 == 0) {
      return result;
    }
    if (dx1 != 0.0) {
      a1 = (p2v - p1v) / dx1;
      b1 = p1v - p1h * a1;
    }

    if (dx2 != 0.0) {
      a2 = (p4v - p3v) / dx2;
      b2 = p3v - p3h * a2;
    }

    if (dx1 != 0.0 && dx2 != 0.0) {
      if (a1 == a2) {
        return result; // lines are parallel
      } else {
        result.x = (float) (-(b1 - b2) / (a1 - a2));
        result.y = (float) (a1 * result.x + b1); // changed to float 17.4.2010
        result.z = (float) nan;
      }
    }
    if (dx1 == 0.0) {
      result.x = (float) p1h;
      result.y = (float) (a2 * result.x + b2);
    }
    if (dx2 == 0) {
      result.x = (float) p3h;
      result.y = (float) (a1 * result.x + b1);
    }

    return result;
  }
  /**
   * Performs a geometric calculation from the top of the vertex stack, or the entire vertex stack.
   * For example if algorithm is "orientation", the top two entries are regarded as two points on a
   * line, whose orientation is put into class variable orientation.
   *
   * @param algorithm
   */
  public void calc(String algorithm) {
    algorithm = algorithm.toLowerCase();
    if (!threeD) { // set all z to zero //15.5.2009
      clearZ();
    }
    LocationOJ aa;
    LocationOJ bb;
    LocationOJ cc;
    LocationOJ dd;

    boolean isRelPPath = algorithm.equalsIgnoreCase("relPartialPath");
    boolean isPerimeter = algorithm.equalsIgnoreCase("perimeter");
    boolean isTotalPath = algorithm.equalsIgnoreCase("totalPath");
    boolean isAbsPPath = algorithm.equalsIgnoreCase("partialPath");
    boolean isCrossPt = algorithm.equalsIgnoreCase("crosspoint");
    boolean isOuterCircle = algorithm.equalsIgnoreCase("outerCircle");
    boolean isInnerCircle = algorithm.equalsIgnoreCase("innerCircle");
    boolean isDeviation = algorithm.equalsIgnoreCase("deviation");
    boolean isHeight = algorithm.equalsIgnoreCase("height");
    boolean isArea = algorithm.equalsIgnoreCase("area");
    boolean isAngle = algorithm.equalsIgnoreCase("angle");
    boolean isOrientation = algorithm.equalsIgnoreCase("orientation");
    boolean isPartialPosition = algorithm.startsWith("partialposition");

    TriangleOJ tri = new TriangleOJ();
    if (isOrientation) {
      if (size() < 2) {
        return;
      }
      double x1 = ((LocationOJ) get(size() - 2)).getX();
      double y1 = ((LocationOJ) get(size() - 2)).getY();
      double x2 = ((LocationOJ) get(size() - 1)).getX();
      double y2 = ((LocationOJ) get(size() - 1)).getY();
      orientation = Math.atan2(y2 - y1, x2 - x1) * 180.0 / Math.PI;
      return;
    }

    if (isDeviation || isAngle) {
      if (size() < 3) {
        return;
      }
      double x0 = ((LocationOJ) get(size() - 3)).getX();
      double y0 = ((LocationOJ) get(size() - 3)).getY();
      double x1 = ((LocationOJ) get(size() - 2)).getX();
      double y1 = ((LocationOJ) get(size() - 2)).getY();
      double x2 = ((LocationOJ) get(size() - 1)).getX();
      double y2 = ((LocationOJ) get(size() - 1)).getY();

      double angle1 = Math.atan2(y1 - y0, x1 - x0) - Math.atan2(y2 - y1, x2 - x1);

      angle1 *= 180.0 / Math.PI;
      angle = 180 - Math.abs(angle1);
      angle1 = -angle1; // clockwise is positive, ccw is negative
      while (angle1 <= -180.0) {
        angle1 += 360;
      }
      while (angle1 > 180.0) {
        angle1 -= 360;
      }
      // scalarResult[0] = angle;
      deviation = angle1;
      return;
    }

    if (isHeight) {
      aa = (LocationOJ) get(0);
      bb = (LocationOJ) get(1);
      cc = (LocationOJ) get(2);
      tri.calcTriangle(aa, cc, bb);
      height = tri.hc;
      return;
    }

    if (isRelPPath || isAbsPPath) {
      cc = (LocationOJ) peek(); // point to be projected is on the top of stack
      int maxIndex = this.size() - 2;
      for (int index = 0; index < maxIndex; index++) {
        aa = (LocationOJ) get(index);
        bb = (LocationOJ) get(index + 1);
        tri.calcTriangle(aa, bb, cc, vertexScale, index);
      }
      partialPath = tri.getMinCcaAccu();
      totalLength = tri.getCcAccu();
      minSpark = tri.getMinSpark();
      signedMinSpark = tri.getSignedMinSpark(); // 19.8.2010
      relPartialPath = partialPath / totalLength;
      leftEdge = tri.getLeftEdge();
      rightEdge = tri.getRightEdge();
      impactX = tri.getImpactX();
      impactY = tri.getImpactY();

      return;
    }

    if (isTotalPath || isPerimeter || isPartialPosition) {
      double subLength = 0;
      double pPosX = nan;
      double pPosY = nan;
      double fraction = 0;
      int passes = 1;
      if (isPartialPosition) {
        passes = 2;
        String subStr = algorithm.replace('%', ' ');
        int index = subStr.indexOf(" ");
        subStr = subStr.substring(index);
        fraction = Double.parseDouble(subStr);
        if (algorithm.contains("%")) {
          fraction *= 0.01;
        }
        if (fraction < 0) {
          fraction = 0;
        }
        if (fraction > 1) {
          fraction = 1;
        }
      }

      for (int pass = 1; pass <= passes; pass++) {
        if (pass == 2) {
          subLength = fraction * totalLength;
        }
        double len = 0;
        int maxIndex = this.size();
        for (int index = 0; index < maxIndex; index++) {
          aa = (LocationOJ) get(index);
          if (index < maxIndex - 1) {
            bb = (LocationOJ) get(index + 1);
          } else {
            bb = (LocationOJ) get(0);
          }
          if (index < maxIndex - 1 || isPerimeter) {
            double thisSegment = Math.sqrt(sqr(aa.x - bb.x) + sqr(aa.y - bb.y) + sqr(aa.z - bb.z));

            len = len + thisSegment;
            if (pass == 2) {
              double delta = len - subLength;
              if (delta >= 0 && Double.isNaN(pPosX)) {
                pPosX = bb.x - delta / thisSegment * (bb.x - aa.x);
                pPosY = bb.y - delta / thisSegment * (bb.y - aa.y);
              }
            }
          }
        }
        if (isPerimeter) {
          perimeter = len;
        }
        if (isTotalPath || isPartialPosition) {
          totalLength = len;
        }
        if (isPartialPosition) {
          partialPositionX = pPosX;
          partialPositionY = pPosY;
        }
      }
      return;
    }

    if (isCrossPt) {
      aa = (LocationOJ) get(0);
      bb = (LocationOJ) get(1);
      cc = (LocationOJ) get(2);
      dd = (LocationOJ) get(3);
      crossPoint =
          calcCrossPoint(
              aa.getX(), aa.getY(), bb.getX(), bb.getY(), cc.getX(), cc.getY(), dd.getX(),
              dd.getY());
      return;
    }

    if (isArea) {
      area = calcArea();
      return;
    }
    if (isInnerCircle || isOuterCircle) {
      double Ax = ((LocationOJ) get(0)).getX();
      double Ay = ((LocationOJ) get(0)).getY();
      double Bx = ((LocationOJ) get(1)).getX();
      double By = ((LocationOJ) get(1)).getY();
      double Cx = ((LocationOJ) get(2)).getX();
      double Cy = ((LocationOJ) get(2)).getY();
      if (isOuterCircle) {
        double midABx = (Bx + Ax) / 2;
        double midABy = (By + Ay) / 2;
        double midCBx = (Cx + Bx) / 2;
        double midCBy = (Cy + By) / 2;
        double dropABx = midABx + By - Ay;
        double dropABy = midABy - (Bx - Ax);
        double dropCBx = midCBx + Cy - By;
        double dropCBy = midCBy - (Cx - Bx);
        // vertexResult[0] = calcCrossPoint(midABx, midABy, dropABx, dropABy, midCBx, midCBy,
        // dropCBx, dropCBy);
        outerCircleCenter =
            calcCrossPoint(midABx, midABy, dropABx, dropABy, midCBx, midCBy, dropCBx, dropCBy);
        outerCircleCenterX = outerCircleCenter.x;
        outerCircleCenterY = outerCircleCenter.y;
        double dx = outerCircleCenter.x - Ax;
        double dy = outerCircleCenter.y - Ay;
        // scalarResult[0] = Math.sqrt(dx * dx + dy * dy);
        outerCircleRadius = Math.sqrt(dx * dx + dy * dy);
      }
      /*
       *              A (0)
       *             / \
       *      sideC /   \sideB
       *           /  m  \
       *          /       \
       *        B(1)-------C(2)
       *            sideA
       **/
      if (isInnerCircle) {
        double sideC = Math.sqrt(sqr(Bx - Ax) + sqr(By - Ay));
        double sideB = Math.sqrt(sqr(Cx - Ax) + sqr(Cy - Ay));
        double sideA = Math.sqrt(sqr(Cx - Bx) + sqr(Cy - By));

        // divide angle C:
        double bSissorAx = Cx + (Ax - Cx) * sideA / sideB;
        double bSissorAy = Cy + (Ay - Cy) * sideA / sideB;
        double angleDividerCx = (Bx + bSissorAx) / 2;
        double angleDividerCy = (By + bSissorAy) / 2;

        // divide angle B:
        double cSissorAx = Bx + (Ax - Bx) * sideA / sideC;
        double cSissorAy = By + (Ay - By) * sideA / sideC;
        double angleDividerBx = (Cx + cSissorAx) / 2;
        double angleDividerBy = (Cy + cSissorAy) / 2;

        innerCircleCenter =
            calcCrossPoint(
                Cx, Cy, angleDividerCx, angleDividerCy, Bx, By, angleDividerBx, angleDividerBy);
        // vertexResult[0] = innerCircleCenter;
        innerCircleCenterX = innerCircleCenter.x;
        innerCircleCenterY = innerCircleCenter.y;
        double mx = innerCircleCenterX; // midpoint of inner circle
        double my = innerCircleCenterY;
        double sideBM = Math.sqrt(sqr(Bx - mx) + sqr(By - my));
        double sideCM = Math.sqrt(sqr(Cx - mx) + sqr(Cy - my));
        double s = (sideA + sideBM + sideCM) / 2; // half perimeter
        double area1 = Math.sqrt(s * (s - sideA) * (s - sideBM) * (s - sideCM));
        double rad = area1 / sideA * 2;
        innerCircleRadius = rad;
      }
      return;
    }
  }