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','./CoordinateSystem','./RendererTileData','./FeatureStyle', './VectorRendererManager'],
 21 	function(Program,CoordinateSystem,RendererTileData,FeatureStyle,VectorRendererManager) {
 22 
 23 /**************************************************************************************************************/
 24 
 25 /** @constructor
 26 	ConvexPolygonRenderer constructor
 27  */
 28 var ConvexPolygonRenderer = function(tileManager)
 29 {
 30 	// Store object for rendering
 31 	this.renderContext = tileManager.renderContext;
 32 	this.tileConfig = tileManager.tileConfig;
 33 	
 34 	this.programs = [];
 35 
 36 	// Bucket management for rendering : a bucket is a texture with its points
 37 	this.buckets = [];
 38 
 39 	this.basicVertexShader = "\
 40 	attribute vec3 vertex;\n\
 41 	uniform mat4 viewProjectionMatrix;\n\
 42 	\n\
 43 	void main(void)\n\
 44 	{\n\
 45 		gl_Position = viewProjectionMatrix * vec4(vertex, 1.0);\n\
 46 	}\n\
 47 	";
 48 	
 49 	this.basicFragmentShader = "\
 50 	precision lowp float; \n\
 51 	uniform vec4 color; \n\
 52 	\n\
 53 	void main(void) \n\
 54 	{ \n\
 55 		gl_FragColor = color; \n\
 56 	} \n\
 57 	";
 58 	
 59 	this.texVertexShader = "\
 60 	attribute vec3 vertex;\n\
 61 	attribute vec2 tcoord;\n\
 62 	uniform mat4 viewProjectionMatrix;\n\
 63 	\n\
 64 	varying vec2 vTextureCoord;\n\
 65 	\n\
 66 	void main(void) \n\
 67 	{\n\
 68 		vTextureCoord = tcoord;\n\
 69 		vTextureCoord.y = 1.0 - vTextureCoord.y; \n\
 70 		gl_Position = viewProjectionMatrix * vec4(vertex, 1.0);\n\
 71 	}\n\
 72 	";
 73 
 74 
 75 	this.texFragmentShader = "\
 76 		precision lowp float; \n\
 77 		uniform vec4 color;\n\
 78 		varying vec2 vTextureCoord;\n\
 79 		uniform sampler2D texture; \n\
 80 		void main(void)\n\
 81 		{\n\
 82 			gl_FragColor = texture2D(texture, vTextureCoord) * color;\n\
 83 		}\n\
 84 		";
 85 
 86 	this.basicFillShader = {
 87 		vertexCode: this.basicVertexShader,
 88 		fragmentCode: this.basicFragmentShader,
 89 		updateUniforms: null
 90 	};
 91 
 92 	this.texFillShader = {
 93 		vertexCode: this.texVertexShader,
 94 		fragmentCode: this.texFragmentShader,
 95 		updateUniforms: null
 96 	};
 97 
 98 
 99 	this.basicProgram = this.createProgram(this.basicFillShader);
100 	this.texProgram = this.createProgram(this.texFillShader);
101 	
102 	this.frameNumber = 0;
103 
104 	var gl = this.renderContext.gl;
105 	// Parameters used to implement ONE shader for color xor texture rendering
106 	this.whiteTexture = gl.createTexture();
107 	gl.bindTexture(gl.TEXTURE_2D, this.whiteTexture);
108 	var whitePixel = new Uint8Array([255, 255, 255, 255]);
109 	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, whitePixel);
110 
111 	// Shared buffer
112 	// Create texCoord buffer
113 	this.tcoordBuffer = gl.createBuffer();
114 	gl.bindBuffer(gl.ARRAY_BUFFER, this.tcoordBuffer);
115 	
116 	var textureCoords = [
117 		0.0, 0.0,
118 		1.0, 0.0,
119 		1.0, 1.0,
120 		0.0, 1.0,
121 		0.0, 0.0
122 	];
123 	
124 	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
125 	this.tcoordBuffer.itemSize = 2;
126 	this.tcoordBuffer.numItems = 5;
127 }
128 
129 /**************************************************************************************************************/
130 
131 /**
132 	Renderable constructor
133 	Attach to a bucket
134  */
135 var Renderable = function(bucket) 
136 {
137 	this.bucket = bucket;
138 	this.geometry2vb = {};
139 	this.vertices = [];
140 	this.lineIndices = [];
141 	this.triangleIndices = [];
142 	this.vertexBuffer = null;
143 	this.lineIndexBuffer = null;
144 	this.triangleIndexBuffer = null;
145 	this.bufferDirty = false;
146 	this.triBufferDirty = false;
147 }
148 
149 /**************************************************************************************************************/
150 
151 /**
152 	Add the geometry to the renderable
153  */
154 Renderable.prototype.add = function(geometry)
155 {
156 	var rings = [];
157 	if ( geometry['type'] == 'MultiPolygon' )
158 	{
159 		for ( var i=0; i<geometry['coordinates'].length; i++ )
160 		{
161 			rings.push( geometry['coordinates'][i][0] );
162 		}
163 	}
164 	else
165 	{
166 		rings.push( geometry['coordinates'][0] );
167 	}
168 
169 	for ( var r=0; r<rings.length; r++ )
170 	{
171 		var coords = rings[r];
172 		// var coords = geometry['coordinates'][0];
173 		var numPoints = coords.length-1;
174 		
175 		// Store information for the geometry in the buffers used for rendering
176 		var data = {
177 			vertexStart: this.vertices.length,
178 			vertexCount: 3 * numPoints,
179 			lineIndexStart: this.lineIndices.length,
180 			lineIndexCount: 2 * numPoints,
181 			triIndexStart: 0,
182 			triIndexCount: 0
183 		};
184 
185 		
186 		// Compute vertices and indices and store them in the buffers
187 		var startIndex = this.vertices.length / 3;
188 		for ( var i = 0; i < numPoints; i++ ) 
189 		{
190 			var pt = CoordinateSystem.fromGeoTo3D( coords[i] );
191 			this.vertices.push( pt[0], pt[1], pt[2] );
192 			this.lineIndices.push( startIndex + i, startIndex + ((i+1) % numPoints) );
193 		}
194 		
195 		// If fill, build the triangle indices
196 		if ( this.bucket.style.fill ) 
197 		{
198 			data.triIndexStart = this.triangleIndices.length;
199 			data.triIndexCount = 3 * (numPoints-2);
200 			
201 			for ( var i = 0; i < numPoints-2; i++ ) 
202 			{
203 				this.triangleIndices.push( startIndex, startIndex + i+1, startIndex + i+2 );
204 			}
205 		}
206 
207 		if ( this.geometry2vb[ geometry.gid ] )
208 		{
209 			this.geometry2vb[ geometry.gid ].vertexCount += data.vertexCount;
210 			this.geometry2vb[ geometry.gid ].lineIndexCount += data.lineIndexCount;
211 			this.geometry2vb[ geometry.gid ].triIndexCount += data.triIndexCount;
212 		}
213 		else
214 		{
215 			this.geometry2vb[ geometry.gid ] = data;
216 		}
217 		
218 		this.bufferDirty = true;
219 		this.triBufferDirty = true;
220 	}
221 }
222 
223 /**************************************************************************************************************/
224 
225 /**
226 	Remove the geometry from the renderable
227  */
228 Renderable.prototype.remove = function(geometry)
229 {
230 	if ( this.geometry2vb.hasOwnProperty(geometry.gid) )
231 	{
232 		// retreive the render data for the geometry
233 		var data = this.geometry2vb[ geometry.gid ];
234 		delete this.geometry2vb[ geometry.gid ];
235 
236 		// Remove geometry vertex
237 		this.vertices.splice( data.vertexStart, data.vertexCount );
238 		
239 		// Update indices after vertex removal
240 		for ( var i = data.lineIndexStart+data.lineIndexCount; i < this.lineIndices.length; i++ ) 
241 		{
242 			this.lineIndices[i] -= (data.vertexCount/3);
243 		}
244 		for ( var i = data.triIndexStart+data.triIndexCount; i < this.triangleIndices.length; i++ ) 
245 		{
246 			this.triangleIndices[i] -= (data.vertexCount/3);
247 		}
248 
249 		this.lineIndices.splice( data.lineIndexStart, data.lineIndexCount );
250 		this.triangleIndices.splice( data.triIndexStart, data.triIndexCount );
251 		
252 		// Update render data for all other geometries
253 		for ( var g in this.geometry2vb ) 
254 		{
255 			if ( g ) 
256 			{
257 				var d = this.geometry2vb[g];
258 				if ( d.vertexStart > data.vertexStart ) 
259 				{
260 					d.vertexStart -= data.vertexCount;
261 					d.lineIndexStart -= data.lineIndexCount;
262 					d.triIndexStart -= data.triIndexCount;
263 				}
264 			}
265 		}
266 		
267 		this.bufferDirty = true;
268 		this.triBufferDirty = true;
269 	}
270 }
271 
272 /**************************************************************************************************************/
273 
274 /**
275 	Dispose the renderable : remove all buffers
276  */
277 Renderable.prototype.dispose = function(renderContext)
278 {
279 	if ( this.vertexBuffer ) 
280 	{
281 		renderContext.gl.deleteBuffer( this.vertexBuffer );
282 	}
283 	if ( this.lineIndexBuffer ) 
284 	{
285 		renderContext.gl.deleteBuffer( this.lineIndexBuffer );
286 	}
287 	if ( this.triangleIndexBuffer ) 
288 	{
289 		renderContext.gl.deleteBuffer( this.triangleIndexBuffer );
290 	}
291 }
292 
293 /**************************************************************************************************************/
294 
295 /**
296 	Add a geometry to the renderer
297  */
298 ConvexPolygonRenderer.prototype.addGeometryToTile = function(bucket,geometry,tile)
299 {
300 	var tileData = tile.extension.polygon;
301 	if (!tileData)
302 	{
303 		tileData = tile.extension.polygon = new RendererTileData();
304 	}
305 	var renderable = tileData.getRenderable(bucket);
306 	if (!renderable) 
307 	{
308 		renderable = new Renderable(bucket);
309 		tileData.renderables.push(renderable);
310 	}
311 	renderable.add(geometry);
312 
313 }
314 
315 /**************************************************************************************************************/
316 
317 /**
318 	Remove a point from the renderer
319  */
320 ConvexPolygonRenderer.prototype.removeGeometryFromTile = function(geometry,tile)
321 {
322 	var tileData = tile.extension.polygon;
323 	if (tileData)
324 	{
325 		for ( var i=0; i < tileData.renderables.length; i++ )
326 		{
327 			tileData.renderables[i].remove(geometry);
328 
329 			// TODO dispose texture from bucket and bucket itself if no more renderable using it
330 			if ( tileData.renderables[i].vertices.length == 0 )
331 			{
332 				tileData.renderables[i].dispose(this.renderContext);
333 				tileData.renderables.splice(i, 1);
334 			}
335 		}
336 	}
337 }
338 
339 /**************************************************************************************************************/
340 
341 /**
342  	Add a geometry to the renderer
343  */
344 ConvexPolygonRenderer.prototype.addGeometry = function(geometry, layer, style)
345 {
346 	var bucket = this.getOrCreateBucket(layer,style);
347 	if (!bucket.mainRenderable)
348 	{
349 		bucket.mainRenderable = new Renderable(bucket);
350 	}
351 	
352 	bucket.mainRenderable.add(geometry);
353 }
354 
355 /**************************************************************************************************************/
356 
357 /**
358  	Remove a geometry from the renderer
359  */
360 ConvexPolygonRenderer.prototype.removeGeometry = function(geometry)
361 {
362 	for ( var n = 0; n < this.buckets.length; n++ )
363 	{
364 		var bucket = this.buckets[n];
365 		if ( bucket.mainRenderable )
366 		{
367 			bucket.mainRenderable.remove(geometry);
368 			if ( bucket.mainRenderable.vertices.length == 0 )
369 			{
370 				bucket.mainRenderable.dispose(this.renderContext);
371 				bucket.mainRenderable = null;
372 			}
373 		}
374 	}
375 }
376 
377 /**************************************************************************************************************/
378 
379 /**
380  	Create program from fillShader object	
381  */
382 ConvexPolygonRenderer.prototype.createProgram = function(fillShader)
383 {
384 	var program = new Program(this.renderContext);
385 	program.createFromSource(fillShader.vertexCode, fillShader.fragmentCode);
386 	
387     // Add program
388     program.id = this.programs.length;
389     this.programs.push({ 
390     	fillShader: fillShader,
391     	program: program,
392     	renderables: []
393 	});
394 	return program;
395 }
396 
397 /**************************************************************************************************************/
398 
399 /**
400  	Get program if known by renderer, create otherwise
401  */
402 ConvexPolygonRenderer.prototype.getProgram = function(fillShader) {
403 
404 	var program;
405 
406     for(var id=0; id<this.programs.length; id++)
407     {
408         if( this.programs[id].fillShader == fillShader )
409         {
410         	program = this.programs[id].program;
411         }
412     }
413 
414     if ( !program )
415     {
416     	program = this.createProgram(fillShader);
417     }
418     return program;
419 }
420 
421 /**************************************************************************************************************/
422 
423 
424 /**
425 	Get or create bucket to render a polygon
426  */
427 ConvexPolygonRenderer.prototype.getOrCreateBucket = function(layer,style)
428 {
429 	// Find an existing bucket for the given style, except if label is set, always create a new one
430 	for ( var i = 0; i < this.buckets.length; i++ )
431 	{
432 		var bucket = this.buckets[i];
433 		if ( bucket.layer == layer 
434 			&& bucket.style.strokeColor[0] == style.strokeColor[0]
435 			&& bucket.style.strokeColor[1] == style.strokeColor[1]
436 			&& bucket.style.strokeColor[2] == style.strokeColor[2]
437 			&& bucket.style.fill == style.fill
438 			&& bucket.style.fillTexture == style.fillTexture
439 			&& bucket.style.fillTextureUrl == style.fillTextureUrl
440 			&& bucket.style.fillShader == style.fillShader )
441 		{
442 			return bucket;
443 		}
444 	}
445 
446 	var gl = this.renderContext.gl;
447 	var vb = gl.createBuffer();
448 
449 
450 	// Create a bucket
451 	var bucket = {
452 		style: new FeatureStyle(style),
453 		layer: layer,
454 		polygonProgram: null,
455 		texture: null,
456 		mainRenderable : null,
457 		currentRenderables : []
458 	};
459 
460 	// Create texture
461 	var self = this;
462 	
463 
464 	if ( style.fill )
465 	{
466 		var hasTexture = false;
467 		if ( style.fillTextureUrl )
468 		{
469 			var image = new Image();
470 			image.crossOrigin = '';
471 			image.onload = function () 
472 			{
473 				bucket.texture = self.renderContext.createNonPowerOfTwoTextureFromImage(image);
474 			}
475 			
476 			image.onerror = function(event)
477 			{
478 				console.log("Cannot load " + image.src );
479 			}
480 			
481 			image.src = style.fillTextureUrl;
482 			hasTexture = true;
483 		}
484 		else if ( style.fillTexture )
485 		{
486 			bucket.texture = style.fillTexture;
487 			hasTexture = true;
488 		}
489 			
490 		if ( style.fillShader&& style.fillShader.fragmentCode )
491 		{
492 			// User defined texture program
493 			if ( !style.fillShader.vertexCode )
494 				style.fillShader.vertexCode = this.texVertexShader;
495 			if ( !style.fillShader.vertexCode )
496 				style.fillShader.fragmentCode = this.texFragmentShader;
497 
498 			bucket.polygonProgram = this.getProgram(style.fillShader);
499 		}
500 		else
501 		{
502 			// Default program
503 			bucket.polygonProgram = hasTexture ? this.texProgram : this.basicProgram;
504 		}
505 	}
506 		
507 	this.buckets.push( bucket );
508 	
509 	return bucket;
510 }
511 
512 /**************************************************************************************************************/
513 
514 /*
515 	Render all the POIs
516  */
517 ConvexPolygonRenderer.prototype.render = function(tiles)
518 {	
519 	var renderContext = this.renderContext;
520 	var gl = this.renderContext.gl;
521 	
522 	// Setup states
523 	gl.disable(gl.DEPTH_TEST);
524 	gl.enable(gl.BLEND);
525 	gl.blendEquation(gl.FUNC_ADD);
526 	gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
527 
528 	// Retrieve renderables stored on the visible tiles
529 	for ( var n = 0; n < tiles.length; n++ )
530 	{
531 		var tile = tiles[n];
532 		var tileData = tile.extension.polygon;
533 		while (tile.parent && !tileData)
534 		{
535 			tile = tile.parent;
536 			tileData = tile.extension.polygon;
537 		}
538 		
539 		if (!tileData || tileData.frameNumber == this.frameNumber)
540 			continue;
541 		
542 		tileData.frameNumber = this.frameNumber;
543 		
544 		for (var i=0; i < tileData.renderables.length; i++ ) 
545 		{
546 			tileData.renderables[i].bucket.currentRenderables.push( tileData.renderables[i] );
547 		}
548 	}
549 	
550 	// Setup the basic program
551 	this.basicProgram.apply();
552 	mat4.multiply(renderContext.projectionMatrix, renderContext.viewMatrix, renderContext.modelViewMatrix)
553 	gl.uniformMatrix4fv(this.basicProgram.uniforms["viewProjectionMatrix"], false, renderContext.modelViewMatrix);
554 	
555 	// Render each bucket
556 	for ( var n = 0; n < this.buckets.length; n++ )
557 	{
558 		var bucket = this.buckets[n];
559 		
560 		if (!bucket.layer.visible())
561 		{
562 			// Remove current renderables from bucket
563 			bucket.currentRenderables.length = 0;
564 			continue;
565 		}
566 			
567 		if ( bucket.mainRenderable ) 
568 			bucket.currentRenderables.push( bucket.mainRenderable );
569 		
570 		if (bucket.currentRenderables.length == 0)
571 			continue;		
572 		
573 		// Set the color
574 		var color = bucket.style.strokeColor;
575 		gl.uniform4f(this.basicProgram.uniforms["color"], color[0], color[1], color[2], color[3] * bucket.layer.opacity() );
576 		
577 		for ( var i = 0; i < bucket.currentRenderables.length; i++ )
578 		{
579 			var renderable = bucket.currentRenderables[i];
580 			
581 			// Update vertex buffer
582 			if ( !renderable.vertexBuffer )
583 			{
584 				renderable.vertexBuffer = gl.createBuffer();
585 				renderable.lineIndexBuffer = gl.createBuffer();
586 			}
587 			
588 			gl.bindBuffer(gl.ARRAY_BUFFER, renderable.vertexBuffer);
589 			gl.vertexAttribPointer(this.basicProgram.attributes['vertex'], 3, gl.FLOAT, false, 0, 0);
590 		
591 			gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderable.lineIndexBuffer);
592 			
593 			if ( renderable.bufferDirty )
594 			{
595 				gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(renderable.vertices), gl.STATIC_DRAW);
596 				gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(renderable.lineIndices), gl.STATIC_DRAW);
597 				renderable.bufferDirty = false;
598 			}
599 
600 			gl.drawElements( gl.LINES, renderable.lineIndices.length, gl.UNSIGNED_SHORT, 0);
601 			
602 			// Construct renderables for second pass with filled polygons
603 			if ( bucket.polygonProgram )
604 				this.programs[ bucket.polygonProgram.id ].renderables.push(renderable);
605 		}
606 		
607 		// Remove current renderables from bucket
608 		bucket.currentRenderables.length = 0;
609 	}
610 	
611 	// Second pass for filled polygons
612 	for ( var i=0; i<this.programs.length; i++ )
613 	{
614 		if ( this.programs[i].renderables.length == 0 )
615 			continue;
616 
617 		var currentPolygonProgram = this.programs[i].program;
618 		currentPolygonProgram.apply();
619 		gl.uniformMatrix4fv(currentPolygonProgram.uniforms["viewProjectionMatrix"], false, renderContext.modelViewMatrix);
620 
621 		gl.uniform1i(currentPolygonProgram.uniforms["texture"], 0);
622 		gl.activeTexture(gl.TEXTURE0);
623 		gl.bindBuffer(gl.ARRAY_BUFFER, this.tcoordBuffer);
624 		gl.vertexAttribPointer(currentPolygonProgram.attributes['tcoord'], 2, gl.FLOAT, false, 0, 0);
625 
626 		for ( var j=0; j<this.programs[i].renderables.length; j++ )
627 		{
628 			renderable = this.programs[i].renderables[j];
629 
630 			if ( this.programs[i].fillShader.updateUniforms )
631 				this.programs[i].fillShader.updateUniforms(gl, renderable.bucket);
632 
633 			gl.bindBuffer(gl.ARRAY_BUFFER, renderable.vertexBuffer);
634 			gl.vertexAttribPointer(currentPolygonProgram.attributes['vertex'], 3, gl.FLOAT, false, 0, 0);
635 
636 			if ( !renderable.triangleIndexBuffer )
637 			{
638 				renderable.triangleIndexBuffer = gl.createBuffer();
639 			}
640 			gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderable.triangleIndexBuffer);
641 			if ( renderable.triBufferDirty )
642 			{
643 				gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(renderable.triangleIndices), gl.STATIC_DRAW);
644 				renderable.triBufferDirty = false;
645 			}
646 			// Add texture
647 			if ( renderable.bucket.texture ) 
648 			{
649 				gl.bindTexture(gl.TEXTURE_2D, renderable.bucket.texture); // use texture of renderable
650 				gl.uniform4f(currentPolygonProgram.uniforms["color"], 1.0, 1.0, 1.0, color[3] * renderable.bucket.layer.opacity());  // use whiteColor
651 			}
652 			else
653 			{
654 				gl.bindTexture(gl.TEXTURE_2D, this.whiteTexture);  // use white texture
655 				color = renderable.bucket.style.fillColor;
656 				gl.uniform4f(currentPolygonProgram.uniforms["color"], color[0], color[1], color[2], color[3] * renderable.bucket.layer.opacity() );
657 			}
658 			
659 			gl.drawElements( gl.TRIANGLES, renderable.triangleIndices.length, gl.UNSIGNED_SHORT, 0);
660 
661 		}
662 		
663 		// Remove all renderables for current program
664 		this.programs[i].renderables.length = 0;
665 	}	
666 
667     gl.enable(gl.DEPTH_TEST);
668     gl.disable(gl.BLEND);
669 	
670 	this.frameNumber++;
671 }
672 
673 
674 /**************************************************************************************************************/
675 
676 // Register the renderer
677 VectorRendererManager.registerRenderer({
678 	id: "ConvexPolygon",
679 	creator: function(globe) { return new ConvexPolygonRenderer(globe.tileManager); },
680 	canApply: function(type,style) {return type == "Polygon" || type == "MultiPolygon"; }
681 });
682 
683 /**************************************************************************************************************/
684 
685 return ConvexPolygonRenderer;
686 
687 });