/** * */ package automenta.spacenet.space.geom.graph.arrange.forcedirect; import automenta.spacenet.space.Repeat; import automenta.spacenet.space.Space; import automenta.spacenet.space.geom.Box; import automenta.spacenet.space.geom.graph.GraphBoxModel; import automenta.spacenet.space.geom.graph.GraphBox; import automenta.spacenet.var.scalar.DoubleVar; import automenta.spacenet.var.Maths; import automenta.spacenet.var.vector.Quat; import automenta.spacenet.var.graph.MemGraph; import com.ardor3d.math.Vector3; import com.ardor3d.scenegraph.Spatial; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; public class ForceDirecting<V, E> extends Repeat implements GraphBoxModel<V, E> { private static final Logger logger = Logger.getLogger(ForceDirecting.class.getName()); private final ForceDirectedParameters params; private final int substeps; public static class ForceDirectedParameters { // private final static double DEFAULT_UPDATE_PERIOD = 0.01; // private final static double DEFAULT_INTERPOLATION_PERIOD = 0.005; // private final static double DEFAULT_INTERPOLATION_SPEED = 0.2; private DoubleVar stiffness; private DoubleVar repulsion; private DoubleVar lengthFactor; private final Vector3 boundsMax; public ForceDirectedParameters(Vector3 boundsMax, double initialStiffness, double initialRepulsion, double initialLengthFactor) { super(); this.boundsMax = boundsMax; this.stiffness = new DoubleVar(initialStiffness); this.repulsion = new DoubleVar(initialRepulsion); this.lengthFactor = new DoubleVar(initialLengthFactor); } public Vector3 getBoundsMax() { return boundsMax; } public DoubleVar getLengthFactor() { return lengthFactor; } public DoubleVar getRepulsion() { return repulsion; } public DoubleVar getStiffness() { return stiffness; } } private double updatePeriod; protected Map<V, Box> nodeVis = new HashMap(); protected Map<E, Space> linkVis = new HashMap(); private final double interpolationSpeed; private DoubleVar timeScale = new DoubleVar(1.0); private DoubleVar maxSpeed = new DoubleVar(0.1); private Map<Box, Vector3> nextPosition = new HashMap(); private Map<Box, Vector3> nextSize = new HashMap(); private Repeat calcRepeat; private Repeat interpRepeat; private GraphBox<V, E> graphBox; public ForceDirecting(ForceDirectedParameters params, double updatePeriod, int interpolationSubSteps, double interpolationSpeed) { super(); this.params = params; this.updatePeriod = updatePeriod; this.substeps = interpolationSubSteps; this.interpolationSpeed = interpolationSpeed; } public ForceDirectedParameters getParams() { return params; } public Box getBox(V v) { return nodeVis.get(v); } public Vector3 getBoundsMax() { return getParams().getBoundsMax(); } // public Node getRandomNode() { // List<Node> nodes = new LinkedList(nodeVis.keySet()); // if (nodes.size() > 0) { // return nodes.get((int) Maths.random(0, nodes.size())); // } // return null; // } @Override public void addedNode(V v, Box nb) { double wx = getBoundsMax().getX() / 2.0; double wy = getBoundsMax().getY() / 2.0; double wz = getBoundsMax().getY() / 2.0; double sx = 0.01; double sy = 0.01; double sz = 0.01; nb.move(Maths.random(-wx, wx), Maths.random(-wy, wy), Maths.random(-wz, wz)); nodeVis.put(v, nb); } @Override public void removedNode(V v) { Box box = getBox(v); Space vis = nodeVis.get(v); nodeVis.remove(v); if (box != null) { nextPosition.remove(box); nextSize.remove(box); } } @Override public void addedEdge(E e, Space s, Box from, Box to) { linkVis.put(e, s); } @Override public void removedEdge(E e) { linkVis.remove(e); } public MemGraph<V, E> getGraph() { return graphBox.getGraph(); } public GraphBox<V, E> getGraphBox() { return graphBox; } @Override public void start(GraphBox<V, E> graphBox) { this.graphBox = graphBox; // calcRepeat = graphBox.add(new Repeat(updatePeriod) { // @Override protected void update(double t, double dt, Spatial s) { // forward(dt); // } // }); // interpRepeat = graphBox.add(new Repeat(interpolationPeriod) { // @Override public void update(double t, double dt, Spatial s) { // interpolate(dt); // } // }); } @Override public void stop() { graphBox.remove(calcRepeat); graphBox.remove(interpRepeat); } public DoubleVar getStiffness() { return getParams().getStiffness(); } public DoubleVar getRepulsion() { return getParams().getRepulsion(); } Vector3 force = new Vector3(); protected Vector3 getNextPosition(Box b) { Vector3 v = nextPosition.get(b); if (v == null) { v = new Vector3(); nextPosition.put(b, v); } return v; } public Vector3 getNextSize(Box b) { Vector3 v = nextSize.get(b); if (v == null) { v = new Vector3(1, 1, 1); nextSize.put(b, v); } return v; } int step = 0; @Override protected void update(double t, double dt, Spatial parent) { if (step % substeps == 0) { forward(dt * substeps); } interpolate(dt); step++; } // public void forward(double dt, int steps) { // for (int i = 0; i < steps; i++) { // double ddt = dt / ((double)steps); // forward(ddt); // interpolate(ddt); // } // } protected synchronized void forward(double dt) { double wx = getBoundsMax().getX() / 2.0; double wy = getBoundsMax().getY() / 2.0; double wz = getBoundsMax().getZ() / 2.0; synchronized (nodeVis) { synchronized (linkVis) { for (V n : nodeVis.keySet()) { Box nBox = getBox(n); updateNode(n, nBox, getNextSize(nBox)); updateOrientation(n, nBox, nBox.getOrientation()); } for (E l : linkVis.keySet()) { Space s = linkVis.get(l); updateEdge(l, s); } for (E l : linkVis.keySet()) { double stiffness = getStiffness(l); //getStiffness().d(); //Line3D line = linkVis.get(l); //Pair endPoints = getGraph().getEndpoints(l); List<V> iv = getGraph().getIncidentVertices(l); if (iv == null) { continue; } V a = (V) iv.get(0); V b = (V) iv.get(1); if (a == null) { continue; } if (b == null) { continue; } Box aBox = getBox(a); double aRad = (aBox.getSize().getMaxComponent() + aBox.getSize().getMinComponent()) / 2.0; //TODO use getAvgRadius Box bBox = getBox(b); double bRad = (bBox.getSize().getMaxComponent() + bBox.getSize().getMinComponent()) / 2.0;//TODO use getAvgRadius //line.getRadius().set(getLineRadius(aRad, bRad)); double naturalLength = getLengthFactor().d() * (aRad + bRad) / 4.0; double currentLength = aBox.getPosition().distance(bBox.getPosition()); double f = stiffness * (currentLength - naturalLength); double sx = f * (bBox.getPosition().getX() - aBox.getPosition().getX()); double sy = f * (bBox.getPosition().getY() - aBox.getPosition().getY()); double sz = f * (bBox.getPosition().getZ() - aBox.getPosition().getZ()); getNextPosition(aBox).addLocal(sx / 2.0, sy / 2.0, sz / 2.0); getNextPosition(bBox).addLocal(-sx / 2.0, -sy / 2.0, -sz / 2.0); } for (V n : nodeVis.keySet()) { Box nBox = getBox(n); double nMass = getMass(nBox);//nBox.getSize().getMaxRadius(); force.set(0, 0, 0); for (V m : nodeVis.keySet()) { if (n == m) { continue; } double repulsion = getRepulsion(n, m); //getRepulsion().d(); Box mBox = nodeVis.get(m); double mMass = getMass(mBox); //mBox.getSize().getMaxRadius(); double dist = nBox.getPosition().distance(mBox.getPosition()); double f = -repulsion * (nMass * mMass) / (dist * dist); double sx = f * (mBox.getPosition().getX() - nBox.getPosition().getX()); double sy = f * (mBox.getPosition().getY() - nBox.getPosition().getY()); double sz = f * (mBox.getPosition().getZ() - nBox.getPosition().getZ()); force.addLocal(sx, sy, sz); } force.multiplyLocal(dt * getTimeScale().d()); if (force.length() > getMaxSpeed().d()) { force.normalizeLocal().multiplyLocal(getMaxSpeed().d()); } Vector3 p = getNextPosition(nBox); //getPosition(); Vector3 s = nBox.getSize(); double nx = p.getX() + force.getX(); double ny = p.getY() + force.getY(); double nz = p.getZ() + force.getZ(); nx = Math.min(nx, wx - s.getX() / 2.0); nx = Math.max(nx, -wx + s.getX() / 2.0); ny = Math.min(ny, wy - s.getY() / 2.0); ny = Math.max(ny, -wy + s.getY() / 2.0); nz = Math.min(nz, wz - s.getZ() / 2.0); nz = Math.max(nz, -wz + s.getZ() / 2.0); p.set(nx, ny, nz); } } } } protected void interpolate(double dt) { synchronized (nodeVis) { for (V n : nodeVis.keySet()) { Box b = getBox(n); Vector3 currentPosition = b.getPosition(); Vector3 currentSize = b.getSize(); double p = interpolationSpeed; double np = 1.0 - p; Vector3 nextPosition = getNextPosition(b); Vector3 nextSize = getNextSize(b); double px = np * currentPosition.getX() + p * nextPosition.getX(); double py = np * currentPosition.getY() + p * nextPosition.getY(); double pz = np * currentPosition.getZ() + p * nextPosition.getZ(); double sx = np * currentSize.getX() + p * nextSize.getX(); double sy = np * currentSize.getY() + p * nextSize.getY(); double sz = np * currentSize.getZ() + p * nextSize.getZ(); currentPosition.set(px, py, pz); currentSize.set(sx, sy, sz); } if (isCentering()) { centerNodes(); } } } public DoubleVar getTimeScale() { return timeScale; } private Double getLineRadius(double rad, double rad2) { return ((rad + rad2) / 2.0) / 20.0; } private DoubleVar getMaxSpeed() { return maxSpeed; } public DoubleVar getLengthFactor() { return getParams().getLengthFactor(); } protected void updateNode(V n, Box nBox, Vector3 nextSize) { return; } protected void updateOrientation(V n, Box nBox, Quat orientation) { return; } protected double getStiffness(E l) { return getStiffness().d(); } /** TODO rename to getGravity and invert values */ protected double getRepulsion(V n, V m) { return getRepulsion().d(); } protected double getMass(Box nBox) { return 1.0; } protected void updateEdge(E e, Space s) { } public boolean isCentering() { return true; } protected void centerNodes() { Vector3 center = new Vector3(); int numBoxes = 0; for (Box b : nodeVis.values()) { center.addLocal(getNextPosition(b)); numBoxes++; } center.multiplyLocal(1.0 / ((double) numBoxes)); for (Box b : nodeVis.values()) { Vector3 nextPosition = getNextPosition(b); nextPosition.subtractLocal(center); } } }