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(['./BoundingBox','./CoordinateSystem','./glMatrix'], 21 function(BoundingBox,CoordinateSystem) { 22 23 /**************************************************************************************************************/ 24 25 /** @constructor 26 Tile constructor 27 */ 28 var Tile = function() 29 { 30 // Parent/child relationship 31 this.parent = null; 32 this.parentIndex = -1; 33 this.children = null; 34 35 // Graphics data to render the tile 36 this.vertices = null; 37 this.texture = null; 38 this.vertexBuffer = null; 39 this.texTransform = [1., 1., 0., 0.]; 40 41 // Tile spatial data 42 this.matrix = null; 43 this.inverseMatrix = null; 44 this.bbox = new BoundingBox(); 45 46 // For culling 47 this.radius = 0.0; 48 this.distance = 0.0; 49 this.closestPointToEye = [ 0.0, 0.0, 0.0 ]; 50 51 // Specific object to store extension from renderers 52 this.extension = {}; 53 54 // For debug 55 //this.color = [ Math.random(), Math.random(), Math.random() ]; 56 57 this.state = Tile.State.NONE; 58 59 // Tile configuration given by tile manager : contains if the tile uses skirt, the tesselation, etc... 60 this.config = null; 61 } 62 63 /**************************************************************************************************************/ 64 65 /** 66 * Tile state enumerations 67 */ 68 Tile.State = 69 { 70 ERROR : -10, 71 NONE : 0, 72 REQUESTED : 1, 73 LOADING : 2, 74 LOADED : 3 75 }; 76 77 78 /**************************************************************************************************************/ 79 80 /** 81 * Compute position on the tile using normalized coordinate between [0,size-1] 82 */ 83 Tile.prototype.computePosition = function(u,v) 84 { 85 var vFloor = Math.floor( v ); 86 var vFrac = v - vFloor; 87 var uFloor = Math.floor( u ); 88 var uFrac = u - uFloor; 89 var size = this.config.tesselation; 90 var vertexSize = this.config.vertexSize; 91 var vertexOffset = vertexSize*( vFloor*size + uFloor ); 92 var vec = [ 0.0, 0.0, 0.0 ]; 93 for ( var i=0; i < 3; i++) 94 { 95 vec[i] = (1.0 - vFrac) * (1.0 - uFrac) * this.vertices[ vertexOffset + i ] 96 + vFrac * (1.0 - uFrac) * this.vertices[ vertexOffset + vertexSize*size + i ] 97 + vFrac * uFrac * this.vertices[ vertexOffset + vertexSize*size + vertexSize + i ] 98 + (1.0 - vFrac) * uFrac * this.vertices[ vertexOffset + vertexSize + i ]; 99 } 100 101 return vec; 102 } 103 104 105 /**************************************************************************************************************/ 106 107 /** 108 * Initialize the tile from its parent 109 */ 110 Tile.prototype.initFromParent = function(parent,i,j) 111 { 112 this.parent = parent; 113 this.parentIndex = j*2 + i; 114 this.matrix = parent.matrix; 115 this.inverseMatrix = parent.inverseMatrix; 116 this.texture = parent.texture; 117 this.config = parent.config; 118 119 this.vertexBuffer = parent.vertexBuffer; 120 121 // Recompute the bounding box 122 // Very fast and coarse version but it does not work with HEALPix tiling 123 //var w = 0.5 * (parent.bbox.max[0] - parent.bbox.min[0]); 124 //var h = -0.5 * (parent.bbox.max[1] - parent.bbox.min[1]); 125 //var min = [ parent.bbox.min[0] + i * w, parent.bbox.max[1] + (j+1) * h, parent.bbox.min[2] ]; 126 //var max = [ parent.bbox.min[0] + (i+1) * w, parent.bbox.max[1] + j * h, parent.bbox.max[2] ]; 127 128 var size = this.config.tesselation; 129 var halfTesselation = (size-1)/2; 130 for (var n = 0; n <= halfTesselation; n++) 131 { 132 var offset = this.config.vertexSize * ( (n+j*halfTesselation)*size + i*halfTesselation ); 133 for (var k = 0; k <= halfTesselation; k++) 134 { 135 this.bbox.extend( parent.vertices[offset], parent.vertices[offset+1], parent.vertices[offset+2] ); 136 offset += this.config.vertexSize; 137 } 138 } 139 140 // Compute the bounding box 141 this.radius = this.bbox.getRadius(); 142 } 143 144 /**************************************************************************************************************/ 145 146 /** 147 * Test if the tile needs to be refined 148 */ 149 Tile.prototype.needsToBeRefined = function(renderContext) 150 { 151 if ( this.distance < this.radius ) 152 return true; 153 154 // Approximate the radius of one texel : the radius of the tile divided by the image size 155 // The radius is taken as the average of the bbox width and length, rather than the actual radius because at the pole, there is a large difference betwen width and length 156 // and the radius (ie maximum width/length) is too pessimistic 157 var radius = 0.25 * ( (this.bbox.max[0] - this.bbox.min[0]) + (this.bbox.max[1] - this.bbox.min[1]) ) / this.config.imageSize; 158 159 // Transform the closest point from the eye in world coordinates 160 var mat = this.matrix; 161 var c = this.closestPointToEye; 162 var px = mat[0]*c[0] + mat[4]*c[1] + mat[8]*c[2] + mat[12]; 163 var py = mat[1]*c[0] + mat[5]*c[1] + mat[9]*c[2] + mat[13]; 164 var pz = mat[2]*c[0] + mat[6]*c[1] + mat[10]*c[2] + mat[14]; 165 166 // Compute the pixel size of the radius texel 167 var pixelSizeVector = renderContext.pixelSizeVector; 168 var pixelSize = radius / ( px * pixelSizeVector[0] + py * pixelSizeVector[1] 169 + pz * pixelSizeVector[2] + pixelSizeVector[3] ); 170 171 // Check if pixel radius of a texel is superior to the treshold 172 // return Math.abs(pixelSize) > renderContext.tileErrorTreshold; 173 return pixelSize > renderContext.tileErrorTreshold; 174 } 175 176 /**************************************************************************************************************/ 177 178 /** 179 * Test if the tile is culled given the current view parameters 180 */ 181 Tile.prototype.isCulled = function(renderContext) 182 { 183 // Compute the eye in tile local space 184 var mat = this.inverseMatrix; 185 var c = renderContext.eyePosition; 186 var ex = mat[0]*c[0] + mat[4]*c[1] + mat[8]*c[2] + mat[12]; 187 var ey = mat[1]*c[0] + mat[5]*c[1] + mat[9]*c[2] + mat[13]; 188 var ez = mat[2]*c[0] + mat[6]*c[1] + mat[10]*c[2] + mat[14]; 189 190 // If the eye is in the radius of the tile, consider the tile is not culled 191 this.distance = Math.sqrt( ex * ex + ey * ey + ez * ez ); 192 if ( this.distance < this.radius ) 193 { 194 this.distance = 0.0; 195 return false; 196 } 197 else 198 { 199 var pt = this.closestPointToEye; 200 201 // Compute closest point to eye with the bbox of the tile 202 pt[0] = Math.min( Math.max( ex, this.bbox.min[0] ), this.bbox.max[0] ); 203 pt[1] = Math.min( Math.max( ey, this.bbox.min[1] ), this.bbox.max[1] ); 204 pt[2] = Math.min( Math.max( ez, this.bbox.min[2] ), this.bbox.max[2] ); 205 206 // Compute horizontal culling only if the eye is "behind" the tile 207 if ( ez < 0.0 ) 208 { 209 // Compute vertical at the closest point. The earth center is [0, 0, -radius] in tile local space. 210 var vx = pt[0]; 211 var vy = pt[1]; 212 var vz = pt[2] + CoordinateSystem.radius; 213 var vl = Math.sqrt( vx * vx + vy * vy + vz * vz ); 214 vx /= vl; vy /= vl; vz /= vl; 215 216 // Compute eye direction at the closest point (clampled on earth to avoid problem with mountains) 217 // The position clamp to earth is Vertical * Radius + EarthCenter. The EarthCenter being 0,0,-radius a lot of simplification is done. 218 var edx = ex - vx * CoordinateSystem.radius; 219 var edy = ey - vy * CoordinateSystem.radius; 220 var edz = ez - (vz - 1.0) * CoordinateSystem.radius; 221 222 // Compute dot product between eye direction and the vertical at the point 223 var el = Math.sqrt( edx * edx + edy * edy + edz * edz ); 224 var eDv = (edx * vx + edy * vy + edz * vz) / el; 225 226 eDv *= this.config.cullSign; 227 228 if ( eDv < -0.05 ) 229 { 230 return true; 231 } 232 } 233 234 // Compute local frustum 235 var localFrustum = renderContext.localFrustum; 236 localFrustum.inverseTransform( renderContext.worldFrustum, this.matrix ); 237 238 // Check if the tile is inside the frustum 239 return !localFrustum.containsBoundingBox(this.bbox); 240 } 241 } 242 243 /**************************************************************************************************************/ 244 245 /** 246 * Dispose the tile 247 */ 248 Tile.prototype.dispose = function(renderContext,tilePool) 249 { 250 if ( this.state == Tile.State.LOADED ) 251 { 252 tilePool.disposeGLBuffer(this.vertexBuffer); 253 tilePool.disposeGLTexture(this.texture); 254 255 for ( var x in this.extension ) 256 { 257 if ( this.extension[x].dispose ) 258 this.extension[x].dispose(renderContext,tilePool); 259 } 260 261 this.vertexBuffer = null; 262 this.texture = null; 263 this.parent = null; 264 265 this.state = Tile.State.NONE; 266 } 267 } 268 269 /**************************************************************************************************************/ 270 271 /** 272 * Delete the children 273 */ 274 Tile.prototype.deleteChildren = function(renderContext,tilePool) 275 { 276 if ( this.children ) 277 { 278 // Dispose children resources, and then delete its children 279 for (var i = 0; i < 4; i++) 280 { 281 this.children[i].dispose(renderContext,tilePool); 282 this.children[i].deleteChildren(renderContext,tilePool); 283 } 284 285 // Cleanup the tile 286 this.children = null; 287 } 288 } 289 290 /**************************************************************************************************************/ 291 292 /** 293 * Build skirt vertices 294 */ 295 Tile.prototype.buildSkirtVertices = function(center,srcOffset,srcStep,dstOffset) 296 { 297 var vertices = this.vertices; 298 var skirtHeight = this.radius * 0.05; 299 300 var size = this.config.tesselation; 301 for ( var i = 0; i < size; i++) 302 { 303 /* //Not optimized version of skirt computation 304 var srcPos = [ vertices[srcOffset], vertices[srcOffset+1], vertices[srcOffset+2] ]; 305 var dir = vec3.subtract( srcPos, center, vec3.create() ); 306 vec3.normalize(dir); 307 vec3.scale( dir, skirtHeight ); 308 vec3.subtract( srcPos, dir );*/ 309 310 // Optimized version of skirt computation 311 var x = vertices[srcOffset] - center[0]; 312 var y = vertices[srcOffset+1] - center[1]; 313 var z = vertices[srcOffset+2] - center[2]; 314 var scale = skirtHeight / Math.sqrt( x*x + y*y + z*z ); 315 x *= scale; 316 y *= scale; 317 z *= scale; 318 319 vertices[ dstOffset ] = vertices[srcOffset] - x; 320 vertices[ dstOffset+1 ] = vertices[srcOffset+1] - y; 321 vertices[ dstOffset+2 ] = vertices[srcOffset+2] - z; 322 323 for (var n = 3; n < this.config.vertexSize; n++) 324 { 325 vertices[ dstOffset+n ] = vertices[srcOffset+n]; 326 } 327 328 dstOffset += this.config.vertexSize; 329 srcOffset += srcStep; 330 } 331 } 332 333 /**************************************************************************************************************/ 334 335 /** 336 * Generate normals for a tile 337 */ 338 Tile.prototype.generateNormals = function() 339 { 340 var size = this.config.tesselation; 341 var vertexSize = this.config.vertexSize; 342 var lineSize = vertexSize*size; 343 344 var vo = 0; 345 for ( var j=0; j < size; j++ ) 346 { 347 var vp1 = j == size-1 ? 0 : lineSize; 348 var vm1 = j == 0 ? 0 : -lineSize; 349 for ( var i=0; i < size; i++ ) 350 { 351 var up1 = i == size-1 ? 0 : vertexSize; 352 var um1 = i == 0 ? 0 : -vertexSize; 353 var u = [ 354 this.vertices[vo+up1] - this.vertices[vo+um1], 355 this.vertices[vo+up1+1] - this.vertices[vo+um1+1], 356 this.vertices[vo+up1+2] - this.vertices[vo+um1+2], 357 ]; 358 var v = [ 359 this.vertices[vo+vp1] - this.vertices[vo+vm1], 360 this.vertices[vo+vp1+1] - this.vertices[vo+vm1+1], 361 this.vertices[vo+vp1+2] - this.vertices[vo+vm1+2], 362 ]; 363 364 var normal = vec3.cross( u, v, [] ); 365 vec3.normalize(normal); 366 this.vertices[vo+3] = normal[0]; 367 this.vertices[vo+4] = normal[1]; 368 this.vertices[vo+5] = normal[2]; 369 370 vo += vertexSize; 371 } 372 } 373 } 374 375 /**************************************************************************************************************/ 376 377 /** 378 * Generate the tile 379 */ 380 Tile.prototype.generate = function(tilePool,image,elevations) 381 { 382 // Generate the vertices 383 this.vertices = this.generateVertices(elevations); 384 385 // Compute the bounding box 386 var size = this.config.tesselation; 387 var vertexSize = this.config.vertexSize; 388 this.bbox.compute(this.vertices,vertexSize*size*size,vertexSize); 389 this.radius = this.bbox.getRadius(); 390 391 // Compute normals if needed 392 if (this.config.normals) 393 { 394 this.generateNormals(); 395 } 396 397 // Compute skirt from vertices 398 if (this.config.skirt) 399 { 400 // Compute local earth center, used to generate skirts 401 var localEarthCenter = [ 0.0, 0.0, 0.0 ]; 402 mat4.multiplyVec3( this.inverseMatrix, localEarthCenter ); 403 404 // Skirts 405 var dstOffset = vertexSize * (size * size); // TOP 406 this.buildSkirtVertices( localEarthCenter, 0, vertexSize, dstOffset ); 407 dstOffset += vertexSize * size; // BOTTOM 408 this.buildSkirtVertices( localEarthCenter, vertexSize * (size * (size-1)), vertexSize, dstOffset ); 409 dstOffset += vertexSize * size; // LEFT 410 this.buildSkirtVertices( localEarthCenter, 0, vertexSize * size, dstOffset ); 411 dstOffset += vertexSize * size; // RIGHT 412 this.buildSkirtVertices( localEarthCenter, vertexSize * (size-1), vertexSize * size, dstOffset ); 413 414 // These skirts are only used by children tile 415 dstOffset += vertexSize * size; // CENTER 416 this.buildSkirtVertices( localEarthCenter, vertexSize * ( size * (size-1)/2 ), vertexSize, dstOffset ); 417 dstOffset += vertexSize * size; // MIDDLE 418 this.buildSkirtVertices( localEarthCenter, vertexSize * ( (size-1)/2 ), vertexSize * size, dstOffset ); 419 } 420 421 // Avoid double creation of vertex buffer for level0Tiles generation 422 if (this.vertexBuffer != null && this.parent == null) 423 { 424 tilePool.disposeGLBuffer(this.vertexBuffer); 425 } 426 this.vertexBuffer = tilePool.createGLBuffer(this.vertices); 427 428 // Create texture 429 if (image) 430 { 431 this.texture = tilePool.createGLTexture(image); 432 } 433 434 this.state = Tile.State.LOADED; 435 } 436 437 /**************************************************************************************************************/ 438 439 return Tile; 440 441 });