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( ['./CoordinateSystem','./VectorRendererManager','./FeatureStyle','./Program'], 
 21 	function(CoordinateSystem,VectorRendererManager,FeatureStyle,Program) {
 22 
 23 /**************************************************************************************************************/
 24 
 25 /** @constructor
 26 	Basic module to generate texture from text
 27  */	
 28 var Text = (function()
 29 {
 30 	var fontSize = 18;
 31 	var margin = 1;
 32 	var canvas2d = null;
 33 	
 34 	var initialize = function()
 35 	{
 36 		canvas2d = document.createElement("canvas");
 37 		canvas2d.width = 512;
 38 		canvas2d.height = fontSize  + 2 * margin;
 39 	}
 40 	
 41 	var generateImageData = function(text, textColor)
 42 	{
 43 		if (!canvas2d)
 44 			initialize();
 45 		
 46 		var fillColor = textColor;
 47 		if (!fillColor)
 48 			fillColor = '#fff';
 49 		else if ( fillColor instanceof Array )
 50 			fillColor = FeatureStyle.fromColorToString(textColor);
 51 		
 52 		var ctx = canvas2d.getContext("2d");
 53 		ctx.clearRect(0,0,canvas2d.width,canvas2d.height);
 54 		ctx.fillStyle = fillColor;
 55 		ctx.font = fontSize + 'px sans-serif';
 56 		ctx.textBaseline = 'top';
 57 		ctx.shadowColor = '#000';
 58 		ctx.shadowOffsetX = 1;
 59 		ctx.shadowOffsetY = 1;
 60 		ctx.shadowBlur = 2;
 61 		ctx.fillText(text, margin, margin);
 62 		//ctx.lineWidth = 1.0;
 63 		//ctx.strokeText(text, margin, margin);
 64 		
 65 		var metrics = ctx.measureText(text);
 66 		return ctx.getImageData(0,0, Math.floor(metrics.width)+2*margin,canvas2d.height)
 67 	}
 68 	
 69 	
 70 	return { generateImageData: generateImageData };
 71 })();
 72 
 73 
 74 /**************************************************************************************************************/
 75 
 76 /** @constructor
 77 	POI Renderer constructor
 78  */
 79 var PointRenderer = function(tileManager)
 80 {
 81 	// Store object for rendering
 82 	this.renderContext = tileManager.renderContext;
 83 	this.tileConfig = tileManager.tileConfig;
 84 	
 85 	// Bucket management for rendering : a bucket is a texture with its points
 86 	this.buckets = [];
 87 	
 88 	// For stats
 89 	this.numberOfRenderPoints = 0;
 90  	
 91 	var vertexShader = "\
 92 	attribute vec3 vertex; // vertex have z = 0, spans in x,y from -0.5 to 0.5 \n\
 93 	uniform mat4 viewProjectionMatrix; \n\
 94 	uniform vec3 poiPosition; // world position \n\
 95 	uniform vec2 poiScale; // x,y scale \n\
 96 	uniform vec2 tst; \n\
 97 	\n\
 98 	varying vec2 texCoord; \n\
 99 	\n\
100 	void main(void)  \n\
101 	{ \n\
102 		// Generate texture coordinates, input vertex goes from -0.5 to 0.5 (on x,y) \n\
103 		texCoord = vertex.xy + vec2(0.5) + tst; \n\
104 		// Invert y \n\
105 		texCoord.y = 1.0 - texCoord.y; \n\
106 		\n\
107 		// Compute poi position in clip coordinate \n\
108 		gl_Position = viewProjectionMatrix * vec4(poiPosition, 1.0); \n\
109 		gl_Position.xy += vertex.xy * gl_Position.w * poiScale; \n\
110 	} \n\
111 	";
112 	
113 	var fragmentShader = "\
114 	precision lowp float; \n\
115 	varying vec2 texCoord; \n\
116 	uniform sampler2D texture; \n\
117 	uniform float alpha; \n\
118 	uniform vec3 color; \n\
119 	\n\
120 	void main(void) \n\
121 	{ \n\
122 		vec4 textureColor = texture2D(texture, texCoord); \n\
123 		gl_FragColor = vec4(textureColor.rgb * color, textureColor.a * alpha); \n\
124 		if (gl_FragColor.a <= 0.0) discard; \n\
125 	} \n\
126 	";
127 
128     this.program = new Program(this.renderContext);
129     this.program.createFromSource(vertexShader, fragmentShader);
130 
131 	var vertices = new Float32Array([-0.5, -0.5, 0.0,
132                     -0.5,  0.5, 0.0,
133                      0.5,  0.5, 0.0,
134                      0.5, -0.5, 0.0]);
135 					 
136 	var gl = this.renderContext.gl;
137 	this.vertexBuffer = gl.createBuffer();
138 	gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
139 	gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
140 
141 	this.defaultTexture = null;
142 }
143 
144 /**************************************************************************************************************/
145 
146 /*
147 	Build a default texture
148  */
149 PointRenderer.prototype._buildDefaultTexture = function(bucket)
150 {  	
151 	if ( !this.defaultTexture )
152 	{
153 		var gl = this.renderContext.gl;
154 		this.defaultTexture = gl.createTexture();
155 		gl.bindTexture(gl.TEXTURE_2D, this.defaultTexture);
156 		var whitePixel = new Uint8Array([255, 255, 255, 255]);
157 		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, whitePixel);
158 	}
159 
160 	bucket.texture = this.defaultTexture;
161 	bucket.textureWidth = 10;
162 	bucket.textureHeight = 10;
163 }
164 
165 /**************************************************************************************************************/
166 
167 /*
168 	Build a texture from an image and store in a bucket
169  */
170 PointRenderer.prototype._buildTextureFromImage = function(bucket,image)
171 {  	
172 	bucket.texture = this.renderContext.createNonPowerOfTwoTextureFromImage(image);
173 	bucket.textureWidth = image.width;
174 	bucket.textureHeight = image.height;
175 }
176 
177 /**************************************************************************************************************/
178 
179 /*
180 	Add a point to the renderer
181  */
182 PointRenderer.prototype.addGeometry = function(geometry,layer,style)
183 {
184 	if ( style )
185 	{
186 		var bucket = this.getOrCreateBucket( layer,style );
187 		
188 		var posGeo = geometry['coordinates'];
189 		var pos3d = CoordinateSystem.fromGeoTo3D( posGeo );
190 		var vertical = vec3.create();
191 		vec3.normalize(pos3d, vertical);
192 		
193 		var pointRenderData = { pos3d: pos3d,
194 							vertical: vertical,
195 							geometry: geometry,
196 							color: style.fillColor };
197 
198 		bucket.points.push( pointRenderData );
199 	}
200 }
201 
202 /**************************************************************************************************************/
203 
204 /*
205 	Remove a point from renderer
206  */
207 PointRenderer.prototype.removeGeometry = function(geometry,layer)
208 {
209 	for ( var i = 0; i < this.buckets.length; i++ )
210 	{
211 		var bucket = this.buckets[i];
212 		if ( bucket.layer == layer )
213 		{
214 			for ( var j = 0; j < bucket.points.length; j++ )
215 			{
216 				if ( bucket.points[j].geometry == geometry )
217 				{
218 					bucket.points.splice( j, 1 );
219 					
220 					if ( bucket.points.length == 0 )
221 					{
222 						this.buckets.splice( i, 1 );
223 					}
224 					return;
225 				}
226 			}
227 		}
228 	}
229 }
230 
231 /**************************************************************************************************************/
232 
233 /*
234 	Get or create bucket to render a point
235  */
236 PointRenderer.prototype.getOrCreateBucket = function(layer,style)
237 {
238 	// Find an existing bucket for the given style, except if label is set, always create a new one
239 	for ( var i = 0; i < this.buckets.length; i++ )
240 	{
241 		var bucket = this.buckets[i];
242 		if ( bucket.layer == layer && bucket.style.isEqualForPoint(style) )
243 		{
244 			return bucket;
245 		}
246 	}
247 
248 
249 	// Create a bucket
250 	var bucket = {
251 		texture: null,
252 		points: [],
253 		style: style,
254 		layer: layer
255 	};
256 		
257 	// Initialize bucket : create the texture	
258 	if ( style['label'] )
259 	{
260 		var imageData = Text.generateImageData(style['label'], style['textColor']);
261 		this._buildTextureFromImage(bucket,imageData);
262 	}
263 	else if ( style['iconUrl'] )
264 	{
265 		var image = new Image();
266 		var self = this;
267 		image.onload = function() {self._buildTextureFromImage(bucket,image); self.renderContext.requestFrame(); }
268 		image.onerror = function() { self._buildDefaultTexture(bucket); }
269 		image.src = style.iconUrl;
270 	}
271 	else if ( style['icon'] )
272 	{
273 		this._buildTextureFromImage(bucket,style.icon);
274 	}
275 	else
276 	{
277 		this._buildDefaultTexture(bucket);
278 	}
279 	
280 	this.buckets.push( bucket );
281 	
282 	return bucket;
283 }
284 
285 /**************************************************************************************************************/
286 
287 /*
288 	Render all the POIs
289  */
290 PointRenderer.prototype.render = function()
291 {
292 	if (this.buckets.length == 0)
293 	{
294 		return;
295 	}
296 	
297 	this.numberOfRenderPoints = 0;
298 	
299 	var renderContext = this.renderContext;
300 	var gl = this.renderContext.gl;
301 	
302 	// Setup states
303 	// gl.disable(gl.DEPTH_TEST);
304 	gl.enable(gl.BLEND);
305 	gl.blendEquation(gl.FUNC_ADD);
306 	gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
307 
308 	// Setup program
309 	this.program.apply();
310 	
311 	// The shader only needs the viewProjection matrix, use modelViewMatrix as a temporary storage
312 	mat4.multiply(renderContext.projectionMatrix, renderContext.viewMatrix, renderContext.modelViewMatrix)
313 	gl.uniformMatrix4fv(this.program.uniforms["viewProjectionMatrix"], false, renderContext.modelViewMatrix);
314 	gl.uniform1i(this.program.uniforms["texture"], 0);
315 
316 	// Compute eye direction from inverse view matrix
317 	mat4.inverse(renderContext.viewMatrix, renderContext.modelViewMatrix);
318 	var camZ = [renderContext.modelViewMatrix[8], renderContext.modelViewMatrix[9], renderContext.modelViewMatrix[10]];
319 	vec3.normalize(camZ);
320 	vec3.scale(camZ, this.tileConfig.cullSign, camZ);
321 	
322 	// Compute pixel size vector to offset the points from the earth
323 	var pixelSizeVector = renderContext.computePixelSizeVector();
324 	
325 	// Warning : use quoted strings to access properties of the attributes, to work correclty in advanced mode with closure compiler
326 	gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
327 	gl.vertexAttribPointer(this.program.attributes['vertex'], 3, gl.FLOAT, false, 0, 0);
328 
329 	for ( var n = 0; n < this.buckets.length; n++ )
330 	{
331 		var bucket = this.buckets[n];
332 		
333 		if ( bucket.texture == null || bucket.points.length == 0
334 			|| !bucket.layer._visible || bucket.layer._opactiy <= 0.0 )
335 			continue;
336 		
337 		// Bind point texture
338 		gl.activeTexture(gl.TEXTURE0);
339 		gl.bindTexture(gl.TEXTURE_2D, bucket.texture);
340 
341 		// 2.0 * because normalized device coordinates goes from -1 to 1
342 		var scale = [2.0 * bucket.textureWidth / renderContext.canvas.width,
343 					 2.0 * bucket.textureHeight / renderContext.canvas.height];
344 		gl.uniform2fv(this.program.uniforms["poiScale"], scale);
345 		gl.uniform2fv(this.program.uniforms["tst"], [ 0.5 / (bucket.textureWidth), 0.5 / (bucket.textureHeight)  ]);
346 
347 		for (var i = 0; i < bucket.points.length; ++i)
348 		{
349 			// Poi culling
350 			var worldPoi = bucket.points[i].pos3d;
351 			var poiVec = bucket.points[i].vertical;
352 			var scale = bucket.textureHeight * ( pixelSizeVector[0] * worldPoi[0] + pixelSizeVector[1] * worldPoi[1] + pixelSizeVector[2] * worldPoi[2] + pixelSizeVector[3] );
353 			scale *= this.tileConfig.cullSign;
354 			var scaleInKm = (scale / CoordinateSystem.heightScale) * 0.001;
355 			if ( scaleInKm > bucket.style.pointMaxSize )
356 				continue;
357 				
358 			if ( vec3.dot(poiVec, camZ) > 0 
359 				&& renderContext.worldFrustum.containsSphere(worldPoi,scale) >= 0 )
360 			{
361 				var x = poiVec[0] * scale + worldPoi[0];
362 				var y = poiVec[1] * scale + worldPoi[1];
363 				var z = poiVec[2] * scale + worldPoi[2];
364 				
365 				gl.uniform3f(this.program.uniforms["poiPosition"], x, y, z);
366 				gl.uniform1f(this.program.uniforms["alpha"], bucket.layer._opacity);
367 				gl.uniform3f(this.program.uniforms["color"], bucket.points[i].color[0], bucket.points[i].color[1], bucket.points[i].color[2] );
368 				
369 				gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
370 				
371 				this.numberOfRenderPoints++;
372 			}
373 		}
374 	}
375 
376 //    gl.enable(gl.DEPTH_TEST);
377     gl.disable(gl.BLEND);
378 }
379 
380 /**************************************************************************************************************/
381 
382 // Register the renderer
383 VectorRendererManager.registerRenderer({
384 										creator: function(globe) { return new PointRenderer(globe.tileManager); },
385 										canApply: function(type,style) {return type == "Point"; }
386 									});
387 									
388 return PointRenderer;
389 
390 });
391