--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.g2d.elementclass.connection;\r
+\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.PathIterator;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.HashSet;\r
+import java.util.Iterator;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Set;\r
+\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.handler.Topology;\r
+import org.simantics.g2d.diagram.handler.Validator;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.diagram.handler.impl.DiagramIssue;\r
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.handler.BendsHandler;\r
+import org.simantics.g2d.element.handler.EdgeVisuals;\r
+import org.simantics.g2d.element.handler.BendsHandler.AngleType;\r
+import org.simantics.g2d.element.handler.BendsHandler.Bend;\r
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
+import org.simantics.g2d.routing.algorithm1.Rectangle;\r
+import org.simantics.g2d.utils.GeometryUtils;\r
+import org.simantics.g2d.utils.PathUtils;\r
+import org.simantics.g2d.utils.geom.DirectionSet;\r
+import org.simantics.utils.datastructures.TreeProblem;\r
+import org.simantics.utils.datastructures.TreeProblem.ProblemNode;\r
+\r
+/**\r
+ * Checks whether Topology meets geometry \r
+ * \r
+ * @author Toni Kalajainen\r
+ */\r
+public class ConnectionValidator implements Validator {\r
+\r
+\r
+\r
+ public static final ConnectionValidator INSTANCE = new ConnectionValidator(); \r
+ private static final double TOLERANCE = 0.001;\r
+ \r
+ private static final Suggestion DISCONNECT_EDGES = new Suggestion() {\r
+ @Override\r
+ public boolean fix(IDiagram d, ICanvasContext ctx) { \r
+ return false;\r
+ }\r
+ @Override\r
+ public String getMessage() {\r
+ return "Disconnect edges";\r
+ }\r
+ };\r
+ \r
+ public static final double OBSTACLE_MARGINAL = 10.0; \r
+ \r
+ static Collection<Rectangle> createObstacles(IDiagram d) {\r
+ ArrayList<Rectangle> obstacles = new ArrayList<Rectangle>();\r
+ for(IElement e : d.getElements())\r
+ {\r
+ if (e.getElementClass().containsClass(EdgeVisuals.class)) continue;\r
+ Rectangle2D rect = ElementUtils.getElementBounds(e);\r
+ obstacles.add(new Rectangle(\r
+ rect.getMinX()-OBSTACLE_MARGINAL,\r
+ rect.getMinY()-OBSTACLE_MARGINAL,\r
+ rect.getMaxX()+OBSTACLE_MARGINAL,\r
+ rect.getMaxY()+OBSTACLE_MARGINAL\r
+ ));\r
+ }\r
+ return obstacles;\r
+ }\r
+ \r
+ // Force path to follow its path requirements \r
+ private static final Suggestion FORCE_PATH_TO_REQUIREMENTS = new Suggestion() {\r
+ @Override\r
+ public boolean fix(IDiagram d, ICanvasContext ctx) {\r
+ \r
+ // Get all edges\r
+ ArrayList<PathRequirement> reqs = new ArrayList<PathRequirement>(); \r
+ for (IElement e : d.getElements())\r
+ {\r
+ BendsHandler bends = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
+ if (bends==null) continue;\r
+ Path2D path = bends.getPath(e);\r
+ assert(path!=null);\r
+ reqs.clear();\r
+ createPathRequirement(e, reqs); \r
+ if (reqs.isEmpty()) continue;\r
+ if (pathFollowsRequirements(path, reqs)) continue;\r
+ \r
+ AngleType at = bends.getAngleType(e);\r
+ if (at==AngleType.Linear) {\r
+ path = createPathLinear(reqs);\r
+ } else if (at==AngleType.RightAngled) {\r
+ path = createPathRightAngled(reqs); \r
+ } else if (at==AngleType.Quadratic) {\r
+ path = createPathQuadratic(reqs);\r
+ } else if (at==AngleType.Line) {\r
+ path = createPathLine(reqs);\r
+ } else assert(false);\r
+ \r
+ // Make up something!\r
+ if (path==null)\r
+ path = createPathLine(reqs);\r
+\r
+ AffineTransform elementToDiagramAt = ElementUtils.getInvTransform(e);\r
+ path.transform(elementToDiagramAt);\r
+ \r
+ bends.setPath(e, path);\r
+ } \r
+ return false;\r
+ }\r
+ @Override\r
+ public String getMessage() {\r
+ return "Update path";\r
+ }\r
+ };\r
+ private static final Issue PATH_IS_BROKEN = \r
+ new DiagramIssue("Path does not follow its requirements (connects and bends)", FORCE_PATH_TO_REQUIREMENTS, DISCONNECT_EDGES);\r
+ \r
+ @Override\r
+ public void validate(IDiagram d, ICanvasContext ctx, Collection<Issue> lst) {\r
+ if (!validateDiagramOk(d, ctx, lst))\r
+ {\r
+ lst.add(PATH_IS_BROKEN);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Return true if connections of a diagram are ok.\r
+ * @param d\r
+ * @param ctx\r
+ * @param lst\r
+ * @return\r
+ */\r
+ boolean validateDiagramOk(IDiagram d, ICanvasContext ctx, Collection<Issue> lst)\r
+ {\r
+ // Get all edges\r
+ ArrayList<PathRequirement> reqs = new ArrayList<PathRequirement>();\r
+ for (IElement e : d.getElements())\r
+ {\r
+ BendsHandler bends = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
+ if (bends==null) continue;\r
+ Path2D path = bends.getPath(e);\r
+\r
+ reqs.clear();\r
+ createPathRequirement(e, reqs); \r
+ if (reqs.isEmpty()) continue;\r
+ if (pathFollowsRequirements(path, reqs)) continue;\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+ \r
+ /**\r
+ * Forces the path to follow path requirements.\r
+ * \r
+ * @param e\r
+ * @param reqs path requirements.\r
+ * @return true if path matches requirements\r
+ */\r
+ public static boolean pathFollowsRequirements(Path2D path, List<PathRequirement> reqs)\r
+ {\r
+ if (reqs.size()==0) return true;\r
+ // To validate path we need to make sure that \r
+ // 1) path goes thru control points \r
+ // 2) The tangent of the path at control points is in direction set\r
+ int reqIndex = 0;\r
+ PathRequirement req = reqs.get(reqIndex++); \r
+ PathIterator pi = path.getPathIterator(null);\r
+ Iterator<double[]> lsi = PathUtils.toLineIterator(pi);\r
+ if (!lsi.hasNext()) return false;\r
+ \r
+ // check begin\r
+ double[] seg = lsi.next();\r
+ Point2D pos = PathUtils.getLinePos(seg, 0);\r
+ Rectangle2D rect = new Rectangle2D.Double(pos.getX()-TOLERANCE, pos.getY()-TOLERANCE, 2*TOLERANCE, 2*TOLERANCE);\r
+ \r
+ // Check pos\r
+ if (!rect.contains(pos)) return false;\r
+ // Check tangent\r
+ Point2D tang = PathUtils.getLineTangent(seg, 0);\r
+ double dir = GeometryUtils.getCompassDirection(tang);\r
+ Double closestDir = req.set.getClosestDirection(dir, 0.1);\r
+ if (closestDir == null) return false; \r
+ \r
+ while (lsi.hasNext()) {\r
+ seg = lsi.next();\r
+ pos = PathUtils.getLinePos(seg, 1);\r
+ rect = new Rectangle2D.Double(pos.getX()-TOLERANCE, pos.getY()-TOLERANCE, 2*TOLERANCE, 2*TOLERANCE);\r
+ \r
+ if (rect.contains(pos)) {\r
+ // check tangent\r
+ tang = PathUtils.getLineTangent(seg, 1);\r
+ dir = GeometryUtils.getCompassDirection(tang);\r
+ closestDir = req.set.getClosestDirection(dir, 0.1);\r
+ if (closestDir != null) \r
+ {\r
+ // next requirement\r
+ req = reqs.get(reqIndex++);\r
+ }\r
+ } \r
+ }\r
+ return reqIndex==reqs.size();\r
+ }\r
+\r
+ static class LinearEdgeSolutionNode implements ProblemNode {\r
+ List<PathRequirement> reqs;\r
+ LinearEdgeSolutionNode prevBranch;\r
+ int index;\r
+ double cost;\r
+ Point2D p0, cp, p1;\r
+ Point2D v0, v1, v1o; // v0=direction vector from p0, v1=direction vector to p1\r
+ // v1o = opposite of v1o\r
+ @Override\r
+ public void branch(Collection<ProblemNode> list) {\r
+ PathRequirement r0 = reqs.get(index );\r
+ PathRequirement r1 = reqs.get(index+1);\r
+ Point2D p0 = r0.pos;\r
+ Point2D p1 = r1.pos;\r
+ Point2D cp1 = new Point2D.Double();\r
+ Point2D cp2 = new Point2D.Double();\r
+ Set<Object> controlPoints = new HashSet<Object>();\r
+ for (Point2D v0 : r0.set.getUnitVectors())\r
+ for (Point2D v1 : r1.set.getUnitVectors())\r
+ {\r
+ Point2D i = PathUtils.findIntersection(p0, v0, p1, v1);\r
+ if (i!=null) {\r
+ controlPoints.add(i);\r
+ \r
+ } else {\r
+ // Impossible requirements\r
+ int flags = PathUtils.findNearestPoints(p0, v0, p1, v1, cp1, cp2); \r
+ if ((flags & 1) == 1) \r
+ controlPoints.add(cp1);\r
+ if ((flags & 2) == 2)\r
+ controlPoints.add(cp2);\r
+ \r
+ }\r
+ }\r
+ if (controlPoints.isEmpty()) {\r
+ Point2D i = new Point2D.Double(\r
+ (p0.getX() + p1.getX())/2,\r
+ (p0.getY() + p1.getY())/2);\r
+ controlPoints.add(i);\r
+ }\r
+ for (Object dada : controlPoints) {\r
+ Point2D c0 = (Point2D) dada;\r
+ double price = p0.distance(c0)+c0.distance(p1);\r
+ price -= p0.distance(p1);\r
+ \r
+ LinearEdgeSolutionNode s = new LinearEdgeSolutionNode(); \r
+ s.cost = cost + price;\r
+ s.prevBranch = this;\r
+ s.reqs = reqs;\r
+ s.index = index+1;\r
+ s.p0 = p0;\r
+ s.p1 = p1;\r
+ if (!c0.equals(p0) && !c0.equals(p1)) \r
+ s.cp = c0;\r
+ \r
+ Point2D np = s.cp == null ? p1 : c0;\r
+ double dist = p0.distance(np);\r
+ s.v0 = new Point2D.Double( (p0.getX() - np.getX())/dist, (p0.getY() - np.getY())/dist);\r
+\r
+ np = s.cp == null ? p0 : c0;\r
+ dist = p1.distance(np);\r
+ s.v1 = new Point2D.Double( (p1.getX() - np.getX())/dist, (p1.getY() - np.getY())/dist);\r
+ s.v1o = new Point2D.Double( (np.getX() - p1.getX())/dist, (np.getY() - p1.getY())/dist); \r
+\r
+ // Penalty for changing direction \r
+ if (v1!=null && !s.v0.equals(v1)) \r
+ s.cost += 1;\r
+ // Penalty for going back\r
+ if (v1o!=null && v1o.equals(s.v0)) \r
+ s.cost += 2;\r
+ \r
+ list.add(s);\r
+ }\r
+ }\r
+ @Override\r
+ public double getCost() {\r
+ return cost;\r
+ }\r
+ @Override\r
+ public boolean isComplete() {\r
+ return index>=reqs.size()-1;\r
+ }\r
+ /**\r
+ * Create path from root to this node\r
+ * @return\r
+ */\r
+ public List<LinearEdgeSolutionNode> toList() {\r
+ LinkedList<LinearEdgeSolutionNode> res = new LinkedList<LinearEdgeSolutionNode>();\r
+ LinearEdgeSolutionNode branch = this;\r
+ while (branch!=null) {\r
+ res.addFirst(branch);\r
+ branch = branch.prevBranch;\r
+ }\r
+ return res;\r
+ }\r
+ public Path2D toPath() {\r
+ Path2D p = new Path2D.Double(); \r
+ for (LinearEdgeSolutionNode s : toList()) \r
+ {\r
+ if (s.p0==null) continue;\r
+ Point2D cur = p.getCurrentPoint();\r
+ if (cur==null) \r
+ p.moveTo(s.p0.getX(), s.p0.getY());\r
+ \r
+ cur = p.getCurrentPoint();\r
+ if ((s.cp!=null)&&(cur==null || !cur.equals(s.cp))) \r
+ p.lineTo(s.cp.getX(), s.cp.getY()); \r
+ \r
+ cur = p.getCurrentPoint();\r
+ if (cur==null || !cur.equals(s.p1)) \r
+ p.lineTo(s.p1.getX(), s.p1.getY()); \r
+ }\r
+ \r
+ return p;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Create a linear path that follows path requirements\r
+ * @param reqs requirements\r
+ * @return path\r
+ */\r
+ public static Path2D createPathLinear(final List<PathRequirement> reqs)\r
+ {\r
+ LinearEdgeSolutionNode s = new LinearEdgeSolutionNode();\r
+ s.reqs = reqs;\r
+ LinearEdgeSolutionNode solution = (LinearEdgeSolutionNode) TreeProblem.findSolution(s, 2);\r
+ if (solution==null) return null; \r
+ return solution.toPath(); \r
+ }\r
+ \r
+ /**\r
+ * Create a line that follows path requirements\r
+ * @param reqs\r
+ * @return\r
+ */\r
+ public static Path2D createPathLine(final List<PathRequirement> reqs)\r
+ {\r
+ Path2D result = new Path2D.Double();\r
+ int index = 0;\r
+ for (PathRequirement r : reqs) {\r
+ if (index++==0)\r
+ result.moveTo(r.pos.getX(), r.pos.getY());\r
+ else\r
+ result.lineTo(r.pos.getX(), r.pos.getY());\r
+ }\r
+ return result;\r
+ }\r
+ \r
+\r
+ /**\r
+ * Create a right-angled (90 deg angles) path that follows requirements \r
+ * @param reqs requirements\r
+ * @return path\r
+ */\r
+ public static Path2D createPathRightAngled(List<PathRequirement> reqs)\r
+ {\r
+ Path2D res = new Path2D.Double();\r
+// int i = 0;\r
+// for (PathRequirement r : reqs)\r
+// {\r
+// \r
+// } \r
+ \r
+ return res;\r
+ }\r
+ \r
+ /**\r
+ * Create a path that follows requirements\r
+ * @param reqs requirements\r
+ * @return path\r
+ */\r
+ public static Path2D createPathQuadratic(List<PathRequirement> reqs)\r
+ {\r
+ Path2D res = new Path2D.Double();\r
+// int i = 0;\r
+// for (PathRequirement r : reqs)\r
+// {\r
+// \r
+// } \r
+ \r
+ return res;\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Path Requirement is a point thru which the path must "travel".\r
+ */\r
+ static class PathRequirement {\r
+ Point2D pos; // Position on diagram\r
+ DirectionSet set; // Allowed directions\r
+ boolean isBegin, isEnd;\r
+ }\r
+ \r
+ /**\r
+ * Takes a look at an edge and creates a path travel plan (requiremetn).\r
+ * Beginning of e must point to (possibly) connected terminal.\r
+ * End of e must point to (possibly) connected terminal.\r
+ * \r
+ * @param e\r
+ * @param pathRequirement Path requirements\r
+ */\r
+ public static void createPathRequirement(IElement e, List<PathRequirement> pathRequirement)\r
+ {\r
+ Topology t = ElementUtils.getDiagram(e).getDiagramClass().getSingleItem(Topology.class);\r
+ pathRequirement.clear();\r
+ if (t==null) return;\r
+ \r
+ // Add begin\r
+ Connection c1 = t.getConnection(e, EdgeEnd.Begin);\r
+ if (c1!=null) {\r
+ PathRequirement req = new PathRequirement();\r
+ AffineTransform at = TerminalUtil.getTerminalPosOnDiagram(c1.node, c1.terminal); \r
+ req.pos = new Point2D.Double( at.getTranslateX(), at.getTranslateY() );\r
+ req.set = ElementUtils.getTerminalDirection(c1.node, c1.terminal);\r
+ req.isBegin = true;\r
+ pathRequirement.add(req);\r
+ } \r
+ \r
+ // Add control points\r
+ AffineTransform elementTransform = ElementUtils.getTransform(e);\r
+ BendsHandler bends = e.getElementClass().getSingleItem(BendsHandler.class); \r
+ ArrayList<Bend> controlPoints = new ArrayList<Bend>();\r
+ bends.getBends(e, controlPoints);\r
+ for (Bend cp : controlPoints) \r
+ {\r
+ Point2D pos = new Point2D.Double();\r
+ bends.getBendPosition(e, cp, pos);\r
+ elementTransform.transform(pos, pos);\r
+ PathRequirement req = new PathRequirement();\r
+ req.set = DirectionSet.NESW;\r
+ req.pos = pos;\r
+ pathRequirement.add(req);\r
+ }\r
+ \r
+ // TODO lisää transformit\r
+ \r
+ \r
+ // Add end\r
+ Connection c2 = t.getConnection(e, EdgeEnd.End);\r
+ if (c2!=null) {\r
+ PathRequirement req = new PathRequirement();\r
+ AffineTransform at = TerminalUtil.getTerminalPosOnDiagram(c2.node, c2.terminal); \r
+ req.pos = new Point2D.Double( at.getTranslateX(), at.getTranslateY() );\r
+ req.set = ElementUtils.getTerminalDirection(c2.node, c2.terminal);\r
+ req.set = req.set.createInverse();\r
+ req.isEnd = true;\r
+ pathRequirement.add(req);\r
+ } \r
+ \r
+ }\r
+\r
+}\r