/**
   * Adds the given edge to those to be displayed in the viewer. Note that the edge must connect
   * nodes that have already been added to the viewer. This version will use the locations of the
   * two nodes to calculate their distance of separation.
   *
   * @param edge Edge to add to the display.
   * @return True if edge was added successfully. False if edge contains nodes that have not been
   *     added to the viewer.
   */
  public boolean addEdge(E edge) {
    Particle p1 = nodes.get(edge.getNode1());
    if (p1 == null) {
      System.err.println("Warning: Node1 not found when creating edge.");
      return false;
    }
    Particle p2 = nodes.get(edge.getNode2());
    if (p2 == null) {
      System.err.println("Warning: Node2 not found when creating edge.");
      return false;
    }

    // Only add edge if it does not already exist in the collection
    if (!edges.containsKey(edge)) {
      float x1 = p1.position().x();
      float y1 = p1.position().y();
      float x2 = p2.position().x();
      float y2 = p2.position().y();
      // Strength, damping, reset length
      edges.put(
          edge,
          physics.makeSpring(
              p1,
              p2,
              EDGE_STRENGTH,
              DAMPING,
              (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))));
    }
    return true;
  }
  /**
   * Updates the particle view. This should be called on each draw cycle in order to update the
   * positions of all nodes and edges in the viewer. If you need to update the positions of
   * particles without drawing it (e.g. to speed up movement, call updateParticles() instead.
   */
  public void draw() {
    parent.pushStyle();
    parent.pushMatrix();
    zoomer.transform();
    updateCentroid();
    centroid.tick();

    parent.translate(width / 2, height / 2);
    parent.scale(centroid.getZ());
    parent.translate(-centroid.getX(), -centroid.getY());

    if (!isPaused) {
      updateParticles();
    }

    // Ensure that any selected element is positioned at the mouse location.
    if (selectedNode != null) {
      Particle p = nodes.get(selectedNode);
      p.makeFixed();
      float mX = (zoomer.getMouseCoord().x - (width / 2)) / centroid.getZ() + centroid.getX();
      float mY = (zoomer.getMouseCoord().y - (height / 2)) / centroid.getZ() + centroid.getY();
      p.position().set(mX, mY, 0);
    }

    // Draw edges if we have positive stroke weight.
    if (parent.g.strokeWeight > 0) {
      parent.stroke(0, 180);
      parent.noFill();

      for (Map.Entry<E, Spring> row : edges.entrySet()) {
        E edge = row.getKey();
        Spring spring = row.getValue();
        Vector3D p1 = spring.getOneEnd().position();
        Vector3D p2 = spring.getTheOtherEnd().position();
        edge.draw(parent, p1.x(), p1.y(), p2.x(), p2.y());
      }
    }

    // Draw nodes.
    parent.noStroke();
    parent.fill(120, 50, 50, 180);

    for (Map.Entry<N, Particle> row : nodes.entrySet()) {
      N node = row.getKey();
      Vector3D p = row.getValue().position();
      node.draw(parent, p.x(), p.y());
    }

    parent.popMatrix();
    parent.popStyle();
  }
  /**
   * Adds the given edge to those to be displayed in the viewer. Note that the edge must connect
   * nodes that have already been added to the viewer. This version will fix the distance of
   * separation between nodes to the given value
   *
   * @param edge Edge to add to the display.
   * @return True if edge was added successfully. False if edge contains nodes that have not been
   *     added to the viewer.
   */
  public boolean addEdge(E edge, float distance) {
    Particle p1 = nodes.get(edge.getNode1());
    if (p1 == null) {
      System.err.println("Warning: Node1 not found when creating edge.");
      return false;
    }
    Particle p2 = nodes.get(edge.getNode2());
    if (p2 == null) {
      System.err.println("Warning: Node2 not found when creating edge.");
      return false;
    }

    // Only add edge if it does not already exist in the collection
    if (!edges.containsKey(edge)) {
      // Strength, damping, reset length
      edges.put(edge, physics.makeSpring(p1, p2, EDGE_STRENGTH, DAMPING, distance));
    }
    return true;
  }