1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.elementclass.connection;
\r
14 import java.awt.geom.AffineTransform;
\r
15 import java.awt.geom.Path2D;
\r
16 import java.awt.geom.PathIterator;
\r
17 import java.awt.geom.Point2D;
\r
18 import java.awt.geom.Rectangle2D;
\r
19 import java.util.ArrayList;
\r
20 import java.util.Collection;
\r
21 import java.util.HashSet;
\r
22 import java.util.Iterator;
\r
23 import java.util.LinkedList;
\r
24 import java.util.List;
\r
25 import java.util.Set;
\r
27 import org.simantics.g2d.canvas.ICanvasContext;
\r
28 import org.simantics.g2d.diagram.IDiagram;
\r
29 import org.simantics.g2d.diagram.handler.Topology;
\r
30 import org.simantics.g2d.diagram.handler.Validator;
\r
31 import org.simantics.g2d.diagram.handler.Topology.Connection;
\r
32 import org.simantics.g2d.diagram.handler.impl.DiagramIssue;
\r
33 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
\r
34 import org.simantics.g2d.element.ElementUtils;
\r
35 import org.simantics.g2d.element.IElement;
\r
36 import org.simantics.g2d.element.handler.BendsHandler;
\r
37 import org.simantics.g2d.element.handler.EdgeVisuals;
\r
38 import org.simantics.g2d.element.handler.BendsHandler.AngleType;
\r
39 import org.simantics.g2d.element.handler.BendsHandler.Bend;
\r
40 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
\r
41 import org.simantics.g2d.routing.algorithm1.Rectangle;
\r
42 import org.simantics.g2d.utils.GeometryUtils;
\r
43 import org.simantics.g2d.utils.PathUtils;
\r
44 import org.simantics.g2d.utils.geom.DirectionSet;
\r
45 import org.simantics.utils.datastructures.TreeProblem;
\r
46 import org.simantics.utils.datastructures.TreeProblem.ProblemNode;
\r
49 * Checks whether Topology meets geometry
\r
51 * @author Toni Kalajainen
\r
53 public class ConnectionValidator implements Validator {
\r
57 public static final ConnectionValidator INSTANCE = new ConnectionValidator();
\r
58 private static final double TOLERANCE = 0.001;
\r
60 private static final Suggestion DISCONNECT_EDGES = new Suggestion() {
\r
62 public boolean fix(IDiagram d, ICanvasContext ctx) {
\r
66 public String getMessage() {
\r
67 return "Disconnect edges";
\r
71 public static final double OBSTACLE_MARGINAL = 10.0;
\r
73 static Collection<Rectangle> createObstacles(IDiagram d) {
\r
74 ArrayList<Rectangle> obstacles = new ArrayList<Rectangle>();
\r
75 for(IElement e : d.getElements())
\r
77 if (e.getElementClass().containsClass(EdgeVisuals.class)) continue;
\r
78 Rectangle2D rect = ElementUtils.getElementBounds(e);
\r
79 obstacles.add(new Rectangle(
\r
80 rect.getMinX()-OBSTACLE_MARGINAL,
\r
81 rect.getMinY()-OBSTACLE_MARGINAL,
\r
82 rect.getMaxX()+OBSTACLE_MARGINAL,
\r
83 rect.getMaxY()+OBSTACLE_MARGINAL
\r
89 // Force path to follow its path requirements
\r
90 private static final Suggestion FORCE_PATH_TO_REQUIREMENTS = new Suggestion() {
\r
92 public boolean fix(IDiagram d, ICanvasContext ctx) {
\r
95 ArrayList<PathRequirement> reqs = new ArrayList<PathRequirement>();
\r
96 for (IElement e : d.getElements())
\r
98 BendsHandler bends = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
\r
99 if (bends==null) continue;
\r
100 Path2D path = bends.getPath(e);
\r
101 assert(path!=null);
\r
103 createPathRequirement(e, reqs);
\r
104 if (reqs.isEmpty()) continue;
\r
105 if (pathFollowsRequirements(path, reqs)) continue;
\r
107 AngleType at = bends.getAngleType(e);
\r
108 if (at==AngleType.Linear) {
\r
109 path = createPathLinear(reqs);
\r
110 } else if (at==AngleType.RightAngled) {
\r
111 path = createPathRightAngled(reqs);
\r
112 } else if (at==AngleType.Quadratic) {
\r
113 path = createPathQuadratic(reqs);
\r
114 } else if (at==AngleType.Line) {
\r
115 path = createPathLine(reqs);
\r
116 } else assert(false);
\r
118 // Make up something!
\r
120 path = createPathLine(reqs);
\r
122 AffineTransform elementToDiagramAt = ElementUtils.getInvTransform(e);
\r
123 path.transform(elementToDiagramAt);
\r
125 bends.setPath(e, path);
\r
130 public String getMessage() {
\r
131 return "Update path";
\r
134 private static final Issue PATH_IS_BROKEN =
\r
135 new DiagramIssue("Path does not follow its requirements (connects and bends)", FORCE_PATH_TO_REQUIREMENTS, DISCONNECT_EDGES);
\r
138 public void validate(IDiagram d, ICanvasContext ctx, Collection<Issue> lst) {
\r
139 if (!validateDiagramOk(d, ctx, lst))
\r
141 lst.add(PATH_IS_BROKEN);
\r
146 * Return true if connections of a diagram are ok.
\r
152 boolean validateDiagramOk(IDiagram d, ICanvasContext ctx, Collection<Issue> lst)
\r
155 ArrayList<PathRequirement> reqs = new ArrayList<PathRequirement>();
\r
156 for (IElement e : d.getElements())
\r
158 BendsHandler bends = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
\r
159 if (bends==null) continue;
\r
160 Path2D path = bends.getPath(e);
\r
163 createPathRequirement(e, reqs);
\r
164 if (reqs.isEmpty()) continue;
\r
165 if (pathFollowsRequirements(path, reqs)) continue;
\r
172 * Forces the path to follow path requirements.
\r
175 * @param reqs path requirements.
\r
176 * @return true if path matches requirements
\r
178 public static boolean pathFollowsRequirements(Path2D path, List<PathRequirement> reqs)
\r
180 if (reqs.size()==0) return true;
\r
181 // To validate path we need to make sure that
\r
182 // 1) path goes thru control points
\r
183 // 2) The tangent of the path at control points is in direction set
\r
185 PathRequirement req = reqs.get(reqIndex++);
\r
186 PathIterator pi = path.getPathIterator(null);
\r
187 Iterator<double[]> lsi = PathUtils.toLineIterator(pi);
\r
188 if (!lsi.hasNext()) return false;
\r
191 double[] seg = lsi.next();
\r
192 Point2D pos = PathUtils.getLinePos(seg, 0);
\r
193 Rectangle2D rect = new Rectangle2D.Double(pos.getX()-TOLERANCE, pos.getY()-TOLERANCE, 2*TOLERANCE, 2*TOLERANCE);
\r
196 if (!rect.contains(pos)) return false;
\r
198 Point2D tang = PathUtils.getLineTangent(seg, 0);
\r
199 double dir = GeometryUtils.getCompassDirection(tang);
\r
200 Double closestDir = req.set.getClosestDirection(dir, 0.1);
\r
201 if (closestDir == null) return false;
\r
203 while (lsi.hasNext()) {
\r
205 pos = PathUtils.getLinePos(seg, 1);
\r
206 rect = new Rectangle2D.Double(pos.getX()-TOLERANCE, pos.getY()-TOLERANCE, 2*TOLERANCE, 2*TOLERANCE);
\r
208 if (rect.contains(pos)) {
\r
210 tang = PathUtils.getLineTangent(seg, 1);
\r
211 dir = GeometryUtils.getCompassDirection(tang);
\r
212 closestDir = req.set.getClosestDirection(dir, 0.1);
\r
213 if (closestDir != null)
\r
215 // next requirement
\r
216 req = reqs.get(reqIndex++);
\r
220 return reqIndex==reqs.size();
\r
223 static class LinearEdgeSolutionNode implements ProblemNode {
\r
224 List<PathRequirement> reqs;
\r
225 LinearEdgeSolutionNode prevBranch;
\r
228 Point2D p0, cp, p1;
\r
229 Point2D v0, v1, v1o; // v0=direction vector from p0, v1=direction vector to p1
\r
230 // v1o = opposite of v1o
\r
232 public void branch(Collection<ProblemNode> list) {
\r
233 PathRequirement r0 = reqs.get(index );
\r
234 PathRequirement r1 = reqs.get(index+1);
\r
235 Point2D p0 = r0.pos;
\r
236 Point2D p1 = r1.pos;
\r
237 Point2D cp1 = new Point2D.Double();
\r
238 Point2D cp2 = new Point2D.Double();
\r
239 Set<Object> controlPoints = new HashSet<Object>();
\r
240 for (Point2D v0 : r0.set.getUnitVectors())
\r
241 for (Point2D v1 : r1.set.getUnitVectors())
\r
243 Point2D i = PathUtils.findIntersection(p0, v0, p1, v1);
\r
245 controlPoints.add(i);
\r
248 // Impossible requirements
\r
249 int flags = PathUtils.findNearestPoints(p0, v0, p1, v1, cp1, cp2);
\r
250 if ((flags & 1) == 1)
\r
251 controlPoints.add(cp1);
\r
252 if ((flags & 2) == 2)
\r
253 controlPoints.add(cp2);
\r
257 if (controlPoints.isEmpty()) {
\r
258 Point2D i = new Point2D.Double(
\r
259 (p0.getX() + p1.getX())/2,
\r
260 (p0.getY() + p1.getY())/2);
\r
261 controlPoints.add(i);
\r
263 for (Object dada : controlPoints) {
\r
264 Point2D c0 = (Point2D) dada;
\r
265 double price = p0.distance(c0)+c0.distance(p1);
\r
266 price -= p0.distance(p1);
\r
268 LinearEdgeSolutionNode s = new LinearEdgeSolutionNode();
\r
269 s.cost = cost + price;
\r
270 s.prevBranch = this;
\r
275 if (!c0.equals(p0) && !c0.equals(p1))
\r
278 Point2D np = s.cp == null ? p1 : c0;
\r
279 double dist = p0.distance(np);
\r
280 s.v0 = new Point2D.Double( (p0.getX() - np.getX())/dist, (p0.getY() - np.getY())/dist);
\r
282 np = s.cp == null ? p0 : c0;
\r
283 dist = p1.distance(np);
\r
284 s.v1 = new Point2D.Double( (p1.getX() - np.getX())/dist, (p1.getY() - np.getY())/dist);
\r
285 s.v1o = new Point2D.Double( (np.getX() - p1.getX())/dist, (np.getY() - p1.getY())/dist);
\r
287 // Penalty for changing direction
\r
288 if (v1!=null && !s.v0.equals(v1))
\r
290 // Penalty for going back
\r
291 if (v1o!=null && v1o.equals(s.v0))
\r
298 public double getCost() {
\r
302 public boolean isComplete() {
\r
303 return index>=reqs.size()-1;
\r
306 * Create path from root to this node
\r
309 public List<LinearEdgeSolutionNode> toList() {
\r
310 LinkedList<LinearEdgeSolutionNode> res = new LinkedList<LinearEdgeSolutionNode>();
\r
311 LinearEdgeSolutionNode branch = this;
\r
312 while (branch!=null) {
\r
313 res.addFirst(branch);
\r
314 branch = branch.prevBranch;
\r
318 public Path2D toPath() {
\r
319 Path2D p = new Path2D.Double();
\r
320 for (LinearEdgeSolutionNode s : toList())
\r
322 if (s.p0==null) continue;
\r
323 Point2D cur = p.getCurrentPoint();
\r
325 p.moveTo(s.p0.getX(), s.p0.getY());
\r
327 cur = p.getCurrentPoint();
\r
328 if ((s.cp!=null)&&(cur==null || !cur.equals(s.cp)))
\r
329 p.lineTo(s.cp.getX(), s.cp.getY());
\r
331 cur = p.getCurrentPoint();
\r
332 if (cur==null || !cur.equals(s.p1))
\r
333 p.lineTo(s.p1.getX(), s.p1.getY());
\r
341 * Create a linear path that follows path requirements
\r
342 * @param reqs requirements
\r
345 public static Path2D createPathLinear(final List<PathRequirement> reqs)
\r
347 LinearEdgeSolutionNode s = new LinearEdgeSolutionNode();
\r
349 LinearEdgeSolutionNode solution = (LinearEdgeSolutionNode) TreeProblem.findSolution(s, 2);
\r
350 if (solution==null) return null;
\r
351 return solution.toPath();
\r
355 * Create a line that follows path requirements
\r
359 public static Path2D createPathLine(final List<PathRequirement> reqs)
\r
361 Path2D result = new Path2D.Double();
\r
363 for (PathRequirement r : reqs) {
\r
365 result.moveTo(r.pos.getX(), r.pos.getY());
\r
367 result.lineTo(r.pos.getX(), r.pos.getY());
\r
374 * Create a right-angled (90 deg angles) path that follows requirements
\r
375 * @param reqs requirements
\r
378 public static Path2D createPathRightAngled(List<PathRequirement> reqs)
\r
380 Path2D res = new Path2D.Double();
\r
382 // for (PathRequirement r : reqs)
\r
391 * Create a path that follows requirements
\r
392 * @param reqs requirements
\r
395 public static Path2D createPathQuadratic(List<PathRequirement> reqs)
\r
397 Path2D res = new Path2D.Double();
\r
399 // for (PathRequirement r : reqs)
\r
409 * Path Requirement is a point thru which the path must "travel".
\r
411 static class PathRequirement {
\r
412 Point2D pos; // Position on diagram
\r
413 DirectionSet set; // Allowed directions
\r
414 boolean isBegin, isEnd;
\r
418 * Takes a look at an edge and creates a path travel plan (requiremetn).
\r
419 * Beginning of e must point to (possibly) connected terminal.
\r
420 * End of e must point to (possibly) connected terminal.
\r
423 * @param pathRequirement Path requirements
\r
425 public static void createPathRequirement(IElement e, List<PathRequirement> pathRequirement)
\r
427 Topology t = ElementUtils.getDiagram(e).getDiagramClass().getSingleItem(Topology.class);
\r
428 pathRequirement.clear();
\r
429 if (t==null) return;
\r
432 Connection c1 = t.getConnection(e, EdgeEnd.Begin);
\r
434 PathRequirement req = new PathRequirement();
\r
435 AffineTransform at = TerminalUtil.getTerminalPosOnDiagram(c1.node, c1.terminal);
\r
436 req.pos = new Point2D.Double( at.getTranslateX(), at.getTranslateY() );
\r
437 req.set = ElementUtils.getTerminalDirection(c1.node, c1.terminal);
\r
438 req.isBegin = true;
\r
439 pathRequirement.add(req);
\r
442 // Add control points
\r
443 AffineTransform elementTransform = ElementUtils.getTransform(e);
\r
444 BendsHandler bends = e.getElementClass().getSingleItem(BendsHandler.class);
\r
445 ArrayList<Bend> controlPoints = new ArrayList<Bend>();
\r
446 bends.getBends(e, controlPoints);
\r
447 for (Bend cp : controlPoints)
\r
449 Point2D pos = new Point2D.Double();
\r
450 bends.getBendPosition(e, cp, pos);
\r
451 elementTransform.transform(pos, pos);
\r
452 PathRequirement req = new PathRequirement();
\r
453 req.set = DirectionSet.NESW;
\r
455 pathRequirement.add(req);
\r
458 // TODO lisää transformit
\r
462 Connection c2 = t.getConnection(e, EdgeEnd.End);
\r
464 PathRequirement req = new PathRequirement();
\r
465 AffineTransform at = TerminalUtil.getTerminalPosOnDiagram(c2.node, c2.terminal);
\r
466 req.pos = new Point2D.Double( at.getTranslateX(), at.getTranslateY() );
\r
467 req.set = ElementUtils.getTerminalDirection(c2.node, c2.terminal);
\r
468 req.set = req.set.createInverse();
\r
470 pathRequirement.add(req);
\r