1 /** 2 * Copyright (C) 2009-2012 Klaus Reimer <k@ailis.de> 3 * See LICENSE.txt for licensing information 4 * 5 * @require threedee.js 6 * @require threedee/Vector.js 7 */ 8 9 /** 10 * Constructs a new render polygon. 11 * 12 * @param {!threedee.Model} model 13 * The original model this polygon is connected to. 14 * @param {!threedee.Polygon} polygon 15 * The original polygon. 16 * @param {!Array.<!threedee.Vector>} vertices 17 * The vertices array. 18 * 19 * @constructor 20 * @class A render polygon is a wrapper around a polygon for internal usage 21 * while rendering. 22 */ 23 threedee.RenderPolygon = function(model, polygon, vertices) 24 { 25 var i, max; 26 27 this.model = model; 28 this.material = polygon.getMaterial(); 29 this.vertices = []; 30 this.size = polygon.countVertices(); 31 this.origSize = this.size; 32 for (i = 0, max = this.size; i < max; i++) 33 this.vertices.push(vertices[polygon.getVertex(i)]); 34 35 this.polygon = []; 36 this.prevPolygon = []; 37 }; 38 39 /** 40 * Speed-optimization vector cache. 41 * @private 42 * @type {!threedee.Vector} 43 */ 44 threedee.RenderPolygon.V1 = new threedee.Vector(); 45 46 /** 47 * Speed-optimization vector cache. 48 * @private 49 * @type {!threedee.Vector} 50 */ 51 threedee.RenderPolygon.V2 = new threedee.Vector(); 52 53 /** 54 * The array with the used vertices. 55 * @private 56 * @type {!Array.<!threedee.Vector>} 57 */ 58 threedee.RenderPolygon.prototype.vertices; 59 60 /** 61 * The current number of used vertices. 62 * @private 63 * @type {number} 64 */ 65 threedee.RenderPolygon.prototype.size = 0; 66 67 /** 68 * The original number of vertices. 69 * @private 70 * @type {number} 71 */ 72 threedee.RenderPolygon.prototype.origSize = 0; 73 74 /** 75 * The extra vectors added to the original ones. 76 * @private 77 * @type {number} 78 */ 79 threedee.RenderPolygon.prototype.extras = 0; 80 81 /** 82 * The vertex indices that forms the polygon. 83 * @private 84 * @type {!Array.<number>} 85 */ 86 threedee.RenderPolygon.prototype.polygon; 87 88 /** 89 * The previous vertex indices that forms the polygon. 90 * @private 91 * @type {!Array.<number>} 92 */ 93 threedee.RenderPolygon.prototype.prevPolygon; 94 95 /** 96 * The normal vector cache. 97 * @private 98 * @type {threedee.Vector} 99 */ 100 threedee.RenderPolygon.prototype.normal = null; 101 102 /** 103 * The center vector cache. 104 * @private 105 * @type {threedee.Vector} 106 */ 107 threedee.RenderPolygon.prototype.center = null; 108 109 /** 110 * The referenced vertices. 111 * @private 112 * @type {!Array.<!threedee.Vector>} 113 */ 114 threedee.RenderPolygon.prototype.vertices; 115 116 /** 117 * The average Z position of the polygon. 118 * @private 119 * @type {number} 120 */ 121 threedee.RenderPolygon.prototype.averageZ = 0; 122 123 /** 124 * The polygon material. 125 * @private 126 * @type {?threedee.Material} 127 */ 128 threedee.RenderPolygon.prototype.material = null; 129 130 /** 131 * The model this polygon is connected to. 132 * @private 133 * @type {!threedee.Model} 134 */ 135 threedee.RenderPolygon.prototype.model; 136 137 /** 138 * Initializes the polygon for the next rendering. This simply resets the 139 * vertex indices to the original order and resets the counter of extra 140 * vertices. 141 */ 142 threedee.RenderPolygon.prototype.init = function() 143 { 144 var i; 145 146 this.size = this.origSize; 147 for (i = this.size - 1; i >= 0; i--) this.polygon[i] = i; 148 this.extras = 0; 149 }; 150 151 /** 152 * Clips this polygon against the specified view plane. 153 * 154 * @param {threedee.Plane} plane 155 * The clipping plane 156 * @return {boolean} 157 * True if polygon was clipped completely away, false if it is 158 * still visible (even if clipped) 159 */ 160 threedee.RenderPolygon.prototype.clip = function(plane) 161 { 162 var max, j, i, a, b, c, tmp, planeNormal, planeDistance, distanceA, 163 distanceB, s, va, vb; 164 165 // Swap the polygon arrays 166 tmp = this.prevPolygon; 167 this.prevPolygon = this.polygon; 168 this.polygon = tmp; 169 170 max = this.size; 171 j = 0; 172 planeNormal = plane.getNormal(); 173 planeDistance = plane.getDistance(); 174 for (i = 0; i < max; i++) 175 { 176 // Get the two vertex indices of the next line 177 a = this.prevPolygon[i]; 178 b = this.prevPolygon[(i + 1) % max]; 179 180 // Get the corresponding vertices and calculate the distances 181 va = this.vertices[a]; 182 vb = this.vertices[b]; 183 distanceA = va.dot(planeNormal) - planeDistance; 184 distanceB = vb.dot(planeNormal) - planeDistance; 185 186 // If both points are on the back-side of the plane then it is 187 // completely clipped away, so return null 188 if (distanceA < 0 && distanceB < 0) continue; 189 190 // If both points are on the front-side of the plane then use the line 191 // as it is (Point B is omitted because it is the starting point of 192 // the next line in the polygon) 193 if (distanceA >= 0 && distanceB >= 0) 194 { 195 this.polygon[j++] = a; 196 } 197 198 // Otherwise calculate the intersection and use a clipped line 199 else 200 { 201 // Calculate the intersection point 202 s = distanceA / (distanceA - distanceB); 203 c = this.origSize + this.extras++; 204 if (!this.vertices[c]) this.vertices[c] = new threedee.Vector(); 205 this.vertices[c].set(va.x + s * (vb.x - va.x), va.y + s 206 * (vb.y - va.y), va.z + s * (vb.z - va.z)); 207 208 // If point A is on the back-side of the plane then use a line 209 // from the intersection point to point B (Where point B is 210 // omitted because it is the original starting point of the next 211 // line in the polygon 212 if (distanceA < 0) 213 { 214 this.polygon[j++] = c; 215 } 216 217 // Otherwise point B is on the back-side so use a line from point A 218 // to intersection point 219 else 220 { 221 this.polygon[j++] = a; 222 this.polygon[j++] = c; 223 } 224 } 225 } 226 227 this.size = j; 228 return !j; 229 }; 230 231 /** 232 * Returns the number of used vertices. 233 * 234 * @return {number} The number of used vertices 235 */ 236 threedee.RenderPolygon.prototype.countVertices = function() 237 { 238 return this.size; 239 }; 240 241 /** 242 * Returns the vertex with the specified index. 243 * 244 * @param {number} index 245 * The vertex index 246 * @return {!threedee.Vector} The vertex 247 */ 248 threedee.RenderPolygon.prototype.getVertex = function(index) 249 { 250 return this.vertices[this.polygon[index]]; 251 }; 252 253 /** 254 * Returns the center of the polygon. 255 * 256 * Speed-optimization: The calculated center vector is cached in an instance 257 * scope variable to prevent creation of vector objects on reach frame render. 258 * WARNING: If you want to use the returned center vector for a longer time 259 * (and not only for a single frame) then you should copy the vector because 260 * otherwise the content may change when the polygon changes. 261 * 262 * @return {!threedee.Vector} The center of the polygon 263 */ 264 threedee.RenderPolygon.prototype.getCenter = function() 265 { 266 var vertexCount, ax, ay, az, i, v; 267 268 vertexCount = this.size; 269 ax = 0; 270 ay = 0; 271 az = 0; 272 273 for (i = 0; i < vertexCount; i++) 274 { 275 v = this.vertices[this.polygon[i]]; 276 ax += v.x; 277 ay += v.y; 278 az += v.z; 279 } 280 ax /= vertexCount; 281 ay /= vertexCount; 282 az /= vertexCount; 283 284 if (!this.center) this.center = new threedee.Vector(); 285 return this.center.set(ax, ay, az); 286 }; 287 288 /** 289 * Calculates the normal of the polygon. May return null if polygon has 290 * fewer then three vertices. 291 * 292 * Speed-optimization: For calculating the normal we need three additional 293 * vectors. Two of them (V1 and V2) are only temporary and therefore are 294 * shared in static scope. The third one is polygon-specific and 295 * is shared in instance scope. WARNING: If you want to use the returned normal 296 * vector for a longer time (and not only for a single frame) then you should 297 * copy the vector because otherwise the content may change when the polygon 298 * changes. 299 * 300 * @return {?threedee.Vector} 301 * The normal or null if polygon has fewer then three vertices 302 */ 303 threedee.RenderPolygon.prototype.getNormal = function() 304 { 305 var a, b, c, v1, v2; 306 307 // If polygon has fewer then three vertices then it has no normal 308 if (this.polygon.length < 3) return null; 309 310 a = this.vertices[this.polygon[0]]; 311 b = this.vertices[this.polygon[1]]; 312 c = this.vertices[this.polygon[2]]; 313 314 v1 = b.copy(threedee.RenderPolygon.V1).sub(a); 315 v2 = a.copy(threedee.RenderPolygon.V2).sub(c); 316 this.normal = v1.cross(v2).toUnit().copy(this.normal); 317 return this.normal; 318 }; 319 320 /** 321 * Updates the average Z value. 322 */ 323 threedee.RenderPolygon.prototype.updateAverageZ = function() 324 { 325 var averageZ, vertexCount, v; 326 327 averageZ = 0; 328 vertexCount = this.size; 329 for (v = 0; v < vertexCount; v++) 330 { 331 averageZ += this.vertices[this.polygon[v]].z; 332 } 333 averageZ /= vertexCount; 334 this.averageZ = averageZ; 335 }; 336 337 /** 338 * Compares two polygons according to their average Z value. This is used 339 * to sort polygons. 340 * 341 * @param {!threedee.RenderPolygon} a 342 * The first polygon to compare 343 * @param {!threedee.RenderPolygon} b 344 * The second polygon to compare 345 * @return {number} 346 * Lower than 0 if z-value of second polygon is lower then z-value 347 * of first polygon. Greater than 0 if the other way around. 0 348 * means z-values are equal 349 */ 350 threedee.RenderPolygon.compare = function(a, b) 351 { 352 return b.averageZ - a.averageZ; 353 }; 354 355 /** 356 * Checks if currently the backface of the polygon is visible. 357 * 358 * @return {boolean} 359 * True if backface is visible, false if front-side. 360 */ 361 threedee.RenderPolygon.prototype.isBackface = function() 362 { 363 return this.getNormal().dot(this.getVertex(0)) > 0; 364 }; 365 366 /** 367 * Returns the polygon material. If it has a specific material then this one 368 * is returned. Otherwise the global modal material is returned. 369 * 370 * @return {threedee.Material} The material to be used for this polygon 371 */ 372 threedee.RenderPolygon.prototype.getMaterial = function() 373 { 374 if (this.material) return this.material; 375 return this.model.getMaterial(); 376 }; 377