001 package physics3d; 002 003 004 /** 005 * Vect3 represents a vector in 3 space 006 * 007 * @specfield x : double // x coordinate 008 * @specfield y : double // y coordinate 009 * @specfield z : double // z coordinate 010 * @specfield rho : double // length of vector 011 * @specfield theta : Angle // azimuthal angle 012 * @specfield phi : Angle // angle from projection on xy plane to line x=0 013 */ 014 public strictfp class Vect3 { 015 private double x; 016 017 private double y; 018 019 private double z; 020 021 private int mode; // -1 for spherical, +1 for x-y-z, 0 for both 022 023 private double rho; 024 025 private Angle theta; 026 027 private Angle phi; 028 029 /** A Vect3 with zero length */ 030 public static final Vect3 ZERO = new Vect3(0.0, 0.0, 0.0); 031 032 /** A unit vector in the positive x direction */ 033 public static final Vect3 X_HAT = new Vect3(1.0, 0.0, 0.0); 034 035 /** A unit vector in the positive y direction */ 036 public static final Vect3 Y_HAT = new Vect3(0.0, 1.0, 0.0); 037 038 /** A unit vector in the positive z direction */ 039 public static final Vect3 Z_HAT = new Vect3(0.0, 0.0, 1.0); 040 041 // Rep. Invariant: 042 // mode is one of -1, 0, 1 043 // if(mode = -1) 044 // theta!= null && phi!= null && rho >= 0 045 // if(mode = 0) 046 // theta!= null && phi!= null && rho >= 0 047 // 048 // Abstraction Function: 049 // The vector in three space, with cartesian coordinates 050 // (x,y,z) and spherical coordinates (rho, theta, phi) 051 // where theta is the azimuthal angle 052 053 /** 054 * @requires <code>theta, phi</code> are not null 055 * @effects constructs a new unit vector in the direction of 056 * <code>theta, phi</code>. 057 */ 058 public Vect3(Angle theta, Angle phi) { 059 this(1.0, theta, phi); 060 // checkRep(); 061 } 062 063 /** 064 * @requires <code>theta, phi</code> are not null 065 * @effects constructs a new vector in the direction of 066 * <code>theta, phi</code> with length <code>rho</code>, where 067 * <code>(rho, theta, phi)</code> is the standard spherical 068 * representation of a point in 3 space. 069 */ 070 public Vect3(double rho, Angle theta, Angle phi) { 071 if (theta == null) 072 throw new IllegalArgumentException(); 073 if (phi == null) 074 throw new IllegalArgumentException(); 075 if (rho < 0.0) 076 throw new IllegalArgumentException(); 077 078 if (rho == 0.0) { 079 this.theta = Angle.ZERO; 080 this.phi = Angle.ZERO; 081 this.rho = 0.0; 082 } else if (rho > 0.0) { 083 this.theta = theta; 084 this.phi = phi; 085 this.rho = rho; 086 } 087 mode = -1; 088 // checkRep(); 089 } 090 091 /** 092 * @effects constructs a new vector in Cartesian space with coordinates 093 * (x,y,z). 094 */ 095 public Vect3(double x, double y, double z) { 096 this.x = x; 097 this.y = y; 098 this.z = z; 099 this.mode = 1; 100 // checkRep(); 101 } 102 103 private Vect3(double x, double y, double z, double rho, Angle theta, Angle phi) { 104 this.x = x; 105 this.y = y; 106 this.z = z; 107 this.rho = rho; 108 this.theta = theta; 109 this.phi = phi; 110 mode = 0; 111 // checkRep(); 112 } 113 114 @SuppressWarnings("unused") 115 private void checkRep() { 116 if (mode != -1 && mode != 0 && mode != 1) { 117 throw new RuntimeException(); 118 } 119 if (mode == -1) { 120 if (theta == null || phi == null || rho < 0) { 121 //System.out.println(theta); 122 //System.out.println(phi); 123 //System.out.println(rho); 124 125 throw new RuntimeException(); 126 } 127 if (mode == 0) 128 if (theta == null || phi == null || rho < 0) { 129 throw new RuntimeException(); 130 } 131 } 132 } 133 134 private void computeXYZ() { 135 // checkRep(); 136 if (mode < 0) { 137 x = rho * theta.sin() * phi.cos(); 138 y = rho * theta.sin() * phi.sin(); 139 z = rho * theta.cos(); 140 mode = 0; 141 } 142 // checkRep(); 143 } 144 145 private void computeRTP() { 146 // checkRep(); 147 if (mode > 0) { 148 rho = Math.sqrt((x * x) + (y * y) + (z * z)); 149 if (x == 0 && y == 0 && z >= 0) { 150 theta = Angle.ZERO; 151 phi = Angle.ZERO; 152 } else if (x == 0 && y == 0 && z < 0) { 153 theta = Angle.DEG_180; 154 phi = Angle.ZERO; 155 } else { 156 phi = new Angle(x, y); 157 theta = new Angle(z, Math.sqrt((x * x) + (y * y))); 158 } 159 mode = 0; 160 } 161 // checkRep(); 162 } 163 164 /** 165 * @return the length of <code>this</code> 166 */ 167 public double rho() { 168 // checkRep(); 169 computeRTP(); 170 return rho; 171 } 172 173 /** 174 * @return the angle of <code>this</code> to the z axis 175 */ 176 public Angle theta() { 177 // checkRep(); 178 computeRTP(); 179 return theta; 180 } 181 182 /** 183 * @return the angle of <code>this</code> to the x axis, measured from the 184 * XY plane. 185 */ 186 public Angle phi() { 187 // checkRep(); 188 computeRTP(); 189 return phi; 190 } 191 192 /** 193 * @return the x coordinate of <code>this</code> in Cartesian coordinates 194 */ 195 public double x() { 196 // checkRep(); 197 computeXYZ(); 198 return x; 199 } 200 201 /** 202 * @return the y coordinate of <code>this</code> in Cartesian coordinates 203 */ 204 public double y() { 205 // checkRep(); 206 computeXYZ(); 207 return y; 208 } 209 210 /** 211 * @return the z coordinate of <code>this</code> in Cartesian coordinates 212 */ 213 public double z() { 214 // checkRep(); 215 computeXYZ(); 216 return z; 217 } 218 219 /** 220 * @requires <code>b</code> is not null 221 * @return the square of the distance between the points represented by 222 * <code>this</code> and <code>b</code> 223 */ 224 public double distanceSquared(Vect3 b) { 225 // effects: returns the distance between <this> and <b> 226 // checkRep(); 227 computeXYZ(); 228 b.computeXYZ(); 229 230 double width = this.x - b.x; 231 double length = this.y - b.y; 232 double height = this.z - b.z; 233 234 return ((width * width) + (length * length) + (height * height)); 235 } 236 237 /** 238 * @return the angle between <code>this</code> and <code>b</code> 239 */ 240 public Angle angleBetween(Vect3 b) { 241 // checkRep(); 242 computeRTP(); 243 b.computeRTP(); 244 if (b.equals(Vect3.ZERO) || this.equals(Vect3.ZERO)) { 245 return Angle.RAD_PI_OVER_TWO; 246 } 247 double cosA = this.dot(b) / (b.rho * this.rho); 248 249 if (cosA > 1) { 250 cosA = 1; 251 } 252 if (cosA < -1) { 253 cosA = -1; 254 } 255 256 return new Angle(cosA, Math.sqrt(1 - cosA * cosA)); 257 } 258 259 // PRODUCERS: 260 261 /** 262 * @requires <code>b</code> is not null 263 * @return the vector sum of <code>this</code> and <code>b</code> 264 */ 265 public Vect3 plus(Vect3 b) { 266 // checkRep(); 267 computeXYZ(); 268 b.computeXYZ(); 269 return new Vect3(this.x + b.x, this.y + b.y, this.z + b.z); 270 } 271 272 /** 273 * @requires <code>b</code> is not null 274 * @return the vector difference of <code>this</code> and <code>b</code> 275 */ 276 public Vect3 minus(Vect3 b) { 277 // checkRep(); 278 computeXYZ(); 279 b.computeXYZ(); 280 return new Vect3(this.x - b.x, this.y - b.y, this.z - b.z); 281 } 282 283 /** 284 * @return a vector that is equivalent to <code>this</code> being rotated by 285 * pi radians. 286 */ 287 public Vect3 neg() { 288 // checkRep(); 289 if (mode < 0) { 290 return new Vect3(rho, Angle.RAD_PI.minus(theta), phi.plus(Angle.RAD_PI)); 291 } else if (mode > 0) { 292 return new Vect3(-x, -y, -z); 293 } else { 294 // mode == 0 295 return new Vect3(-x, -y, -z, rho, Angle.RAD_PI.minus(theta), phi 296 .plus(Angle.RAD_PI)); 297 } 298 } 299 300 /** 301 * @return a vector equivalent to <code>this</code> scaled by 302 * <code>amt</code>. 303 */ 304 public Vect3 times(double amt) { 305 // checkRep(); 306 if (Double.isNaN(amt) || Double.isInfinite(amt)) { 307 throw new RuntimeException(); 308 } 309 if (mode < 0) { 310 if (amt >= 0) { 311 return new Vect3(rho * amt, theta, phi); 312 } else { 313 return new Vect3(rho * amt * -1, Angle.DEG_180.minus(theta), phi 314 .plus(Angle.DEG_180)); 315 } 316 } else if (mode > 0) { 317 return new Vect3(x * amt, y * amt, z * amt); 318 } else { 319 // mode == 0 320 if (amt >= 0) 321 return new Vect3(x * amt, y * amt, z * amt, rho * amt, theta, phi); 322 else 323 return new Vect3(x * amt, y * amt, z * amt, rho * amt * -1, 324 Angle.RAD_PI.minus(theta), phi.plus(Angle.RAD_PI)); 325 } 326 } 327 328 /** 329 * Returns the projection of this onto <code>b</code><br> 330 * 331 * <img src="doc-files/project.gif"> 332 * 333 * @requires <code>b</code> is not null 334 * @return a vector resulting from projecting <code>this</code> onto 335 * <code>b</code>. The resulting vector <code>c</code>has the 336 * same angle as <code>b</code>, but its length is such that 337 * <code>this</code> - <code>c</code> is perpendicular to 338 * <code>c</code>. 339 */ 340 public Vect3 projectOnToB(Vect3 b) { 341 // checkRep(); 342 return b.times(b.dot(this) / b.rho() / b.rho()); 343 } 344 345 /** 346 * @requires <code>b</code> is not null 347 * @return the vector c = (this.y()*b.z() - b.y()*this.z()) i, (this.z()*b.x() - 348 * b.z()*this.x()) j, (this.x()*b.y() - b.x()*this.y()) k c satisfies: 349 * c.rho() = this.rho()*b.rho()*this.angleBetween(b).sin() 350 */ 351 public Vect3 cross(Vect3 b) { 352 // checkRep(); 353 computeXYZ(); 354 b.computeXYZ(); 355 return new Vect3(this.y * b.z - b.y * this.z, this.z * b.x - b.z * this.x, 356 this.x * b.y - b.x * this.y); 357 } 358 359 /** 360 * @requires <code>this.rho()</code> >= 0 361 * @return a unit vector with the direction as <code>this</code> 362 */ 363 public Vect3 unitSize() { 364 // checkRep(); 365 computeRTP(); 366 return new Vect3(theta, phi); 367 } 368 369 /** 370 * @requires <code>axis</code> is not null && <code>axis.rho()</code> > 0 && 371 * <code>ccwRotAngle</code> is not null 372 * @return a vector which is <code>this</code> rotated counter clockwise by 373 * an angle <code>ccwRotAngle</code> about the axis of rotation 374 * <code>axis</code>. 375 */ 376 public Vect3 rotateAroundVect(Vect3 axis, Angle ccwRotAngle) { 377 // checkRep(); 378 // decompose into parallel and perpendicular components 379 // parallel component doesn't change 380 Vect3 parallelToAxis = this.projectOnToB(axis); 381 // perp vector rotates 382 Vect3 perpToAxis = this.minus(parallelToAxis); 383 // find cos and sin components of perpendicular vector 384 Vect3 cosUnitVect = perpToAxis.unitSize(); 385 Vect3 sinUnitVect = (axis.unitSize()).cross(cosUnitVect); 386 // should be 1, but just in case 387 sinUnitVect = sinUnitVect.unitSize(); 388 389 Vect3 rotatedUnitVect = (cosUnitVect.times(ccwRotAngle.cos())) 390 .plus(sinUnitVect.times(ccwRotAngle.sin())); 391 Vect3 rotatedVect = rotatedUnitVect.times(perpToAxis.rho()); 392 return parallelToAxis.plus(rotatedVect); 393 } 394 395 /** 396 * @requires <code>b</code> is not null 397 * @return the dot product of <code>this</code> and <code>b</code>. 398 */ 399 public double dot(Vect3 b) { 400 // checkRep(); 401 computeXYZ(); 402 b.computeXYZ(); 403 return x * b.x + y * b.y + z * b.z; 404 } 405 406 // tostring methods 407 public String toString() { 408 return "<" + x() + "," + y() + "," + z() + ">"; 409 } 410 411 public boolean equals(Vect3 v) { 412 if (v == null) 413 return false; 414 if (mode < 0 && v.mode < 0) { 415 return ((this.rho == v.rho) && this.theta.equals(v.theta) && this.phi 416 .equals(v.phi)); 417 } else { 418 computeXYZ(); 419 v.computeXYZ(); 420 return (this.x == v.x) && (this.y == v.y) && (this.z == v.z); 421 } 422 } 423 424 public boolean equals(Object o) { 425 return (o instanceof Vect3) && this.equals((Vect3) o); 426 } 427 428 public int hashCode() { 429 return (new Double(x())).hashCode() + (new Double(y())).hashCode() 430 + (new Double(z())).hashCode(); 431 } 432 433 /** 434 * @requires <code>v1</code> is not null 435 * @param d1 436 * @param d2 437 * @return true if <code>|d1-d2|</code> < 438 * <code>GameConstants.Tolerance</code> false otherwise 439 */ 440 public boolean isAbout(Vect3 v1) { 441 // checkRep(); 442 if (Math.abs((v1.minus(this)).rho()) < GameConstants.TOLERANCE) { 443 return true; 444 } 445 return false; 446 } 447 448 /** 449 * 450 * @param d1 451 * @param d2 452 * @return true if |d1-d2| < <code>GameConstants.Tolerance</code> false 453 * otherwise 454 */ 455 public static boolean isAbout(double d1, double d2) { 456 if (Math.abs(d1 - d2) < GameConstants.TOLERANCE) { 457 return true; 458 } 459 return false; 460 } 461 462 /** 463 * @return d rounded to nearest hundreth 464 */ 465 public static double roundToNearest100(double d) { 466 double bigd = 100 * d; 467 double remainder = bigd - Math.floor(bigd); 468 if (remainder < .005) { 469 return Math.floor(bigd) / 100.0; 470 } else 471 return Math.floor(bigd + 1) / 100.0; 472 } 473 474 }