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