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 });