//////////////////////////////////////// // Programming project code // UniBw M, 2022, 2023, 2024 // www.unibw.de/inf2 // (c) Mark Minas (mark.minas@unibw.de) //////////////////////////////////////// package pp.util; import java.util.Objects; import static pp.util.FloatMath.DEG_TO_RAD; import static pp.util.FloatMath.FLT_EPSILON; import static pp.util.FloatMath.RAD_TO_DEG; import static pp.util.FloatMath.abs; import static pp.util.FloatMath.atan2; import static pp.util.FloatMath.cos; import static pp.util.FloatMath.sin; import static pp.util.FloatMath.sqrt; /** * A class for representing angles by unit vectors where angles define * polar coordinates. */ public class Angle implements Comparable { /** * 0 degrees, i.e., along the x-axis */ public static final Angle ZERO = new Angle(1f, 0f); /** * 180 degrees, i.e., along the negative x-axis */ public static final Angle PI = new Angle(-1f, 0f); /** * Returns the minimum of the specified angles with respect to * {@linkplain #compareTo(Angle)}. * * @param u first angle to compare * @param v second angle to compare */ public static Angle min(Angle u, Angle v) { return u.compareTo(v) <= 0 ? u : v; } /** * The x-coordinate of the unit vector. */ public final float x; /** * The y-coordinate of the unit vector. */ public final float y; /** * Creates a new angle represented by the vector with the specified * coordinates. Note that the vector must have length 1. */ private Angle(float x, float y) { this.x = x; this.y = y; } /** * Returns the polar coordinates angle of the specified vector. * * @param x the vectors x value * @param y the vectors y value */ public static Angle fromVector(float x, float y) { final float len = sqrt(x * x + y * y); if (len < FLT_EPSILON) throw new IllegalArgumentException("null vector"); return new Angle(x / len, y / len); } /** * Returns the polar coordinates angle when looking from the first to the second * specified position. * * @param from first position * @param to second position */ public static Angle direction(Position from, Position to) { return fromVector(to.getX() - from.getX(), to.getY() - from.getY()); } /** * Returns an Angle object for the specified angle in radians. * * @param radians the specified radian value */ public static Angle fromRadians(float radians) { return new Angle(cos(radians), sin(radians)); } /** * Returns an Angle object for the specified angle in degrees. * * @param degrees the specified degrees value */ public static Angle fromDegrees(float degrees) { return fromRadians(degrees * DEG_TO_RAD); } /** * Returns the value of this angle in radians in the range (-pi,pi]. */ public float radians() { return atan2(y, x); } /** * Returns the value of this angle in degrees in the range (-180,180]. */ public float degrees() { return radians() * RAD_TO_DEG; } /** * Returns the x-coordinate of the unit vector, that is the cosine of the angle. */ public float getX() { return x; } /** * Returns the y-coordinate of the unit vector, that is the sinus of the angle. */ public float getY() { return y; } /** * Returns the angle obtained by adding the specified angle to this angle. * * @param o the other angle */ public Angle plus(Angle o) { return new Angle(x * o.x - y * o.y, x * o.y + y * o.x); } /** * Returns the angle obtained by subtracting the specified angle from this angle. * * @param o the other angle */ public Angle minus(Angle o) { return new Angle(y * o.y + x * o.x, y * o.x - x * o.y); } /** * Returns the bisector angle between this angle as left angle and the * specified right angle, that is, the angle halfway from the right to this * angle when turning from right to left. * * @param right right angle * @return the bisector angle between this as left and the specified right angle */ public Angle bisector(Angle right) { // compute vector of this.minus(right) final float dx = y * right.y + x * right.x; final float dy = y * right.x - x * right.y; if (abs(dy) < FLT_EPSILON) { // the difference is either 0° or 180° if (dx > 0f) return this; else return new Angle(-right.y, right.x); } final float mid = 0.5f * atan2(dy, dx); final float sum = right.radians() + mid; if (mid > 0f) return new Angle(cos(sum), sin(sum)); else return new Angle(-cos(sum), -sin(sum)); } /** * Returns true if turning left from the specified angle towards this angle is * shorter than turning right towards this angle. * * @param o another angle */ public boolean leftOf(Angle o) { return y * o.x - x * o.y > 0f; } /** * Returns true if turning right from the specified angle towards this angle is * shorter than turning left towards this angle. * * @param o another angle */ public boolean rightOf(Angle o) { return y * o.x - x * o.y < 0f; } /** * Compares this angle with the specified one and returns -1, 0, or 1 if * the value of this angle is less than, equal to, or greater than the value * of the specified angle, respectively, where angle values are in the * range [0,2*pi) and 0 means along the x-axis. * * @param o the other angle */ @Override public int compareTo(Angle o) { if (y == 0f) { if (o.y < 0f) return -1; if (o.y > 0f) return x > 0f ? -1 : 1; if (x > 0f) return o.x > 0f ? 0 : 1; return o.x > 0f ? -1 : 0; } if (y > 0f) { if (o.y < 0f) return -1; if (o.y == 0f) return o.x > 0f ? 1 : -1; } else if (o.y >= 0f) return 1; final float det = x * o.y - y * o.x; if (det == 0f) return 0; return det > 0f ? -1 : 1; } @Override public boolean equals(Object o) { if (this == o) return true; if (o instanceof Angle angle) return compareTo(angle) == 0; return false; } @Override public int hashCode() { return Objects.hash(x, y); } /** * Returns a string representation of this angle in degrees in the range [0,2*pi). */ @Override public String toString() { if (degrees() < 0f) return (degrees() + 360f) + "°"; return degrees() + "°"; } }