package layout; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; import javafx.beans.property.ReadOnlyMapWrapper; import javafx.beans.property.ReadOnlySetWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableMap; import javafx.collections.ObservableSet; import javafx.scene.Node; import javafx.scene.layout.Pane; import javafx.util.Pair; import physics.ForceField; import physics.Spring; import physics.Tether; public class PhysLayout { private final Pane root; private final ReadOnlySetWrapper<Node> nodes; private final ReadOnlyMapWrapper<Node, Double> masses; private final Set<ForceField> fields; private final Map<Pair<Node, Node>, Set<Spring>> connections; private final Map<Node, Set<Tether>> tethers; private final Map<Node, Set<Node>> neighbors; public PhysLayout(Pane root) { this.root = root; nodes = new ReadOnlySetWrapper<>(FXCollections.observableSet()); connections = new HashMap<>(); tethers = new HashMap<>(); neighbors = new HashMap<>(); masses = new ReadOnlyMapWrapper(FXCollections.observableMap(new HashMap<>())); fields = new HashSet<>(); } public Set<Spring> getConnections(Node a, Node b) { return connections.get(new Pair<>(a, b)); } /** * Set the mass of a node. If set to infinity, the node will be fixed in * place. * * @param a * @param m */ public void setMass(Node a, double m) { addNode(a); masses.put(a, m); } public double getMass(Node a) { return masses.getOrDefault(a, 1.0); } public ObservableMap<Node, Double> getMasses() { return masses.getReadOnlyProperty(); } public void addNode(Node a) { nodes.add(a); } public void removeNode(Node a) { nodes.remove(a); masses.remove(a); Set<Node> nA = neighbors.get(a); if (nA != null) { nA.stream().forEach((b) -> { clearConnections(a, b); }); } } public void addConnection(Node a, Node b, Spring... s) { addNode(a); addNode(b); Set<Spring> cAB = getConnections(a, b); Set<Spring> cBA = getConnections(b, a); if (cAB == null) { cAB = new HashSet<>(); connections.put(new Pair<>(a, b), cAB); } if (cBA == null) { cBA = new HashSet<>(); connections.put(new Pair<>(b, a), cBA); } cAB.addAll(Arrays.asList(s)); cBA.addAll(Arrays.asList(s).stream().map((x) -> { return x.reverse(); }).collect(Collectors.toList())); Set<Node> nA = neighbors.get(a); Set<Node> nB = neighbors.get(b); if (nA == null) { nA = new HashSet<>(); neighbors.put(a, nA); } if (nB == null) { nB = new HashSet<>(); neighbors.put(b, nB); } nA.add(b); nB.add(a); } public void removeConnection(Node a, Node b, Spring s) { Set<Spring> cAB = connections.get(new Pair<>(a, b)); Set<Spring> cBA = connections.get(new Pair<>(b, a)); if (cAB != null) { cAB.remove(s); if (cAB.isEmpty()) { clearConnections(a, b); } else { cBA.remove(s.reverse()); } } } public void addTether(Node node, Tether... tether) { addNode(node); Set<Tether> t = tethers.get(node); if (t == null) { t = new HashSet<>(); tethers.put(node, t); } t.addAll(Arrays.asList(tether)); } public void removeTether(Node node, Tether tether) { Set<Tether> t = tethers.get(node); if (t != null) { t.remove(tether); if (t.isEmpty()) { clearTethers(node); } } } public void clearConnections(Node a, Node b) { connections.remove(new Pair<>(a, b)); connections.remove(new Pair<>(b, a)); Set<Node> nA = neighbors.get(a); Set<Node> nB = neighbors.get(b); if (nA != null) { nA.remove(b); nB.remove(a); } } public void clearAllConnections() { connections.clear(); neighbors.clear(); } public void clearTethers(Node node) { tethers.remove(node); } public void clearAllTethers() { tethers.clear(); } public void clearAllMasses() { masses.clear(); } public Set<Node> getNeighbors(Node a) { return neighbors.get(a); } public ObservableSet<Node> getNodes() { return nodes.getReadOnlyProperty(); } public Set<Entry<Pair<Node, Node>, Set<Spring>>> getAllConnections() { return connections.entrySet(); } public Set<Entry<Node, Set<Tether>>> getAllTethers() { return tethers.entrySet(); } public void addField(ForceField... field) { fields.addAll(Arrays.asList(field)); } public void removeField(ForceField field) { fields.remove(field); } public Collection<ForceField> getFields() { return fields; } }