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( ['./Program','./Tile','./RendererTileData'], function(Program,Tile,RendererTileData) { 21 22 //************************************************************************* 23 24 /** 25 @constructor 26 */ 27 var RasterOverlayRenderer = function(tileManager) 28 { 29 var vertexShader = "\ 30 attribute vec3 vertex;\n\ 31 attribute vec2 tcoord;\n\ 32 uniform mat4 modelViewMatrix;\n\ 33 uniform mat4 projectionMatrix;\n\ 34 uniform vec4 textureTransform; \n\ 35 varying vec2 texCoord;\n\ 36 void main(void) \n\ 37 {\n\ 38 gl_Position = projectionMatrix * modelViewMatrix * vec4(vertex, 1.0);\n\ 39 texCoord = tcoord * textureTransform.xy + textureTransform.zw;\n\ 40 }\n\ 41 "; 42 43 var fragmentShader = "\ 44 precision lowp float;\n\ 45 varying vec2 texCoord;\n\ 46 uniform sampler2D overlayTexture;\n\ 47 uniform float opacity; \n\ 48 void main(void)\n\ 49 {\n\ 50 gl_FragColor.rgba = texture2D(overlayTexture, texCoord.xy); \n\ 51 gl_FragColor.a *= opacity; \n\ 52 }\n\ 53 "; 54 55 this.requestHighestResolutionFirst = true; 56 this.tileManager = tileManager; 57 58 this.program = new Program(tileManager.renderContext); 59 this.program.createFromSource( vertexShader, fragmentShader ); 60 61 this.overlays = []; 62 this.imageRequests = []; 63 this.frameNumber = 0; 64 65 66 var self = this; 67 for ( var i = 0; i < 4; i++ ) { 68 var image = new Image(); 69 image.renderable = null; 70 image.frameNumber = -1; 71 image.crossOrigin = ''; 72 image.onload = function() 73 { 74 if ( this.renderable ) 75 { 76 this.renderable.texture = tileManager.tilePool.createGLTexture(this); 77 this.renderable.onRequestFinished(true); 78 this.renderable = null; 79 self.tileManager.renderContext.requestFrame(); 80 } 81 }; 82 image.onerror = function() 83 { 84 if ( this.renderable ) 85 { 86 this.renderable.onRequestFinished(true); 87 this.renderable = null; 88 } 89 }; 90 image.onabort = function() 91 { 92 console.log("Raster overlay request abort."); 93 if ( this.renderable ) 94 { 95 this.renderable.onRequestFinished(false); 96 this.renderable = null; 97 } 98 }; 99 100 this.imageRequests.push( image ); 101 } 102 103 104 this.needsOffset = true; 105 } 106 107 /**************************************************************************************************************/ 108 109 /** 110 @constructor 111 Create a renderable for the overlay. 112 There is one renderable per overlay and per tile. 113 */ 114 var RasterOverlayRenderable = function( layer ) 115 { 116 this.bucket = layer; 117 this.texture = null; 118 this.request = null; 119 this.requestFinished = false; 120 } 121 122 /**************************************************************************************************************/ 123 124 /** 125 Called when a request is started 126 */ 127 RasterOverlayRenderable.prototype.onRequestStarted = function(request) 128 { 129 this.request = request; 130 this.requestFinished = false; 131 // Bucket is in fact the layer! 132 var layer = this.bucket; 133 if ( layer._numRequests == 0 ) 134 { 135 layer.globe.publish('startLoad',layer); 136 } 137 layer._numRequests++; 138 } 139 140 /**************************************************************************************************************/ 141 142 /** 143 Called when a request is finished 144 */ 145 RasterOverlayRenderable.prototype.onRequestFinished = function(completed) 146 { 147 this.request = null; 148 this.requestFinished = completed; 149 // Bucket is in fact the layer! 150 var layer = this.bucket; 151 layer._numRequests--; 152 if ( layer._numRequests == 0 ) 153 { 154 layer.globe.publish('endLoad',layer); 155 } 156 } 157 158 /**************************************************************************************************************/ 159 160 /** 161 Dispose the renderable 162 */ 163 RasterOverlayRenderable.prototype.dispose = function(renderContext,tilePool) 164 { 165 if ( this.texture ) 166 { 167 tilePool.disposeGLTexture(this.texture); 168 this.texture = null; 169 } 170 } 171 172 /**************************************************************************************************************/ 173 174 /** 175 Add an overlay into the renderer. 176 The overlay is added to all loaded tiles. 177 */ 178 RasterOverlayRenderer.prototype.addOverlay = function( overlay ) 179 { 180 // Initialize num requests to 0 181 overlay._numRequests = 0; 182 183 this.overlays.push( overlay ); 184 for ( var i = 0; i < this.tileManager.level0Tiles.length; i++ ) 185 { 186 var tile = this.tileManager.level0Tiles[i]; 187 if ( tile.state == Tile.State.LOADED ) 188 { 189 this.addOverlayToTile( tile, overlay ); 190 } 191 } 192 } 193 194 /**************************************************************************************************************/ 195 196 /** 197 Remove an overlay 198 The overlay is removed from all loaded tiles. 199 */ 200 RasterOverlayRenderer.prototype.removeOverlay = function( overlay ) 201 { 202 var index = this.overlays.indexOf( overlay ); 203 this.overlays.splice(index,1); 204 205 var rc = this.tileManager.renderContext; 206 var tp = this.tileManager.tilePool; 207 this.tileManager.visitTiles( function(tile) 208 { 209 var rs = tile.extension.rasterOverlay; 210 var renderable = rs ? rs.getRenderable( overlay ) : null; 211 if ( renderable ) 212 { 213 // Remove the renderable 214 var index = rs.renderables.indexOf(renderable); 215 rs.renderables.splice(index,1); 216 217 // Dispose its data 218 renderable.dispose(rc,tp); 219 220 // Remove tile data if not needed anymore 221 if ( rs.renderables.length == 0 ) 222 delete tile.extension.rasterOverlay; 223 } 224 } 225 ); 226 } 227 228 /**************************************************************************************************************/ 229 230 /** 231 Add an overlay into a tile. 232 Create tile data if needed, and create the renderable for the overlay. 233 */ 234 RasterOverlayRenderer.prototype.addOverlayToTile = function( tile, overlay ) 235 { 236 if ( !tile.extension.rasterOverlay ) 237 tile.extension.rasterOverlay = new RendererTileData(); 238 239 tile.extension.rasterOverlay.renderables.push( new RasterOverlayRenderable(overlay) ); 240 241 if ( tile.children ) 242 { 243 // Add the overlay to loaded children 244 for ( var i = 0; i < 4; i++ ) 245 { 246 if ( tile.children[i].state == Tile.State.LOADED 247 && this.overlayIntersects( tile.children[i].geoBound, overlay ) ) 248 { 249 this.addOverlayToTile( tile.children[i], overlay ); 250 } 251 } 252 } 253 254 } 255 256 /**************************************************************************************************************/ 257 258 /** 259 Create an interpolated for polygon clipping 260 */ 261 var _createInterpolatedVertex = function( t, p1, p2 ) 262 { 263 return [ p1[0] + t * (p2[0] - p1[0]), p1[1] + t * (p2[1] - p1[1]) ]; 264 } 265 266 /**************************************************************************************************************/ 267 268 /** 269 Clip polygon to a side (used by bound-overlay intersection) 270 */ 271 RasterOverlayRenderer.prototype.clipPolygonToSide = function( coord, sign, value, polygon ) 272 { 273 var clippedPolygon = []; 274 275 // iterate through vertices 276 for ( var i = 0; i < polygon.length; i++ ) 277 { 278 var p1 = polygon[i]; 279 var p2 = polygon[ (i+1) % polygon.length ]; 280 var val1 = p1[coord]; 281 var val2 = p2[coord]; 282 283 // test containement 284 var firstInside = (val1 - value) * sign >= 0.0; 285 var secondInside = (val2 - value) * sign >= 0.0; 286 287 // output vertices for inside polygon 288 if ( !firstInside && secondInside ) 289 { 290 var t = (value - val1) / (val2- val1); 291 var newPoint = _createInterpolatedVertex( t, p1, p2 ); 292 clippedPolygon.push( newPoint ); 293 clippedPolygon.push( p2 ); 294 } 295 else if ( firstInside && secondInside ) 296 { 297 clippedPolygon.push( p2 ); 298 } 299 else if ( firstInside && !secondInside ) 300 { 301 var t = (value - val1) / (val2- val1); 302 var newPoint = _createInterpolatedVertex( t, p1, p2 ); 303 clippedPolygon.push( newPoint ); 304 } 305 } 306 307 return clippedPolygon; 308 } 309 310 /**************************************************************************************************************/ 311 312 /** 313 Check the intersection between a geo bound and an overlay 314 */ 315 RasterOverlayRenderer.prototype.overlayIntersects = function( bound, overlay ) 316 { 317 if ( overlay.coordinates ) 318 { 319 var c; 320 c = this.clipPolygonToSide( 0, 1, bound.west, overlay.coordinates ); 321 c = this.clipPolygonToSide( 0, -1, bound.east, c ); 322 c = this.clipPolygonToSide( 1, 1, bound.south, c ); 323 c = this.clipPolygonToSide( 1, -1, bound.north, c ); 324 return c.length > 0; 325 } 326 else if ( overlay.geoBound ) 327 { 328 return overlay.geoBound.intersects( bound ); 329 } 330 331 // No geobound or coordinates : always return true 332 return true; 333 } 334 335 /**************************************************************************************************************/ 336 337 /** 338 Generate Raster overlay data on the tile. 339 The method is called by TileManager when a new tile has been generated. 340 */ 341 RasterOverlayRenderer.prototype.generate = function( tile ) 342 { 343 if ( tile.parent ) 344 { 345 // Only add feature from parent tile (if any) 346 var data = tile.parent.extension.rasterOverlay; 347 var rl = data ? data.renderables.length : 0; 348 for ( var i = 0; i < rl; i++ ) 349 { 350 var overlay = data.renderables[i].bucket; 351 if ( this.overlayIntersects( tile.geoBound, overlay ) ) 352 this.addOverlayToTile(tile,overlay); 353 } 354 } 355 else 356 { 357 // Traverse all overlays 358 for ( var i = 0; i < this.overlays.length; i++ ) 359 { 360 var overlay = this.overlays[i]; 361 if ( this.overlayIntersects( tile.geoBound, overlay ) ) 362 this.addOverlayToTile(tile,overlay); 363 } 364 } 365 } 366 367 /**************************************************************************************************************/ 368 369 /** 370 Request the overlay texture for a tile 371 */ 372 RasterOverlayRenderer.prototype.requestOverlayTextureForTile = function( tile, renderable ) 373 { 374 if ( !renderable.request ) 375 { 376 var imageRequest; 377 for ( var i = 0; i < this.imageRequests.length; i++ ) 378 { 379 if ( !this.imageRequests[i].renderable ) 380 { 381 imageRequest = this.imageRequests[i]; 382 break; 383 } 384 } 385 386 if ( imageRequest ) 387 { 388 renderable.onRequestStarted(imageRequest); 389 imageRequest.renderable = renderable; 390 imageRequest.frameNumber = this.frameNumber; 391 imageRequest.src = renderable.bucket.getUrl(tile); 392 } 393 } 394 else 395 { 396 renderable.request.frameNumber = this.frameNumber; 397 } 398 } 399 400 //************************************************************************* 401 402 /** 403 * Render the raster overlays for the given tiles 404 */ 405 RasterOverlayRenderer.prototype.render = function( tiles ) 406 { 407 // First check if there is someting to do 408 if ( this.overlays.length == 0 ) 409 return; 410 411 var rc = this.tileManager.renderContext; 412 var gl = rc.gl; 413 414 // Setup program 415 this.program.apply(); 416 417 var attributes = this.program.attributes; 418 419 gl.uniformMatrix4fv(this.program.uniforms["projectionMatrix"], false, rc.projectionMatrix); 420 gl.uniform1i(this.program.uniforms["overlayTexture"], 0); 421 gl.enable(gl.BLEND); 422 gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA ); 423 gl.depthFunc( gl.LEQUAL ); 424 425 var modelViewMatrix = mat4.create(); 426 427 var currentIB = null; 428 429 for ( var i = 0; i < tiles.length; i++ ) 430 { 431 var tile = tiles[i]; 432 433 // First retreive tileData for overlay 434 var isTileLoaded = (tile.state == Tile.State.LOADED); 435 var tileData; 436 437 if ( isTileLoaded ) 438 tileData = tile.extension.rasterOverlay; 439 else if ( tile.parent) 440 tileData = tile.parent.extension.rasterOverlay; 441 442 if ( tileData ) 443 { 444 mat4.multiply( rc.viewMatrix, tile.matrix, modelViewMatrix ); 445 gl.uniformMatrix4fv(this.program.uniforms["modelViewMatrix"], false, modelViewMatrix); 446 447 // Bind the vertex buffer 448 gl.bindBuffer(gl.ARRAY_BUFFER, tile.vertexBuffer); 449 gl.vertexAttribPointer(attributes['vertex'], 3, gl.FLOAT, false, 0, 0); 450 451 // Bind the index buffer 452 var indexBuffer = isTileLoaded ? this.tileManager.tileIndexBuffer.getSolid() : this.tileManager.tileIndexBuffer.getSubSolid(tile.parentIndex); 453 // Bind the index buffer only if different (index buffer is shared between tiles) 454 if ( currentIB != indexBuffer ) 455 { 456 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); 457 currentIB = indexBuffer; 458 } 459 460 for ( var j = 0; j < tileData.renderables.length; j++ ) 461 { 462 var renderable = tileData.renderables[j]; 463 464 // Skip not visible layer 465 if ( !renderable.bucket._visible ) 466 continue; 467 468 var uvScale = 1.0; 469 var uTrans = 0.0; 470 var vTrans = 0.0; 471 472 // Retrieve the texture to use 473 var textureTile = isTileLoaded ? tile : tile.parent; 474 475 if ( !textureTile ) 476 continue; 477 478 var prevTextureTile = textureTile; 479 480 // Request high resolution first : always request the texture for the given tile 481 if ( this.requestHighestResolutionFirst ) 482 { 483 if ( !renderable.requestFinished ) 484 { 485 this.requestOverlayTextureForTile( textureTile, renderable ); 486 } 487 } 488 489 // If no texture on tile, try to find a valid texture with parent 490 while ( !renderable.requestFinished && textureTile ) 491 { 492 prevTextureTile = textureTile; 493 textureTile = textureTile.parent; 494 if ( textureTile ) 495 { 496 uTrans *= 0.5; 497 vTrans *= 0.5; 498 uvScale *= 0.5; 499 uTrans += (prevTextureTile.parentIndex & 1) ? 0.5 : 0; 500 vTrans += (prevTextureTile.parentIndex & 2) ? 0.5 : 0; 501 var data = textureTile.extension.rasterOverlay; 502 renderable = data.getRenderable( renderable.bucket ); 503 } 504 } 505 506 // Request low resolution texture 507 if ( !this.requestHighestResolutionFirst ) 508 { 509 if ( prevTextureTile != textureTile ) 510 { 511 this.requestOverlayTextureForTile( prevTextureTile, renderable ); 512 } 513 } 514 515 if ( textureTile && renderable.texture ) 516 { 517 var tileGeoBound = isTileLoaded ? tile.geoBound : tile.parent.geoBound; 518 gl.uniform1f(this.program.uniforms["opacity"], renderable.bucket._opacity ); 519 gl.uniform4f(this.program.uniforms["textureTransform"], uvScale, uvScale, uTrans, vTrans ); 520 521 gl.activeTexture(gl.TEXTURE0); 522 gl.bindTexture(gl.TEXTURE_2D, renderable.texture ); 523 524 // Finally draw the tiles 525 gl.drawElements(gl.TRIANGLES, currentIB.numIndices, gl.UNSIGNED_SHORT, 0); 526 } 527 } 528 } 529 } 530 531 gl.disable(gl.BLEND); 532 gl.depthFunc( gl.LESS ); 533 534 // Abort image requests not requested for this renderering 535 for ( var i = 0; i < this.imageRequests.length; i++ ) 536 { 537 var iq = this.imageRequests[i]; 538 if ( iq.renderable && iq.frameNumber < this.frameNumber ) 539 { 540 iq.renderable.onRequestFinished(false); 541 iq.renderable = null; 542 iq.src = ''; 543 } 544 } 545 546 this.frameNumber++; 547 } 548 549 //************************************************************************* 550 551 return RasterOverlayRenderer; 552 553 }); 554