*******************************************************************************/
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;
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;
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,
needsUpdate = true;
}
- void removeTransientRouteLines() {
+ protected void removeTransientRouteLines() {
for(RouteLine line : transientLines)
line.remove();
transientLines.clear();
line.addPoint(b);
line.terminal = a;
transientLines.add(line);
+
+ // Path terminal
+ a.line = line;
+ b.line = line;
+
break;
}
case SimpleConnectionUtility.ONE_BEND_HORIZONTAL_VERTICAL: {
line2.terminal = b;
transientLines.add(line1);
transientLines.add(line2);
+
+ // Path terminal
+ a.line = line1;
+ b.line = line2;
break;
}
case SimpleConnectionUtility.ONE_BEND_VERTICAL_HORIZONTAL: {
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:
break;
}
}
+ //routeFromTerminals(caseId==SimpleConnectionUtility.MORE_BENDS_BBS_INTERSECT);
}
else {
caseId = SimpleConnectionUtility.COMPLEX_CONNECTION;
}
}
- static class Interval {
+ public static class Interval {
public final double min;
public final double max;
public Interval(double min, double max) {
}
}
- class IntervalCache {
+ public class IntervalCache {
THashMap<RouteLine, Interval> cache =
new THashMap<RouteLine, Interval>();
public Interval get(RouteLine line) {
}
}
- private void routeFromTerminals(boolean boundingBoxesIntersect) {
+ protected void routeFromTerminals(boolean boundingBoxesIntersect) {
IntervalCache cache = new IntervalCache();
for(RouteTerminal terminal : terminals)
if(terminal.line != null) {
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) {
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);
*
* 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())
*
* 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>();
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) {
}
// 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);
}
}
// 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;
}
}
- private static void add(THashMap<RoutePoint, RouteLine> begins,
+ private static void add(Map<RoutePoint, RouteLine> begins,
RouteLine line) {
if(line.points.size() > 1) {
{
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();
}
public void reclaimTransientMemory() {
- removeTransientRouteLines();
- needsUpdate = true;
+ removeTransientRouteLines();
+ needsUpdate = true;
}
}