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 }