/** Computes the ordinary diagram and sets the results */
  protected synchronized void voroOrdinaryDiagram(OpenList sites) {

    diagram.setSites(sites);
    diagram.setClipPoly(clipPolygon);
    try {
      diagram.computeDiagram();
    } catch (Exception e) {
      System.out.println("Error on computing power diagram");
      e.printStackTrace();
    }
  }
  /** Computes the diagram and sets the results */
  public synchronized void voroDiagram() {

    diagram.setSites(sites);
    diagram.setClipPoly(clipPolygon);
    try {
      diagram.computeDiagram();
    } catch (Exception e) {
      System.out.println("Error on computing power diagram");
      e.printStackTrace();
    }
  }
  public VoronoiCore(PolygonSimple clipPolygon) {
    sites = new OpenList();

    this.clipPolygon = clipPolygon;
    init();
    diagram.setClipPoly(clipPolygon);
  }
  /**
   * The resulting Voronoi Cells are clipped with this polygon
   *
   * @param polygon
   */
  public void setClipPolygon(PolygonSimple polygon) {
    clipPolygon = polygon;
    maxDelta =
        Math.max(clipPolygon.getBounds2D().getWidth(), clipPolygon.getBounds2D().getHeight());

    if (diagram != null) {
      diagram.setClipPoly(polygon);
    }
  }
  public VoronoiCore(Rectangle2D rectangle) {
    PolygonSimple poly = new PolygonSimple(4);
    poly.add(rectangle.getMinX(), rectangle.getMinY());
    poly.add(rectangle.getMinX() + rectangle.getWidth(), rectangle.getMinY());
    poly.add(
        rectangle.getMinX() + rectangle.getWidth(), rectangle.getMinY() + rectangle.getHeight());
    poly.add(rectangle.getMinX(), rectangle.getMinY() + rectangle.getHeight());

    this.clipPolygon = poly;
    init();
    diagram.setClipPoly(poly);
  }
  public void iterate() {
    currentMaxNegativeWeight = 0;
    currentEuclidChange = 0;
    currentAreaError = 0;
    currentMaxError = 0;
    // long start=System.currentTimeMillis();

    // move the sites to their center
    completeArea = clipPolygon.getArea();
    drawCurrentState(false);
    double errorArea = 0;

    /** Extrapolation */
    int amount = 0;
    if (isUseExtrapolation() && !preflowFinished) {
      Site[] array = sites.array;
      int size = sites.size;
      for (int z = 0; z < size; z++) {
        Site site = array[z];
        PolygonSimple poly = site.getPolygon();
        double percentage = site.getPercentage();
        double wantedArea = completeArea * percentage;
        double currentArea = poly.getArea();
        double increase = wantedArea / currentArea;
        /** Extrapolation */
        if (percentage >= getPreflowPercentage() && increase >= getPreflowIncrease()) {
          // System.out.println("Percentage:" + percentage
          // + "\t Increase:" + preflowIncrease);
          amount++;
          double radiusIncrease = Math.sqrt(increase);
          double radiusCurrent = Math.sqrt(site.getWeight());
          double deltaRadius = radiusCurrent * (radiusIncrease) - radiusCurrent;

          for (int y = 0; y < size; y++) {
            Site other = array[y];
            if (other != site && other.getPercentage() < getPreflowPercentage()) {
              Point2D vector = new Point2D();
              // Vector2d vector=new Vec
              vector.x = other.getX() - site.getX();
              vector.y = other.getY() - site.getY();

              double length = vector.length();
              double linearDistanceScaledDeltaRadius =
                  deltaRadius * (1 - ((length - radiusCurrent) / maxDelta));
              double scale = linearDistanceScaledDeltaRadius / length;
              // double scale = deltaRadius
              // / length;
              vector.scale(scale);

              other.preflowVector.x += vector.x;
              other.preflowVector.y += vector.y;
            }
          }
        }
      }
    }
    if (amount == 0) {
      preflowFinished = true;
    }

    /** move to centers */
    Site[] array = sites.array;
    int size = sites.size;
    for (int z = 0; z < size; z++) {
      Site point = array[z];
      // point.preflowVector.scale(1.0/(double)amount);

      double error = 0;

      double percentage = point.getPercentage();
      PolygonSimple poly = point.getPolygon();
      if (poly != null) {
        Point2D centroid = poly.getCentroid();
        double centroidX = centroid.getX();
        double centroidY = centroid.getY();
        double dx = centroidX - point.getX();
        double dy = centroidY - point.getY();
        currentEuclidChange += dx * dx + dy * dy;
        double currentArea = poly.getArea();
        double wantedArea = completeArea * point.getPercentage();
        double increase = (wantedArea / currentArea);
        error = Math.abs(wantedArea - currentArea);
        double minDistanceClipped = poly.getMinDistanceToBorder(centroidX, centroidY);
        minDistanceClipped = minDistanceClipped * nearlyOne;
        // scale preflow vector to have minDistanceClipped length
        double rootBorder = clipPolygon.getMinDistanceToBorder(point.getX(), point.getY());
        // if (isUseExtrapolation() && !preflowFinished &&
        // rootBorder>maxDelta/Math.sqrt(sites.size()))) {
        if (isUseExtrapolation() && !preflowFinished) {
          if (point.preflowVector.length() != 0) {
            point.preflowVector.scale(minDistanceClipped / point.preflowVector.length());
            if (point.preflowVector.length() > 5) {
              centroidX += point.preflowVector.x;
              centroidY += point.preflowVector.y;
            }
            point.preflowVector.x = 0;
            point.preflowVector.y = 0;
          }

          /** This could also be used as further speedup, but is not 100% stable */
          // if (point.preflowVector.length()!=0){
          // Point2D centroidNew = poly.getRelativePosition(new
          // Point2D.Double(point.preflowVector.x,
          // point.preflowVector.y));
          // centroidX=centroidNew.getX();
          // centroidY=centroidNew.getY();
          // }

        }
        double minDistance = point.nonClippedPolyon.getMinDistanceToBorder(centroidX, centroidY);

        /** radius has to be at most the minimal distance to the border segments of the polygon */
        double weight = Math.min(point.getWeight(), minDistance * minDistance);
        if (weight < 0.00000001) {
          weight = 0.00000001;
        }

        // set new position to the centroid
        point.setXYW(centroidX, centroidY, weight);
      }

      error = error / (completeArea * 2);

      errorArea += error;
      // System.out.println("ErrorArea:"+error);
    }
    // if (site 0) {
    // errorArea = 1;
    // } else {
    // errorArea = errorArea / (completeArea * 2);
    currentAreaError += errorArea;
    // }
    // drawCurrentState(false);
    // System.out.println("Area Error: "+errorArea);
    voroDiagram();
    // drawCurrentState(false);
    // try to improve the radius of the circle so that the wanted area gets
    // improved
    // int a = 0;
    // if (a == 0)
    // ;

    /** adapt weights */
    /** if activated also allow negative weights */
    /** compute the ordinary Voronoi diagram */
    OpenList sitesCopy = null;
    if (guaranteeInvariant) {
      sitesCopy = sites.cloneWithZeroWeights();
      diagram.setSites(sitesCopy);
      diagram.setClipPoly(clipPolygon);
      diagram.computeDiagram();
    }
    for (int z = 0; z < size; z++) {
      Site point = array[z];

      PolygonSimple poly = point.getPolygon();
      double completeArea = clipPolygon.getArea();
      double currentArea = poly.getArea();
      double wantedArea = completeArea * point.getPercentage();

      double currentRadius = Math.sqrt(currentArea / Math.PI);
      double wantedRadius = Math.sqrt(wantedArea / Math.PI);
      double deltaCircle = currentRadius - wantedRadius;

      double increase = wantedArea / currentArea;
      if (getAggressiveMode() == false) {
        increase = Math.sqrt(increase);
      }

      // get the minimal distance to the neighbours. otherwise the
      // neighbours could be dominated

      double minDistance = 0;
      if (guaranteeInvariant) {
        Site pointOrdinary = sitesCopy.array[z];
        minDistance = getMinNeighbourDistance(pointOrdinary);
      } else {
        minDistance = getMinNeighbourDistance(point);
      }
      minDistance = minDistance * nearlyOne;
      // minDistance = Math.max(1, minDistance);
      // change the radius of the circles
      /** we have to take care that the radius doesn't get to big to dominate other sites. */
      double radiusOld = Math.sqrt(point.getWeight());

      double radiusNew = radiusOld * increase;

      double deltaRadius = radiusNew - radiusOld;
      if (radiusNew > minDistance) {
        radiusNew = minDistance;
      }
      double finalWeight = radiusNew * radiusNew;
      if (useNegativeWeights) {
        Point2D center = poly.getCentroid();
        double distanceBorder = poly.getMinDistanceToBorder(center.x, center.y);
        double maxDelta = Math.min(distanceBorder, deltaCircle);
        if (finalWeight < 0.0001) {
          double radiusNew2 = radiusNew - maxDelta;
          if (radiusNew2 < 0) {
            finalWeight = -(radiusNew2 * radiusNew2);
            if (finalWeight < currentMaxNegativeWeight) {
              currentMaxNegativeWeight = finalWeight;
            }
          }
        }
      }

      point.setWeight(finalWeight);
    }

    /** make weights non negative again by adding the smallest negative value */
    if (useNegativeWeights) {

      if (currentMaxNegativeWeight < 0) {
        currentMaxNegativeWeight += (1 - nearlyOne);
        currentMaxNegativeWeight = -currentMaxNegativeWeight;
        for (int z = 0; z < size; z++) {
          Site s = array[z];
          double w = s.getWeight();
          w += currentMaxNegativeWeight;
          s.setWeight(w);
        }
      }
    }
    // drawCurrentState(false);
    voroDiagram();
    drawCurrentState(false);
    // drawCurrentState(false);
    /** compute the maximal error of the area. */
    currentMaxError = 0;
    array = sites.array;
    size = sites.size;
    for (int z = 0; z < size; z++) {
      Site site = array[z];
      PolygonSimple poly = site.getPolygon();
      double percentage = site.getPercentage();

      double wantedArea = completeArea * percentage;
      double currentArea = poly.getArea();
      double singleError = Math.abs(1 - (currentArea / wantedArea));
      if (singleError > currentMaxError) {
        currentMaxError = singleError;
      }
    }
    lastEuclidChange = currentEuclidChange / size;
    lastSumErrorChange = Math.abs(lastAreaError - currentAreaError);
    lastAreaError = currentAreaError;
    lastMaxError = currentMaxError;
    lastAVGError = currentAreaError / size;
    // long ende=System.currentTimeMillis();
    // System.out.println((ende-start));
  }