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