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 }