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