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 	PointSpriteRenderer constructor
 27  */
 28 var PointSpriteRenderer = function(tileManager,style)
 29 {
 30 	// Store object for rendering
 31 	this.renderContext = tileManager.renderContext;
 32 	this.tileConfig = tileManager.tileConfig;
 33 	
 34 	// Bucket management for rendering : a bucket is a texture with its points
 35 	this.buckets = [];
 36 	
 37 	// For stats
 38 	this.numberOfRenderPoints = 0;
 39  	
 40 	var vertexShader = "\
 41 	attribute vec3 vertex; \n\
 42 	uniform mat4 viewProjectionMatrix; \n\
 43 	uniform float pointSize; \n\
 44 	void main(void)  \n\
 45 	{ \n\
 46 		gl_Position = viewProjectionMatrix * vec4(vertex,1.0); \n\
 47 		gl_PointSize = pointSize; \n\
 48 	} \n\
 49 	";
 50 	
 51 	var fragmentShader = "\
 52 	precision lowp float; \n\
 53 	uniform sampler2D texture; \n\
 54 	uniform float alpha; \n\
 55 	uniform vec3 color; \n\
 56 	\n\
 57 	void main(void) \n\
 58 	{ \n\
 59 		vec4 textureColor = texture2D(texture, gl_PointCoord); \n\
 60 		gl_FragColor = vec4(textureColor.rgb * color, textureColor.a * alpha); \n\
 61 		if (gl_FragColor.a <= 0.0) discard; \n\
 62 		//gl_FragColor = vec4(1.0); \n\
 63 	} \n\
 64 	";
 65 
 66     this.program = new Program(this.renderContext);
 67     this.program.createFromSource(vertexShader, fragmentShader);
 68 	
 69 	this.frameNumber = 0;
 70 
 71 	this.defaultTexture = null;
 72 }
 73 
 74 /**************************************************************************************************************/
 75 
 76 /**
 77  * Renderable constructor for PointSprite
 78  */
 79 var Renderable = function(bucket) 
 80 {
 81 	this.bucket = bucket;
 82 	this.geometry2vb = {};
 83 	this.vertices = [];
 84 	this.vertexBuffer = null;
 85 	this.vertexBufferDirty = false;
 86 }
 87 
 88 /**************************************************************************************************************/
 89 
 90 /**
 91  * Add a geometry to the renderbale
 92  */
 93 Renderable.prototype.add = function(geometry)
 94 {
 95 	this.geometry2vb[ geometry.gid ] = this.vertices.length;
 96 	var pt = CoordinateSystem.fromGeoTo3D( geometry['coordinates'] );
 97 	// Hack : push away the point, only works for AstroWeb, sufficient for now
 98 	this.vertices.push( 0.99 * pt[0], 0.99 * pt[1], 0.99 * pt[2] );
 99 	this.vertexBufferDirty = true;
100 }
101 
102 /**************************************************************************************************************/
103 
104 /**
105  * Remove a geometry from the renderable
106  */
107 Renderable.prototype.remove = function(geometry)
108 {
109 	if ( this.geometry2vb.hasOwnProperty(geometry.gid) )
110 	{
111 		var vbIndex = this.geometry2vb[ geometry.gid ];
112 		delete this.geometry2vb[ geometry.gid ];
113 		this.vertices.splice( vbIndex, 3 );
114 		this.vertexBufferDirty = true;
115 		
116 		// Update render data for all other geometries
117 		for ( var g in this.geometry2vb ) 
118 		{
119 			if ( g ) 
120 			{
121 				if ( this.geometry2vb[g] > vbIndex ) 
122 				{
123 					this.geometry2vb[g] -= 3;
124 				}
125 			}
126 		}
127 	}
128 }
129 
130 /**************************************************************************************************************/
131 
132 /**
133  * Dispose the renderable
134  */
135 Renderable.prototype.dispose = function(renderContext)
136 {
137 	if ( this.vertexBuffer ) 
138 	{
139 		renderContext.gl.deleteBuffer( this.vertexBuffer );
140 	}
141 }
142 
143 /**************************************************************************************************************/
144 
145 /*
146 	Build a default texture
147  */
148 PointSpriteRenderer.prototype._buildDefaultTexture = function(bucket)
149 {  	
150 	if ( !this.defaultTexture )
151 	{
152 		var gl = this.renderContext.gl;
153 		this.defaultTexture = gl.createTexture();
154 		gl.bindTexture(gl.TEXTURE_2D, this.defaultTexture);
155 		var whitePixel = new Uint8Array([255, 255, 255, 255]);
156 		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, whitePixel);
157 	}
158 
159 	bucket.texture = this.defaultTexture;
160 	bucket.textureWidth = 10;
161 	bucket.textureHeight = 10;
162 }
163 
164 /**************************************************************************************************************/
165 
166 /*
167 	Build a texture from an image and store in a bucket
168  */
169 PointSpriteRenderer.prototype._buildTextureFromImage = function(bucket,image)
170 {  	
171 	bucket.texture = this.renderContext.createNonPowerOfTwoTextureFromImage(image);
172 	bucket.textureWidth = image.width;
173 	bucket.textureHeight = image.height;
174 }
175 
176 
177 /**************************************************************************************************************/
178 
179 /**
180 	Add a point to the renderer
181  */
182 PointSpriteRenderer.prototype.addGeometryToTile = function(bucket,geometry,tile)
183 {
184 	var tileData = tile.extension.pointSprite;
185 	if (!tileData)
186 	{
187 		tileData = tile.extension.pointSprite = new RendererTileData();
188 	}
189 	var renderable = tileData.getRenderable(bucket);
190 	if (!renderable) 
191 	{
192 		renderable = new Renderable(bucket);
193 		tileData.renderables.push(renderable);
194 	}
195 	renderable.add(geometry);
196 
197 }
198 
199 /**************************************************************************************************************/
200 
201 /**
202 	Remove a point from the renderer
203  */
204 PointSpriteRenderer.prototype.removeGeometryFromTile = function(geometry,tile)
205 {
206 	var tileData = tile.extension.pointSprite;
207 	if (tileData)
208 	{
209 		for ( var i=0; i < tileData.renderables.length; i++ )
210 		{
211 			tileData.renderables[i].remove(geometry);
212 		}
213 	}
214 }
215 
216 PointSpriteRenderer.prototype.removeGeometry = function()
217 {
218 }
219 
220 /**************************************************************************************************************/
221 
222 /*
223 	Get or create bucket to render a point
224  */
225 PointSpriteRenderer.prototype.getOrCreateBucket = function(layer,style)
226 {
227 	// Find an existing bucket for the given style, except if label is set, always create a new one
228 	for ( var i = 0; i < this.buckets.length; i++ )
229 	{
230 		var bucket = this.buckets[i];
231 		if ( bucket.layer == layer && bucket.style.iconUrl == style.iconUrl
232 			&& bucket.style.icon == style.icon
233 			&& bucket.style.label == style.label
234 			&& bucket.style.fillColor[0] == style.fillColor[0]
235 			&& bucket.style.fillColor[1] == style.fillColor[1]
236 			&& bucket.style.fillColor[2] == style.fillColor[2])
237 		{
238 			return bucket;
239 		}
240 	}
241 
242 	var gl = this.renderContext.gl;
243 	var vb = gl.createBuffer();
244 
245 
246 	// Create a bucket
247 	var bucket = {
248 		style: new FeatureStyle(style),
249 		layer: layer,
250 		texture: null
251 	};
252 		
253 	// Initialize bucket : create the texture	
254 	if ( style['label'] )
255 	{
256 		var imageData = Text.generateImageData(style['label'], style['textColor']);
257 		this._buildTextureFromImage(bucket,imageData);
258 	}
259 	else if ( style['iconUrl'] )
260 	{
261 		var image = new Image();
262 		var self = this;
263 		image.onload = function() {self._buildTextureFromImage(bucket,image); self.renderContext.requestFrame(); }
264 		image.onerror = function() { self._buildDefaultTexture(bucket); }
265 		image.src = style.iconUrl;
266 	}
267 	else if ( style['icon'] )
268 	{
269 		this._buildTextureFromImage(bucket,style.icon);
270 	}
271 	else
272 	{
273 		this._buildDefaultTexture(bucket);
274 	}
275 	
276 	this.buckets.push( bucket );
277 	
278 	return bucket;
279 }
280 
281 /**************************************************************************************************************/
282 
283 /*
284 	Render all the POIs
285  */
286 PointSpriteRenderer.prototype.render = function(tiles)
287 {	
288 	var renderContext = this.renderContext;
289 	var gl = this.renderContext.gl;
290 	
291 	// Setup states
292 	gl.enable(gl.BLEND);
293 	gl.blendEquation(gl.FUNC_ADD);
294 	gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
295 
296 	// Setup program
297 	this.program.apply();
298 	
299 	// The shader only needs the viewProjection matrix, use GlobWeb.modelViewMatrix as a temporary storage
300 	mat4.multiply(renderContext.projectionMatrix, renderContext.viewMatrix, renderContext.modelViewMatrix)
301 	gl.uniformMatrix4fv(this.program.uniforms["viewProjectionMatrix"], false, renderContext.modelViewMatrix);
302 	gl.uniform1i(this.program.uniforms["texture"], 0);
303 	
304 	for ( var n = 0; n < tiles.length; n++ )
305 	{
306 		var tile = tiles[n];
307 		var tileData = tile.extension.pointSprite;
308 		while (tile.parent && !tileData)
309 		{
310 			tile = tile.parent;
311 			tileData = tile.extension.pointSprite;
312 		}
313 		
314 		if (!tileData || tileData.frameNumber == this.frameNumber)
315 			continue;
316 		
317 		tileData.frameNumber = this.frameNumber;
318 		
319 		for (var i=0; i < tileData.renderables.length; i++ ) 
320 		{
321 			var renderable = tileData.renderables[i];
322 			if (!renderable.bucket.layer._visible)
323 				continue;
324 			gl.uniform1f(this.program.uniforms["alpha"], renderable.bucket.layer._opacity);
325 			var color = renderable.bucket.style.fillColor;
326 			gl.uniform3f(this.program.uniforms["color"], color[0], color[1], color[2] );
327 			gl.uniform1f(this.program.uniforms["pointSize"], renderable.bucket.textureWidth);
328 				
329 			// Warning : use quoted strings to access properties of the attributes, to work correclty in advanced mode with closure compiler
330 			if ( !renderable.vertexBuffer )
331 			{
332 				renderable.vertexBuffer = gl.createBuffer();
333 			}
334 			
335 			gl.bindBuffer(gl.ARRAY_BUFFER, renderable.vertexBuffer);
336 			gl.vertexAttribPointer(this.program.attributes['vertex'], 3, gl.FLOAT, false, 0, 0);
337 			
338 			if ( renderable.vertexBufferDirty )
339 			{
340 				gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(renderable.vertices), gl.STATIC_DRAW);
341 				renderable.vertexBufferDirty = false;
342 			}
343 
344 			
345 			// Bind point texture
346 			gl.activeTexture(gl.TEXTURE0);
347 			gl.bindTexture(gl.TEXTURE_2D, renderable.bucket.texture);
348 					
349 			gl.drawArrays(gl.POINTS, 0, renderable.vertices.length/3);
350 		}
351 			
352 		
353 	}
354 
355     gl.disable(gl.BLEND);
356 	
357 	this.frameNumber++;
358 }
359 
360 /**************************************************************************************************************/
361 
362 /*
363 	Render all the POIs
364  */
365 /*PointSpriteRenderer.prototype.render = function()
366 {
367 	if (this.buckets.length == 0)
368 	{
369 		return;
370 	}
371 	
372 	this.numberOfRenderPoints = 0;
373 	
374 	var renderContext = this.renderContext;
375 	var gl = this.renderContext.gl;
376 	
377 	// Setup states
378 	//gl.disable(gl.DEPTH_TEST);
379 	gl.enable(gl.BLEND);
380 	gl.blendEquation(gl.FUNC_ADD);
381 	gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
382 
383 	// Setup program
384 	this.program.apply();
385 	
386 	// The shader only needs the viewProjection matrix, use GlobWeb.modelViewMatrix as a temporary storage
387 	mat4.multiply(renderContext.projectionMatrix, renderContext.viewMatrix, renderContext.modelViewMatrix)
388 	gl.uniformMatrix4fv(this.program.uniforms["viewProjectionMatrix"], false, renderContext.modelViewMatrix);
389 	gl.uniform1i(this.program.uniforms["texture"], 0);
390 	
391 	for ( var n = 0; n < this.buckets.length; n++ )
392 	{
393 		var bucket = this.buckets[n];
394 		
395 		if ( bucket.texture == null || bucket.vertices.length == 0
396 			|| !bucket.layer._visible || bucket.layer._opactiy <= 0.0 )
397 			continue;
398 			
399 		gl.uniform1f(this.program.uniforms["alpha"], bucket.layer._opacity);
400 		gl.uniform3f(this.program.uniforms["color"], 1.0, 1.0, 1.0 );
401 		gl.uniform1f(this.program.uniforms["pointSize"], bucket.textureWidth);
402 			
403 		// Warning : use quoted strings to access properties of the attributes, to work correclty in advanced mode with closure compiler
404 		gl.bindBuffer(gl.ARRAY_BUFFER, bucket.vertexBuffer);
405 		gl.vertexAttribPointer(this.program.attributes['vertex'], 3, gl.FLOAT, false, 0, 0);
406 		
407 		if ( bucket.vertexBufferDirty )
408 		{
409 			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(bucket.vertices), gl.STATIC_DRAW);
410 			bucket.vertexBufferDirty = false;
411 		}
412 
413 		
414 		// Bind point texture
415 		gl.activeTexture(gl.TEXTURE0);
416 		gl.bindTexture(gl.TEXTURE_2D, bucket.texture);
417 				
418 		gl.drawArrays(gl.POINTS, 0, bucket.vertices.length/3);
419 	}
420 
421    // gl.enable(gl.DEPTH_TEST);
422     gl.disable(gl.BLEND);
423 }*/
424 
425 /**************************************************************************************************************/
426 
427 // Register the renderer
428 VectorRendererManager.registerRenderer({
429 	id: "PointSprite",
430 	creator: function(globe) { return new PointSpriteRenderer(globe.tileManager); },
431 	canApply: function(type,style) {return false; }
432 });
433 
434 return PointSpriteRenderer;
435 
436 });