/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package tufts.vue; import java.awt.geom.Point2D; import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.Set; /** * A class for some utility functions. * @author theoky * */ public class Toolbox { /** * Compute the angle between two points with p as base. * Points are assumed to be in Java coordinate space (-1 in y up) * @see angleBetween2PointsCoord */ public static double angleBetween2Points(Point2D p, Point2D q) { return angleBetween2PointsCoord(p, q, true); } /** * Compute the angle between two points p and q with point p as base. * @param p Point 1 * @param q Point 2 * @param javaCoord true if coordinates are with negative y up * @return angle in radian */ public static double angleBetween2PointsCoord(Point2D p, Point2D q, boolean javaCoord) { double angle; double factorY = 1; if (javaCoord) { factorY = -1; } double k1 = q.getX() - p.getX(); double k2 = factorY * (q.getY() - p.getY()); if (k1 == 0) { if (k2 > 0) { angle = Math.PI/2; } else { angle = -Math.PI/2; } } else { angle = Math.atan(k2/k1); } if (k1 < 0) { angle += Math.PI; } else { if (angle < 0) { angle += Math.PI*2; } } return angle; } /** * This function fills the HashSet deepSelection with all objects at depth "depth" which * are reachable from the set comps. Objects in the set userSelection are not added * to deepSelection * @param userSelection the currently selected objects * @param deepSelection the result set containing the selected objects * @param comps the set of components to start with * @param depth the maximal depth to search * @param expandIncoming follow incoming links * @param expandOutgoing follow outgoing links * @param alreadyVisited stores the components already visited and the depth where they were found. */ public static void findChildrenToDepth( HashSet<LWComponent> userSelection, HashSet<LWComponent> deepSelection, Collection<LWComponent> comps, int depth, boolean expandIncoming, boolean expandOutgoing, Hashtable<LWComponent, Integer> alreadyVisited) { for (LWComponent comp : comps) { Integer alreadyVisitedAtLevel = alreadyVisited.get(comp); // If this component has already been visited at a higher depth, don't revisit it. if (alreadyVisitedAtLevel == null || alreadyVisitedAtLevel.intValue() < depth) { alreadyVisited.put(comp, new Integer(depth)); boolean compIsUserSelected = userSelection.contains(comp), compIsLink = (comp.getClass() == LWLink.class); int nextDepth = depth - (compIsLink ? (compIsUserSelected ? 1 : 0) : 1); if (!compIsUserSelected) { deepSelection.add(comp); } if (compIsLink) { // Recurse for link's endpoints. LWLink link = (LWLink)comp; LWComponent head = link.getHead(), tail = link.getTail(); int arrowState = link.getArrowState(); HashSet<LWComponent> compsToTraverse = new HashSet<LWComponent>(); if (head != null && (expandIncoming && arrowState != LWLink.ARROW_HEAD || expandOutgoing && arrowState != LWLink.ARROW_TAIL)) { compsToTraverse.add(head); } if (tail != null && (expandIncoming && arrowState != LWLink.ARROW_TAIL || expandOutgoing && arrowState != LWLink.ARROW_HEAD)) { compsToTraverse.add(tail); } if (!compsToTraverse.isEmpty()) { findChildrenToDepth(userSelection, deepSelection, compsToTraverse, nextDepth, expandIncoming, expandOutgoing, alreadyVisited); } } if (nextDepth > -1) { // Recurse for component's links. HashSet<LWComponent> linksToTraverse = new HashSet<LWComponent>(); if (expandIncoming && expandOutgoing) { linksToTraverse.addAll(comp.getLinks()); } else if (expandIncoming) { linksToTraverse.addAll(comp.getIncomingLinks()); } else if (expandOutgoing) { linksToTraverse.addAll(comp.getOutgoingLinks()); } if (!linksToTraverse.isEmpty()) { findChildrenToDepth(userSelection, deepSelection, linksToTraverse, nextDepth, expandIncoming, expandOutgoing, alreadyVisited); } } } } } /** * Returns the angle from a set with the minimum difference * to a given reference angle * @param angles the set of angles to compare against * @param directionAlpha the reference angle * @return the angle with the minimal difference to the reference angle */ public static double minAlphaDifference( Set<Double> angles, double directionAlpha) { double tmpAlpha = 0; double oldAlpha = Math.PI * 2; double curAlpha = 0; for (Double alpha : angles) { tmpAlpha = angleDifference(directionAlpha, alpha); if (tmpAlpha < oldAlpha) { oldAlpha = tmpAlpha; curAlpha = alpha; } } return curAlpha; } /** * Computes the difference between two angles given in radian. * @param alpha angle 1 * @param beta angle 2 * @return the difference in radian */ public static double angleDifference(double alpha, double beta) { double tmpAlpha; tmpAlpha = Math.abs(alpha - beta); if (tmpAlpha > Math.PI) { // more than 180 degrees, reduce tmpAlpha = 2*Math.PI - tmpAlpha; } return tmpAlpha; } }