X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.diagram.connection%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fconnection%2FRouteGraph.java;h=6357e537ac5c5d356b89803c25ad2ea3166c8706;hp=1905cfe2b2c172969e2883a5659a0d807a106761;hb=452d45fd64c45cbfadc15e8edc11c0cda4e101e0;hpb=0ae2b770234dfc3cbb18bd38f324125cf0faca07 diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteGraph.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteGraph.java index 1905cfe2b..6357e537a 100644 --- a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteGraph.java +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteGraph.java @@ -11,26 +11,31 @@ *******************************************************************************/ 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 lines = new ArrayList(4); - ArrayList terminals = new ArrayList(4); - ArrayList transientLines = new ArrayList(4); - int caseId; - boolean isSimpleConnection; - boolean needsUpdate = false; + protected ArrayList lines = new ArrayList(4); + protected ArrayList terminals = new ArrayList(4); + protected ArrayList transientLines = new ArrayList(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 { * * @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 + * null 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 cache = new THashMap(); 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 removedLines = new THashSet(); @@ -1328,6 +1371,18 @@ public class RouteGraph implements Serializable { path.moveTo(x, y); } + private static final Comparator 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 begins = - new THashMap(); + Map begins = new HashMap(); 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 begins, + private void drawContinuousPath(Path2D path, Map begins, RoutePoint cur, RouteLine curLine) { if(curLine == null) return; @@ -1415,7 +1471,7 @@ public class RouteGraph implements Serializable { } } - private static void add(THashMap begins, + private static void add(Map 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; } }