View Javadoc
1   package org.djutils.base;
2   
3   /**
4    * AngleUtil has some base methods to deal with angles, such as normalization between -PI and PI, and between 0 and 2*PI. <br>
5    * <br>
6    * Copyright (c) 2020-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
7    * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
8    * distributed under a three-clause BSD-style license, which can be found at
9    * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. <br>
10   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
11   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
12   */
13  public final class AngleUtil
14  {
15      /** Utility constructor. */
16      private AngleUtil()
17      {
18          // Utility constructor
19      }
20  
21      /** The value 2&pi;. */
22      public static final double PI2 = 2.0 * Math.PI;
23  
24      /**
25       * Normalize an angle (in radians) in a 2&pi; wide interval around &pi;, resulting in angles in the 0 to 2&pi; interval. An
26       * angle value of NaN or Infinity returns NaN.
27       * @param angle double; the angle in radians to normalize
28       * @return double; the normalized angle in radians
29       */
30      public static double normalizeAroundPi(final double angle)
31      {
32          return angle - PI2 * Math.floor(angle / PI2);
33      }
34  
35      /**
36       * Normalize an angle (in radians) in a 2&pi; wide interval around 0, resulting in angles in the -&pi; to &pi; interval. An
37       * angle value of NaN or Infinity returns NaN.
38       * @param angle double; the angle in radians to normalize
39       * @return double; the normalized angle in radians
40       */
41      public static double normalizeAroundZero(final double angle)
42      {
43          return angle + PI2 * Math.floor((-angle + Math.PI) / PI2);
44      }
45  
46      /**
47       * Return whether two angles (in radians) are less than epsilon apart. The method returns true, for instance when
48       * epsilonEquals(-Math.PI, Math.PI, 1E-6) is called, since the -PI and +PI angles have the same angle.
49       * @param angle1 double; the first angle in radians
50       * @param angle2 double; the second angle in radians
51       * @param epsilon double; the precision
52       * @return whether the two angles are less than epsilon apart
53       */
54      public static boolean epsilonEquals(final double angle1, final double angle2, final double epsilon)
55      {
56          if (Math.abs(angle1 - angle2) < epsilon)
57          {
58              return true;
59          }
60          double diff = Math.abs(normalizeAroundZero(angle2) - normalizeAroundZero(angle1));
61          return diff < epsilon || Math.abs(PI2 - diff) < epsilon;
62      }
63  
64      /**
65       * Interpolate between two angles that will first be normalized between 0 and 2&pi;, and take the interpolated angle when
66       * going clockwise from the first angle to the second angle. The fraction indicates where the angle between angle1 and
67       * angle2 will be. A fraction of 0.0 returns angle1, a fraction of 1.0 returns angle2, and a fraction of 0.5 returns the
68       * shortest angle halfway. A fraction of less than 0 or larger than 1 can be used for extrapolation. The resulting angle is
69       * normalized between -&pi; and &pi;. This means that the halfway interpolation between 0 and 2&pi; and between 2&pi; and 0
70       * are both 0, since the two angles are the same. Likewise, the halfway interpolation between -&pi; and &pi; is +/- &pi;
71       * where the sign depends on rounding.
72       * @param angle1 double; the first angle in radians
73       * @param angle2 double; the second angle in radians
74       * @param fraction double; the fraction for interpolation; 0.5 is halfway
75       * @return the normalized angle between angle1 and angle2
76       */
77      public static double interpolateClockwise(final double angle1, final double angle2, final double fraction)
78      {
79          double a1 = normalizeAroundPi(angle1);
80          double a2 = normalizeAroundPi(angle2);
81          if (a2 < a1)
82          {
83              a2 += PI2;
84          }
85          return normalizeAroundZero((1.0 - fraction) * a1 + fraction * a2);
86      }
87  
88      /**
89       * Interpolate between two angles taking the <i>shortest way</i>.
90       * @param angle1 double; the first angle in radians
91       * @param angle2 double; the second angle in radians
92       * @param fraction double; the fraction for interpolation; 0.5 is halfway, 0.0 returns angle1, 1.0 returns angle2.
93       * @return double; The interpolated angle, normalized around zero. If any input angle is not normalized around zero the
94       *         result will still be normalized, and the exact equality at fraction is 0.0 or 1.0 may not hold. When the
95       *         difference between angle1 and angle2 is very close to an odd multiple of PI, the <i>shortest way</i> between
96       *         those angles is ill-defined. The result of this method reflects this (fundamental) problem.
97       */
98      public static double interpolateShortest(final double angle1, final double angle2, final double fraction)
99      {
100         double result;
101         if (fraction == 1.0)
102         {
103             result = angle2; // Make sure that the promise of the exact result holds
104         }
105         else
106         {
107             double difference = angle2 - angle1;
108             if (difference < -Math.PI)
109             {
110                 difference = difference - PI2 * Math.ceil((difference - Math.PI) / PI2);
111             }
112             if (difference > Math.PI)
113             {
114                 difference = difference - PI2 * Math.floor((difference + Math.PI) / PI2);
115             }
116             result = angle1 + fraction * difference;
117         }
118         return normalizeAroundZero(result);
119     }
120 
121 }