/** * 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(); }
/** * Reports the node nearest to the given screen coordinates but within the given radius. * * @param x x screen coordinate to query * @param y y screen coordinate to query * @param radius Radius within which to search for nodes. If negative, all nodes are searched. * @return Node nearest to the given screen coordinates or null if no nodes found within the given * radius of the coordinates. */ public N getNearest(float x, float y, float radius) { float mX = (x - width / 2) / centroid.getZ() + centroid.getX(); float mY = (y - height / 2) / centroid.getZ() + centroid.getY(); float nearestDSq = radius * radius; N nearestNode = null; for (Map.Entry<N, Particle> row : nodes.entrySet()) { N node = row.getKey(); Particle p = row.getValue(); float px = p.position().x(); float py = p.position().y(); float dSq = (px - mX) * (px - mX) + (py - mY) * (py - mY); if (dSq < nearestDSq) { nearestDSq = dSq; nearestNode = node; } } return nearestNode; }
/** Allows a node to be selected with the mouse. */ public void selectNearestWithMouse() { if (!zoomer.isMouseCaptured()) { float mX = (zoomer.getMouseCoord().x - (width / 2)) / centroid.getZ() + centroid.getX(); float mY = (zoomer.getMouseCoord().y - (height / 2)) / centroid.getZ() + centroid.getY(); if (selectedNode == null) { float nearestDSq = Float.MAX_VALUE; for (Map.Entry<N, Particle> row : nodes.entrySet()) { N node = row.getKey(); Particle p = row.getValue(); float px = p.position().x(); float py = p.position().y(); float dSq = (px - mX) * (px - mX) + (py - mY) * (py - mY); if (dSq < nearestDSq) { nearestDSq = dSq; selectedNode = node; } } } } }
/** Centres the particle view on the currently visible nodes. */ private void updateCentroid() { float xMax = Float.NEGATIVE_INFINITY, xMin = Float.POSITIVE_INFINITY, yMin = Float.POSITIVE_INFINITY, yMax = Float.NEGATIVE_INFINITY; for (int i = 0; i < physics.getNumParticles(); ++i) { Particle p = physics.getParticle(i); xMax = Math.max(xMax, p.position().x()); xMin = Math.min(xMin, p.position().x()); yMin = Math.min(yMin, p.position().y()); yMax = Math.max(yMax, p.position().y()); } float xRange = xMax - xMin; float yRange = yMax - yMin; if ((xRange == 0) && (yRange == 0)) { xRange = Math.max(1, xMax); yRange = Math.max(1, yMax); } float zScale = (float) Math.min(height / (yRange * 1.2), width / (xRange * 1.2)); centroid.setTarget(xMin + 0.5f * xRange, yMin + 0.5f * yRange, zScale); }
/** * Tethers the given node to its location with the given strength. * * @param node The node to be tethered. * @param strength Strength of the tether. * @return True if the viewer contains the given node and it was tethered successfully. */ public boolean tether(N node, float strength) { Particle p1 = nodes.get(node); if (p1 == null) { return false; } // Grab the tethering stake if it has already been created, otherwise create a new one. Particle stake = stakes.get(node); if (stake == null) { stake = physics.makeParticle(1, node.getLocation().x, node.getLocation().y, 0); stake.makeFixed(); stakes.put(node, stake); } // Grab the tether if it has already been created, otherwise create a new one. Spring tether = tethers.get(stake); if (tether == null) { tether = physics.makeSpring(stake, p1, strength, DAMPING, Float.MIN_VALUE); tethers.put(stake, tether); } else { tether.setStrength(strength); } return true; }