]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteGraph.java
Don't create degenerated transient RouteLines to RouteGraph
[simantics/platform.git] / bundles / org.simantics.diagram.connection / src / org / simantics / diagram / connection / RouteGraph.java
index 1905cfe2b2c172969e2883a5659a0d807a106761..6357e537ac5c5d356b89803c25ad2ea3166c8706 100644 (file)
  *******************************************************************************/
 package org.simantics.diagram.connection;
 
-import gnu.trove.list.array.TDoubleArrayList;
-import gnu.trove.map.hash.THashMap;
-import gnu.trove.map.hash.TObjectIntHashMap;
-import gnu.trove.set.hash.THashSet;
-
 import java.awt.geom.Line2D;
 import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.io.PrintStream;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
 import org.simantics.diagram.connection.segments.Segment;
 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
 
+import gnu.trove.list.array.TDoubleArrayList;
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.map.hash.TObjectIntHashMap;
+import gnu.trove.set.hash.THashSet;
+
 public class RouteGraph implements Serializable {
 
     private static final long serialVersionUID = 2004022454972623908L;
@@ -38,12 +43,19 @@ public class RouteGraph implements Serializable {
     public static final boolean RETURN_UNMODIFIABLE_COLLECTIONS = false;
     public static final boolean CHECK_PARAMERS = true;
 
-    ArrayList<RouteLine> lines = new ArrayList<RouteLine>(4);
-    ArrayList<RouteTerminal> terminals = new ArrayList<RouteTerminal>(4);
-    ArrayList<RouteLine> transientLines = new ArrayList<RouteLine>(4);
-    int caseId;
-    boolean isSimpleConnection;
-    boolean needsUpdate = false;
+    protected ArrayList<RouteLine> lines = new ArrayList<RouteLine>(4);
+    protected ArrayList<RouteTerminal> terminals = new ArrayList<RouteTerminal>(4);
+    protected ArrayList<RouteLine> transientLines = new ArrayList<RouteLine>(4);
+    protected int caseId;
+    protected boolean isSimpleConnection;
+    protected boolean needsUpdate = false;
+
+    public void updateTerminals() {
+        boolean changed = false;
+        for(RouteTerminal terminal : terminals)
+            changed |= terminal.updateDynamicPosition();
+        if(changed) update();
+    }
 
     /**
      * Adds a route line to the graph.
@@ -78,21 +90,31 @@ public class RouteGraph implements Serializable {
      * </pre>
      * @param style Tells what kind of line end is drawn 
      *        to the connection.
+     * @param position a provider for a dynamic position for the terminal or
+     *        <code>null</code> if terminal position is not dynamic
      * @return The new terminal.
      */
+    public RouteTerminal addTerminal(double x, double y, 
+            double minX, double minY,
+            double maxX, double maxY, 
+            int allowedDirections,
+            ILineEndStyle style, RouteTerminalPosition position) {
+        return addTerminal(x, y, minX, minY, maxX, maxY, allowedDirections, style, null, position);
+    }
+
     public RouteTerminal addTerminal(double x, double y, 
             double minX, double minY,
             double maxX, double maxY, 
             int allowedDirections,
             ILineEndStyle style) {
-       return addTerminal(x, y, minX, minY, maxX, maxY, allowedDirections, style, null);
-       
+        return addTerminal(x, y, minX, minY, maxX, maxY, allowedDirections, style, null, null);
     }
+
     public RouteTerminal addTerminal(double x, double y, 
             double minX, double minY,
             double maxX, double maxY, 
             int allowedDirections,
-            ILineEndStyle style, ILineEndStyle dynamicStyle) {
+            ILineEndStyle style, ILineEndStyle dynamicStyle, RouteTerminalPosition position) {
         if(CHECK_PARAMERS) {
             if(allowedDirections > 0x1f)
                 throw new IllegalArgumentException("Illegal allowedDirection flags.");
@@ -102,7 +124,7 @@ public class RouteGraph implements Serializable {
         if(style == null)
             style = PlainLineEndStyle.INSTANCE;
         RouteTerminal terminal = new RouteTerminal(x, y, minX, minY,
-                maxX, maxY, allowedDirections, false, style); 
+                maxX, maxY, allowedDirections, false, style, position); 
         terminal.setDynamicStyle(dynamicStyle);
         terminals.add(terminal);
         return terminal;
@@ -112,7 +134,7 @@ public class RouteGraph implements Serializable {
             double minX, double minY,
             double maxX, double maxY,
             ILineEndStyle style) {
-       return addBigTerminal(minX, minY, maxX, maxY, style, null);
+        return addBigTerminal(minX, minY, maxX, maxY, style, null);
     }
     public RouteTerminal addBigTerminal( 
             double minX, double minY,
@@ -124,7 +146,7 @@ public class RouteGraph implements Serializable {
                 0.5*(minX+maxX), 0.5*(minY+maxY),
                 minX, minY,
                 maxX, maxY, 
-                0xf, true, style); 
+                0xf, true, style, null); 
         terminal.setDynamicStyle(dynamicStyle);
         
         terminals.add(terminal);
@@ -142,7 +164,7 @@ public class RouteGraph implements Serializable {
             ILineEndStyle style) {
         return addTerminal(x, y, 
                 bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(), 
-                allowedDirections, style);
+                allowedDirections, style, null);
     }
 
     /**
@@ -152,7 +174,7 @@ public class RouteGraph implements Serializable {
         RouteTerminal newTerminal = addTerminal(terminal.x, terminal.y,
                 terminal.getMinX(), terminal.getMinY(),
                 terminal.getMaxX(), terminal.getMaxY(),
-                terminal.getAllowedDirections(), terminal.getStyle(), terminal.getDynamicStyle());
+                terminal.getAllowedDirections(), terminal.getStyle(), terminal.getDynamicStyle(), terminal.getDynamicPosition());
         newTerminal.setData(terminal.getData());
         return terminal;
     }
@@ -166,7 +188,7 @@ public class RouteGraph implements Serializable {
             double maxX, double maxY, 
             int allowedDirections) {
         return addTerminal(x, y, minX, minY, maxX, maxY, allowedDirections, 
-                PlainLineEndStyle.INSTANCE);
+                PlainLineEndStyle.INSTANCE, null);
     }
     
     /**
@@ -249,12 +271,18 @@ public class RouteGraph implements Serializable {
         needsUpdate = true;
     }
     
-    void removeTransientRouteLines() {
+    protected void removeTransientRouteLines() {
         for(RouteLine line : transientLines)
             line.remove();
         transientLines.clear();
     }
     
+    protected void removeRouteTerminalsFromRouteLines() {
+        for(RouteLine line : lines) {
+            line.removeRouteTerminals();
+        }
+    }
+
     /**
      * Rotates given terminal clockwise by given amount
      * (also negative numbers are allowed). 
@@ -289,6 +317,7 @@ public class RouteGraph implements Serializable {
     public void update() {
         needsUpdate = false;
         removeTransientRouteLines();
+        removeRouteTerminalsFromRouteLines();
 
         //print();
         
@@ -315,6 +344,11 @@ public class RouteGraph implements Serializable {
                 line.addPoint(b);
                 line.terminal = a;
                 transientLines.add(line);
+                
+                // Path terminal
+                a.line = line;
+                b.line = line;
+                
                 break;
             }
             case SimpleConnectionUtility.ONE_BEND_HORIZONTAL_VERTICAL: {
@@ -327,6 +361,10 @@ public class RouteGraph implements Serializable {
                 line2.terminal = b;
                 transientLines.add(line1);
                 transientLines.add(line2);
+               
+                // Path terminal
+                a.line = line1;
+                b.line = line2;
                 break;
             }
             case SimpleConnectionUtility.ONE_BEND_VERTICAL_HORIZONTAL: {
@@ -339,6 +377,10 @@ public class RouteGraph implements Serializable {
                 line2.terminal = b;
                 transientLines.add(line1);
                 transientLines.add(line2);
+                
+                //Path terminal
+                a.line = line1;
+                b.line = line2;
                 break;
             }
             case SimpleConnectionUtility.MORE_BENDS_BBS_DONT_INTERSECT: 
@@ -354,6 +396,7 @@ public class RouteGraph implements Serializable {
                 break;
             }
             }
+            //routeFromTerminals(caseId==SimpleConnectionUtility.MORE_BENDS_BBS_INTERSECT);
         }
         else {
             caseId = SimpleConnectionUtility.COMPLEX_CONNECTION;
@@ -375,7 +418,7 @@ public class RouteGraph implements Serializable {
         }
     }
     
-    static class Interval {
+    public static class Interval {
         public final double min;
         public final double max;
         public Interval(double min, double max) {
@@ -384,7 +427,7 @@ public class RouteGraph implements Serializable {
         }
     }
     
-    class IntervalCache {
+    public class IntervalCache {
         THashMap<RouteLine, Interval> cache = 
                 new THashMap<RouteLine, Interval>();
         public Interval get(RouteLine line) {
@@ -436,7 +479,7 @@ public class RouteGraph implements Serializable {
         }
     } 
     
-    private void routeFromTerminals(boolean boundingBoxesIntersect) {
+    protected void routeFromTerminals(boolean boundingBoxesIntersect) {
         IntervalCache cache = new IntervalCache();
         for(RouteTerminal terminal : terminals) 
             if(terminal.line != null) {
@@ -492,9 +535,9 @@ public class RouteGraph implements Serializable {
         for(RouteTerminal terminal : terminals)
             if((terminal.getAllowedDirections()&0x10) != 0) {     
                 try {
-                       RouteLine line = terminal.getLine();
-                       if(line == null)
-                               continue;
+                    RouteLine line = terminal.getLine();
+                    if(line == null)
+                        continue;
                     RoutePoint b = line.getBegin();
                     double distSq = Line2D.ptSegDistSq(terminal.x, terminal.y, b.x, b.y, x, y); 
                     if(distSq <= toleranceSq) {
@@ -625,7 +668,7 @@ public class RouteGraph implements Serializable {
         if((mask & PICK_POINTS) != 0) {
             Object point = pickPoint(x, y, tolerance, mask);
             if(point != null)
-               return point;
+                return point;
         }
         /*if((mask & PICK_DIRECT_LINES) != 0) {
             RouteTerminal terminal = pickDirectLine(x, y, tolerance);
@@ -900,12 +943,12 @@ public class RouteGraph implements Serializable {
              * 
              * Update 14.2.2014: With Sulca, there is a constant issue when line.terminal == null but point is RouteTerminal. 
              */
-               if (point instanceof RouteLink) {
-                   RouteLine l = ((RouteLink)point).getOther(line);
-                   l.points.remove(point);
-                   if(!l.isTransient())
-                       nLines.add(l);
-               }
+            if (point instanceof RouteLink) {
+                RouteLine l = ((RouteLink)point).getOther(line);
+                l.points.remove(point);
+                if(!l.isTransient())
+                    nLines.add(l);
+            }
         }
         
         if(nLines.isEmpty())
@@ -919,10 +962,10 @@ public class RouteGraph implements Serializable {
                  * 
                  * Update 16.10.2014: With Sulca, there is a constant issue when line.terminal == null but point is RouteTerminal. 
                  */
-               if (point instanceof RouteLink) {
-                       RouteLink link = (RouteLink)point;
-                       link.replace(l, merged);
-               }
+                if (point instanceof RouteLink) {
+                    RouteLink link = (RouteLink)point;
+                    link.replace(l, merged);
+                }
             }
         }
         THashSet<RouteLine> removedLines = new THashSet<RouteLine>();
@@ -1328,6 +1371,18 @@ public class RouteGraph implements Serializable {
         path.moveTo(x, y);
     }
     
+    private static final Comparator<RoutePoint> RG_COMP = (o1, o2) -> {
+        if (o1.getX() < o2.getX())
+            return -1;
+        else if (o1.getX() > o2.getX())
+            return 1;
+        if (o1.getY() < o2.getY())
+            return -1;
+        else if (o1.getY() > o2.getY())
+            return 1;
+        return 0;
+    };
+    
     private static void addPathEnd(Path2D path, RoutePoint cur, RouteLine line) {
         double x = cur.x, y = cur.y;
         if(cur instanceof RouteTerminal) {
@@ -1365,8 +1420,7 @@ public class RouteGraph implements Serializable {
         }
 
         // Analyze graph
-        THashMap<RoutePoint, RouteLine> begins = 
-                new THashMap<RoutePoint, RouteLine>();
+        Map<RoutePoint, RouteLine> begins = new HashMap<RoutePoint, RouteLine>();
         for(RouteLine line : lines) {
             add(begins, line);
         }
@@ -1380,13 +1434,15 @@ public class RouteGraph implements Serializable {
             }
 
         // Create paths
-        for(RoutePoint begin : begins.keySet().toArray(new RoutePoint[begins.size()])) {
+        // Sort the begins so that the order of segments is deterministic. This is required when comparing e.g. SVG diagrams. 
+        // In case of overlapping begins the order may vary.
+        for(RoutePoint begin : begins.keySet().stream().sorted(RG_COMP).collect(Collectors.toList())) {
             RouteLine curLine = begins.remove(begin);
             drawContinuousPath(path, begins, begin, curLine);
         }
     }
     
-    private void drawContinuousPath(Path2D path, THashMap<RoutePoint, RouteLine> begins,
+    private void drawContinuousPath(Path2D path, Map<RoutePoint, RouteLine> begins,
             RoutePoint cur, RouteLine curLine) {
         if(curLine == null)
             return;
@@ -1415,7 +1471,7 @@ public class RouteGraph implements Serializable {
         }
     }
 
-    private static void add(THashMap<RoutePoint, RouteLine> begins,
+    private static void add(Map<RoutePoint, RouteLine> begins,
             RouteLine line) { 
         if(line.points.size() > 1) {
             {
@@ -1441,6 +1497,46 @@ public class RouteGraph implements Serializable {
             routeLine.collectSegments(segments);
         return segments;
     }
+    
+    public Segment findNearestSegment(double x, double y) {
+        Segment nearest = null;
+        double minDistanceSq = Double.MAX_VALUE;
+
+        for (Segment segment : getSegments()) {
+            RoutePoint p1 = segment.p1;
+            RoutePoint p2 = segment.p2;
+
+            double distanceSq = Line2D.ptSegDistSq(p1.x, p1.y, p2.x, p2.y, x, y);
+            if (distanceSq < minDistanceSq) {
+                minDistanceSq = distanceSq;
+                nearest = segment;
+            }
+        }
+        return nearest;
+    }
+
+    public Point2D findNearestPoint(double x, double y) {
+        Segment nearest = findNearestSegment(x, y);
+        if (nearest == null) return null;
+
+        RoutePoint p1 = nearest.p1;
+        RoutePoint p2 = nearest.p2;
+
+        double d = Math.pow(p2.x - p1.x, 2.0) + Math.pow(p2.y - p1.y, 2.0);
+
+        if (d == 0) {
+            return new Point2D.Double(p1.x, p1.y);
+        } else {
+            double u = ((x - p1.x) * (p2.x - p1.x) + (y - p1.y) * (p2.y - p1.y)) / d;
+            if (u > 1.0) {
+                return new Point2D.Double(p2.x, p2.y);
+            } else if (u <= 0.0) {
+                return new Point2D.Double(p1.x, p1.y);
+            } else {
+                return new Point2D.Double(p2.x * u + p1.x * (1.0-u), (p2.y * u + p1.y * (1.0- u)));
+            }
+        }
+    }
 
     public Path2D getPath2D() {
         Path2D result = new Path2D.Double();
@@ -1541,8 +1637,8 @@ public class RouteGraph implements Serializable {
     }
     
     public void reclaimTransientMemory() {
-       removeTransientRouteLines();
-       needsUpdate = true;
+        removeTransientRouteLines();
+        needsUpdate = true;
     }
 
 }