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 });