@Override
  public void generate(
      Vector<GraphNode> nodes,
      Vector<GraphEdge> edges,
      int nodesNum,
      int gridWidth,
      int gridHeight,
      int cellWidth,
      int minNodeWeight,
      int maxNodeWeight,
      int minEdgeCost,
      int maxEdgeCost,
      Random random,
      int randomWeight,
      int gradientWeight,
      int optimaWeight,
      int blurIterations,
      int optimaPercentage) {

    //		Random random = new Random(randomSeed);

    // prepare for generating only half the required nodes in one half of the grid
    int oldGridWidth = gridWidth;
    nodesNum /= 2;
    gridWidth /= 2;

    // get the maximal distance to the grid's center
    double maxDistance =
        Math.sqrt(Math.pow(gridWidth * cellWidth / 2, 2) + Math.pow(gridHeight * cellWidth / 2, 2));
    int centerX = (oldGridWidth + 1) * cellWidth / 2;
    int centerY = (gridHeight + 1) * cellWidth / 2;

    int i = 0;
    while (nodes.size() < nodesNum) {

      int gridX = random.nextInt(gridWidth + 1);
      int gridY = random.nextInt(gridHeight + 1);
      int x = gridX * cellWidth + cellWidth / 2; // + random.nextInt(10) - 5;
      int y = gridY * cellWidth + cellWidth / 2; // + random.nextInt(10) - 5;

      double distance = Math.sqrt(Math.pow(centerX - x, 2) + Math.pow(centerY - y, 2));

      double percentage = distance / (maxDistance / 100);

      double d = 1 - percentage / 100;

      double a = 1 + minNodeWeight + Math.ceil((maxNodeWeight - minNodeWeight - 1) * d);

      int b = minNodeWeight + random.nextInt(maxNodeWeight - minNodeWeight + 1);

      //			int weight = minNodeWeight + random.nextInt(maxNodeWeight - minNodeWeight);

      int weight = (int) (((100 - randomWeight) * a + randomWeight * b) / 100);
      if (weight > maxNodeWeight) {
        weight = maxNodeWeight;
      } else if (weight < minNodeWeight) {
        weight = minNodeWeight;
      }

      GraphNode n = new GraphNode(i, weight, gridX, gridY, x, y);

      if (!nodes.contains(n)) {
        nodes.add(n);
        i++;
      }
    }

    int c = 4;
    double limit =
        c
            * Math.sqrt(
                Math.pow(
                        ((double) (cellWidth * gridHeight)
                            / Math.sqrt(
                                ((double) gridHeight / (double) gridWidth) * (double) nodesNum)),
                        2)
                    + Math.pow(
                        ((double) (cellWidth * gridWidth)
                            / Math.sqrt(
                                ((double) gridWidth / (double) gridHeight) * (double) nodesNum)),
                        2));

    // create a full graph... the edges will be sorted by length
    Vector<GraphEdge> edgesFullSorted = new Vector<GraphEdge>();
    for (int a = 0; a < nodes.size(); a++) {

      for (int b = a + 1; b < nodes.size(); b++) {

        GraphNode n1 = nodes.elementAt(a);
        GraphNode n2 = nodes.elementAt(b);

        int weight = minEdgeCost + random.nextInt(maxEdgeCost - minEdgeCost);
        GraphEdge e = new GraphEdge(weight, n1, n2);
        // edgesFull.add(e);

        // only add if shorter than some value
        if (e.getLength() < limit) {
          edgesFullSorted.add(e);
        }
      }
    }

    Collections.sort(
        edgesFullSorted,
        new Comparator<GraphEdge>() {
          @Override
          public int compare(GraphEdge o1, GraphEdge o2) {
            if (o1.getLength() < o2.getLength()) {
              return 1;
            } else if (o1.getLength() > o2.getLength()) {
              return -1;
            } else {
              return 0;
            }
          }
        });

    // filter remaining edges
    Vector<GraphEdge> remove = new Vector<GraphEdge>();
    for (int a = 0; a < edgesFullSorted.size(); a++) {

      // should be sorted descending
      if (a + 1 != edgesFullSorted.size())
        assert edgesFullSorted.get(a).getLength() >= edgesFullSorted.get(a + 1).getLength();

      // if intersects remove edge
      for (int b = a + 1; b < edgesFullSorted.size(); b++) {

        // do not intersect adjacent edges
        GraphNode id1 = edgesFullSorted.get(a).node1;
        GraphNode id2 = edgesFullSorted.get(a).node2;
        GraphNode id3 = edgesFullSorted.get(b).node1;
        GraphNode id4 = edgesFullSorted.get(b).node2;

        if (id1.equals(id3)) {
          if (checkSameLine(id1, id2, id4)) {
            remove.add(edgesFullSorted.get(a));
            break; // stop looking for intersections
          } else {
            continue;
          }
        }
        if (id1.equals(id4)) {
          if (checkSameLine(id1, id2, id3)) {
            remove.add(edgesFullSorted.get(a));
            break; // stop looking for intersections
          } else {
            continue;
          }
        }
        if (id2.equals(id3)) {
          if (checkSameLine(id2, id1, id4)) {
            remove.add(edgesFullSorted.get(a));
            break; // stop looking for intersections
          } else {
            continue;
          }
        }
        if (id2.equals(id4)) {
          if (checkSameLine(id2, id1, id3)) {
            remove.add(edgesFullSorted.get(a));
            break; // stop looking for intersections
          } else {
            continue;
          }
        }

        // intersection algorithm stolen from:
        // http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
        double x1 = id1.x;
        double y1 = id1.y;
        double x2 = id2.x;
        double y2 = id2.y;
        double x3 = id3.x;
        double y3 = id3.y;
        double x4 = id4.x;
        double y4 = id4.y;

        double ua =
            ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3))
                / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
        double ub =
            ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3))
                / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));

        boolean intersect = ua > 0.0f && ua < 1.0f && ub > 0.0f && ub < 1.0f;

        if (intersect) {

          remove.add(edgesFullSorted.get(a));
          break; // stop looking for intersections
        }
      }
    }

    // remaining edges are the triangulation
    for (GraphEdge e : edgesFullSorted) {

      if (!remove.contains(e)) {
        edges.add(e);
      }
    }

    // mirror everything at one vertical axis
    Vector<GraphNode> mirroredNodes = new Vector<GraphNode>();
    // store every single node's counterpart to easily assign the edges later
    counterparts = new HashMap<GraphNode, GraphNode>();
    // store the nodes at the right edge so that they can be linked to their counterparts
    HashMap<Integer, GraphNode> boundaryNodes = new HashMap<Integer, GraphNode>();

    for (GraphNode n : nodes) {

      GraphNode mirroredNode =
          new GraphNode(
              i,
              n.weight,
              oldGridWidth - n.gridX,
              n.gridY,
              (oldGridWidth + 1) * cellWidth - n.x,
              n.y);
      if (!nodes.contains(mirroredNode)) {
        mirroredNodes.add(mirroredNode);
        counterparts.put(n, mirroredNode);
        i++;
        // update the boundary nodes
        if (!boundaryNodes.containsKey(n.gridY)) {
          boundaryNodes.put(n.gridY, n);
        } else {
          if (boundaryNodes.get(n.gridY).gridX < n.gridX) {
            boundaryNodes.put(n.gridY, n);
          }
        }
      } else {
        counterparts.put(n, n);
      }
    }

    nodes.addAll(mirroredNodes);

    Vector<GraphEdge> mirroredEdges = new Vector<GraphEdge>();
    for (GraphEdge e : edges) {

      GraphEdge newEdge =
          new GraphEdge(e.weight, counterparts.get(e.node1), counterparts.get(e.node2));
      mirroredEdges.add(newEdge);
    }
    edges.addAll(mirroredEdges);

    Vector<GraphEdge> connectingEdges = new Vector<GraphEdge>();
    // connect both halves
    for (GraphNode n : boundaryNodes.values()) {
      if (n != counterparts.get(n)) {
        int weight = minEdgeCost + random.nextInt(maxEdgeCost - minEdgeCost);
        GraphEdge newEdge = new GraphEdge(weight, n, counterparts.get(n));
        connectingEdges.add(newEdge);
      }
    }

    // check the new edges for possible intersections
    for (GraphEdge e1 : connectingEdges) {

      boolean add = true;

      for (GraphEdge e2 : edges) {

        GraphNode id1 = e1.node1;
        GraphNode id2 = e1.node2;
        GraphNode id3 = e2.node1;
        GraphNode id4 = e2.node2;

        double x1 = id1.x;
        double y1 = id1.y;
        double x2 = id2.x;
        double y2 = id2.y;
        double x3 = id3.x;
        double y3 = id3.y;
        double x4 = id4.x;
        double y4 = id4.y;

        double ua =
            ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3))
                / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
        double ub =
            ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3))
                / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));

        boolean intersect = ua > 0.0f && ua < 1.0f && ub > 0.0f && ub < 1.0f;

        if (intersect) {
          add = false;
          break;
        }
      }

      if (add) {
        edges.add(e1);
      }
    }

    // shuffle nodes to assign new "names"
    Collections.shuffle(nodes, random);
    int j = 0;
    for (GraphNode n : nodes) {
      n.name = GraphNode.NODE_NAME_PREFIX + j;
      j++;
    }
  }