001    package physics3d;
002    
003    import java.util.ArrayList;
004    import java.util.Collections;
005    import java.util.Iterator;
006    import java.util.List;
007    
008    /**
009     * PlanePolygon represents a plane polygon in 3 space
010     * 
011     * @specfield vertices : Sequence[Vect3] // sequence of vertices with each
012     *            adjacent to the previous and next
013     * @specfield normal : Vect3 // a unit vector perpendicular to the polygon
014     * @specfield texture : String // texture of this object
015     */
016    
017    public strictfp class PlanePolygon implements PhysicsShape {
018      private final Vect3 normal;
019    
020      private final List<Vect3> vertices = new ArrayList<Vect3>();
021    
022      private final String texture;
023    
024      // Rep. Invariant:
025      // vertices != null &&
026      // normal != null &&
027      // for all i v = vertices.get(i)
028      // v != null and Vect3.isAbout(v.dot(normal),vertices.get(0).dot(normal))
029      // (it's the same for all the vectors)
030    
031      // Abstraction Function:
032      // The plane polygon with the list of points 'vertices' and a
033      // normal vector 'normal'
034    
035      /**
036       * @requires <code>vertices</code> is not null, <code>texture</code> is
037       *           not null, <code>vertices.size()</code> >=3,
038       *           <code>vertices</code> contains no null elements, the points
039       *           represented by the end of the vectors lie in a plane and are
040       *           arranged in sequential order of occurrence, the i, i+1, and i+2
041       *           mod vertices.size() elements of vertices are not collinear for
042       *           any i, and the polygon is convex and not self intersecting.
043       * @effects constructs a 3D Polygon
044       */
045      public PlanePolygon(List<Vect3> vertices, String texture) {
046        if (vertices == null) {
047          throw new IllegalArgumentException();
048        }
049        for (Vect3 v : vertices) {
050          this.vertices.add(v);
051        }
052    
053        Vect3 p1 = this.vertices.get(0);
054        Vect3 p2 = this.vertices.get(1);
055        Vect3 p3 = this.vertices.get(2);
056        p1 = p2.minus(p1);
057        p2 = p3.minus(p2);
058    
059        this.normal = (p1.cross(p2)).unitSize();
060        this.texture = texture;
061      }
062    
063      /**
064       * @requires <code>vertices</code> is not null, <code>vertices.size()</code>
065       *           >=3, <code>vertices</code> contains no null elements, the
066       *           points represented by the end of the vectors lie in a plane and
067       *           are arranged in sequential order of occurrence, the i, i+1, and
068       *           i+2 mod vertices.size() elements of vertices are not collinear
069       *           for any i, and the polygon is convex and not self intersecting.
070       * @effects constructs a 3D Polygon
071       */
072      public PlanePolygon(List<Vect3> vertices) {
073        if (vertices == null) {
074          throw new IllegalArgumentException();
075        }
076        for (Vect3 v : vertices) {
077          this.vertices.add(v);
078        }
079    
080        Vect3 p1 = this.vertices.get(0);
081        Vect3 p2 = this.vertices.get(1);
082        Vect3 p3 = this.vertices.get(2);
083        p1 = p2.minus(p1);
084        p2 = p3.minus(p2);
085    
086        this.normal = (p1.cross(p2)).unitSize();
087        this.texture = null;
088        // checkRep();
089      }
090    
091      @SuppressWarnings("unused")
092      private void checkRep() {
093        if (vertices == null || normal == null || normal.rho() != 1) {
094          throw new RuntimeException();
095        }
096        double d = vertices.get(0).dot(normal);
097        for (Vect3 v : vertices) {
098          if (v == null || !Vect3.isAbout(v.dot(normal), d)) {
099            throw new RuntimeException();
100          }
101        }
102      }
103    
104      /**
105       * @return a unit vector perpendicular to <code>this</code>.
106       */
107      public Vect3 getNormal() {
108        // checkRep();
109        return normal;
110      }
111    
112      /**
113       * @return an iterator over the vertices, going in the order with which they
114       *         were passed upon creation
115       */
116      public Iterator<Vect3> getVertices() {
117        // checkRep();
118        return Collections.unmodifiableList(vertices).iterator();
119      }
120    
121      /**
122       * @return texture of this object
123       */
124      public String getTexture() {
125        // checkRep();
126        return this.texture;
127      }
128    
129      /**
130       * @return the Plane containing <code>this</code>
131       */
132      public Plane planeContainingObject() {
133        // checkRep();
134        return new Plane(normal, vertices.get(0));
135      }
136    
137      /**
138       * @requires <code>p</code> is not null
139       * @return true if <code>p</code> is within the geometric shape created by
140       *         moving <code>this</code> up and down <code>this.normal</code>
141       *         by <code>GameConstants.TOLERANCE</code>, false otherwise
142       * 
143       */
144      public boolean containsPoint(Vect3 p) {
145        // checkRep();
146        if (Vect3.isAbout(this.planeContainingObject()
147            .perpDistanceFromPlaneToPoint(p), 0)) {
148          Vect3 v1 = p.minus(vertices.get(0));
149          Vect3 v2 = vertices.get((0 + 1) % vertices.size()).minus(vertices.get(0));
150          Vect3 v3 = v2.cross(v1);
151    
152          for (int i = 0; i < vertices.size(); i++) {
153            v1 = p.minus(vertices.get(i));
154            v2 = vertices.get((i + 1) % vertices.size()).minus(vertices.get(i));
155            v3 = v2.cross(v1);
156            if (v3.dot(normal) < 0) {
157              return false;
158            }
159          }
160          return true;
161        }
162        return false;
163      }
164    
165      /**
166       * @requires <code>p</code> is not null
167       * @return the minimum distance between <code>p</code> and <code>this</code>
168       */
169      public double minDistanceToObjectFromP(Vect3 p) {
170        // checkRep();
171        Vect3 v = this.planeContainingObject().perpVectorFromPlaneToPoint(p);
172        Vect3 pOnPlane = p.minus(v);
173        // see if closest point is inside plane
174        if (this.containsPoint(pOnPlane)) {
175          return v.rho();
176        }
177        // find closest vertex
178        double minVertDist = vertices.get(0).minus(p).rho();
179        for (Vect3 v1 : vertices) {
180          double temp = v1.minus(p).rho();
181          if (temp < minVertDist) {
182            minVertDist = temp;
183          }
184        }
185        double minLineDist = Double.MAX_VALUE;
186        // find minimum distance to a line segment
187        for (int i = 0; i < vertices.size(); i++) {
188          Vect3 p1 = vertices.get(i);
189          Vect3 p2 = vertices.get((i + 1) % vertices.size());
190          Line line = Line.MakeLineFromTwoPoints(p1, p2);
191          Vect3 closeP = line.getPointOnLineClosestToP(p);
192          double d1 = p1.minus(closeP).rho();
193          double d2 = p2.minus(closeP).rho();
194          double d3 = p1.minus(p2).rho();
195          if (Vect3.isAbout(d1 + d2, d3)) {
196            if (closeP.minus(p).rho() < minLineDist) {
197              minLineDist = closeP.minus(p).rho();
198            }
199          }
200        }
201        if (minLineDist < minVertDist) {
202          return minLineDist;
203        }
204        return minVertDist;
205      }
206    
207      /**
208       * @requires center != null, axis != null, axis.rho() > 0, tAngle != null.
209       * @return A PlanePolygon rotated about the line created by center and axis
210       *         counterclockwise by an amount tAngle with same texture.
211       */
212      public PlanePolygon rotateAboutCwithAxisAandAngleT(Vect3 center, Vect3 axis,
213          Angle tAngle) {
214        ArrayList<Vect3> newVertices = new ArrayList<Vect3>();
215    
216        Iterator<Vect3> iter = this.getVertices();
217    
218        while (iter.hasNext()) {
219          Vect3 temp = iter.next();
220          newVertices.add(temp.minus(center).rotateAroundVect(axis, tAngle).plus(
221              center));
222        }
223        return new PlanePolygon(newVertices, this.texture);
224      }
225    
226      /**
227       * @requires t != null
228       * @return A PlanePolygon translated by t with same texture
229       */
230      public PlanePolygon translateByT(Vect3 t) {
231        ArrayList<Vect3> newVertices = new ArrayList<Vect3>();
232    
233        Iterator<Vect3> iter = this.getVertices();
234    
235        while (iter.hasNext()) {
236          Vect3 temp = iter.next();
237          newVertices.add(temp.plus(t));
238        }
239        return new PlanePolygon(newVertices, this.texture);
240      }
241    
242      public String toString() {
243        String ans = "PlanePolygon(";
244        Iterator<Vect3> vs = vertices.iterator();
245        if (vs.hasNext()) {
246          ans = ans.concat(vs.next().toString());
247        }
248    
249        while (vs.hasNext()) {
250          ans = ans.concat("-" + vs.next().toString());
251        }
252        ans = ans.concat(")");
253        return ans;
254      }
255    
256    public ShapeClassification getShapeClassification() {
257            return ShapeClassification.PLANE_POLYGON;
258    }
259    }