/******************************************************************************* * 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; } }