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','./FeatureStyle','./Tile','./RendererTileData'], function(Program,FeatureStyle,Tile,RendererTileData) {
 21 
 22 /**************************************************************************************************************/
 23 
 24 
 25 /** @constructor
 26 	TiledVectorRenderer constructor
 27  */
 28 var TiledVectorRenderer = function(tileManager)
 29 {
 30 	this.tileManager = tileManager;
 31 	
 32 	// Create a bucket with default style
 33 	// Bucket aggregate geometries that shares a common style
 34 	this.buckets = [  { style: new FeatureStyle(), geometries: [] } ];
 35 	
 36 	var vertexShader = "\
 37 	attribute vec3 vertex; \n\
 38 	uniform float zOffset; \n\
 39 	uniform mat4 modelViewMatrix;\n\
 40 	uniform mat4 projectionMatrix;\n\
 41 	\n\
 42 	void main(void)  \n\
 43 	{ \n\
 44 		gl_Position = projectionMatrix * modelViewMatrix * vec4(vertex.x, vertex.y, vertex.z + zOffset, 1.0); \n\
 45 	} \n\
 46 	";
 47 	
 48 	var fragmentShader = "\
 49 	#ifdef GL_ES \n\
 50 	precision highp float; \n\
 51 	#endif \n\
 52 	uniform vec4 color; \n\
 53 	\n\
 54 	void main(void) \n\
 55 	{ \n\
 56 		gl_FragColor = color; \n\
 57 	} \n\
 58 	";
 59 
 60     this.program = new Program(this.tileManager.renderContext);
 61     this.program.createFromSource(vertexShader, fragmentShader);
 62 	
 63 	// Customization for different renderer : lineString or polygon
 64 	this.styleEquals = null;
 65 	this.renderableConstuctor = null;
 66 	this.id = "empty";
 67 }
 68 
 69 /**************************************************************************************************************/
 70 
 71 /**
 72 	Get or create a bucket to store a feature with the given style
 73  */
 74 TiledVectorRenderer.prototype.getOrCreateBucket = function( layer, style )
 75 {
 76 	for ( var i = 0; i < this.buckets.length; i++ )
 77 	{
 78 		if ( this.buckets[i].layer == layer && this.styleEquals(style, this.buckets[i].style ) )
 79 		{
 80 			return this.buckets[i];
 81 		}
 82 	}
 83 	
 84 	var bucket = { layer: layer, style: style, geometries: [] };
 85 	this.buckets.push( bucket );
 86 	return bucket;
 87 }
 88 
 89 /**************************************************************************************************************/
 90 
 91 /**
 92 	Remove a geometry from the tile
 93  */
 94 TiledVectorRenderer.prototype.removeGeometryFromTile = function( bucket, geometry, tile )
 95 {
 96 	var renderable = this.findRenderable( bucket, tile );
 97 	if ( renderable && renderable.removeGeometry( geometry ) && tile.children )
 98 	{
 99 		// Remove the geometry from loaded children
100 		for ( var i = 0; i < 4; i++ )
101 		{
102 			if ( tile.children[i].state == Tile.State.LOADED )
103 			{
104 				this.removeGeometryFromTile( bucket, geometry, tile.children[i] );
105 			}
106 		}
107 	}
108 }
109 
110 /**************************************************************************************************************/
111 
112 /**
113 	Remove a geometry from the renderer
114  */
115 TiledVectorRenderer.prototype.removeGeometry = function( geometry, layer )
116 {
117 	var foundBucket = null;
118 	
119 	// Remove geometry from buckets
120 	for ( var i = 0; i < this.buckets.length && !foundBucket; i++ )
121 	{
122 		var bucket = this.buckets[i];
123 		if ( bucket.layer == layer ) 
124 		{
125 			var index = bucket.geometries.indexOf( geometry );
126 			if ( index != -1 )
127 			{
128 				bucket = this.buckets[i];
129 				bucket.geometries.splice( index, 1 );
130 				foundBucket = bucket;
131 			}
132 		}
133 	}
134 	
135 	// Remove geometry 
136 	if ( foundBucket )
137 	{
138 		for ( var i = 0; i < this.tileManager.level0Tiles.length; i++ )
139 		{
140 			this.removeGeometryFromTile( foundBucket, geometry, this.tileManager.level0Tiles[i] );
141 		}
142 	}
143 }
144 
145 /**************************************************************************************************************/
146 
147 /**
148 	Clean-up a tile
149 	TODO : the method is only used by TileManager.removePostRenderer, maybe remove it, TileManager can use directly the extension id.
150  */
151 TiledVectorRenderer.prototype.cleanupTile = function( tile )
152 {
153 	if ( tile.extension[this.id] )
154 	{
155 		tile.extension[this.id].dispose();
156 		delete tile.extension[this.id];
157 	}
158 }
159 
160 /**************************************************************************************************************/
161 
162 /**
163 	Add a geometry to the renderer.
164 	Public method to add geometry to the renderer
165  */
166 TiledVectorRenderer.prototype.addGeometry = function( geometry, layer, style )
167 {
168 	var bucket = this.getOrCreateBucket( layer, style );
169 	bucket.geometries.push( geometry );
170 	
171 	for ( var i = 0; i < this.tileManager.level0Tiles.length; i++ )
172 	{
173 		var tile = this.tileManager.level0Tiles[i];
174 		if ( tile.state == Tile.State.LOADED )
175 			this.addGeometryToTile( bucket, geometry, tile );
176 	}
177 }
178 
179 /**************************************************************************************************************/
180 
181 /**
182 	Add a geometry to the given tile.
183 	The method is recursive, it will also add the geometry to children if exists
184  */
185 TiledVectorRenderer.prototype.addGeometryToTile = function( bucket, geometry, tile )
186 {
187 	var isNewRenderable = false;
188 	
189 	// Try to find an existing renderable on the tile
190 	var renderable;
191 	if ( tile.extension[this.id] )
192 	{
193 		renderable = tile.extension[this.id].getRenderable( bucket );
194 	}
195 	
196 	// If no renderable on the tile, create a new renderable (or reuse an existing one)
197 	if ( !renderable )
198 	{
199 		renderable = new this.renderableConstuctor(bucket,this.tileManager.renderContext.gl);
200 		isNewRenderable = true;
201 	}
202 	
203 	if ( renderable.addGeometry( geometry, tile ) && tile.children )
204 	{
205 		// Recursively add the geometry to loaded children
206 		for ( var i = 0; i < 4; i++ )
207 		{
208 			if ( tile.children[i].state == Tile.State.LOADED )
209 			{
210 				this.addGeometryToTile( bucket, geometry, tile.children[i] );
211 			}
212 		}
213 	}
214 	
215 	// 	If the renderable is new, add it to the tile
216 	if (isNewRenderable)
217 	{
218 		this.addRenderableToTile(tile,renderable);
219 	}
220 }
221 
222 /**************************************************************************************************************/
223 
224 /**
225 	Add a renderable to the tile
226  */
227 TiledVectorRenderer.prototype.addRenderableToTile = function( tile, renderable )
228 {
229 	if ( renderable.vertices.length > 0 )
230 	{
231 		if ( !tile.extension[this.id] )
232 			tile.extension[this.id] = new RendererTileData();
233 		
234 		tile.extension[this.id].renderables.push( renderable );
235 	}
236 }
237 
238 /**************************************************************************************************************/
239 
240 /**
241 	Generate renderable data on the tile
242  */
243 TiledVectorRenderer.prototype.generate = function( tile )
244 {
245 	if ( tile.parent )
246 	{	
247 		// Only add geometry from parent tile (if any)
248 		var ls = tile.parent.extension[this.id];
249 		var ll = ls ?  ls.renderables.length : 0;
250 		for ( var i = 0; i < ll; i++ )
251 		{
252 			var parentRenderable = ls.renderables[i];
253 			var renderable = new this.renderableConstuctor(parentRenderable.bucket,this.tileManager.renderContext.gl);
254 			
255 			var parentGeometryInfos = parentRenderable.geometryInfos;
256 			for ( var j = 0; j < parentGeometryInfos.length; j++ )
257 			{
258 				renderable.addGeometry( parentGeometryInfos[j].geometry, tile );
259 			}
260 			
261 			this.addRenderableToTile(tile,renderable);
262 		}
263 	}
264 	else
265 	{
266 		// No parent tile : traverse all geometries to generate data on tile
267 		for ( var i = 0; i < this.buckets.length; i++ )
268 		{
269 			var bucket = this.buckets[i];
270 			var renderable = new this.renderableConstuctor(bucket,this.tileManager.renderContext.gl);
271 			
272 			for ( var j = 0; j < bucket.geometries.length; j++ )
273 			{
274 				renderable.addGeometry( bucket.geometries[j], tile );
275 			}
276 			
277 			this.addRenderableToTile(tile,renderable);
278 		}
279 	}
280 }
281 
282 /**************************************************************************************************************/
283 
284 /**
285 	Render all redenrable on the given tiles
286  */
287 TiledVectorRenderer.prototype.render = function( visibleTiles )
288 {
289 	var renderContext = this.tileManager.renderContext;
290 	var gl = renderContext.gl;
291 	
292 	var modelViewMatrix = mat4.create();
293 	
294     // Setup program
295     this.program.apply();
296 	
297 	gl.depthFunc(gl.LEQUAL);
298  	gl.uniformMatrix4fv( this.program.uniforms["projectionMatrix"], false, renderContext.projectionMatrix);
299     
300 	var currentStyle = null;
301 	
302     for (var i = 0; i < visibleTiles.length; ++i)
303     {
304 		var tile = visibleTiles[i];
305 		
306 		// If the tile is loaded and contains renderable, render them
307 		if ( tile.extension[this.id] )
308 		{
309 			mat4.multiply( renderContext.viewMatrix, tile.matrix, modelViewMatrix );
310 			gl.uniformMatrix4fv( this.program.uniforms["modelViewMatrix"], false, modelViewMatrix );
311 			gl.uniform1f( this.program.uniforms["zOffset"], tile.radius * 0.0007 );
312 			
313 			var renderables = tile.extension[this.id].renderables;
314 			for (var j=0; j < renderables.length; j++)
315 			{
316 				var renderable = renderables[j];
317 				
318 				if ( renderable.bucket.layer._visible )
319 				{
320 					if ( renderable.bucket.style != currentStyle )
321 					{
322 						currentStyle = renderable.bucket.style;
323 						gl.lineWidth( currentStyle.strokeWidth );
324 						gl.uniform4f( this.program.uniforms["color"], currentStyle.strokeColor[0], currentStyle.strokeColor[1], currentStyle.strokeColor[2], 
325 							currentStyle.strokeColor[3] * renderable.bucket.layer._opacity );
326 							
327 						// TODO : manage opacity
328 					}
329 					
330 					renderables[j].render( this.program.attributes );
331 				}
332 			}
333 		}
334 		// If the tile is not loaded, but its parent contains some renderable, render them 'clipped' to the child tile to avoid 'glitch' when zooming
335 		else if ( tile.state != Tile.State.LOADED && tile.parent.extension[this.id] )
336 		{
337 			mat4.multiply( renderContext.viewMatrix, tile.parent.matrix, modelViewMatrix );
338 			gl.uniformMatrix4fv( this.program.uniforms["modelViewMatrix"], false, modelViewMatrix );
339 			gl.uniform1f( this.program.uniforms["zOffset"], tile.parent.radius * 0.0007 );
340 			
341 			var renderables = tile.parent.extension[this.id].renderables;
342 			for (var j=0; j < renderables.length; j++)
343 			{
344 				var renderable = renderables[j];
345 				if ( renderable.bucket.layer._visible )
346 				{
347 					if ( renderable.bucket.style != currentStyle )
348 					{
349 						currentStyle = renderable.bucket.style;
350 						gl.lineWidth( currentStyle.strokeWidth );
351 						gl.uniform4f( this.program.uniforms["color"], currentStyle.strokeColor[0], currentStyle.strokeColor[1], currentStyle.strokeColor[2], 
352 							currentStyle.strokeColor[3] * renderable.bucket.layer._opacity );
353 							
354 						// TODO : manage opacity
355 					}
356 					
357 					renderables[j].renderChild( this.program.attributes, tile.parentIndex );
358 				}
359 			}
360 		
361 		}
362     }
363 
364 	gl.depthFunc(gl.LESS);
365 }
366 
367 /**************************************************************************************************************/
368 
369 return TiledVectorRenderer;
370 
371 });
372