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 }