/******************************************************************************* * Copyright (c) 2016 itemis AG and others. * 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: * Alexander Nyßen (itemis AG) - initial API and implementation * Matthias Wienand (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef.fx.anchors; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.gef.fx.anchors.DynamicAnchor.PreferredOrientation; import org.eclipse.gef.geometry.planar.ICurve; import org.eclipse.gef.geometry.planar.Line; import org.eclipse.gef.geometry.planar.Point; import org.eclipse.gef.geometry.planar.Rectangle; import javafx.geometry.Orientation; /** * An {@link IComputationStrategy} that computes anchor position by orthogonally * projecting the respective anchored reference point to the outline of the * anchorage reference geometry so that the respective point has minimal * distance to the anchored reference point and resembles the same x- (vertical * projection) or y-coordinate (horizontal projection). * * @author anyssen * @author mwienand */ public class OrthogonalProjectionStrategy extends ProjectionStrategy { @Override protected Point computeProjectionInScene( List<ICurve> anchorageOutlinesInScene, Point anchoredReferencePointInScene, Set<Parameter<?>> parameters) { // obtain additionally required parameter PreferredOrientation parameter = Parameter.get(parameters, PreferredOrientation.class); Orientation orientationHint = parameter.get(); Point nearestOrthogonalProjectionInScene = null; double nearestOrthogonalProjectionDistance = Double.MAX_VALUE; for (ICurve segment : anchorageOutlinesInScene) { // determine nearest orthogonal projection of each curve Point projection = getOrthogonalProjection(segment, anchoredReferencePointInScene, orientationHint); if (projection != null) { double distance = projection .getDistance(anchoredReferencePointInScene); if (nearestOrthogonalProjectionInScene == null || distance < nearestOrthogonalProjectionDistance) { nearestOrthogonalProjectionInScene = projection; nearestOrthogonalProjectionDistance = distance; } } } if (nearestOrthogonalProjectionInScene != null) { return nearestOrthogonalProjectionInScene; } else { // Fall back to nearest projection return super.computeProjectionInScene(anchorageOutlinesInScene, anchoredReferencePointInScene, parameters); } } /** * Returns a point on the {@link ICurve} for which holds that its * y-coordinate is the same as that of the given reference point, and its * distance to the given reference point is minimal (i.e. there is no other * point with the same y-coordinate that has a smaller distance), if such a * point exists. * * @param curve * The {@link ICurve} to test. The returned {@link Point} has to * be contained by it. * * @param reference * The reference point which is used to determine the distance. * @return The point on the {@link ICurve} that is horizontally nearest to * the given reference point. */ private Point getHorizontalProjection(ICurve curve, Point reference) { // Determine points on curve with same y-coordinate; by computing a // line with the respective y-coordinate inside its bounds; then // computing the nearest intersection on the curve Rectangle bounds = curve.getBounds(); Line line = new Line(bounds.getX(), reference.y, bounds.getX() + bounds.getWidth(), reference.y); Point projection = getNearestOrthogonalProjection(curve, reference, line); // a horizontal projection is constant in y, therefore, we can // ensure that the projection has the same y coordinate as the // reference if (projection != null) { projection.y = reference.y; } return projection; } private Point getNearestOrthogonalProjection(ICurve curve, Point reference, Line line) { if (curve.overlaps(line)) { ICurve[] overlaps = curve.getOverlaps(line); // XXX: All overlaps have to be lines since a line can only // overlap with another line. As such, it is sufficient to check // the start and end points of the overlaps. Point nearest = null; double distance = 0; for (ICurve overlap : overlaps) { Point currentNearest = Point.nearest(reference, new Point[] { overlap.getP1(), overlap.getP2() }); double currentDistance = reference.getDistance(currentNearest); if (nearest == null || currentDistance < distance) { nearest = currentNearest; distance = currentDistance; } } return nearest; } else if (curve.intersects(line)) { Point nearest = Point.nearest(reference, curve.getIntersections(line)); return nearest; } // no point found for the given y-coordinate return null; } /** * Returns a point on the {@link ICurve} for which holds that its * x-coordinate or y-coordinate is the same as that of the given reference * point, and its distance to the given reference point is minimal (i.e. * there is no other point with the same x-coordinate or y-coordinate that * has a smaller distance). * * @param curve * The {@link ICurve} to test. The returned {@link Point} has to * be contained by it. * * @param reference * The reference point which is used to determine the distance. * @param orientationHint * A preferred {@link Orientation} or <code>null</code> to * indicate no preference. * @return The point on the {@link ICurve} that is horizontally or * vertically nearest to the given reference point. */ private Point getOrthogonalProjection(ICurve curve, Point reference, Orientation orientationHint) { Point nearestHorizonalProjection = getHorizontalProjection(curve, reference); if (nearestHorizonalProjection == null) { // if there is no horizontal projection, the vertical one has to // be minimal (if it exists) return getVerticalProjection(curve, reference); } else if (orientationHint == Orientation.HORIZONTAL) { return nearestHorizonalProjection; } else { Point nearestVerticalProjection = getVerticalProjection(curve, reference); if (nearestVerticalProjection == null) { // if there is no vertical projection, the horizontal one // has to be minimal return nearestHorizonalProjection; } else if (orientationHint == Orientation.VERTICAL) { return nearestVerticalProjection; } else { // compute whether horizontal or vertical is minimal double horizontalDistance = nearestHorizonalProjection .getDistance(reference); double verticalDistance = nearestVerticalProjection .getDistance(reference); if (horizontalDistance <= verticalDistance) { return nearestHorizonalProjection; } return nearestVerticalProjection; } } } @Override public Set<Class<? extends Parameter<?>>> getRequiredParameters() { Set<Class<? extends Parameter<?>>> dynamicParameters = new HashSet<>(); dynamicParameters.addAll(super.getRequiredParameters()); dynamicParameters.add(PreferredOrientation.class); return dynamicParameters; } /** * Returns a point on the {@link ICurve} for which holds that its * x-coordinate is the same as that of the given reference point, and its * distance to the given reference point is minimal (i.e. there is no other * point with the same x-coordinate that has a smaller distance), if such a * point exists. * * @param curve * The {@link ICurve} to test. The returned {@link Point} has to * be contained by it. * * @param reference * The reference point which is used to determine the distance. * @return The point on the {@link ICurve} that is vertically nearest to the * given reference point. */ private Point getVerticalProjection(ICurve curve, Point reference) { // Determine points on curve with same x-coordinate; by computing a // line with the respective x-coordinate inside its bounds; then // computing the nearest intersection on the curve Rectangle bounds = curve.getBounds(); Line line = new Line(reference.x, bounds.getY(), reference.x, bounds.getY() + bounds.getHeight()); Point projection = getNearestOrthogonalProjection(curve, reference, line); // a vertical projection is constant in x, therefore, we can // ensure that the projection has the same x coordinate as the // reference if (projection != null) { projection.x = reference.x; } return projection; } }