1 /**
  2  * Copyright (C) 2009-2011 Klaus Reimer <k@ailis.de>
  3  * See LICENSE.txt for licensing information.
  4  * 
  5  * @require twodee.js
  6  * @require twodee/Matrix.js
  7  * @require twodee/BoundingBox.js
  8  */
  9 
 10 /**
 11  * Constructs a new polygon
 12  * 
 13  * @param {Array.<twodee.Vector>} vertices
 14  *            The vertices as array of vectors.
 15  *            
 16  * @constructor
 17  * @class 
 18  * A polygon.
 19  */
 20 twodee.Polygon = function(vertices)
 21 {
 22     this.vertices = vertices;
 23     this.transformedVertices = [];
 24     this.boundingBox = new twodee.BoundingBox();
 25     this.reset();
 26     twodee.Polygon.counter++;
 27 };
 28 
 29 /**
 30  * Instance counter.
 31  * 
 32  * @type {number}
 33  */
 34 twodee.Polygon.counter = 0;
 35 
 36 /**
 37  * A static temporary vector for speed optimization.
 38  * 
 39  * @private
 40  * @type {twodee.Vector}
 41  */
 42 twodee.Polygon.V1 = new twodee.Vector();
 43 
 44 /**
 45  * Another static temporary vector for speed optimization.
 46  * 
 47  * @private
 48  * @type {twodee.Vector}
 49  */
 50 twodee.Polygon.V2 = new twodee.Vector();
 51 
 52 /**
 53  * The transformed vertices.
 54  * 
 55  * @private
 56  * @type {Array.<twodee.Vector>}
 57  */
 58 twodee.Polygon.prototype.transformedVertices = null;
 59 
 60 /**
 61  * The vertices of the polygon.
 62  * 
 63  * @private
 64  * @type {Array.<twodee.Vector>}
 65  */
 66 twodee.Polygon.prototype.vertices = null;
 67 
 68 /** 
 69  * The bounding box.
 70  * 
 71  * @private
 72  * @type {twodee.BoundingBox}
 73  */
 74 twodee.Polygon.prototype.boundingBox = null;
 75 
 76 /**
 77  * Resets the transformed state of the polygon.
 78  * 
 79  * @private
 80  */
 81 twodee.Polygon.prototype.reset = function()
 82 {
 83     var i, transformedVertices, vertices, vertex;
 84  
 85     transformedVertices = this.transformedVertices;
 86     vertices = this.vertices;
 87     i = transformedVertices.length = vertices.length;
 88     this.boundingBox.reset();
 89     while (--i >= 0)
 90     {
 91         vertex = vertices[i];
 92         transformedVertices[i] = vertex.copy(
 93             transformedVertices[i]);
 94         this.boundingBox.update(vertex);
 95     }
 96 };
 97 
 98 /**
 99  * Returns a copy of this polygon.
100  * 
101  * @return {twodee.Polygon} The polygon copy
102  */
103 twodee.Polygon.prototype.copy = function()
104 {
105     var newVertices, vertices, i, max;
106     
107     vertices = this.vertices;
108     newVertices = [];
109     for (i = 0, max = vertices.length; i < max; i++)
110         newVertices.push(vertices[i].copy());
111     return new twodee.Polygon(newVertices);
112 };
113 
114 /**
115  * Applies the polygon as a path to the specified canvas 2D context.
116  * 
117  * @param {CanvasRenderingContext2D} g
118  *            The canvas 2D context to apply the polygon to
119  */
120 twodee.Polygon.prototype.apply = function(g)
121 {
122     var max, vertices, vertex, i;
123     
124     vertices = this.transformedVertices;
125     max = vertices.length;
126     g.beginPath();
127     vertex = vertices[0];
128     g.moveTo(vertex.x, vertex.y);
129     for (i = 1; i < max; i++)
130     {
131         vertex = vertices[i];
132         g.lineTo(vertex.x, vertex.y);
133     }
134     if (max > 2) g.closePath();    
135 };
136 
137 /**
138  * Transforms the vertices of the polygon with the specified matrix.
139  * 
140  * @param {twodee.Matrix} m
141  *            The matrix to transform this polygon with
142  */
143 twodee.Polygon.prototype.transform = function(m)
144 {
145     var vertices, i;
146     
147     vertices = this.transformedVertices;
148     this.boundingBox.reset();
149     for (i = vertices.length - 1; i >= 0; i--)
150         this.boundingBox.update(vertices[i].transform(m));
151 };
152 
153 /**
154  * Sets the transformation of the polygon. This method uses the original
155  * vertices instead of the already transformed vertices. So using this
156  * method is effectively the same as calling reset() and then transform().
157  * But its a little bit faster.
158  * 
159  * @param {twodee.Matrix} m
160  *            The matrix to transform this polygon with
161  */
162 twodee.Polygon.prototype.setTransform = function(m)
163 {
164     var vertices, transformedVertices, i;
165     
166     vertices = this.vertices;
167     transformedVertices = this.transformedVertices;
168     this.boundingBox.reset();
169     for (i = vertices.length - 1; i >= 0; i--)
170         this.boundingBox.update(vertices[i].copy(
171             transformedVertices[i]).transform(m));
172 };
173 
174 /**
175  * Returns the number of polygon vertices.
176  * 
177  * @return {number} The number of polygon vertices
178  */
179 twodee.Polygon.prototype.countVertices = function()
180 {
181     return this.vertices.length;
182 };
183 
184 /**
185  * Returns the vertex with the specified index.
186  * 
187  * @param {number} index
188  *            The vertex index
189  * @return {twodee.Vector} The vertex
190  */
191 twodee.Polygon.prototype.getVertex = function(index)
192 {
193     return this.vertices[index];
194 };
195 
196 /**
197  * Checks if the this polygon collides with the specified other polygon.
198  * Note that this check only works for convex polygons. A simple bounding box
199  * check is done before the complex polygon collision detection is used.
200  * 
201  * @param {twodee.Polygon} other
202  *            The other polygon to test collision with
203  * @return {boolean}
204  *            True if the polygons collide, false if not 
205  */
206 twodee.Polygon.prototype.collidesWith = function(other)
207 {
208     var i, max;
209     
210     // If bounding boxes do not collide then the polygons can't collide
211     if (!this.boundingBox.collidesWith(other.boundingBox)) return false;
212     
213     // Process all faces of the first polygon
214     for (i = 0, max = this.countVertices(); i < max; i++)
215         if (this.isSeparationAxis(other, i)) return false;
216 
217     // Process all faces of the second polygon
218     max = other.countVertices();
219     if (max > 2)
220         for (i = 0; i < max; i++)
221             if (other.isSeparationAxis(this, i)) return false;
222     
223     // Found no separation axis so the polygons must collide
224     return true;
225 };
226 
227 /**
228  * Checks if the specified face is the separation axis between this polygon
229  * and the specified other polygon. The faceIndex parameter specifies the index
230  * of the vector pair (defining the face) in the current polygon.
231  * 
232  * @param {twodee.Polygon} other
233  *            The other polygon 
234  * @param {number} faceIndex
235  *            The index of the first vector of the face.
236  * @return {boolean}
237  *             If the face vector is a separation axis or not
238  * @private
239  */
240 twodee.Polygon.prototype.isSeparationAxis = function(other, faceIndex)
241 {
242     var normal, point, base, dir1, dir2, i, max, faceIndex2, pointIndex,
243         vertices, otherVertices, tmp1, tmp2;
244 
245     // Get static temporary vectors (Prevents garbage collector stressing)
246     tmp1 = twodee.Polygon.V1;
247     tmp2 = twodee.Polygon.V2;
248 
249     // Create some shortcuts
250     vertices = this.transformedVertices;
251     otherVertices = other.transformedVertices;
252 
253     max = this.vertices.length;
254     faceIndex2 = faceIndex == max - 1 ? 0 : faceIndex + 1;
255     pointIndex = faceIndex2 == max - 1 ? 0: faceIndex2 + 1;
256 
257     // Get the face (and it's normal)
258     base = vertices[faceIndex];
259     normal = vertices[faceIndex2].copy(tmp1).sub(base).orthogonal();
260     
261     // Check on which side the first polygon is
262     point = vertices[pointIndex].copy(tmp2).sub(base);
263     dir1 = point.dot(normal) > 0;
264     
265     // Check if all points of second polygon are on the other side
266     for (i = 0, max = otherVertices.length; i < max; i++)
267     {
268         point = otherVertices[i].copy(tmp2).sub(base);
269         dir2 = point.dot(normal) > 0;
270         
271         // If point is on the same side as the first polygon then this is
272         // not a separation axis
273         if (dir1 == dir2) return false;
274     }
275     
276     // Separation axis is valid
277     return true;    
278 };
279 
280 /**
281  * Returns the current bounding box of this polygon.
282  * 
283  * @return {twodee.BoundingBox} The current bounding box
284  */
285 twodee.Polygon.prototype.getBoundingBox = function()
286 {
287     return this.boundingBox;
288 };
289