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