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