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    }