1 /*************************************** 2 * Copyright 2011, 2012 GlobWeb contributors. 3 * 4 * This file is part of GlobWeb. 5 * 6 * GlobWeb is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU Lesser General Public License as published by 8 * the Free Software Foundation, version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * GlobWeb is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with GlobWeb. If not, see <http://www.gnu.org/licenses/>. 18 ***************************************/ 19 20 define(['./Tile','./TilePool', './TileRequest', './TileIndexBuffer', './Program', './CoordinateSystem'], 21 function (Tile,TilePool,TileRequest,TileIndexBuffer,Program, CoordinateSystem) { 22 23 /** @constructor 24 TileManager constructor 25 */ 26 var TileManager = function( globe ) 27 { 28 this.globe = globe; 29 this.renderContext = this.globe.renderContext; 30 this.tilePool = new TilePool(this.renderContext); 31 this.imageryProvider = null; 32 this.elevationProvider = null; 33 this.tilesToRender = []; 34 this.tilesToRequest = []; 35 this.postRenderers = []; 36 this.level0Tiles = []; 37 this.levelZeroTexture = null; 38 39 // Tile requests : limit to 4 at a given time 40 this.maxRequests = 4; 41 this.availableRequests = []; 42 for ( var i=0; i < this.maxRequests; i++ ) 43 { 44 this.availableRequests[i] = new TileRequest(this); 45 } 46 this.completedRequests = []; 47 48 this.level0TilesLoaded = false; 49 50 // Configuration for tile 51 this.tileConfig = { 52 tesselation: 9, 53 skirt: true, 54 cullSign: 1.0, 55 imageSize: 256, 56 vertexSize: this.renderContext.lighting ? 6 : 3, 57 normals: this.renderContext.lighting 58 }; 59 60 // Shared index and texture coordinate buffer : all tiles uses the same 61 this.tcoordBuffer = null; 62 this.tileIndexBuffer = new TileIndexBuffer(this.renderContext,this.tileConfig); 63 this.identityTextureTransform = [ 1.0, 1.0, 0.0, 0.0 ]; 64 65 // For debug 66 this.showWireframe = false; 67 this.freeze = false; 68 69 // Stats 70 this.numTilesGenerated = 0; 71 this.frameNumber = 0; 72 73 var vertexShader = "\ 74 attribute vec3 vertex;\n\ 75 attribute vec2 tcoord;\n\ 76 uniform mat4 modelViewMatrix;\n\ 77 uniform mat4 projectionMatrix;\n\ 78 uniform vec4 texTransform;\n\ 79 varying vec2 texCoord;\n"; 80 if ( this.renderContext.lighting ) 81 vertexShader += "attribute vec3 normal;\nvarying vec3 color;\n"; 82 vertexShader += "\ 83 void main(void) \n\ 84 {\n\ 85 gl_Position = projectionMatrix * modelViewMatrix * vec4(vertex, 1.0);\n"; 86 if ( this.renderContext.lighting ) 87 vertexShader += "vec4 vn = modelViewMatrix * vec4(normal,0);\ncolor = max( vec3(-vn[2],-vn[2],-vn[2]), 0.0 );\n"; 88 vertexShader += "\ 89 texCoord = vec2(tcoord.s * texTransform.x + texTransform.z, tcoord.t * texTransform.y + texTransform.w);\n\ 90 }\n\ 91 "; 92 93 var fragmentShader = "\ 94 precision highp float; \n\ 95 varying vec2 texCoord;\n"; 96 if ( this.renderContext.lighting ) 97 fragmentShader += "varying vec3 color;\n"; 98 fragmentShader += "\ 99 uniform sampler2D colorTexture;\n\ 100 void main(void)\n\ 101 {\n\ 102 gl_FragColor.rgb = texture2D(colorTexture, texCoord).rgb;\n"; 103 if ( this.renderContext.lighting ) 104 fragmentShader += "gl_FragColor.rgb *= color;\n"; 105 fragmentShader += "\ 106 gl_FragColor.a = 1.0;\n\ 107 }\n\ 108 "; 109 110 this.program = new Program(this.renderContext); 111 this.program.createFromSource( vertexShader, fragmentShader ); 112 } 113 114 /**************************************************************************************************************/ 115 116 /** 117 Add post renderer 118 */ 119 TileManager.prototype.addPostRenderer = function(renderer) 120 { 121 this.postRenderers.push( renderer ); 122 } 123 124 /**************************************************************************************************************/ 125 126 /** 127 Remove a post renderer 128 */ 129 TileManager.prototype.removePostRenderer = function(renderer) 130 { 131 var rendererIndex = this.postRenderers.indexOf(renderer); 132 if ( rendererIndex != -1 ) 133 { 134 // Remove the renderer from all the tiles if it has a cleanupTile method 135 if ( renderer.cleanupTile ) 136 this.visitTiles( function(tile) { renderer.cleanupTile(tile); } ); 137 138 // Remove renderer from the list 139 this.postRenderers.splice( rendererIndex, 1 ); 140 } 141 } 142 143 /**************************************************************************************************************/ 144 145 /** 146 Set the imagery provider to be used 147 */ 148 TileManager.prototype.setImageryProvider = function(ip) 149 { 150 this.reset(); 151 this.imageryProvider = ip; 152 153 if (ip) 154 { 155 // Rebuild level zero tiles 156 this.tileConfig.imageSize = ip.tilePixelSize; 157 this.level0Tiles = ip.tiling.generateLevelZeroTiles(this.tileConfig,this.tilePool); 158 } 159 } 160 161 /**************************************************************************************************************/ 162 163 /** 164 Set the elevation provider to be used 165 */ 166 TileManager.prototype.setElevationProvider = function(tp) 167 { 168 this.reset(); 169 this.elevationProvider = tp; 170 this.tileConfig.tesselation = tp ? tp.tilePixelSize : 9; 171 } 172 173 /**************************************************************************************************************/ 174 175 /** 176 Reset the tile manager : remove all the tiles 177 */ 178 TileManager.prototype.reset = function() 179 { 180 // Reset all level zero tiles : destroy render data, and reset state to NONE 181 for (var i = 0; i < this.level0Tiles.length; i++) 182 { 183 this.level0Tiles[i].deleteChildren(this.renderContext,this.tilePool); 184 this.level0Tiles[i].dispose(this.renderContext,this.tilePool); 185 } 186 187 // Reset the shared buffers : texture coordinate and indices 188 var gl = this.renderContext.gl; 189 this.tileIndexBuffer.reset(); 190 gl.deleteBuffer( this.tcoordBuffer ); 191 this.tcoordBuffer = null; 192 193 this.levelZeroTexture = null; 194 195 this.level0TilesLoaded = false; 196 } 197 198 /**************************************************************************************************************/ 199 200 /** 201 Tile visitor 202 */ 203 TileManager.prototype.visitTiles = function( callback ) 204 { 205 // Store the tiles to process in an array, first copy level0 tiles 206 var tilesToProcess = this.level0Tiles.concat([]); 207 208 while( tilesToProcess.length > 0 ) 209 { 210 // Retreive the first tile and remove it from the array 211 var tile = tilesToProcess.shift(); 212 213 callback( tile ); 214 215 // Add tile children to array to be processed later 216 if ( tile.children ) 217 { 218 tilesToProcess.push( tile.children[0] ); 219 tilesToProcess.push( tile.children[1] ); 220 tilesToProcess.push( tile.children[2] ); 221 tilesToProcess.push( tile.children[3] ); 222 } 223 } 224 } 225 226 /**************************************************************************************************************/ 227 228 /** 229 Traverse tiless tiles 230 */ 231 TileManager.prototype.traverseTiles = function() 232 { 233 this.tilesToRender.length = 0; 234 this.tilesToRequest.length = 0; 235 this.numTraversedTiles = 0; 236 237 // First load level 0 tiles if needed 238 if ( !this.level0TilesLoaded && !this.levelZeroTexture ) 239 { 240 this.level0TilesLoaded = true; 241 for ( var i = 0; i < this.level0Tiles.length; i++ ) 242 { 243 var tile = this.level0Tiles[i]; 244 var tileIsLoaded = tile.state == Tile.State.LOADED; 245 246 // Update frame number 247 tile.frameNumber = this.frameNumber; 248 249 this.level0TilesLoaded = this.level0TilesLoaded && tileIsLoaded; 250 if ( !tileIsLoaded ) 251 { 252 // Request tile if necessary 253 if ( tile.state == Tile.State.NONE ) 254 { 255 tile.state = Tile.State.REQUESTED; 256 this.tilesToRequest.push(tile); 257 } 258 else if ( tile.state == Tile.State.ERROR ) 259 { 260 this.globe.publish("baseLayersError"); 261 this.imageryProvider._ready = false; 262 } 263 } 264 } 265 if ( this.level0TilesLoaded ) 266 { 267 this.globe.publish("baseLayersReady"); 268 } 269 } 270 271 // Traverse tiles 272 if ( this.level0TilesLoaded || this.levelZeroTexture ) 273 { 274 // Normal traversal, iterate through level zero tiles and process them recursively 275 for ( var i = 0; i < this.level0Tiles.length; i++ ) 276 { 277 var tile = this.level0Tiles[i]; 278 if ( !tile.isCulled(this.renderContext) ) 279 { 280 this.processTile(tile,0); 281 } 282 else 283 { 284 var tileIsLoaded = (tile.state == Tile.State.LOADED); 285 // Remove texture from level 0 tile, only if there is a global level zero texture 286 if( this.levelZeroTexture && tileIsLoaded ) 287 { 288 this.tilePool.disposeGLTexture( tile.texture ); 289 tile.texture = null; 290 tile.state = Tile.State.NONE; 291 } 292 // Delete its children 293 tile.deleteChildren(this.renderContext,this.tilePool); 294 } 295 } 296 } 297 } 298 299 /**************************************************************************************************************/ 300 301 /** 302 Process a tile 303 */ 304 TileManager.prototype.processTile = function(tile,level) 305 { 306 this.numTraversedTiles++; 307 308 // Update frame number 309 tile.frameNumber = this.frameNumber; 310 311 // Request the tile if needed 312 if ( tile.state == Tile.State.NONE ) 313 { 314 tile.state = Tile.State.REQUESTED; 315 316 // Add it to the request 317 this.tilesToRequest.push(tile); 318 } 319 320 // Check if the tiles needs to be refined 321 if ( (tile.state == Tile.State.LOADED) && (level+1 < this.imageryProvider.numberOfLevels) && (tile.needsToBeRefined(this.renderContext) ) ) 322 { 323 // Create the children if needed 324 if ( tile.children == null ) 325 { 326 tile.createChildren(); 327 } 328 329 for ( var i = 0; i < 4; i++ ) 330 { 331 if (!tile.children[i].isCulled(this.renderContext)) 332 { 333 this.processTile(tile.children[i],level+1); 334 } 335 else 336 { 337 tile.children[i].deleteChildren(this.renderContext,this.tilePool); 338 } 339 } 340 } 341 else 342 { 343 // Push the tiles to render 344 this.tilesToRender.push( tile ); 345 } 346 } 347 348 /**************************************************************************************************************/ 349 350 /** 351 Generate tiles 352 */ 353 TileManager.prototype.generateReceivedTiles = function() 354 { 355 while ( this.completedRequests.length > 0 ) 356 { 357 var tileRequest = this.completedRequests.pop(); 358 var tile = tileRequest.tile; 359 if ( tile.frameNumber == this.frameNumber ) 360 { 361 // Generate the tile using data from tileRequest 362 if ( this.elevationProvider ) 363 { 364 tile.generate( this.tilePool, tileRequest.image, this.elevationProvider.parseElevations( tileRequest.elevations ) ); 365 } 366 else 367 tile.generate( this.tilePool, tileRequest.image ); 368 369 // Now post renderers can generate their data on the new tile 370 for (var i=0; i < this.postRenderers.length; i++ ) 371 { 372 if ( this.postRenderers[i].generate ) 373 this.postRenderers[i].generate(tile); 374 } 375 376 this.numTilesGenerated++; 377 this.renderContext.requestFrame(); 378 } 379 else 380 { 381 tile.state = Tile.State.NONE; 382 } 383 this.availableRequests.push(tileRequest); 384 } 385 386 // All requests have been processed, send endBackgroundLoad event 387 if ( this.availableRequests.length == this.maxRequests ) 388 this.globe.publish("endBackgroundLoad"); 389 390 } 391 392 /**************************************************************************************************************/ 393 394 /** 395 Render tiles 396 */ 397 TileManager.prototype.renderTiles = function() 398 { 399 var rc = this.renderContext; 400 var gl = rc.gl; 401 402 gl.enable(gl.POLYGON_OFFSET_FILL); 403 gl.polygonOffset(0,4); 404 // TODO : remove this 405 gl.disable(gl.CULL_FACE); 406 407 // Setup program 408 this.program.apply(); 409 410 var attributes = this.program.attributes; 411 412 // Compute near/far from tiles 413 var nr; 414 var fr; 415 if ( this.tileConfig.cullSign < 0 ) 416 { 417 // When in "Astro" mode, do not compute near/far from tiles not really needed 418 // And the code used for "Earth" does not works really well, when the earth is seen from inside... 419 nr = 0.2 * CoordinateSystem.radius; 420 fr = 1.1 * CoordinateSystem.radius; 421 } 422 else 423 { 424 nr = 1e9; 425 fr = 0.0; 426 for ( var i = 0; i < this.tilesToRender.length; i++ ) 427 { 428 var tile = this.tilesToRender[i]; 429 // Update near/far to take into account the tile 430 nr = Math.min( nr, tile.distance - 2.0 * tile.radius ); 431 fr = Math.max( fr, tile.distance + 2.0 * tile.radius ); 432 } 433 } 434 rc.near = Math.max( rc.minNear, Math.min(nr,rc.near) ); 435 rc.far = Math.max( fr, rc.far ); 436 437 // Update projection matrix with new near and far values 438 mat4.perspective(rc.fov, rc.canvas.width / rc.canvas.height, rc.near, rc.far, rc.projectionMatrix); 439 440 // Setup state 441 gl.activeTexture(gl.TEXTURE0); 442 gl.uniformMatrix4fv(this.program.uniforms["projectionMatrix"], false, rc.projectionMatrix); 443 gl.uniform1i(this.program.uniforms["colorTexture"], 0); 444 445 // Bind the texture coordinate buffer (shared between all tiles 446 if ( !this.tcoordBuffer ) 447 this.buildSharedTexCoordBuffer(); 448 gl.bindBuffer(gl.ARRAY_BUFFER, this.tcoordBuffer); 449 gl.vertexAttribPointer(attributes['tcoord'], 2, gl.FLOAT, false, 0, 0); 450 451 var currentIB = null; 452 453 var currentTextureTransform = null; 454 455 for ( var i = 0; i < this.tilesToRender.length; i++ ) 456 { 457 var tile = this.tilesToRender[i]; 458 459 var isLoaded = ( tile.state == Tile.State.LOADED ); 460 var isLevelZero = ( tile.parentIndex == -1 ); 461 462 // Bind tile texture 463 var textureTransform; 464 if ( !isLoaded && isLevelZero ) 465 { 466 // The texture is not yet loaded but there is a full texture to render the tile 467 gl.bindTexture(gl.TEXTURE_2D, this.levelZeroTexture); 468 textureTransform = tile.texTransform; 469 } 470 else 471 { 472 gl.bindTexture(gl.TEXTURE_2D, tile.texture); 473 textureTransform = this.identityTextureTransform; 474 } 475 476 // Update texture transform 477 if ( currentTextureTransform != textureTransform ) 478 { 479 gl.uniform4f(this.program.uniforms["texTransform"], textureTransform[0], textureTransform[1], textureTransform[2], textureTransform[3]); 480 currentTextureTransform = textureTransform; 481 } 482 483 // Update uniforms for modelview matrix 484 mat4.multiply( rc.viewMatrix, tile.matrix, rc.modelViewMatrix ); 485 gl.uniformMatrix4fv(this.program.uniforms["modelViewMatrix"], false, rc.modelViewMatrix); 486 487 // Bind the vertex buffer 488 gl.bindBuffer(gl.ARRAY_BUFFER, tile.vertexBuffer); 489 gl.vertexAttribPointer(attributes['vertex'], 3, gl.FLOAT, false, 4*this.tileConfig.vertexSize, 0); 490 if (this.tileConfig.normals) 491 gl.vertexAttribPointer(attributes['normal'], 3, gl.FLOAT, false, 4*this.tileConfig.vertexSize, 12); 492 493 var indexBuffer = ( isLoaded || isLevelZero ) ? this.tileIndexBuffer.getSolid() : this.tileIndexBuffer.getSubSolid(tile.parentIndex); 494 // Bind the index buffer only if different (index buffer is shared between tiles) 495 if ( currentIB != indexBuffer ) 496 { 497 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); 498 currentIB = indexBuffer; 499 } 500 501 // Finally draw the tiles 502 gl.drawElements(gl.TRIANGLES, currentIB.numIndices, gl.UNSIGNED_SHORT, 0); 503 } 504 505 for (var i=0; i < this.postRenderers.length; i++ ) 506 { 507 if (this.postRenderers[i].needsOffset) 508 this.postRenderers[i].render( this.tilesToRender ); 509 } 510 511 gl.disable(gl.POLYGON_OFFSET_FILL); 512 513 for (var i=0; i < this.postRenderers.length; i++ ) 514 { 515 if (!this.postRenderers[i].needsOffset) 516 this.postRenderers[i].render( this.tilesToRender ); 517 } 518 } 519 520 // Internal function to sort tiles 521 var _sortTilesByDistance = function(t1,t2) 522 { 523 return t1.distance - t2.distance; 524 }; 525 526 /**************************************************************************************************************/ 527 528 /** 529 Request tiles 530 */ 531 TileManager.prototype.launchRequests = function() 532 { 533 // Process request 534 this.tilesToRequest.sort( _sortTilesByDistance ); 535 536 var trl = this.tilesToRequest.length; 537 for ( var i = 0; i < trl; i++ ) 538 { 539 var tile = this.tilesToRequest[i]; 540 if ( this.availableRequests.length > 0 ) // Check to limit the number of requests done per frame 541 { 542 // First launch request, send an event 543 if ( this.availableRequests.length == this.maxRequests ) 544 this.globe.publish("startBackgroundLoad"); 545 546 var tileRequest = this.availableRequests.pop(); 547 tileRequest.launch( tile ); 548 tile.state = Tile.State.LOADING; 549 } 550 else 551 { 552 tile.state = Tile.State.NONE; 553 } 554 } 555 } 556 557 /**************************************************************************************************************/ 558 559 /** 560 Render the tiles 561 */ 562 TileManager.prototype.render = function() 563 { 564 if ( this.imageryProvider == null 565 || !this.imageryProvider._ready ) 566 { 567 return; 568 } 569 570 // Create the texture for level zero 571 if ( this.levelZeroTexture == null && this.imageryProvider.levelZeroImage ) 572 { 573 this.levelZeroTexture = this.renderContext.createNonPowerOfTwoTextureFromImage(this.imageryProvider.levelZeroImage); 574 this.globe.publish("baseLayersReady"); 575 } 576 577 var stats = this.renderContext.stats; 578 579 if (!this.freeze) 580 { 581 if (stats) stats.start("traverseTime"); 582 this.traverseTiles(); 583 if (stats) stats.end("traverseTime"); 584 } 585 586 if ( this.level0TilesLoaded || this.levelZeroTexture ) 587 { 588 if (stats) stats.start("renderTime"); 589 this.renderTiles(); 590 if (stats) stats.end("renderTime"); 591 } 592 593 if (stats) stats.start("generateTime"); 594 this.generateReceivedTiles(); 595 if (stats) stats.end("generateTime"); 596 597 if (stats) stats.start("requestTime"); 598 this.launchRequests(); 599 if (stats) stats.end("requestTime"); 600 601 this.frameNumber++; 602 } 603 604 /**************************************************************************************************************/ 605 606 /** 607 Returns visible tile for given longitude/latitude, null otherwise 608 */ 609 TileManager.prototype.getVisibleTile = function(lon, lat) 610 { 611 return this.imageryProvider.tiling.findInsideTile(lon, lat, this.tilesToRender); 612 } 613 614 /**************************************************************************************************************/ 615 616 /** 617 Build shared texture coordinate buffer 618 */ 619 TileManager.prototype.buildSharedTexCoordBuffer = function() 620 { 621 var size = this.tileConfig.tesselation; 622 var skirt = this.tileConfig.skirt; 623 var bufferSize = 2*size*size; 624 if (skirt) 625 bufferSize += 2*size*6; 626 627 var tcoords = new Float32Array( bufferSize ); 628 629 var step = 1.0 / (size-1); 630 631 var offset = 0; 632 var v = 0.0; 633 for ( var j=0; j < size; j++) 634 { 635 var u = 0.0; 636 for ( var i=0; i < size; i++) 637 { 638 tcoords[offset] = u; 639 tcoords[offset+1] = v; 640 641 offset += 2; 642 u += step; 643 } 644 645 v += step; 646 } 647 648 if ( skirt ) 649 { 650 // Top skirt 651 u = 0.0; 652 v = 0.0; 653 for ( var i=0; i < size; i++) 654 { 655 tcoords[offset] = u; 656 tcoords[offset+1] = v; 657 u += step; 658 offset += 2; 659 } 660 // Bottom skirt 661 u = 0.0; 662 v = 1.0; 663 for ( var i=0; i < size; i++) 664 { 665 tcoords[offset] = u; 666 tcoords[offset+1] = v; 667 u += step; 668 offset += 2; 669 } 670 // Left skirt 671 u = 0.0; 672 v = 0.0; 673 for ( var i=0; i < size; i++) 674 { 675 tcoords[offset] = u; 676 tcoords[offset+1] = v; 677 v += step; 678 offset += 2; 679 } 680 // Right skirt 681 u = 1.0; 682 v = 0.0; 683 for ( var i=0; i < size; i++) 684 { 685 tcoords[offset] = u; 686 tcoords[offset+1] = v; 687 v += step; 688 offset += 2; 689 } 690 691 // Center skirt 692 u = 0.0; 693 v = 0.5; 694 for ( var i=0; i < size; i++) 695 { 696 tcoords[offset] = u; 697 tcoords[offset+1] = v; 698 u += step; 699 offset += 2; 700 } 701 702 // Middle skirt 703 u = 0.5; 704 v = 0.0; 705 for ( var i=0; i < size; i++) 706 { 707 tcoords[offset] = u; 708 tcoords[offset+1] = v; 709 v += step; 710 offset += 2; 711 } 712 } 713 714 var gl = this.renderContext.gl; 715 var tcb = gl.createBuffer(); 716 gl.bindBuffer(gl.ARRAY_BUFFER, tcb); 717 gl.bufferData(gl.ARRAY_BUFFER, tcoords, gl.STATIC_DRAW); 718 719 this.tcoordBuffer = tcb; 720 } 721 722 /**************************************************************************************************************/ 723 724 return TileManager; 725 726 });