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/Color.js 7 * @require threedee/Vector.js 8 * @use threedee/rendering/FpsCounter.js 9 * @use threedee/rendering/RenderOptions.js 10 * @use threedee/rendering/TransformedLight.js 11 * @use threedee/rendering/Frustum.js 12 */ 13 14 /** 15 * @constructor 16 * Constructs a new polygon buffer. 17 * 18 * @class 19 * A polygon buffer 20 */ 21 22 threedee.PolygonBuffer = function() 23 { 24 this.vertices = []; 25 this.polygons = []; 26 this.lights = []; 27 this.fpsCounter = new threedee.FpsCounter(); 28 this.renderOptions = new threedee.RenderOptions(); 29 }; 30 31 /** 32 * Speed-optimization vector cache. 33 * @private 34 * @type {!threedee.Vector} 35 */ 36 threedee.PolygonBuffer.V = new threedee.Vector(); 37 38 /** 39 * The render options. 40 * @private 41 * @type {!threedee.RenderOptions} 42 */ 43 threedee.PolygonBuffer.prototype.renderOptions; 44 45 /** 46 * The last gathered debug info. 47 * @private 48 * @type {string} 49 */ 50 threedee.PolygonBuffer.prototype.debugInfo = ""; 51 52 /** 53 * The vertices in the buffer. 54 * @private 55 * @type {!Array.<!threedee.Vector>} 56 */ 57 threedee.PolygonBuffer.prototype.vertices; 58 59 /** 60 * The polygons in the buffer. 61 * @private 62 * @type {!Array.<!threedee.RenderPolygon>} 63 */ 64 threedee.PolygonBuffer.prototype.polygons; 65 66 /** 67 * The registered light sources. 68 * @private 69 * @type {!Array.<!threedee.TransformedLight>} 70 */ 71 threedee.PolygonBuffer.prototype.lights; 72 73 /** 74 * The screen width. 75 * @private 76 * @type {number} 77 */ 78 threedee.PolygonBuffer.prototype.width = 0; 79 80 /** 81 * The screen height. 82 * @private 83 * @type {number} 84 */ 85 threedee.PolygonBuffer.prototype.height = 0; 86 87 /** 88 * The factor used for scaling the perspective view transformation. 89 * @private 90 * @type {number} 91 */ 92 threedee.PolygonBuffer.prototype.factor = 1; 93 94 /** 95 * The view frustum. 96 * @private 97 * @type {!threedee.Frustum} */ 98 threedee.PolygonBuffer.prototype.frustum; 99 100 /** 101 * The FPS counter. 102 * @private 103 * @type {!threedee.FpsCounter} 104 */ 105 threedee.PolygonBuffer.prototype.fpsCounter; 106 107 /** 108 * The global ambient color. 109 * @private 110 * @type {!threedee.Color} 111 */ 112 threedee.PolygonBuffer.prototype.globalAmbient = threedee.Color.DARK_GRAY; 113 114 /** 115 * Sets the global ambient color. 116 * 117 * @param {!threedee.Color} globalAmbient 118 * The global ambient color to set 119 */ 120 threedee.PolygonBuffer.prototype.setGlobalAmbient = function(globalAmbient) 121 { 122 this.globalAmbient = globalAmbient; 123 }; 124 125 /** 126 * Sets the render options. 127 * 128 * @param {!threedee.RenderOptions} renderOptions 129 * The render options to set. 130 */ 131 threedee.PolygonBuffer.prototype.setRenderOptions = function(renderOptions) 132 { 133 this.renderOptions = renderOptions; 134 }; 135 136 /** 137 * Prepares the polygon buffer for the next use. 138 * 139 * @param {number} width 140 * The output width in pixels. 141 * @param {number} height 142 * The output height in pixels. 143 */ 144 threedee.PolygonBuffer.prototype.prepare = function(width, height) 145 { 146 var eyeDistance, screenInMeters, screenInPixels, dpm; 147 148 // Reset the buffer 149 this.vertices = []; 150 this.polygons = []; 151 this.lights = []; 152 153 // Setup the view frustum (Repeated when output size changes) 154 if (width != this.width || height != this.height) 155 { 156 eyeDistance = 0.5; // Eye is 0.5 meters from screen 157 screenInMeters = 0.38; 158 screenInPixels = Math.min(width, height); 159 dpm = screenInPixels / screenInMeters; 160 this.factor = eyeDistance * dpm; 161 162 // Generate the view frustum for clipping the polygons 163 this.frustum = new threedee.Frustum(width, height, this.factor); 164 165 // Remember output size 166 this.width = width; 167 this.height = height; 168 } 169 }; 170 171 /** 172 * Adds the specified model to the polygon buffer. 173 * 174 * @param {!threedee.RenderModel} model 175 * The model to add 176 * @param {!threedee.Matrix} transform 177 * The transformation matrix to use 178 */ 179 threedee.PolygonBuffer.prototype.addModel = function(model, transform) 180 { 181 var backfaceCulling, i, max, polygon; 182 183 // Pull render options into local variables 184 backfaceCulling = this.renderOptions.backfaceCulling; 185 186 // Inform the model about the transformation if it is a LoD model 187 //if (model instanceof threedee.LoDModel) 188 //model.prepareLoD(transform, this.factor); 189 190 // Transform the vertices of the model 191 model.transform(transform); 192 193 // Create the transformed polygons and add them to the buffer 194 for (i = 0, max = model.countPolygons(); i < max; i++) 195 { 196 polygon = model.getPolygon(i); 197 198 polygon.init(); 199 200 // Perform back-face culling 201 if (backfaceCulling && polygon.isBackface()) continue; 202 203 // Clip the polygon with the frustum. If it was completely clipped 204 // away then ignore it 205 if (this.frustum.clipPolygon(polygon)) continue; 206 207 // Calculate the average Z position of the polygon so it can be 208 // z-sorted later 209 polygon.updateAverageZ(); 210 211 // Add the polygon to the buffer 212 this.polygons.push(polygon); 213 } 214 }; 215 216 /** 217 * Adds the specified light to the polygon buffer. 218 * 219 * @param {!threedee.Light} light 220 * The light to add 221 * @param {!threedee.Matrix} transform 222 * The transformation matrix to use 223 */ 224 threedee.PolygonBuffer.prototype.addLight = function(light, transform) 225 { 226 this.lights.push(new threedee.TransformedLight(light, transform)); 227 }; 228 229 /** 230 * Renders the polygon buffer. 231 * 232 * @param {!CanvasRenderingContext2D} g 233 * The graphics context 234 */ 235 threedee.PolygonBuffer.prototype.render = function(g) 236 { 237 var x, y, factor, i, max, vertexCount, polygon, v, vertex, dx, dy, dz, 238 displayNormals, lighting, solid, normal, center, normalEnd, cx, cy, 239 cx2, cy2, sortZ, debugInfo, fpsInfo, polygonCounter, vertexCounter; 240 241 // Pull render options in local variables 242 displayNormals = this.renderOptions.displayNormals; 243 sortZ = this.renderOptions.sortZ; 244 lighting = this.renderOptions.lighting; 245 solid = this.renderOptions.solid; 246 debugInfo = this.renderOptions.debugInfo; 247 fpsInfo = this.renderOptions.fpsInfo; 248 249 // Initialize counters if needed 250 polygonCounter = 0; 251 vertexCounter = 0; 252 253 // Do Z-sorting 254 if (sortZ) this.polygons.sort(threedee.RenderPolygon.compare); 255 256 // Remember the old transformation and then apply the screen center 257 // transformation 258 g.save(); 259 g.translate(this.width / 2, this.height / 2); 260 261 // Get scale factor 262 factor = this.factor; 263 264 // Set the default stroke color 265 g.strokeStyle = "#fff"; 266 267 // Draw the polygons 268 for (i = 0, max = this.polygons.length; i < max; i++) 269 { 270 polygon = this.polygons[i]; 271 272 // Project the 3D vertices into 2D coordinates and draw the 273 // polygon 274 vertexCount = polygon.countVertices(); 275 g.beginPath(); 276 for (v = 0; v < vertexCount; v++) 277 { 278 vertex = polygon.getVertex(v); 279 280 dx = vertex.x; 281 dy = vertex.y; 282 dz = vertex.z; 283 284 x = dx * factor / dz; 285 y = -dy * factor / dz; 286 287 if (v) 288 g.lineTo(x, y); 289 else 290 g.moveTo(x, y); 291 } 292 g.closePath(); 293 294 // Set fill or stroke color (Depends on if solid polygons are rendered) 295 if (solid) 296 { 297 if (lighting) 298 this.applyPolygonColor(polygon, g); 299 else 300 g.fillStyle = polygon.getMaterial().getDiffuse().toCSS(); 301 g.fill(); 302 if (this.renderOptions.outline) 303 { 304 g.strokeStyle = this.renderOptions.outlineColor || g.fillStyle; 305 g.stroke(); 306 } 307 } 308 else 309 { 310 g.stroke(); 311 } 312 313 // Display normals if needed 314 if (displayNormals) 315 { 316 normal = polygon.getNormal(); 317 g.save(); 318 g.strokeStyle = threedee.Color.YELLOW.toCSS(); 319 center = polygon.getCenter(); 320 normalEnd = normal.add(center); 321 cx = center.x * factor / center.z; 322 cy = -center.y * factor / center.z; 323 cx2 = normalEnd.x * factor / normalEnd.z; 324 cy2 = -normalEnd.y * factor / normalEnd.z; 325 g.beginPath(); 326 g.moveTo(cx, cy); 327 g.lineTo(cx2, cy2); 328 g.stroke(); 329 g.restore(); 330 } 331 332 // Update counters if needed 333 if (debugInfo) 334 { 335 polygonCounter++; 336 vertexCounter += vertexCount; 337 } 338 } 339 340 // Gather debugging info if requested 341 if (fpsInfo || debugInfo) 342 { 343 if (fpsInfo) 344 { 345 this.fpsCounter.frame(); 346 this.debugInfo = "Frames/s: " + this.fpsCounter.getFps(); 347 if (debugInfo) this.debugInfo += "\n\n"; 348 } 349 else this.debugInfo = ""; 350 351 if (debugInfo) 352 { 353 this.debugInfo += "Vertices: " + vertexCounter + 354 "\nPolygons: " + polygonCounter + 355 "\n\nNew objects for this frame:" + 356 "\n Vector: " + threedee.Vector.count() + 357 "\n Matrix: " + threedee.Matrix.count() + 358 "\n Color: " + threedee.Color.count() + 359 "\n Material: " + threedee.Material.count() + 360 "\n Polygon: " + threedee.Polygon.count(); 361 } 362 } 363 364 // Restore the original transformation 365 g.restore(); 366 }; 367 368 /** 369 * Calculates the polygon color according to the material and the light 370 * sources and applies it to the specified graphics context. 371 * 372 * @param {!threedee.RenderPolygon} polygon 373 * The polygon 374 * @param {!CanvasRenderingContext2D} g 375 * The graphics context 376 */ 377 threedee.PolygonBuffer.prototype.applyPolygonColor = function(polygon, g) 378 { 379 var material, position, normal, globalAmbient, ambient, diffuse, emissive, 380 result, addedDiffuse, i, j, max, transLight, light, lightColor, 381 lightPosition, L, diffuseLight; 382 383 // Get the material from the polygon 384 material = polygon.getMaterial(); 385 386 // Get the position (The center) and the normal from the polygon 387 position = polygon.getCenter(); 388 normal = polygon.getNormal(); 389 390 // Get the color components of the global ambient color 391 globalAmbient = this.globalAmbient.getComponents(); 392 393 // Get the ambient, diffuse and emissive color components of the 394 // material 395 ambient = material.getAmbient().getComponents(); 396 diffuse = material.getDiffuse().getComponents(); 397 emissive = material.getEmissive().getComponents(); 398 399 // We store the resulting color components here: 400 result = new Array(3); 401 402 // We store the added diffuse color components here: 403 addedDiffuse = (/** @type {!Array.<number>} */ []); 404 addedDiffuse[0] = 0; 405 addedDiffuse[1] = 0; 406 addedDiffuse[2] = 0; 407 408 // Now we cycle through all color components and calculate the values 409 for (i = 0; i < 3; i++) 410 { 411 // Calculate the ambient color value 412 ambient[i] = ambient[i] * globalAmbient[i]; 413 414 // Iterate over all registered light sources and process them 415 for (j = 0, max = this.lights.length; j < max; j++) 416 { 417 transLight = this.lights[j]; 418 light = transLight.getLight(); 419 if (light instanceof threedee.PointLight) 420 { 421 lightColor = light.getColor().getComponent(i); 422 423 lightPosition = transLight.getPosition().copy(threedee.PolygonBuffer.V); 424 L = lightPosition.sub(position).toUnit(); 425 diffuseLight = Math.max(normal.dot(L), 0); 426 427 addedDiffuse[i] += diffuse[i] * lightColor * diffuseLight; 428 } 429 } 430 431 result[i] = Math.min(ambient[i] + emissive[i] + addedDiffuse[i], 1); 432 } 433 434 // Apply color to graphics context 435 g.fillStyle = "rgb(" + 436 parseInt(result[0] * 255, 10) + "," + 437 parseInt(result[1] * 255, 10) + "," + 438 parseInt(result[2] * 255, 10) + ")"; 439 }; 440 441 442 /** 443 * Returns debugging info. The debugInfo flag in the rendering options must 444 * be set to true to get up-to-date debug info. 445 * 446 * @return {string} The debug info 447 */ 448 449 threedee.PolygonBuffer.prototype.getDebugInfo = function() 450 { 451 return this.debugInfo; 452 }; 453