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;fp=bundles%2Forg.simantics.diagram.connection%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fconnection%2FRouteGraph.java;h=e72c42fbceafbaf7f29e3d08387ad09981fb9813;hp=0000000000000000000000000000000000000000;hb=969bd23cab98a79ca9101af33334000879fb60c5;hpb=866dba5cd5a3929bbeae85991796acb212338a08 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 new file mode 100644 index 000000000..e72c42fbc --- /dev/null +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteGraph.java @@ -0,0 +1,1548 @@ +/******************************************************************************* + * Copyright (c) 2011 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +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.Rectangle2D; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +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; + +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 lines = new ArrayList(4); + ArrayList terminals = new ArrayList(4); + ArrayList transientLines = new ArrayList(4); + int caseId; + boolean isSimpleConnection; + boolean needsUpdate = false; + + /** + * Adds a route line to the graph. + * @param isHorizontal true, if the line is horizontal + * @param position y coordinate of the line if horizontal, x coordinate if vertical + * @return The new line. + */ + public RouteLine addLine(boolean isHorizontal, double position) { + RouteLine line = new RouteLine(isHorizontal, position); + lines.add(line); + return line; + } + + /** + * Adds a route terminal to the graph. + * @param x The location of the terminal. + * @param y + * @param minX The avoidance area of the terminal. + * @param minY + * @param maxX + * @param maxY + * @param allowedDirections Allowed directions. + * First four bits tell with which directions + * a connection can leave the terminal. The + * Fifth bit indicates whether direct lines + * can be drawn to the terminal. Directions are + *
+     * 0 right (1,0)
+     * 1 down  (0,1)
+     * 2 left  (-1,0)
+     * 3 up    (0,-1)
+     * 
+ * @param style Tells what kind of line end is drawn + * to the connection. + * @return The new terminal. + */ + 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); + + } + public RouteTerminal addTerminal(double x, double y, + double minX, double minY, + double maxX, double maxY, + int allowedDirections, + ILineEndStyle style, ILineEndStyle dynamicStyle) { + if(CHECK_PARAMERS) { + if(allowedDirections > 0x1f) + throw new IllegalArgumentException("Illegal allowedDirection flags."); + if(minX > x || x > maxX || minY > y || y > maxY) + throw new IllegalArgumentException("Illegal position attributes for a terminal, (" + x + ", " + y + ") is outside of (" + minX + ", " + minY + ")x(" + maxX + ", " + maxY + ")."); + } + if(style == null) + style = PlainLineEndStyle.INSTANCE; + RouteTerminal terminal = new RouteTerminal(x, y, minX, minY, + maxX, maxY, allowedDirections, false, style); + terminal.setDynamicStyle(dynamicStyle); + terminals.add(terminal); + return terminal; + } + + public RouteTerminal addBigTerminal( + double minX, double minY, + double maxX, double maxY, + ILineEndStyle style) { + return addBigTerminal(minX, minY, maxX, maxY, style, null); + } + public RouteTerminal addBigTerminal( + double minX, double minY, + double maxX, double maxY, + ILineEndStyle style, ILineEndStyle dynamicStyle) { + if(style == null) + style = PlainLineEndStyle.INSTANCE; + RouteTerminal terminal = new RouteTerminal( + 0.5*(minX+maxX), 0.5*(minY+maxY), + minX, minY, + maxX, maxY, + 0xf, true, style); + terminal.setDynamicStyle(dynamicStyle); + + terminals.add(terminal); + return terminal; + } + + /** + * Adds a route terminal to the graph. The avoidance + * area is given as a rectangle. + * @see RouteGraph#addTerminal(double, double, double, double, double, double, int, ILineEndStyle) + */ + public RouteTerminal addTerminal(double x, double y, + Rectangle2D bounds, + int allowedDirections, + ILineEndStyle style) { + return addTerminal(x, y, + bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(), + allowedDirections, style); + } + + /** + * Adds a copy of the given terminal to the graph. + */ + public RouteTerminal addTerminal(RouteTerminal terminal) { + RouteTerminal newTerminal = addTerminal(terminal.x, terminal.y, + terminal.getMinX(), terminal.getMinY(), + terminal.getMaxX(), terminal.getMaxY(), + terminal.getAllowedDirections(), terminal.getStyle(), terminal.getDynamicStyle()); + newTerminal.setData(terminal.getData()); + return terminal; + } + + /** + * Adds a route terminal to the graph. A default line end style is used. + * @see RouteGraph#addTerminal(double, double, double, double, double, double, int, ILineEndStyle) + */ + public RouteTerminal addTerminal(double x, double y, + double minX, double minY, + double maxX, double maxY, + int allowedDirections) { + return addTerminal(x, y, minX, minY, maxX, maxY, allowedDirections, + PlainLineEndStyle.INSTANCE); + } + + /** + * Links nodes. + */ + public void link(RouteNode node1, RouteNode node2) { + if(node1 instanceof RouteLine) { + if(node2 instanceof RouteLine) { + link((RouteLine)node1, (RouteLine)node2); + } + else { + link((RouteLine)node1, (RouteTerminal)node2); + } + } + else { + if(node2 instanceof RouteLine) { + link((RouteTerminal)node1, (RouteLine)node2); + } + else { + link((RouteTerminal)node1, (RouteTerminal)node2); + } + } + } + + /** + * Links nodes. + */ + public void link(RouteNode ... nodes) { + for(int i=1;i it = line.points.iterator(); + while(it.hasNext()) { + RoutePoint point = it.next(); + if(point instanceof RouteTerminal) + it.remove(); + } + line.terminal = null; + line.nextTransient = null; + } + else { + line.terminal.line = line; + do { + transientLines.remove(line); + lines.add(line); + Iterator it = line.points.iterator(); + while(it.hasNext()) { + RoutePoint point = it.next(); + if(point instanceof RouteTerminal) + it.remove(); + } + line.terminal = null; + RouteLine temp = line.nextTransient; + line.nextTransient = null; + line = temp; + } while(line != null); + } + needsUpdate = true; + } + } + + void prepareForModification() { + if(caseId == 4) { + RouteLine line = transientLines.remove(0); + line.terminal = null; + lines.add(line); + for(RouteTerminal terminal : terminals) + terminal.line = line; + isSimpleConnection = false; + caseId = 6; + } + } + + public double[] getLineLengths(RouteTerminal terminal) { + if(needsUpdate) + update(); + if(lines.size()==0 && transientLines.size()==1) + return new double[] { transientLines.get(0).getLength() }; + + RouteLine line = null; + fLoop: + for(RouteLine l : transientLines) + if(l.isTransient()) { + for(RoutePoint p : l.points) + if(!(p instanceof RouteLink)) { + line = l; + break fLoop; + } + } + TDoubleArrayList result = new TDoubleArrayList(); + THashSet set = new THashSet(); + set.add(line); + loop: + while(true) { + result.add(line.getLength()); + for(RoutePoint point : line.points) + if(point instanceof RouteLink) { + RouteLink link = (RouteLink)point; + if(set.add(link.a)) { + line = link.a; + continue loop; + } + if(set.add(link.b)) { + line = link.b; + continue loop; + } + } + break; + } + return result.toArray(); + } + + public void split(RouteLine rl1, double splitPosition) { + if(needsUpdate) + update(); + boolean isHorizontal = rl1.isHorizontal(); + if(isSimpleConnection) { + isSimpleConnection = false; + if(caseId < 2) { + RouteLine sp = addLine(!isHorizontal, splitPosition); + for(RouteTerminal terminal : terminals) + terminal.line = sp; + update(); + return; + } + else if(caseId < 4) { + RouteLine sp = addLine(!isHorizontal, splitPosition); + RouteLine l = addLine(isHorizontal, rl1.position); + link(l, sp); + rl1.terminal.line = sp; + for(RouteTerminal terminal : terminals) + if(terminal != rl1.terminal) + terminal.line = l; + update(); + return; + } + else + prepareForModification(); + } + + RouteLine rl2 = new RouteLine(isHorizontal, rl1.getPosition()); + RouteLine sp = new RouteLine(!isHorizontal, splitPosition); + + ArrayList points = rl1.points; + + int splitPos; + { + int minPos = 0; + int maxPos = points.size(); + while(minPos != maxPos) { + int c = (minPos + maxPos)/2; + if(isHorizontal + ? points.get(c).getX() > splitPosition + : points.get(c).getY() > splitPosition) + maxPos = c; + else + minPos = c+1; + } + splitPos = minPos; + } + + for(int i=points.size()-1;i>=splitPos;--i) { + RoutePoint point = points.remove(i); + if(point instanceof RouteLink) { + RouteLink link = (RouteLink)point; + link.replace(rl1, rl2); + } + } + + if(rl1.isTransient()) { + boolean p1 = rl1.isConnectedToPeristentLine(); + boolean p2 = rl2.isConnectedToPeristentLine(); + + RouteTerminal terminal = rl1.terminal; + if(p1) { + makePersistent(rl1); + transientLines.add(rl2); + } + else if(p2) { + lines.add(rl2); + } + else { + transientLines.add(rl2); + if(lines.isEmpty()) + for(RouteTerminal t : terminals) + t.line = sp; + } + terminal.line = sp; + } + else + lines.add(rl2); + + new RouteLink(rl1, sp); + new RouteLink(sp, rl2); + lines.add(sp); + update(); + } + + public boolean merge(RouteLine line) { + int count = 0; + double sum = 0; + for(RoutePoint point : line.points) { + if(point instanceof RouteLink) { + RouteLink link = (RouteLink)point; + RouteLine other = link.getOther(line); + if(!other.isTransient()) { + sum += other.position; + ++count; + } + } + } + return merge(line, sum / count); + } + + /** + * Removes given route line and collapses all its neighbor + * route lines into one line with given position. + */ + public boolean merge(RouteLine line, double position) { + if(needsUpdate) + update(); + if(isSimpleConnection || line.isTransient()) + return false; + + if(lines.size() == 1) { + if(terminals.size() != 2) + return false; + lines.remove(0); + isSimpleConnection = true; + for(RouteTerminal terminal : terminals) + terminal.line = null; + update(); + return true; + } + + ArrayList nLines = new ArrayList(); + for(RoutePoint point : line.points) { + /* Because line.terminal == null, there should be + * not RouteTerminals + * + * 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(nLines.isEmpty()) + return false; + RouteLine merged = nLines.remove(nLines.size()-1); + merged.position = position; + for(RouteLine l : nLines) { + for(RoutePoint point : l.points) { + /* Because l.terminal == null, there should be + * not RouteTerminals + * + * 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); + } + } + } + THashSet removedLines = new THashSet(); + removedLines.addAll(nLines); + lines.removeAll(removedLines); + removedLines.add(line); + lines.remove(line); + for(RouteTerminal terminal : terminals) + if(removedLines.contains(terminal.line)) + terminal.line = merged; + update(); + return true; + } + + public void deleteCorner(RouteLink link) { + if(needsUpdate) + update(); + RouteLine a = link.getA(); + RouteLine b = link.getB(); + if(a.isTransient() || b.isTransient() + || a.points.size() != 2 || b.points.size() != 2) + return; + RouteLine na=null, nb=null; + for(RoutePoint p : a.points) { + RouteLink l = (RouteLink)p; + if(l.a == a && l.b != b) { + na = l.b; + break; + } + else if(l.b == a && l.a != b) { + na = l.a; + break; + } + } + for(RoutePoint p : b.points) { + RouteLink l = (RouteLink)p; + if(l.a == b && l.b != a) { + nb = l.b; + break; + } + else if(l.b == b && l.a != a) { + nb = l.a; + break; + } + } + if(na == null || nb == null) { + System.err.println("Internal error in router."); + return; + } + a.remove(); + b.remove(); + lines.remove(a); + lines.remove(b); + link(na, nb); + if(na.terminal != null) + na.terminal.line = nb; + if(nb.terminal != null) + nb.terminal.line = na; + update(); + } + + public boolean connectTerminal(RouteTerminal terminal, double x, double y, double tolerance) { + Object target = pick(x, y, tolerance); + if(target instanceof RouteLine) { + RouteLine line = (RouteLine)target; + RouteTerminal involvedTerminal = + line.isTransient() ? line.terminal : null; + makePersistent(line); + terminal.line = null; // TODO this is a workaround + + int lineDir = line.isHorizontal + ? (line.position < terminal.y ? 3 : 1) + : (line.position < terminal.x ? 2 : 0) + ; + + if(line.isHorizontal && Math.abs(x - terminal.x) > 30) { + RouteLine l2; + if(Directions.isAllowed(terminal.getAllowedDirections(), lineDir)) { + RouteLine l1 = addLine(true, 0.5*(y+terminal.y)); + l2 = addLine(false, x); + link(terminal, l1, l2, line); + } + else { + l2 = addLine(false, x); + link(terminal, l2, line); + } + if(involvedTerminal != null) + involvedTerminal.line = l2; + } + else if(!line.isHorizontal && Math.abs(y - terminal.y) > 30) { + RouteLine l2; + if(Directions.isAllowed(terminal.getAllowedDirections(), lineDir)) { + RouteLine l1 = addLine(false, 0.5*(x+terminal.x)); + l2 = addLine(true, y); + link(terminal, l1, l2, line); + } + else { + l2 = addLine(true, y); + link(terminal, l2, line); + } + if(involvedTerminal != null) + involvedTerminal.line = l2; + } + else { + link(terminal, line); + } + update(); + return true; + } + else + return false; + } + + public boolean connectLine(RouteLine sLine, double x, double y, double tolerance) { + Object target = pick(x, y, tolerance); + if(target instanceof RouteLine) { + RouteLine line = (RouteLine)target; + RouteTerminal involvedTerminal = + line.isTransient() ? line.terminal : null; + makePersistent(line); + if(line.isHorizontal == sLine.isHorizontal) { + RouteLine l = addLine(!sLine.isHorizontal, + sLine.isHorizontal ? x : y); + link(sLine, l, line); + if(involvedTerminal != null) + involvedTerminal.line = l; + } + else { + link(sLine, line); + if(involvedTerminal != null) + involvedTerminal.line = sLine; + } + update(); + return true; + } + else + return false; + } + + /** + * Makes a deep copy of the route graph and stores + * the correspondences between original and copied + * objects into the given map. + */ + public RouteGraph copy(THashMap map) { + RouteGraph copy = new RouteGraph(); + copy.isSimpleConnection = isSimpleConnection; + copy.caseId = caseId; + copy.needsUpdate = needsUpdate; + for(RouteLine line : lines) + copy.lines.add(line.copy(map)); + for(RouteLine line : transientLines) + copy.transientLines.add(line.copy(map)); + for(RouteTerminal terminal : terminals) + copy.terminals.add(terminal.copy(map)); + return copy; + } + + /** + * Makes a deep copy of the route graph. + */ + public RouteGraph copy() { + THashMap map = new THashMap(); + return copy(map); + } + + /** + * Removes route lines that are conneted to at most + * one other route line or terminal. + */ + public void removeExtraConnections() { + TObjectIntHashMap counts = + new TObjectIntHashMap(); + removeTransientRouteLines(); + for(RouteLine line : lines) { + int count = 0; + for(RoutePoint point : line.points) + if(point instanceof RouteLink) + ++count; + counts.put(line, count); + } + for(RouteTerminal terminal : terminals) + counts.adjustOrPutValue(terminal.line, 1, 1); + boolean removed; + do { + removed = false; + Iterator it = lines.iterator(); + while(it.hasNext()) { + RouteLine line = it.next(); + if(counts.get(line) <= 1) { + for(RoutePoint point : line.points) + if(point instanceof RouteLink) + counts.adjustValue(((RouteLink)point).getOther(line), -1); + line.remove(); + it.remove(); + removed = true; + } + } + } while(removed); + update(); + } + + /** + * Removes a terminal and all route lines that become degenerated by the removal. + */ + public void remove(RouteTerminal terminal) { + terminals.remove(terminal); + removeExtraConnections(); + } + + public void disconnect(RouteTerminal terminal) { + terminal.line = null; + removeExtraConnections(); + } + + public void remove(RouteLink link) { + link.a.points.remove(link); + link.b.points.remove(link); + } + + public void toggleDirectLines(RouteTerminal terminal) { + terminal.toggleDirectLines(); + needsUpdate = true; + } + + /** + * Returns all persistent route lines of the route graph. + */ + public Collection getLines() { + if(needsUpdate) + update(); + if(RETURN_UNMODIFIABLE_COLLECTIONS) + return Collections.unmodifiableList(lines); + else + return lines; + } + + /* + * Returns all transient route lines of the route graph. + */ + public Collection getTransientLines() { + if(needsUpdate) + update(); + if(RETURN_UNMODIFIABLE_COLLECTIONS) + return Collections.unmodifiableList(transientLines); + else + return transientLines; + } + + /** + * Returns all terminals of the route graph. + */ + public Collection getTerminals() { + if(RETURN_UNMODIFIABLE_COLLECTIONS) + return Collections.unmodifiableList(terminals); + else + return terminals; + } + + /** + * Returns all route lines, both persistent and transient, + * of the route graph. + */ + public Collection getAllLines() { + if(needsUpdate) + update(); + ArrayList allLines = new ArrayList(lines.size() + transientLines.size()); + allLines.addAll(lines); + allLines.addAll(transientLines); + return allLines; + } + + /** + * Returns all route lines, both persistent and transient, of the route + * graph, added to the specified collection. Avoids allocation of new + * {@link ArrayList} compared to {@link #getAllLines()}. + */ + public Collection getAllLines(Collection result) { + if (result == null) + throw new NullPointerException("null result collection"); + if(needsUpdate) + update(); + result.addAll(lines); + result.addAll(transientLines); + return result; + } + + /** + * Returns true, if the route graph is connected and contains no cycles. + */ + public boolean isTree() { + if(isSimpleConnection) + return true; + for(RouteTerminal terminal : terminals) + if(terminal.line == null) + return false; + THashSet visited = new THashSet(); + ArrayList stack = new ArrayList(); + int linkCount = 0; + visited.add(lines.get(0)); + stack.add(lines.get(0)); + while(!stack.isEmpty()) { + RouteLine cur = stack.remove(stack.size()-1); + for(RouteLine n : cur.getPersistentNeighbors()) { + ++linkCount; + if(visited.add(n)) + stack.add(n); + } + } + return visited.size() == lines.size() && linkCount == 2*(lines.size()-1); + } + + /** + * Returns if the connection is simple. A connection is simple, if it has just + * two terminals connected together directly without (persistent) route lines. + */ + public boolean isSimpleConnection() { + return isSimpleConnection; + } + + public void replaceBy(RouteGraph rg) { + this.lines = rg.lines; + this.terminals = rg.terminals; + this.transientLines = rg.transientLines; + this.caseId = rg.caseId; + this.isSimpleConnection = rg.isSimpleConnection; + this.needsUpdate = rg.needsUpdate; + rg.reset(); + } + + private void reset() { + lines = new ArrayList(); + terminals = new ArrayList(); + transientLines = new ArrayList(); + caseId = 0; + isSimpleConnection = false; + needsUpdate = false; + } + + public Rectangle2D getBounds() { + Rectangle2D bounds = new Rectangle2D.Double(); + getBounds(bounds); + return bounds; + } + + public void getBounds(Rectangle2D bounds) { + if(needsUpdate) + update(); + double minX = Double.POSITIVE_INFINITY; + double maxX = Double.NEGATIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + double maxY = Double.NEGATIVE_INFINITY; + for(RouteLine line : lines) { + double position = line.position; + if(line.isHorizontal) { + minY = Math.min(minY, position); + maxY = Math.max(maxY, position); + } + else { + minX = Math.min(minX, position); + maxX = Math.max(maxX, position); + } + } + for(RouteLine line : transientLines) { + double position = line.position; + if(line.isHorizontal) { + minY = Math.min(minY, position); + maxY = Math.max(maxY, position); + } + else { + minX = Math.min(minX, position); + maxX = Math.max(maxX, position); + } + } + for(RouteTerminal terminal : terminals) { + double x = terminal.x; + double y = terminal.y; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minY = Math.min(minY, y); + maxY = Math.max(maxY, y); + } + bounds.setFrame(minX, minY, maxX-minX, maxY-minY); + } + + private static void addPathBegin(Path2D path, RoutePoint cur, RouteLine line) { + double x = cur.x, y = cur.y; + if(cur instanceof RouteTerminal) { + ILineEndStyle style = ((RouteTerminal)cur).getRenderStyle(); + if(line.isHorizontal()) { + if(cur == line.getBegin()) + x += style.getLineEndLength(0); + else + x -= style.getLineEndLength(2); + } + else { + if(cur == line.getBegin()) + y += style.getLineEndLength(1); + else + y -= style.getLineEndLength(3); + } + } + path.moveTo(x, y); + } + + private static void addPathEnd(Path2D path, RoutePoint cur, RouteLine line) { + double x = cur.x, y = cur.y; + if(cur instanceof RouteTerminal) { + ILineEndStyle style = ((RouteTerminal)cur).getRenderStyle(); + if(line.isHorizontal()) { + if(cur == line.getBegin()) + x += style.getLineEndLength(0); + else + x -= style.getLineEndLength(2); + } + else { + if(cur == line.getBegin()) + y += style.getLineEndLength(1); + else + y -= style.getLineEndLength(3); + } + } + path.lineTo(x, y); + } + + public void getPath2D(Path2D path) { + if(needsUpdate) + update(); + + if(isSimpleConnection && transientLines.isEmpty()) { + if(terminals.size() == 2) { + RouteTerminal a = terminals.get(0); + RouteTerminal b = terminals.get(1); + if(a.hasDirectConnection() || b.hasDirectConnection()) { + path.moveTo(a.x, a.y); + path.lineTo(b.x, b.y); + return; + } + } + } + + // Analyze graph + THashMap begins = + new THashMap(); + for(RouteLine line : lines) { + add(begins, line); + } + for(RouteLine line : transientLines) { + add(begins, line); + } + for(RouteTerminal terminal : terminals) + if((terminal.getAllowedDirections() & 0x10)!=0 && terminal.line != null) { + begins.remove(terminal.line.getBegin()); + drawContinuousPath(path, begins, terminal, terminal.line); + } + + // Create paths + for(RoutePoint begin : begins.keySet().toArray(new RoutePoint[begins.size()])) { + RouteLine curLine = begins.remove(begin); + drawContinuousPath(path, begins, begin, curLine); + } + } + + private void drawContinuousPath(Path2D path, THashMap begins, + RoutePoint cur, RouteLine curLine) { + if(curLine == null) + return; + addPathBegin(path, cur, curLine); + + while(true) { + if(cur != curLine.getEnd()) + cur = curLine.getEnd(); + else + cur = curLine.getBegin(); + if(begins.remove(cur) != null || !(cur instanceof RouteLink)) { + addPathEnd(path, cur, curLine); + return; + } + if(cur instanceof RouteLink) { + if(!curLine.isDegenerated() || + path.getCurrentPoint().getX() != cur.x || + path.getCurrentPoint().getY() != cur.y) + path.lineTo(cur.x, cur.y); + RouteLink link = (RouteLink)cur; + if(link.a != curLine) + curLine = link.a; + else + curLine = link.b; + } + } + } + + private static void add(THashMap begins, + RouteLine line) { + if(line.points.size() > 1) { + { + RoutePoint p = line.getBegin(); + if(begins.remove(p) == null) + begins.put(p, line); + } + { + RoutePoint p = line.getEnd(); + if(begins.remove(p) == null) + begins.put(p, line); + } + } + } + + public Collection getSegments() { + if (needsUpdate) + update(); + ArrayList segments = new ArrayList(); + for(RouteLine routeLine : lines) + routeLine.collectSegments(segments); + for(RouteLine routeLine : transientLines) + routeLine.collectSegments(segments); + return segments; + } + + public Path2D getPath2D() { + Path2D result = new Path2D.Double(); + getPath2D(result); + return result; + } + + public SplittedRouteGraph splitGraph(RouteLine splitLine, double position) { + THashSet interfaceNodes1 = new THashSet(); + THashSet lines1 = new THashSet(); + THashSet terminals1 = new THashSet(); + THashSet interfaceNodes2 = new THashSet(); + THashSet lines2 = new THashSet(); + THashSet terminals2 = new THashSet(); + + if(splitLine.isTransient()) { + RouteTerminal terminal = splitLine.terminal; + + if(splitLine.beginsWithTerminal()) { + lines2.addAll(getLines()); + terminals2.addAll(getTerminals()); + terminals1.add(terminal); + terminals2.remove(terminal); + interfaceNodes1.add(terminal); + if(isSimpleConnection()) + interfaceNodes2.addAll(terminals2); + else + interfaceNodes2.add(terminal.line); + } + else { + lines1.addAll(getLines()); + terminals1.addAll(getTerminals()); + terminals2.add(terminal); + terminals1.remove(terminal); + interfaceNodes2.add(terminal); + if(isSimpleConnection()) + interfaceNodes1.addAll(terminals1); + else + interfaceNodes1.add(terminal.line); + } + } + else { + for(RoutePoint rp : splitLine.getPoints()) { + double p = splitLine.isHorizontal ? rp.x : rp.y; + if(rp instanceof RouteLink) { + RouteLink link = (RouteLink)rp; + RouteLine otherLine = link.getA() != splitLine ? link.getA() + : link.getB(); + if(otherLine.isTransient()) { + if(p < position) { + interfaceNodes1.add(otherLine.terminal); + terminals1.add(otherLine.terminal); + } + else { + interfaceNodes2.add(otherLine.terminal); + terminals2.add(otherLine.terminal); + } + continue; + } + + if(p < position) { + interfaceNodes1.add(otherLine); + traverseGraph(link, otherLine, lines1); + } + else { + interfaceNodes2.add(otherLine); + traverseGraph(link, otherLine, lines2); + } + } + } + + for(RouteTerminal terminal : getTerminals()) + if(lines1.contains(terminal.line)) + terminals1.add(terminal); + else if(lines2.contains(terminal.line)) + terminals2.add(terminal); + } + + return new SplittedRouteGraph(splitLine, + interfaceNodes1, lines1, terminals1, + interfaceNodes2, lines2, terminals2); + } + + private void traverseGraph(RoutePoint previousPoint, RouteLine line, + THashSet lines) { + if(lines.add(line)) { + for(RoutePoint rp : line.getPoints()) { + if(rp != previousPoint && rp instanceof RouteLink) { + RouteLink link = (RouteLink)rp; + RouteLine otherLine = line != link.getA() + ? link.getA() : link.getB(); + if(otherLine.isTransient()) + continue; + traverseGraph(rp, otherLine, lines); + } + } + } + } + + public void reclaimTransientMemory() { + removeTransientRouteLines(); + needsUpdate = true; + } + +}