001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.util.imagery; 003 004import org.openstreetmap.josm.tools.Utils; 005 006/** 007 * A basic 3D vector class (immutable) 008 * @author Taylor Smock (documentation, spherical conversions) 009 * @since 18246 010 */ 011public final class Vector3D { 012 /** 013 * This determines how arguments are used in {@link Vector3D#Vector3D(VectorType, double, double, double)}. 014 */ 015 public enum VectorType { 016 /** Standard cartesian coordinates (x, y, z) */ 017 XYZ, 018 /** Physics (radial distance, polar angle, azimuthal angle) */ 019 RPA, 020 /** Mathematics (radial distance, azimuthal angle, polar angle) */ 021 RAP 022 } 023 024 /** A non-null default vector */ 025 public static final Vector3D DEFAULT_VECTOR_3D = new Vector3D(0, 0, 1); 026 027 private final double x; 028 private final double y; 029 private final double z; 030 /* The following are all lazily calculated, but should always be the same */ 031 /** The radius r */ 032 private volatile double radialDistance = Double.NaN; 033 /** The polar angle theta (inclination) */ 034 private volatile double polarAngle = Double.NaN; 035 /** Cosine of polar angle (angle from Z axis, AKA straight up) */ 036 private volatile double polarAngleCos = Double.NaN; 037 /** Sine of polar angle (angle from Z axis, AKA straight up) */ 038 private volatile double polarAngleSin = Double.NaN; 039 /** The azimuthal angle phi */ 040 private volatile double azimuthalAngle = Double.NaN; 041 /** Cosine of azimuthal angle (angle from X axis) */ 042 private volatile double azimuthalAngleCos = Double.NaN; 043 /** Sine of azimuthal angle (angle from X axis) */ 044 private volatile double azimuthalAngleSin = Double.NaN; 045 046 /** 047 * Create a new Vector3D object using the XYZ coordinate system 048 * 049 * @param x The x coordinate 050 * @param y The y coordinate 051 * @param z The z coordinate 052 */ 053 public Vector3D(double x, double y, double z) { 054 this(VectorType.XYZ, x, y, z); 055 } 056 057 /** 058 * Create a new Vector3D object. See ordering in {@link VectorType}. 059 * 060 * @param first The first coordinate 061 * @param second The second coordinate 062 * @param third The third coordinate 063 * @param vectorType The coordinate type (determines how the other variables are treated) 064 */ 065 public Vector3D(VectorType vectorType, double first, double second, double third) { 066 if (vectorType == VectorType.XYZ) { 067 this.x = first; 068 this.y = second; 069 this.z = third; 070 } else { 071 this.radialDistance = first; 072 if (vectorType == VectorType.RPA) { 073 this.azimuthalAngle = third; 074 this.polarAngle = second; 075 } else { 076 this.azimuthalAngle = second; 077 this.polarAngle = third; 078 } 079 // Since we have to run the calculations anyway, ensure they are cached. 080 this.x = this.radialDistance * this.getAzimuthalAngleCos() * this.getPolarAngleSin(); 081 this.y = this.radialDistance * this.getAzimuthalAngleSin() * this.getPolarAngleSin(); 082 this.z = this.radialDistance * this.getPolarAngleCos(); 083 } 084 } 085 086 /** 087 * Get the x coordinate 088 * 089 * @return The x coordinate 090 */ 091 public double getX() { 092 return x; 093 } 094 095 /** 096 * Get the y coordinate 097 * 098 * @return The y coordinate 099 */ 100 public double getY() { 101 return y; 102 } 103 104 /** 105 * Get the z coordinate 106 * 107 * @return The z coordinate 108 */ 109 public double getZ() { 110 return z; 111 } 112 113 /** 114 * Get the radius 115 * 116 * @return The radius 117 */ 118 public double getRadialDistance() { 119 if (Double.isNaN(this.radialDistance)) { 120 this.radialDistance = Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2) + Math.pow(this.z, 2)); 121 } 122 return this.radialDistance; 123 } 124 125 /** 126 * Get the polar angle (inclination) 127 * 128 * @return The polar angle 129 */ 130 public double getPolarAngle() { 131 if (Double.isNaN(this.polarAngle)) { 132 // This was Math.atan(x, z) in the Mapillary plugin 133 // This should be Math.atan(y, z) 134 this.polarAngle = Math.atan2(this.x, this.z); 135 } 136 return this.polarAngle; 137 } 138 139 /** 140 * Get the polar angle cossine (inclination) 141 * 142 * @return The polar angle cosine 143 */ 144 public double getPolarAngleCos() { 145 if (Double.isNaN(this.polarAngleCos)) { 146 this.polarAngleCos = Math.cos(this.getPolarAngle()); 147 } 148 return this.polarAngleCos; 149 } 150 151 /** 152 * Get the polar angle sine (inclination) 153 * 154 * @return The polar angle sine 155 */ 156 public double getPolarAngleSin() { 157 if (Double.isNaN(this.polarAngleSin)) { 158 this.polarAngleSin = Math.sin(this.getPolarAngle()); 159 } 160 return this.polarAngleSin; 161 } 162 163 /** 164 * Get the azimuthal angle 165 * 166 * @return The azimuthal angle 167 */ 168 public double getAzimuthalAngle() { 169 if (Double.isNaN(this.azimuthalAngle)) { 170 if (Double.isNaN(this.radialDistance)) { 171 // Force calculation 172 this.getRadialDistance(); 173 } 174 // Avoid issues where x, y, and z are 0 175 if (this.radialDistance == 0) { 176 this.azimuthalAngle = 0; 177 } else { 178 // This was Math.acos(y / radialDistance) in the Mapillary plugin 179 // This should be Math.acos(z / radialDistance) 180 this.azimuthalAngle = Math.acos(this.y / this.radialDistance); 181 } 182 } 183 return this.azimuthalAngle; 184 } 185 186 /** 187 * Get the azimuthal angle cosine 188 * 189 * @return The azimuthal angle cosine 190 */ 191 public double getAzimuthalAngleCos() { 192 if (Double.isNaN(this.azimuthalAngleCos)) { 193 this.azimuthalAngleCos = Math.cos(this.getAzimuthalAngle()); 194 } 195 return this.azimuthalAngleCos; 196 } 197 198 /** 199 * Get the azimuthal angle sine 200 * 201 * @return The azimuthal angle sine 202 */ 203 public double getAzimuthalAngleSin() { 204 if (Double.isNaN(this.azimuthalAngleSin)) { 205 this.azimuthalAngleSin = Math.sin(this.getAzimuthalAngle()); 206 } 207 return this.azimuthalAngleSin; 208 } 209 210 /** 211 * Normalize the vector 212 * 213 * @return A normalized vector 214 */ 215 public Vector3D normalize() { 216 final double length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)); 217 final double newX; 218 final double newY; 219 final double newZ; 220 if (length == 0 || Double.isNaN(length)) { 221 newX = 0; 222 newY = 0; 223 newZ = 0; 224 } else { 225 newX = x / length; 226 newY = y / length; 227 newZ = z / length; 228 } 229 return new Vector3D(newX, newY, newZ); 230 } 231 232 @Override 233 public int hashCode() { 234 return Double.hashCode(this.x) + 31 * Double.hashCode(this.y) + 31 * 31 * Double.hashCode(this.z); 235 } 236 237 @Override 238 public boolean equals(Object o) { 239 if (o instanceof Vector3D) { 240 Vector3D other = (Vector3D) o; 241 return Utils.equalsEpsilon(this.x, other.x) && Utils.equalsEpsilon(this.y, other.y) && Utils.equalsEpsilon(this.z, other.z); 242 } 243 return false; 244 } 245 246 @Override 247 public String toString() { 248 return "[x=" + this.x + ", y=" + this.y + ", z=" + this.z + ", r=" + this.radialDistance + ", inclination=" 249 + this.polarAngle + ", azimuthal=" + this.azimuthalAngle + "]"; 250 } 251}