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 }