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(['./CoordinateSystem', './RenderContext','./TileManager','./Tile' , './VectorRendererManager', './Numeric', './GeoBound' ], 21 function(CoordinateSystem, RenderContext, TileManager, Tile, VectorRendererManager, Numeric, GeoBound) { 22 23 /**************************************************************************************************************/ 24 25 /** 26 @name Globe 27 @class 28 Create a virtual globe in a HTML canvas element, passed in options parameter. 29 The virtual globe data is set using setBaseImage/addLayer methods. 30 31 @param options Configuration properties for the Globe : 32 <ul> 33 <li>canvas : the canvas for WebGL, can be string (id) or a canvas element</li> 34 <li>contextAttribs : the attributes when creating WebGL context, see WebGL specification</li> 35 <li>backgroundColor : the background color of the canvas (an array of 4 floats)</li> 36 <li>shadersPath : the path to shaders file</li> 37 <li>continuousRendering: if true rendering is done continuously, otherwise it is done only if needed</li> 38 </ul> 39 40 */ 41 var Globe = function(options) 42 { 43 this.renderContext = new RenderContext(options); 44 this.tileManager = new TileManager( this ); 45 this.vectorRendererManager = new VectorRendererManager( this ); 46 this.attributionHandler = null; 47 this.activeAnimations = []; 48 this.preRenderers = []; 49 this.nbCreatedLayers = 0; 50 51 // Event callbacks 52 this.callbacks = {}; 53 54 var glob = this; 55 this.renderContext.frame = function() 56 { 57 // Resest frame requested flag first 58 glob.renderContext.frameRequested = false; 59 60 // Render the globe 61 glob.render(); 62 63 // Request next frame 64 if ( glob.renderContext.continuousRendering ) 65 { 66 glob.renderContext.requestFrame(); 67 } 68 else if ( glob.activeAnimations.length > 0 ) 69 { 70 glob.renderContext.requestFrame(); 71 } 72 73 }; 74 75 this.renderContext.requestFrame(); 76 } 77 78 /**************************************************************************************************************/ 79 80 /** 81 Dispose the globe and all its ressources 82 */ 83 Globe.prototype.dispose = function() 84 { 85 this.tileManager.tilePool.disposeAll(); 86 this.tileManager.reset(); 87 } 88 89 90 /**************************************************************************************************************/ 91 92 /** 93 Refresh rendering, must be called when canvas size is modified 94 */ 95 Globe.prototype.refresh = function() 96 { 97 this.renderContext.requestFrame(); 98 } 99 100 /**************************************************************************************************************/ 101 102 /** 103 Set the base imagery layer for the globe 104 105 @param {RasterLayer} layer the layer to use, must be an imagery RasterLayer 106 */ 107 Globe.prototype.setBaseImagery = function(layer) 108 { 109 if ( this.tileManager.imageryProvider ) 110 { 111 this.removeLayer( this.tileManager.imageryProvider ); 112 } 113 this.tileManager.setImageryProvider(layer); 114 if ( layer ) 115 { 116 layer._overlay = false; 117 this.addLayer(layer); 118 } 119 } 120 121 /**************************************************************************************************************/ 122 123 /** 124 Set the base elevation layer for the globe 125 126 @param {RasterLayer} layer the layer to use, must be an elevation RasterLayer 127 */ 128 Globe.prototype.setBaseElevation = function(layer) 129 { 130 if ( this.tileManager.elevationProvider ) 131 { 132 this.removeLayer( this.tileManager.elevationProvider ); 133 } 134 this.tileManager.setElevationProvider(layer); 135 if ( layer ) 136 { 137 layer._overlay = false; 138 this.addLayer(layer); 139 } 140 } 141 142 143 /**************************************************************************************************************/ 144 145 /** 146 Add a layer to the globe. 147 A layer must be added to be visualized on the globe. 148 149 @param layer the layer to add 150 */ 151 Globe.prototype.addLayer = function(layer) 152 { 153 layer.id = this.nbCreatedLayers; 154 layer._attach(this); 155 this.renderContext.requestFrame(); 156 this.nbCreatedLayers++; 157 } 158 159 /**************************************************************************************************************/ 160 161 /** 162 Remove a layer 163 164 @param layer the layer to remove 165 */ 166 Globe.prototype.removeLayer = function(layer) 167 { 168 layer._detach(); 169 this.renderContext.requestFrame(); 170 } 171 172 /**************************************************************************************************************/ 173 174 /** 175 Add an animation 176 177 @param anim the animation to add 178 */ 179 Globe.prototype.addAnimation = function(anim) 180 { 181 anim.globe = this; 182 } 183 184 /**************************************************************************************************************/ 185 186 /** 187 Remove an animation 188 189 @param anim the animation to remove 190 */ 191 Globe.prototype.removeAnimation = function(anim) 192 { 193 anim.globe = null; 194 } 195 196 /**************************************************************************************************************/ 197 198 /** 199 Get the elevation at a geo position 200 201 @param lon the longitude in degree 202 @param lat the latitude in degree 203 @return the elevation in meter at the position [lon,lat] 204 */ 205 Globe.prototype.getElevation = function(lon,lat) 206 { 207 var tiling = this.tileManager.imageryProvider.tiling; 208 var levelZeroTile = this.tileManager.level0Tiles[ tiling.lonlat2LevelZeroIndex(lon,lat) ]; 209 if ( levelZeroTile.state == Tile.State.LOADED ) 210 return levelZeroTile.getElevation(lon,lat); 211 else 212 return 0.0; 213 } 214 215 /**************************************************************************************************************/ 216 217 /** 218 Get the viewport geo bound 219 @return the geo bound of the viewport 220 */ 221 Globe.prototype.getViewportGeoBound = function() 222 { 223 var rc = this.renderContext; 224 var tmpMat = mat4.create(); 225 226 // Compute eye in world space 227 mat4.inverse(rc.viewMatrix, tmpMat); 228 var eye = [tmpMat[12], tmpMat[13], tmpMat[14]]; 229 230 // Compute the inverse of view/proj matrix 231 mat4.multiply(rc.projectionMatrix, rc.viewMatrix, tmpMat); 232 mat4.inverse(tmpMat); 233 234 // Transform the four corners of the frustum into world space 235 // and then for each corner compute the intersection of ray starting from the eye with the earth 236 var points = [ [ -1, -1, 1, 1 ], [ 1, -1, 1, 1 ], [ -1, 1, 1, 1 ], [ 1, 1, 1, 1 ] ]; 237 var tmpPt = vec3.create(); 238 var earthCenter = [ 0, 0, 0 ]; 239 for ( var i = 0; i < 4; i++ ) 240 { 241 mat4.multiplyVec4( tmpMat, points[i] ); 242 vec3.scale( points[i], 1.0 / points[i][3] ); 243 vec3.subtract(points[i], eye, points[i]); 244 vec3.normalize( points[i] ); 245 246 var t = Numeric.raySphereIntersection( eye, points[i], earthCenter, CoordinateSystem.radius); 247 if ( t < 0.0 ) 248 return null; 249 250 points[i] = CoordinateSystem.from3DToGeo( Numeric.pointOnRay(eye, points[i], t, tmpPt) ); 251 } 252 253 var geoBound = new GeoBound(); 254 geoBound.computeFromCoordinates( points ); 255 256 return geoBound; 257 } 258 259 /**************************************************************************************************************/ 260 261 /** 262 Get the lon-lat from a pixel. 263 The pixel is expressed in the canvas frame, i.e. (0,0) corresponds to the lower-left corner of the pixel 264 265 @param x the pixel x coordinate 266 @param y the pixel y coordinate 267 @return an array of two numbers [lon,lat] or null if the pixel is not on the globe 268 */ 269 Globe.prototype.getLonLatFromPixel = function(x,y) 270 { 271 var pos3d = this.renderContext.get3DFromPixel(x,y); 272 if ( pos3d ) 273 { 274 return CoordinateSystem.from3DToGeo(pos3d); 275 } 276 else 277 { 278 return null; 279 } 280 } 281 282 /**************************************************************************************************************/ 283 284 /** 285 Get pixel from lon-lat 286 The pixel is expressed in the canvas frame, i.e. (0,0) corresponds to the lower-left corner of the pixel 287 288 @param lon the longitude 289 @param lat the latitude 290 @return an array of two numbers [x,y] or null if the pixel is not on the globe 291 */ 292 Globe.prototype.getPixelFromLonLat = function(lon,lat) 293 { 294 var pos3d = vec3.create(); 295 CoordinateSystem.fromGeoTo3D([lon,lat], pos3d); 296 var pixel = this.renderContext.getPixelFrom3D(pos3d[0],pos3d[1],pos3d[2]); 297 return pixel 298 } 299 300 /**************************************************************************************************************/ 301 302 /** 303 Render the globe 304 TODO : private for now because it is automatically called in requestAnimationFrame. 305 306 @private 307 */ 308 Globe.prototype.render = function() 309 { 310 var rc = this.renderContext; 311 var stats = rc.stats; 312 var gl = rc.gl; 313 314 if (stats) stats.start("globalRenderTime"); 315 316 // Update active animations 317 if ( this.activeAnimations.length > 0) 318 { 319 var time = Date.now(); 320 for (var i = 0; i < this.activeAnimations.length; i++) 321 { 322 this.activeAnimations[i].update(time); 323 } 324 } 325 326 // Clear the buffer 327 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 328 329 // Check canvas size is valid 330 if ( rc.canvas.width == 0 || rc.canvas.height == 0 ) 331 return; 332 333 gl.viewport(0, 0, rc.canvas.width, rc.canvas.height); 334 335 // Update view dependent properties to be used during rendering : view matrix, frustum, projection, etc... 336 rc.updateViewDependentProperties(); 337 338 // Call pre-renderers 339 for ( var i = 0 ; i < this.preRenderers.length; i++ ) 340 this.preRenderers[i].preRender(); 341 342 // Render tiles 343 this.tileManager.render(); 344 345 if ( this.tileManager.tilesToRender.length == 0 ) 346 return; 347 348 if (stats) stats.end("globalRenderTime"); 349 } 350 351 /**************************************************************************************************************/ 352 353 /** 354 Subscribe to an event 355 356 @param name Event name 357 <ul> 358 <li>startNavigation : called when navigation is started (by the user or through animation)</li> 359 <li>endNavigation : called when navigation is ended (by the user or through animation)t</li> 360 <li>baseLayersReady : called when the base layers are ready to be displayed</li> 361 <li>baseLayersError : called when the base layers are not valid, or not accessible, in that case nothing is displayed so this event is useful to provide an error message to the user</li> 362 <li>startBackgroundLoad : called when background layers (imagery and/or elevation) start to be loaded by GlobWeb</li> 363 <li>endBackgroundLoad : called when background layers (imagery and/or elevation) end loadinh by GlobWeb</li> 364 </ul> 365 @param callback Callback function 366 */ 367 Globe.prototype.subscribe = function(name,callback) 368 { 369 if( !this.callbacks[name] ) { 370 this.callbacks[name] = [ callback ]; 371 } else { 372 this.callbacks[name].push( callback ); 373 } 374 } 375 376 /**************************************************************************************************************/ 377 378 /** 379 Unsubscribe to an event 380 381 @param name Event name {@link Globe#subscribe} 382 @param callback Callback function 383 */ 384 Globe.prototype.unsubscribe = function(name,callback) 385 { 386 if( this.callbacks[name] ) { 387 var i = this.callbacks[name].indexOf( callback ); 388 if ( i != -1 ) { 389 this.callbacks[name].splice(i,1); 390 } 391 } 392 } 393 394 /**************************************************************************************************************/ 395 396 /** 397 Publish an event 398 399 @param name Event name 400 @param context Context 401 402 @private 403 */ 404 Globe.prototype.publish = function(name,context) 405 { 406 if ( this.callbacks[name] ) { 407 var cbs = this.callbacks[name]; 408 for ( var i = 0; i < cbs.length; i++ ) { 409 cbs[i](context); 410 } 411 } 412 } 413 414 return Globe; 415 416 }); 417