/*******************************************************************************
* Copyright (c) 2011, 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:
* Matthias Wienand (itemis AG) - initial API and implementation
* Colin Sharples - contribution for Bugzilla #460754, #491402
*
*******************************************************************************/
package org.eclipse.gef.geometry.euclidean;
import java.io.Serializable;
import org.eclipse.gef.geometry.internal.utils.PrecisionUtils;
/**
* <p>
* An {@link Angle} object abstracts the angle's unit. It provides a simple
* interface to construct it from degrees or radians. Additionally, some useful
* calculations are implemented. But for sine/cosine/tangent calculations you
* may use the Math package.
* </p>
* <p>
* Every {@link Angle} object is normalized. That means, you will never
* encounter an {@link Angle} object beyond 360/2pi or below 0/0
* (degrees/radians).
* </p>
*
* @author mwienand
*
*/
public class Angle implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private static final double DEG_TO_RAD = Math.PI / 180d;
private static final double RAD_TO_DEG = 180d / Math.PI;
private static final double RAD_180 = Math.PI;
private static final double RAD_360 = 2 * Math.PI;
/**
* Constructs a new {@link Angle} object representing the given value. The
* value is interpreted as being in degrees.
*
* @param degrees
* the angle in degrees
* @return an {@link Angle} object representing the passed-in angle given in
* degrees
*/
public static Angle fromDeg(double degrees) {
return new Angle(degrees * DEG_TO_RAD);
}
/**
* Constructs a new {@link Angle} object representing the given value. The
* value is interpreted as being in radians.
*
* @param radians
* the angle in radians
* @return an {@link Angle} object representing the passed-in angle given in
* radians
*/
public static Angle fromRad(double radians) {
return new Angle(radians);
}
private double rad = 0d;
/**
* Constructs a new {@link Angle} object initialized with 0deg/0rad.
*/
public Angle() {
}
/**
* Constructs a new {@link Angle} object with the given value in radians.
*
* @param rad
* the angle's value
*/
public Angle(double rad) {
setRad(rad);
}
/**
* Overridden with public visibility as proposed in {@link Cloneable}.
*/
@Override
public Angle clone() {
return getCopy();
}
/**
* Returns the value of this {@link Angle} object in degrees.
*
* @return this {@link Angle}'s value in degrees.
*/
public double deg() {
return rad * RAD_TO_DEG;
}
@Override
public boolean equals(Object otherObj) {
if (otherObj != null && otherObj instanceof Angle) {
Angle other = (Angle) otherObj;
double myRad = this.rad;
double otherRad = other.rad;
final double hi = 1.5 * Math.PI;
final double lo = 0.5 * Math.PI;
if (myRad > hi && otherRad < lo) {
otherRad += RAD_360;
} else if (myRad < lo && otherRad > hi) {
myRad += RAD_360;
}
return PrecisionUtils.equal(myRad, otherRad);
}
return false;
}
/**
* Returns the sum of this and the given other {@link Angle} object as a new
* {@link Angle} object.
*
* @param other
* the {@link Angle} to add
* @return the sum of this and the given other {@link Angle} as a new
* {@link Angle} object
*/
public Angle getAdded(Angle other) {
return Angle.fromRad(this.rad + other.rad);
}
/**
* Creates and returns a copy of this {@link Angle}.
*
* @return a copy of this {@link Angle}
*/
public Angle getCopy() {
return Angle.fromRad(this.rad);
}
/**
* Returns the difference between this {@link Angle} and another
* {@link Angle} in a counter-clockwise direction
*
* @param other
* the other angle to compare to
* @return the difference between this {@link Angle} and another
* {@link Angle} in a counter-clockwise direction
*/
public Angle getDeltaCCW(Angle other) {
return new Angle(this.rad - other.rad);
}
/**
* Returns the difference between this {@link Angle} and another
* {@link Angle} in a clockwise direction
*
* @param other
* the other angle to compare to
* @return the difference between this {@link Angle} and another
* {@link Angle} in a clockwise direction
*/
public Angle getDeltaCW(Angle other) {
return new Angle(other.rad - this.rad);
}
/**
* Returns a new {@link Angle} object representing this {@link Angle}
* multiplied by the given factor.
*
* @param factor
* the multiplication factor
* @return a new {@link Angle} object representing this {@link Angle}
* multiplied by the given factor
*/
public Angle getMultiplied(double factor) {
return Angle.fromRad(rad * factor);
}
/**
* Returns the opposite {@link Angle} of this {@link Angle} in a full circle
* as a new {@link Angle} object.
*
* @return the opposite {@link Angle} of this {@link Angle} in a full circle
* as a new {@link Angle} object
*/
public Angle getOppositeFull() {
return Angle.fromRad(RAD_360 - rad);
}
/**
* Returns the opposite {@link Angle} of this {@link Angle} in a semi-circle
* as a new {@link Angle} object.
*
* @return the opposite {@link Angle} of this {@link Angle} in a semi-circle
* as a new {@link Angle} object
*/
public Angle getOppositeSemi() {
return Angle.fromRad(RAD_180 - rad);
}
/**
* Returns the reverse {@link Angle} of this {@link Angle} in a full circle
* as a new {@link Angle} object.
*
* @return the reverse {@link Angle} of this {@link Angle} in a full circle
* as a new {@link Angle} object
*/
public Angle getReverse() {
return Angle.fromRad(rad + RAD_180);
}
@Override
public int hashCode() {
// calculating a better hashCode is not possible, because due to the
// imprecision, equals() is no longer transitive
return 0;
}
/**
* Tests if the other {@link Angle} is within a half-circle clockwise
* rotation from this {@link Angle}
*
* @param other
* the other angle to compare to
* @return true if the a clockwise rotation to the other angle is less than
* 180deg
*/
public boolean isClockwise(Angle other) {
return getDeltaCW(other).rad <= RAD_180;
}
/**
* Normalizes this {@link Angle} to the range from 0deg to 360deg or rather
* from 0 to 2pi (radians).
*/
private Angle normalize() {
rad -= RAD_360 * Math.floor(rad / RAD_360);
return this;
}
/**
* Returns this {@link Angle}'s value in radians.
*
* @return This {@link Angle}'s value in radians.
*/
public double rad() {
return rad;
}
/**
* Sets this {@link Angle}'s value to the passed-in value in degrees.
*
* @param degrees
* the angle's value in degrees
* @return <code>this</code> for convenience
*/
public Angle setDeg(double degrees) {
rad = degrees * DEG_TO_RAD;
normalize();
return this;
}
/**
* Sets this {@link Angle}'s value to the passed-in value in radians.
*
* @param radians
* the angle's value in radians
* @return <code>this</code> for convenience
*/
public Angle setRad(double radians) {
rad = radians;
normalize();
return this;
}
@Override
public String toString() {
return "Angle(" + Double.toString(rad) + "rad ("
+ Double.toString(deg()) + "deg))";
}
}