View Javadoc
1   package org.djutils.draw;
2   
3   import java.util.Objects;
4   
5   import org.djutils.exceptions.Throw;
6   
7   /**
8    * Class encoding a direction in 3d space. It combines dirY (similar to tilt; measured as an angle from the positive
9    * z-direction) and dirZ (similar to pan; measured as an angle from the positive x-direction).
10   * <p>
11   * Copyright (c) 2023-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
12   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
13   * distributed under a three-clause BSD-style license, which can be found at
14   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
15   * </p>
16   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
17   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
18   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
19   */
20  public class Direction3d
21  {
22      /** Rotation around y-axis. */
23      @SuppressWarnings("checkstyle:visibilitymodifier")
24      public final double dirY;
25  
26      /** Rotation around z-axis. */
27      @SuppressWarnings("checkstyle:visibilitymodifier")
28      public final double dirZ;
29  
30      /**
31       * Construct a Direction3d.
32       * @param dirY the dirY component for the new Direction3d
33       * @param dirZ the dirZ component for the new Direction3d
34       * @throws ArithmeticException when <code>dirY</code>, or <code>dirZ</code> is <code>NaN</code>
35       * @throws IllegalArgumentException when <code>dirY</code>, or <code>dirZ</code> is infinite
36       */
37      public Direction3d(final double dirY, final double dirZ)
38      {
39          Throw.whenNaN(dirY, "dirY");
40          Throw.whenNaN(dirZ, "dirZ");
41          Throw.when((!Double.isFinite(dirY)) || (!Double.isFinite(dirZ)), IllegalArgumentException.class,
42                  "dirY and dirZ must be finite");
43          this.dirY = dirY;
44          this.dirZ = dirZ;
45      }
46  
47      /**
48       * Retrieve the dirY component of this Direction3d.
49       * @return the <code>dirY</code> component of this <code>Direction3d</code>
50       */
51      public double getDirY()
52      {
53          return this.dirY;
54      }
55  
56      /**
57       * Retrieve the dirZ component of this Direction3d.
58       * @return the <code>dirZ</code> component of this <code>Direction3d</code>
59       */
60      public double getDirZ()
61      {
62          return this.dirZ;
63      }
64  
65      /**
66       * Determine the angle between this Direction3d and another Direction3d. Liberally based on
67       * https://www.cuemath.com/geometry/angle-between-vectors/
68       * @param otherDirection the other Direction3d
69       * @return double the angle in Radians
70       * @throws NullPointerException when <code>otherDirection</code> is <code>null</code>
71       */
72      public double directionDifference(final Direction3d otherDirection)
73      {
74          double sinDirY = Math.sin(this.dirY);
75          double uX = Math.cos(this.dirZ) * sinDirY;
76          double uY = Math.sin(this.dirZ) * sinDirY;
77          double uZ = Math.cos(this.dirY);
78          double otherSinDirY = Math.sin(otherDirection.dirY);
79          double oX = Math.cos(otherDirection.dirZ) * otherSinDirY;
80          double oY = Math.sin(otherDirection.dirZ) * otherSinDirY;
81          double oZ = Math.cos(otherDirection.dirY);
82          double cosine = uX * oX + uY * oY + uZ * oZ;
83          if (Math.abs(cosine) > 1.0 && Math.abs(cosine) < 1.0 + 10 * Math.ulp(1.0))
84          {
85              cosine = Math.signum(cosine); // Fix rounding error
86          }
87          return (Math.acos(cosine));
88      }
89  
90      @Override
91      public int hashCode()
92      {
93          return Objects.hash(this.dirY, this.dirZ);
94      }
95  
96      @Override
97      @SuppressWarnings("checkstyle:needbraces")
98      public boolean equals(final Object obj)
99      {
100         if (this == obj)
101             return true;
102         if (obj == null)
103             return false;
104         if (getClass() != obj.getClass())
105             return false;
106         Direction3d other = (Direction3d) obj;
107         return Double.doubleToLongBits(this.dirY) == Double.doubleToLongBits(other.dirY)
108                 && Double.doubleToLongBits(this.dirZ) == Double.doubleToLongBits(other.dirZ);
109     }
110 
111     @Override
112     public String toString()
113     {
114         return "Direction3d [dirY=" + this.dirY + ", dirZ=" + this.dirZ + "]";
115     }
116 
117 }