/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation 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: * IBM Corporation - initial API and implementation * Alexander Nyßen (Research Group Software Construction, RWTH Aachen University) - contribution for Bugzilla #245182 * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef.geometry.euclidean; import java.io.Serializable; import org.eclipse.gef.geometry.internal.utils.PrecisionUtils; import org.eclipse.gef.geometry.planar.Point; /** * Represents a {@link Vector} within 2-dimensional Euclidean space. * * @author ebordeau * @author rhudson * @author pshah * @author ahunter * @author anyssen * @author mwienand * */ public class Vector implements Cloneable, Serializable { private static final long serialVersionUID = 1L; /** * The (0,0) vector. */ public static final Vector NULL = new Vector(0, 0); /** The x coordinate of this {@link Vector}. */ public double x; /** The y coordinate of this {@link Vector}. */ public double y; /** * Constructs a {@link Vector} that points in the specified direction. * * @param x * x coordinate * @param y * y coordinate */ public Vector(double x, double y) { if (Double.isNaN(x)) { throw new IllegalArgumentException( "x coordinate has to be differen from NaN."); } if (Double.isNaN(y)) { throw new IllegalArgumentException( "y coordinate has to be differen from NaN."); } this.x = x; this.y = y; } /** * Constructs a {@link Vector} that is the position {@link Vector} of the * given {@link Point}. * * @param p * the {@link Point} to construct a position {@link Vector} for */ public Vector(Point p) { this(p.x, p.y); } /** * Constructs a {@link Vector} representing the direction and magnitude * between to provided {@link Point}s. * * @param start * the start {@link Point} * @param end * the end {@link Point} */ public Vector(Point start, Point end) { x = end.x - start.x; y = end.y - start.y; } /** * Constructs a {@link Vector} representing the difference between two * provided {@link Vector}s. * * @param start * the start {@link Vector} * @param end * the end {@link Vector} */ public Vector(Vector start, Vector end) { x = end.x - start.x; y = end.y - start.y; } /** * Clones the given {@link Vector} using {@link Vector#getCopy()}. * * @return a copy of this {@link Vector} object */ @Override public Vector clone() { return getCopy(); } /** * @see java.lang.Object#equals(Object) */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Vector) { Vector r = (Vector) obj; return PrecisionUtils.equal(this.x, r.x) && PrecisionUtils.equal(this.y, r.y); } return false; } /** * Returns a new {@link Vector} that represents the sum of this * {@link Vector} and the given other {@link Vector}. * * @param other * the {@link Vector} that is added to this {@link Vector} * @return a new {@link Vector} representing the sum of this {@link Vector} * and the given other {@link Vector} */ public Vector getAdded(Vector other) { return new Vector(x + other.x, y + other.y); } /** * Returns the smallest {@link Angle} between this {@link Vector} and the * provided one. * * @param other * the {@link Vector} for which the smallest {@link Angle} to * this {@link Vector} is calculated * @return the smallest {@link Angle} between this {@link Vector} and the * provided one */ public Angle getAngle(Vector other) { double length = getLength() * other.getLength(); if (length == 0) { throw new ArithmeticException("Division by zero."); } double cosAlpha = getDotProduct(other) / length; // compensate rounding effects if (cosAlpha > 1) { cosAlpha = 1; } else if (cosAlpha < -1) { cosAlpha = -1; } return Angle.fromRad(Math.acos(cosAlpha)); } /** * Returns the counter-clockwise (CCW) {@link Angle} between this * {@link Vector} and the provided one. * * @param other * the {@link Vector} for which the CCW {@link Angle} to this * {@link Vector} is calculated * @return the counter-clockwise {@link Angle} between this {@link Vector} * and the provided one */ public Angle getAngleCCW(Vector other) { Angle angle = getAngle(other); if (getCrossProduct(other) > 0) { return angle.getOppositeFull(); } return angle; } /** * Returns the clockwise (CW) {@link Angle} between this {@link Vector} and * the provided one. * * @param other * the {@link Vector} for which the CW {@link Angle} to this * {@link Vector} is calculated * @return the clockwise {@link Angle} between this {@link Vector} and the * provided one */ public Angle getAngleCW(Vector other) { return getAngleCCW(other).getOppositeFull(); } /** * Creates a new {@link Vector} which represents the average of this * {@link Vector} with the provided one. * * @param other * the {@link Vector} for which the average with this * {@link Vector} is calculated * @return a new {@link Vector} which represents the average of this * {@link Vector} and the provided one */ public Vector getAveraged(Vector other) { return new Vector((x + other.x) / 2, (y + other.y) / 2); } /** * Returns a copy of this {@link Vector} object. * * @return a copy of this {@link Vector} object */ public Vector getCopy() { return new Vector(x, y); } /** * Calculates the cross product of this {@link Vector} (lhs) and the given * other {@link Vector} (rhs). * * @param other * the rhs {@link Vector} for which the cross product with this * {@link Vector} is calculated * @return the cross product of this {@link Vector} (lhs) and the given * other {@link Vector} (rhs) */ public double getCrossProduct(Vector other) { return x * other.y - y * other.x; } /** * Calculates the magnitude of the cross product of this {@link Vector} with * the given other {@link Vector}. This method normalizes both * {@link Vector}s before calculating the cross product. The resulting * dissimilarity value represents the amount by which two {@link Vector}s * are directionally different. For parallel {@link Vector}s 0 is returned. * * @param other * the {@link Vector} to compare to this {@link Vector} * @return the dissimilarity of both {@link Vector}s */ public double getDissimilarity(Vector other) { return Math.abs(getNormalized().getCrossProduct(other.getNormalized())); } /** * Creates a new {@link Vector} which represents this {@link Vector} divided * by the provided scalar value. * * @param factor * the divisor * @return a new {@link Vector} which represents this {@link Vector} divided * by the provided scalar value */ public Vector getDivided(double factor) { if (factor == 0) { throw new ArithmeticException("Division by zero."); } return new Vector(x / factor, y / factor); } /** * Calculates the dot product of this {@link Vector} and the given other * {@link Vector}. * * @param other * the {@link Vector} for which the dot product with this * {@link Vector} is calculated * @return the dot product of the two {@link Vector}s */ public double getDotProduct(Vector other) { return x * other.x + y * other.y; } /** * Returns the length of this {@link Vector}. * * @return the length of this {@link Vector} */ public double getLength() { return Math.sqrt(getDotProduct(this)); } /** * Creates a new {@link Vector} which represents this {@link Vector} * multiplied by the provided scalar value. * * @param factor * the scalar multiplication factor to scale this {@link Vector} * @return a new {@link Vector} which represents this {@link Vector} * multiplied by the provided scalar value */ public Vector getMultiplied(double factor) { return new Vector(x * factor, y * factor); } /** * Creates a new {@link Vector} that has the same direction as this * {@link Vector} and a length of 1. * * @return a new {@link Vector} with the same direction as this * {@link Vector} and a length of 1 */ public Vector getNormalized() { return clone().getMultiplied(1 / getLength()); } /** * Returns the orthogonal complement of this {@link Vector}, which is * defined to be (-y, x). * * @return the orthogonal complement of this {@link Vector} */ public Vector getOrthogonalComplement() { return new Vector(-y, x); } /** * Returns a new {@link Vector} which corresponds to this {@link Vector} * after rotating it counter-clockwise (CCW) by the given {@link Angle}. * * @param angle * the rotation {@link Angle} * @return a new {@link Vector} which represents the result of the CCW * rotation of this {@link Vector} */ public Vector getRotatedCCW(Angle angle) { return clone().rotateCCW(angle); } /** * Returns a new {@link Vector} which corresponds to this {@link Vector} * after rotating it clockwise (CW) by the given {@link Angle}. * * @param angle * the rotation {@link Angle} * @return a new {@link Vector} which represents the result of the CW * rotation of this {@link Vector} */ public Vector getRotatedCW(Angle angle) { return clone().rotateCW(angle); } /** * Calculates the similarity of this {@link Vector} and the provided one. * The similarity is defined as the absolute value of the dotProduct(). For * orthogonal {@link Vector}s, 0 is returned. * * @param other * the {@link Vector} for which the similarity to this * {@link Vector} is calculated * @return the similarity of this {@link Vector} and the provided one * @see Vector#getDissimilarity(Vector) */ public double getSimilarity(Vector other) { return Math.abs(getDotProduct(other)); } /** * Returns a new {@link Vector} that represents the difference of this * {@link Vector} and the provided one. * * @param other * the {@link Vector} that is subtracted from this {@link Vector} * @return a new {@link Vector} representing the difference of this * {@link Vector} and the provided one */ public Vector getSubtracted(Vector other) { return new Vector(x - other.x, y - other.y); } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { // calculating a better hashCode is not possible, because due to the // imprecision, equals() is no longer transitive return 0; } /** * Checks if this {@link Vector} is horizontal, i.e. whether its horizontal * component (the x coordinate) does not equal 0, while its vertical * component (the y coordinate) does. * * @return <code>true</code> if this {@link Vector}'s x coordinate does not * equal 0 and this {@link Vector}'s y coordinate does equal 0, * otherwise <code>false</code> */ public boolean isHorizontal() { return !PrecisionUtils.equal(x, 0) && PrecisionUtils.equal(y, 0); } /** * Checks if this {@link Vector}'s x and y coordinates are equal to 0. * * @return <code>true</code> if this {@link Vector}'s x and y coordinates * are equal to 0, otherwise <code>false</code> */ public boolean isNull() { return equals(NULL); } /** * Checks if this {@link Vector} and the provided one are orthogonal to each * other. * * @param other * the {@link Vector} which is checked for orthogonality to this * {@link Vector} * @return <code>true</code> if this {@link Vector} and the provided one are * orthogonal to each other, otherwise <code>false</code> */ public boolean isOrthogonalTo(Vector other) { return PrecisionUtils.equal(getSimilarity(other), 0); } /** * Checks if this {@link Vector} and the provided one are parallel to each * other. * * @param other * the {@link Vector} that is checked to be parallel to this * {@link Vector} * @return <code>true</code> if this {@link Vector} and the provided one are * parallel, otherwise <code>false</code> */ public boolean isParallelTo(Vector other) { Angle alpha = getAngle(other); alpha.setRad(2d * alpha.rad()); return alpha.equals(Angle.fromRad(0d)); } /** * Checks if this {@link Vector} is vertical, i.e. whether its vertical * component (the x coordinate) does not equal 0, while its horizontal * component (the y coordinate) does. * * @return <code>true</code> if this {@link Vector}'s y coordinate does not * equal 0 and this {@link Vector}'s x coordinate does equal 0, */ public boolean isVertical() { return !PrecisionUtils.equal(y, 0) && PrecisionUtils.equal(x, 0); } /** * Rotates this {@link Vector} counter-clockwise (CCW) by the given * {@link Angle}. * * @param angle * the rotation {@link Angle} * @return <code>this</code> for convenience */ public Vector rotateCCW(Angle angle) { return rotateCW(angle.getOppositeFull()); } /** * Rotates this {@link Vector} clockwise (CW) by the given {@link Angle}. * * @param angle * the rotation {@link Angle} * @return <code>this</code> for convenience */ public Vector rotateCW(Angle angle) { double alpha = angle.rad(); double nx = x * Math.cos(alpha) - y * Math.sin(alpha); double ny = x * Math.sin(alpha) + y * Math.cos(alpha); x = nx; y = ny; return this; } /** * Returns a {@link Point} representing this {@link Vector}. * * @return a {@link Point} representing this {@link Vector} */ public Point toPoint() { return new Point(x, y); } /** * @see java.lang.Object#toString() */ @Override public String toString() { return "Vector: [" + x + "," + y + "]";//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ } }