1//============================================================ 2// 3// Copyright (C) 2014 Matthew Wagerfield 4// 5// Twitter: https://twitter.com/wagerfield 6// 7// Permission is hereby granted, free of charge, to any 8// person obtaining a copy of this software and associated 9// documentation files (the "Software"), to deal in the 10// Software without restriction, including without limitation 11// the rights to use, copy, modify, merge, publish, distribute, 12// sublicense, and/or sell copies of the Software, and to 13// permit persons to whom the Software is furnished to do 14// so, subject to the following conditions: 15// 16// The above copyright notice and this permission notice 17// shall be included in all copies or substantial portions 18// of the Software. 19// 20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 21// OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 22// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 24// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 25// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 26// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 28// OR OTHER DEALINGS IN THE SOFTWARE. 29// 30//============================================================ 31 32/** 33 * Defines the Flat Surface Shader namespace for all the awesomeness to exist upon. 34 * @author Matthew Wagerfield 35 */ 36FSS = { 37 FRONT : 0, 38 BACK : 1, 39 DOUBLE : 2, 40 SVGNS : 'http://www.w3.org/2000/svg' 41}; 42 43/** 44 * @class Array 45 * @author Matthew Wagerfield 46 */ 47FSS.Array = typeof Float32Array === 'function' ? Float32Array : Array; 48 49/** 50 * @class Utils 51 * @author Matthew Wagerfield 52 */ 53FSS.Utils = { 54 isNumber: function(value) { 55 return !isNaN(parseFloat(value)) && isFinite(value); 56 } 57}; 58 59/** 60 * Request Animation Frame Polyfill. 61 * @author Paul Irish 62 * @see https://gist.github.com/paulirish/1579671 63 */ 64(function() { 65 66 var lastTime = 0; 67 var vendors = ['ms', 'moz', 'webkit', 'o']; 68 69 for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 70 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 71 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; 72 } 73 74 if (!window.requestAnimationFrame) { 75 window.requestAnimationFrame = function(callback, element) { 76 var currentTime = new Date().getTime(); 77 var timeToCall = Math.max(0, 16 - (currentTime - lastTime)); 78 var id = window.setTimeout(function() { 79 callback(currentTime + timeToCall); 80 }, timeToCall); 81 lastTime = currentTime + timeToCall; 82 return id; 83 }; 84 } 85 86 if (!window.cancelAnimationFrame) { 87 window.cancelAnimationFrame = function(id) { 88 clearTimeout(id); 89 }; 90 } 91 92}()); 93 94/** 95 * @object Math Augmentation 96 * @author Matthew Wagerfield 97 */ 98Math.PIM2 = Math.PI*2; 99Math.PID2 = Math.PI/2; 100Math.randomInRange = function(min, max) { 101 return min + (max - min) * Math.random(); 102}; 103Math.clamp = function(value, min, max) { 104 value = Math.max(value, min); 105 value = Math.min(value, max); 106 return value; 107}; 108 109/** 110 * @object Vector3 111 * @author Matthew Wagerfield 112 */ 113FSS.Vector3 = { 114 create: function(x, y, z) { 115 var vector = new FSS.Array(3); 116 this.set(vector, x, y, z); 117 return vector; 118 }, 119 clone: function(a) { 120 var vector = this.create(); 121 this.copy(vector, a); 122 return vector; 123 }, 124 set: function(target, x, y, z) { 125 target[0] = x || 0; 126 target[1] = y || 0; 127 target[2] = z || 0; 128 return this; 129 }, 130 setX: function(target, x) { 131 target[0] = x || 0; 132 return this; 133 }, 134 setY: function(target, y) { 135 target[1] = y || 0; 136 return this; 137 }, 138 setZ: function(target, z) { 139 target[2] = z || 0; 140 return this; 141 }, 142 copy: function(target, a) { 143 target[0] = a[0]; 144 target[1] = a[1]; 145 target[2] = a[2]; 146 return this; 147 }, 148 add: function(target, a) { 149 target[0] += a[0]; 150 target[1] += a[1]; 151 target[2] += a[2]; 152 return this; 153 }, 154 addVectors: function(target, a, b) { 155 target[0] = a[0] + b[0]; 156 target[1] = a[1] + b[1]; 157 target[2] = a[2] + b[2]; 158 return this; 159 }, 160 addScalar: function(target, s) { 161 target[0] += s; 162 target[1] += s; 163 target[2] += s; 164 return this; 165 }, 166 subtract: function(target, a) { 167 target[0] -= a[0]; 168 target[1] -= a[1]; 169 target[2] -= a[2]; 170 return this; 171 }, 172 subtractVectors: function(target, a, b) { 173 target[0] = a[0] - b[0]; 174 target[1] = a[1] - b[1]; 175 target[2] = a[2] - b[2]; 176 return this; 177 }, 178 subtractScalar: function(target, s) { 179 target[0] -= s; 180 target[1] -= s; 181 target[2] -= s; 182 return this; 183 }, 184 multiply: function(target, a) { 185 target[0] *= a[0]; 186 target[1] *= a[1]; 187 target[2] *= a[2]; 188 return this; 189 }, 190 multiplyVectors: function(target, a, b) { 191 target[0] = a[0] * b[0]; 192 target[1] = a[1] * b[1]; 193 target[2] = a[2] * b[2]; 194 return this; 195 }, 196 multiplyScalar: function(target, s) { 197 target[0] *= s; 198 target[1] *= s; 199 target[2] *= s; 200 return this; 201 }, 202 divide: function(target, a) { 203 target[0] /= a[0]; 204 target[1] /= a[1]; 205 target[2] /= a[2]; 206 return this; 207 }, 208 divideVectors: function(target, a, b) { 209 target[0] = a[0] / b[0]; 210 target[1] = a[1] / b[1]; 211 target[2] = a[2] / b[2]; 212 return this; 213 }, 214 divideScalar: function(target, s) { 215 if (s !== 0) { 216 target[0] /= s; 217 target[1] /= s; 218 target[2] /= s; 219 } else { 220 target[0] = 0; 221 target[1] = 0; 222 target[2] = 0; 223 } 224 return this; 225 }, 226 cross: function(target, a) { 227 var x = target[0]; 228 var y = target[1]; 229 var z = target[2]; 230 target[0] = y*a[2] - z*a[1]; 231 target[1] = z*a[0] - x*a[2]; 232 target[2] = x*a[1] - y*a[0]; 233 return this; 234 }, 235 crossVectors: function(target, a, b) { 236 target[0] = a[1]*b[2] - a[2]*b[1]; 237 target[1] = a[2]*b[0] - a[0]*b[2]; 238 target[2] = a[0]*b[1] - a[1]*b[0]; 239 return this; 240 }, 241 min: function(target, value) { 242 if (target[0] < value) { target[0] = value; } 243 if (target[1] < value) { target[1] = value; } 244 if (target[2] < value) { target[2] = value; } 245 return this; 246 }, 247 max: function(target, value) { 248 if (target[0] > value) { target[0] = value; } 249 if (target[1] > value) { target[1] = value; } 250 if (target[2] > value) { target[2] = value; } 251 return this; 252 }, 253 clamp: function(target, min, max) { 254 this.min(target, min); 255 this.max(target, max); 256 return this; 257 }, 258 limit: function(target, min, max) { 259 var length = this.length(target); 260 if (min !== null && length < min) { 261 this.setLength(target, min); 262 } else if (max !== null && length > max) { 263 this.setLength(target, max); 264 } 265 return this; 266 }, 267 dot: function(a, b) { 268 return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; 269 }, 270 normalise: function(target) { 271 return this.divideScalar(target, this.length(target)); 272 }, 273 negate: function(target) { 274 return this.multiplyScalar(target, -1); 275 }, 276 distanceSquared: function(a, b) { 277 var dx = a[0] - b[0]; 278 var dy = a[1] - b[1]; 279 var dz = a[2] - b[2]; 280 return dx*dx + dy*dy + dz*dz; 281 }, 282 distance: function(a, b) { 283 return Math.sqrt(this.distanceSquared(a, b)); 284 }, 285 lengthSquared: function(a) { 286 return a[0]*a[0] + a[1]*a[1] + a[2]*a[2]; 287 }, 288 length: function(a) { 289 return Math.sqrt(this.lengthSquared(a)); 290 }, 291 setLength: function(target, l) { 292 var length = this.length(target); 293 if (length !== 0 && l !== length) { 294 this.multiplyScalar(target, l / length); 295 } 296 return this; 297 } 298}; 299 300/** 301 * @object Vector4 302 * @author Matthew Wagerfield 303 */ 304FSS.Vector4 = { 305 create: function(x, y, z, w) { 306 var vector = new FSS.Array(4); 307 this.set(vector, x, y, z); 308 return vector; 309 }, 310 set: function(target, x, y, z, w) { 311 target[0] = x || 0; 312 target[1] = y || 0; 313 target[2] = z || 0; 314 target[3] = w || 0; 315 return this; 316 }, 317 setX: function(target, x) { 318 target[0] = x || 0; 319 return this; 320 }, 321 setY: function(target, y) { 322 target[1] = y || 0; 323 return this; 324 }, 325 setZ: function(target, z) { 326 target[2] = z || 0; 327 return this; 328 }, 329 setW: function(target, w) { 330 target[3] = w || 0; 331 return this; 332 }, 333 add: function(target, a) { 334 target[0] += a[0]; 335 target[1] += a[1]; 336 target[2] += a[2]; 337 target[3] += a[3]; 338 return this; 339 }, 340 multiplyVectors: function(target, a, b) { 341 target[0] = a[0] * b[0]; 342 target[1] = a[1] * b[1]; 343 target[2] = a[2] * b[2]; 344 target[3] = a[3] * b[3]; 345 return this; 346 }, 347 multiplyScalar: function(target, s) { 348 target[0] *= s; 349 target[1] *= s; 350 target[2] *= s; 351 target[3] *= s; 352 return this; 353 }, 354 min: function(target, value) { 355 if (target[0] < value) { target[0] = value; } 356 if (target[1] < value) { target[1] = value; } 357 if (target[2] < value) { target[2] = value; } 358 if (target[3] < value) { target[3] = value; } 359 return this; 360 }, 361 max: function(target, value) { 362 if (target[0] > value) { target[0] = value; } 363 if (target[1] > value) { target[1] = value; } 364 if (target[2] > value) { target[2] = value; } 365 if (target[3] > value) { target[3] = value; } 366 return this; 367 }, 368 clamp: function(target, min, max) { 369 this.min(target, min); 370 this.max(target, max); 371 return this; 372 } 373}; 374 375/** 376 * @class Color 377 * @author Matthew Wagerfield 378 */ 379FSS.Color = function(hex, opacity) {
380 this.rgba = FSS.Vector4.create(); 381 this.hex = hex || '#000000'; 382 this.opacity = FSS.Utils.isNumber(opacity) ? opacity : 1; 383 this.set(this.hex, this.opacity); 384}; 385 386FSS.Color.prototype = { 387 set: function(hex, opacity) { 388 hex = hex.replace('#', ''); 389 var size = hex.length / 3; 390 this.rgba[0] = parseInt(hex.substring(size*0, size*1), 16) / 255; 391 this.rgba[1] = parseInt(hex.substring(size*1, size*2), 16) / 255; 392 this.rgba[2] = parseInt(hex.substring(size*2, size*3), 16) / 255; 393 this.rgba[3] = FSS.Utils.isNumber(opacity) ? opacity : this.rgba[3]; 394 return this; 395 }, 396 hexify: function(channel) { 397 var hex = Math.ceil(channel*255).toString(16); 398 if (hex.length === 1) { hex = '0' + hex; } 399 return hex; 400 }, 401 format: function() { 402 var r = this.hexify(this.rgba[0]); 403 var g = this.hexify(this.rgba[1]); 404 var b = this.hexify(this.rgba[2]); 405 this.hex = '#' + r + g + b; 406 return this.hex; 407 } 408}; 409 410/** 411 * @class Object 412 * @author Matthew Wagerfield 413 */ 414FSS.Object = function() { 415 this.position = FSS.Vector3.create(); 416}; 417 418FSS.Object.prototype = { 419 setPosition: function(x, y, z) { 420 FSS.Vector3.set(this.position, x, y, z); 421 return this; 422 } 423}; 424 425/** 426 * @class Light 427 * @author Matthew Wagerfield 428 */ 429FSS.Light = function(ambient, diffuse) { 430 FSS.Object.call(this); 431 this.ambient = new FSS.Color(ambient || '#FFFFFF'); 432 this.diffuse = new FSS.Color(diffuse || '#FFFFFF'); 433 this.ray = FSS.Vector3.create(); 434}; 435 436FSS.Light.prototype = Object.create(FSS.Object.prototype); 437 438/** 439 * @class Vertex 440 * @author Matthew Wagerfield 441 */ 442FSS.Vertex = function(x, y, z) { 443 this.position = FSS.Vector3.create(x, y, z); 444}; 445 446FSS.Vertex.prototype = { 447 setPosition: function(x, y, z) { 448 FSS.Vector3.set(this.position, x, y, z); 449 return this; 450 } 451}; 452 453/** 454 * @class Triangle 455 * @author Matthew Wagerfield 456 */ 457FSS.Triangle = function(a, b, c) { 458 this.a = a || new FSS.Vertex(); 459 this.b = b || new FSS.Vertex(); 460 this.c = c || new FSS.Vertex(); 461 this.vertices = [this.a, this.b, this.c]; 462 this.u = FSS.Vector3.create(); 463 this.v = FSS.Vector3.create(); 464 this.centroid = FSS.Vector3.create(); 465 this.normal = FSS.Vector3.create(); 466 this.color = new FSS.Color(); 467 this.polygon = document.createElementNS(FSS.SVGNS, 'polygon'); 468 this.polygon.setAttributeNS(null, 'stroke-linejoin', 'round'); 469 this.polygon.setAttributeNS(null, 'stroke-miterlimit', '1'); 470 this.polygon.setAttributeNS(null, 'stroke-width', '1'); 471 this.computeCentroid(); 472 this.computeNormal(); 473}; 474 475FSS.Triangle.prototype = { 476 computeCentroid: function() { 477 this.centroid[0] = this.a.position[0] + this.b.position[0] + this.c.position[0]; 478 this.centroid[1] = this.a.position[1] + this.b.position[1] + this.c.position[1]; 479 this.centroid[2] = this.a.position[2] + this.b.position[2] + this.c.position[2]; 480 FSS.Vector3.divideScalar(this.centroid, 3); 481 return this; 482 }, 483 computeNormal: function() { 484 FSS.Vector3.subtractVectors(this.u, this.b.position, this.a.position); 485 FSS.Vector3.subtractVectors(this.v, this.c.position, this.a.position); 486 FSS.Vector3.crossVectors(this.normal, this.u, this.v); 487 FSS.Vector3.normalise(this.normal); 488 return this; 489 } 490}; 491 492/** 493 * @class Geometry 494 * @author Matthew Wagerfield 495 */ 496FSS.Geometry = function() { 497 this.vertices = []; 498 this.triangles = []; 499 this.dirty = false; 500}; 501 502FSS.Geometry.prototype = { 503 update: function() { 504 if (this.dirty) { 505 var t,triangle; 506 for (t = this.triangles.length - 1; t >= 0; t--) { 507 triangle = this.triangles[t]; 508 triangle.computeCentroid(); 509 triangle.computeNormal(); 510 } 511 this.dirty = false; 512 } 513 return this; 514 } 515}; 516 517/** 518 * @class Plane 519 * @author Matthew Wagerfield 520 */ 521FSS.Plane = function(width, height, segments, slices) { 522 FSS.Geometry.call(this); 523 this.width = width || 100; 524 this.height = height || 100; 525 this.segments = segments || 4; 526 this.slices = slices || 4; 527 this.segmentWidth = this.width / this.segments; 528 this.sliceHeight = this.height / this.slices; 529 530 // Cache Variables 531 var x, y, v0, v1, v2, v3, 532 vertex, triangle, vertices = [], 533 offsetX = this.width * -0.5, 534 offsetY = this.height * 0.5; 535 536 // Add Vertices 537 for (x = 0; x <= this.segments; x++) { 538 vertices.push([]); 539 for (y = 0;
539 y <= this.slices; y++) { 540 vertex = new FSS.Vertex(offsetX + x*this.segmentWidth, offsetY - y*this.sliceHeight); 541 vertices[x].push(vertex); 542 this.vertices.push(vertex); 543 } 544 } 545 546 // Add Triangles 547 for (x = 0; x < this.segments; x++) { 548 for (y = 0; y < this.slices; y++) { 549 v0 = vertices[x+0][y+0]; 550 v1 = vertices[x+0][y+1]; 551 v2 = vertices[x+1][y+0]; 552 v3 = vertices[x+1][y+1]; 553 t0 = new FSS.Triangle(v0, v1, v2); 554 t1 = new FSS.Triangle(v2, v1, v3); 555 this.triangles.push(t0, t1); 556 } 557 } 558}; 559 560FSS.Plane.prototype = Object.create(FSS.Geometry.prototype); 561 562/** 563 * @class Material 564 * @author Matthew Wagerfield 565 */ 566FSS.Material = function(ambient, diffuse) { 567 this.ambient = new FSS.Color(ambient || '#444444'); 568 this.diffuse = new FSS.Color(diffuse || '#FFFFFF'); 569 this.slave = new FSS.Color(); 570}; 571 572/** 573 * @class Mesh 574 * @author Matthew Wagerfield 575 */ 576FSS.Mesh = function(geometry, material) { 577 FSS.Object.call(this); 578 this.geometry = geometry || new FSS.Geometry(); 579 this.material = material || new FSS.Material(); 580 this.side = FSS.FRONT; 581 this.visible = true; 582}; 583 584FSS.Mesh.prototype = Object.create(FSS.Object.prototype); 585 586FSS.Mesh.prototype.update = function(lights, calculate) { 587 var t,triangle, l,light, illuminance; 588 589 // Update Geometry 590 this.geometry.update(); 591 592 // Calculate the triangle colors 593 if (calculate) { 594 595 // Iterate through Triangles 596 for (t = this.geometry.triangles.length - 1; t >= 0; t--) { 597 triangle = this.geometry.triangles[t]; 598 599 // Reset Triangle Color 600 FSS.Vector4.set(triangle.color.rgba); 601 602 // Iterate through Lights 603 for (l = lights.length - 1; l >= 0; l--) { 604 light = lights[l]; 605 606 // Calculate Illuminance 607 FSS.Vector3.subtractVectors(light.ray, light.position, triangle.centroid); 608 FSS.Vector3.normalise(light.ray); 609 illuminance = FSS.Vector3.dot(triangle.normal, light.ray); 610 if (this.side === FSS.FRONT) { 611 illuminance = Math.max(illuminance, 0); 612 } else if (this.side === FSS.BACK) { 613 illuminance = Math.abs(Math.min(illuminance, 0)); 614 } else if (this.side === FSS.DOUBLE) { 615 illuminance = Math.max(Math.abs(illuminance), 0); 616 } 617 618 // Calculate Ambient Light 619 FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.ambient.rgba, light.ambient.rgba); 620 FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba); 621 622 // Calculate Diffuse Light 623 FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.diffuse.rgba, light.diffuse.rgba); 624 FSS.Vector4.multiplyScalar(this.material.slave.rgba, illuminance); 625 FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba); 626 } 627 628 // Clamp & Format Color 629 FSS.Vector4.clamp(triangle.color.rgba, 0, 1); 630 } 631 } 632 return this; 633}; 634 635/** 636 * @class Scene 637 * @author Matthew Wagerfield 638 */ 639FSS.Scene = function() { 640 this.meshes = []; 641 this.lights = []; 642}; 643 644FSS.Scene.prototype = { 645 add: function(object) { 646 if (object instanceof FSS.Mesh && !~this.meshes.indexOf(object)) { 647 this.meshes.push(object); 648 } else if (object instanceof FSS.Light && !~this.lights.indexOf(object)) { 649 this.lights.push(object); 650 } 651 return this; 652 }, 653 remove: function(object) { 654 if (object instanceof FSS.Mesh && ~this.meshes.indexOf(object)) { 655 this.meshes.splice(this.meshes.indexOf(object), 1); 656 } else if (object instanceof FSS.Light && ~this.lights.indexOf(object)) { 657 this.lights.splice(this.lights.indexOf(object), 1); 658 } 659 return this; 660 } 661}; 662 663/** 664 * @class Renderer 665 * @author Matthew Wagerfield 666 */ 667FSS.Renderer = function() { 668 this.width = 0; 669 this.height = 0; 670 this.halfWidth = 0; 671 this.halfHeight = 0; 672}; 673 674FSS.Renderer.prototype = { 675 setSize: function(width, height) { 676 if (this.width === width && this.height === height) return; 677 this.width = width; 678 this.height = height; 679 this.halfWidth = this.width * 0.5; 680 this.halfHeight = this.height * 0.5; 681 return this; 682 }, 683 clear: function() { 684 return this; 685 }, 686 render: function(scene) { 687 return this; 688 } 689}; 690 691/** 692 * @class Canvas Renderer 693 * @author Matthew Wagerfield 694 */ 695FSS.CanvasRenderer = function() { 696 FSS.Renderer.call(this); 697 this.element = document.createElement('canvas'); 698 this.element.style.display = 'block'; 699 this.context = this.element.getContext('2d'); 700 this.setSize(this.element.width, this.element.height); 701}; 702 703FSS.CanvasRenderer.prototype = Object.create(FSS.Renderer.prototype); 704 705FSS.CanvasRenderer.prototype.setSize = function(width, height) { 706 FSS.Renderer.prototype.setSize.call(this, width, height); 707 this.element.width = width; 708 this.element.height = height; 709 this.context.setTransform(1, 0, 0, -1, this.halfWidth, this.halfHeight); 710 return this; 711}; 712 713FSS.CanvasRenderer.prototype.clear = function() { 714 FSS.Renderer.prototype.clear.call(this); 715 this.context.clearRect(-this.halfWidth, -this.halfHeight, this.width, this.height); 716 return this; 717}; 718 719FSS.CanvasRenderer.prototype.render = function(scene) { 720 FSS.Renderer.prototype.render.call(this, scene); 721 var m,mesh, t,triangle, color; 722 723 // Clear Context 724 this.clear(); 725 726 // Configure Context 727 this.context.lineJoin = 'round'; 728 this.context.lineWidth = 1; 729 730 // Update Meshes 731 for (m = scene.meshes.length - 1; m >= 0; m--) { 732 mesh = scene.meshes[m]; 733 if (mesh.visible) { 734 mesh.update(scene.lights, true); 735 736 // Render Triangles 737 for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) { 738 triangle = mesh.geometry.triangles[t]; 739 color = triangle.color.format(); 740 this.context.beginPath(); 741 this.context.moveTo(triangle.a.position[0], triangle.a.position[1]); 742 this.context.lineTo(triangle.b.position[0], triangle.b.position[1]); 743 this.context.lineTo(triangle.c.position[0], triangle.c.position[1]); 744 this.context.closePath(); 745 this.context.strokeStyle = color; 746 this.context.fillStyle = color; 747 this.context.stroke(); 748 this.context.fill(); 749 } 750 } 751 } 752 return this; 753}; 754 755/** 756 * @class WebGL Renderer 757 * @author Matthew Wagerfield 758 */ 759FSS.WebGLRenderer = function() { 760 FSS.Renderer.call(this); 761 this.element = document.createElement('canvas'); 762 this.element.style.display = 'block'; 763 764 // Set initial vertex and light count 765 this.vertices = null; 766 this.lights = null; 767 768 // Create parameters object 769 var parameters = { 770 preserveDrawingBuffer: false, 771 premultipliedAlpha: true, 772 antialias: true, 773 stencil: true,
774 alpha: true 775 }; 776 777 // Create and configure the gl context 778 this.gl = this.getContext(this.element, parameters); 779 780 // Set the internal support flag 781 this.unsupported = !this.gl; 782 783 // Setup renderer 784 if (this.unsupported) { 785 return 'WebGL is not supported by your browser.'; 786 } else { 787 this.gl.clearColor(0.0, 0.0, 0.0, 0.0); 788 this.gl.enable(this.gl.DEPTH_TEST); 789 this.setSize(this.element.width, this.element.height); 790 } 791}; 792 793FSS.WebGLRenderer.prototype = Object.create(FSS.Renderer.prototype); 794 795FSS.WebGLRenderer.prototype.getContext = function(canvas, parameters) { 796 var context = false; 797 try { 798 if (!(context = canvas.getContext('experimental-webgl', parameters))) { 799 throw 'Error creating WebGL context.'; 800 } 801 } catch (error) { 802 console.error(error); 803 } 804 return context; 805}; 806 807FSS.WebGLRenderer.prototype.setSize = function(width, height) { 808 FSS.Renderer.prototype.setSize.call(this, width, height); 809 if (this.unsupported) return; 810 811 // Set the size of the canvas element 812 this.element.width = width; 813 this.element.height = height; 814 815 // Set the size of the gl viewport 816 this.gl.viewport(0, 0, width, height); 817 return this; 818}; 819 820FSS.WebGLRenderer.prototype.clear = function() { 821 FSS.Renderer.prototype.clear.call(this); 822 if (this.unsupported) return; 823 this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); 824 return this; 825}; 826 827FSS.WebGLRenderer.prototype.render = function(scene) { 828 FSS.Renderer.prototype.render.call(this, scene); 829 if (this.unsupported) return; 830 var m,mesh, t,tl,triangle, l,light, 831 attribute, uniform, buffer, data, location, 832 update = false, lights = scene.lights.length, 833 index, v,vl,vetex,vertices = 0; 834 835 // Clear context 836 this.clear(); 837 838 // Build the shader program 839 if (this.lights !== lights) { 840 this.lights = lights; 841 if (this.lights > 0) { 842 this.buildProgram(lights); 843 } else { 844 return; 845 } 846 } 847 848 // Update program 849 if (!!this.program) { 850 851 // Increment vertex counter 852 for (m = scene.meshes.length - 1; m >= 0; m--) { 853 mesh = scene.meshes[m]; 854 if (mesh.geometry.dirty) update = true; 855 mesh.update(scene.lights, false); 856 vertices += mesh.geometry.triangles.length*3; 857 } 858 859 // Compare vertex counter 860 if (update || this.vertices !== vertices) { 861 this.vertices = vertices; 862 863 // Build buffers 864 for (attribute in this.program.attributes) { 865 buffer = this.program.attributes[attribute]; 866 buffer.data = new FSS.Array(vertices*buffer.size); 867 868 // Reset vertex index 869 index = 0; 870 871 // Update attribute buffer data 872 for (m = scene.meshes.length - 1; m >= 0; m--) { 873 mesh = scene.meshes[m]; 874 875 for (t = 0, tl = mesh.geometry.triangles.length; t < tl; t++) { 876 triangle = mesh.geometry.triangles[t]; 877 878 for (v = 0, vl = triangle.vertices.length; v < vl; v++) { 879 vertex = triangle.vertices[v]; 880 switch (attribute) { 881 case 'side': 882 this.setBufferData(index, buffer, mesh.side); 883 break; 884 case 'position': 885 this.setBufferData(index, buffer, vertex.position); 886 break; 887 case 'centroid': 888 this.setBufferData(index, buffer, triangle.centroid); 889 break; 890 case 'normal': 891 this.setBufferData(index, buffer, triangle.normal); 892 break; 893 case 'ambient': 894 this.setBufferData(index, buffer, mesh.material.ambient.rgba); 895 break; 896 case 'diffuse': 897 this.setBufferData(index, buffer, mesh.material.diffuse.rgba); 898 break; 899 } 900 index++; 901 } 902 } 903 } 904 905 // Upload attribute buffer data 906 this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer.buffer); 907 this.gl.bufferData(this.gl.ARRAY_BUFFER, buffer.data, this.gl.DYNAMIC_DRAW); 908 this.gl.enableVertexAttribArray(buffer.location); 909 this.gl.vertexAttribPointer(buffer.location, buffer.size, this.gl.FLOAT, false, 0, 0); 910 } 911 } 912 913 // Build uniform buffers 914 this.setBufferData(0, this.program.uniforms.resolution, [this.width, this.height, this.width]); 915 for (l = lights-1; l >= 0; l--) { 916 light = scene.lights[l]; 917 this.setBufferData(l, this.program.uniforms.lightPosition, light.position); 918 this.setBufferData(l, this.program.uniforms.lightAmbient, light.ambient.rgba); 919 this.setBufferData(l, this.program.uniforms.lightDiffuse, light.diffuse.rgba); 920 } 921 922 // Update uniforms 923 for (uniform in this.program.uniforms) { 924 buffer = this.program.uniforms[uniform]; 925 location = buffer.location; 926 data = buffer.data; 927 switch (buffer.structure) { 928 case '3f': 929 this.gl.uniform3f(location, data[0], data[1], data[2]); 930 break; 931 case '3fv': 932 this.gl.uniform3fv(location, data); 933 break; 934 case '4fv': 935 this.gl.uniform4fv(location, data); 936 break; 937 } 938 } 939 } 940 941 // Draw those lovely triangles 942 this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertices); 943 return this; 944}; 945 946FSS.WebGLRenderer.prototype.setBufferData = function(index, buffer, value) { 947 if (FSS.Utils.isNumber(value)) {
948 buffer.data[index*buffer.size] = value; 949 } else { 950 for (var i = value.length - 1; i >= 0; i--) { 951 buffer.data[index*buffer.size+i] = value[i]; 952 } 953 } 954}; 955 956/** 957 * Concepts taken from three.js WebGLRenderer 958 * @see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js 959 */ 960FSS.WebGLRenderer.prototype.buildProgram = function(lights) { 961 if (this.unsupported) return; 962 963 // Create shader source 964 var vs = FSS.WebGLRenderer.VS(lights); 965 var fs = FSS.WebGLRenderer.FS(lights); 966 967 // Derive the shader fingerprint 968 var code = vs + fs; 969 970 // Check if the program has already been compiled 971 if (!!this.program && this.program.code === code) return; 972 973 // Create the program and shaders 974 var program = this.gl.createProgram(); 975 var vertexShader = this.buildShader(this.gl.VERTEX_SHADER, vs); 976 var fragmentShader = this.buildShader(this.gl.FRAGMENT_SHADER, fs); 977 978 // Attach an link the shader 979 this.gl.attachShader(program, vertexShader); 980 this.gl.attachShader(program, fragmentShader); 981 this.gl.linkProgram(program); 982 983 // Add error handling 984 if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) { 985 var error = this.gl.getError(); 986 var status = this.gl.getProgramParameter(program, this.gl.VALIDATE_STATUS); 987 console.error('Could not initialise shader.\nVALIDATE_STATUS: '+status+'\nERROR: '+error); 988 return null; 989 } 990 991 // Delete the shader 992 this.gl.deleteShader(fragmentShader); 993 this.gl.deleteShader(vertexShader); 994 995 // Set the program code 996 program.code = code; 997 998 // Add the program attributes 999 program.attributes = { 1000 side: this.buildBuffer(program, 'attribute', 'aSide', 1, 'f' ), 1001 position: this.buildBuffer(program, 'attribute', 'aPosition', 3, 'v3'), 1002 centroid: this.buildBuffer(program, 'attribute', 'aCentroid', 3, 'v3'), 1003 normal: this.buildBuffer(program, 'attribute', 'aNormal', 3, 'v3'), 1004 ambient: this.buildBuffer(program, 'attribute', 'aAmbient', 4, 'v4'), 1005 diffuse: this.buildBuffer(program, 'attribute', 'aDiffuse', 4, 'v4') 1006 }; 1007 1008 // Add the program uniforms 1009 program.uniforms = { 1010 resolution: this.buildBuffer(program, 'uniform', 'uResolution', 3, '3f', 1 ), 1011 lightPosition: this.buildBuffer(program, 'uniform', 'uLightPosition', 3, '3fv', lights), 1012 lightAmbient: this.buildBuffer(program, 'uniform', 'uLightAmbient', 4, '4fv', lights), 1013 lightDiffuse: this.buildBuffer(program, 'uniform', 'uLightDiffuse', 4, '4fv', lights) 1014 }; 1015 1016 // Set the renderer program 1017 this.program = program; 1018 1019 // Enable program 1020 this.gl.useProgram(this.program); 1021 1022 // Return the program 1023 return program; 1024}; 1025 1026FSS.WebGLRenderer.prototype.buildShader = function(type, source) { 1027 if (this.unsupported) return; 1028 1029 // Create and compile shader 1030 var shader = this.gl.createShader(type); 1031 this.gl.shaderSource(shader, source); 1032 this.gl.compileShader(shader); 1033 1034 // Add error handling 1035 if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { 1036 console.error(this.gl.getShaderInfoLog(shader)); 1037 return null; 1038 } 1039 1040 // Return the shader 1041 return shader; 1042}; 1043 1044FSS.WebGLRenderer.prototype.buildBuffer = function(program, type, identifier, size, structure, count) { 1045 var buffer = {buffer:this.gl.createBuffer(), size:size, structure:structure, data:null}; 1046 1047 // Set the location 1048 switch (type) { 1049 case 'attribute': 1050 buffer.location = this.gl.getAttribLocation(program, identifier); 1051 break; 1052 case 'uniform': 1053 buffer.location = this.gl.getUniformLocation(program, identifier); 1054 break; 1055 } 1056 1057 // Create the buffer if count is provided 1058 if (!!count) { 1059 buffer.data = new FSS.Array(count*size); 1060 } 1061 1062 // Return the buffer 1063 return buffer; 1064}; 1065 1066FSS.WebGLRenderer.VS = function(lights) { 1067 var shader = [ 1068 1069 // Precision 1070 'precision mediump float;', 1071 1072 // Lights 1073 '#define LIGHTS ' + lights, 1074 1075 // Attributes 1076 'attribute float aSide;', 1077 'attribute vec3 aPosition;', 1078 'attribute vec3 aCentroid;', 1079 'attribute vec3 aNormal;', 1080 'attribute vec4 aAmbient;', 1081 'attribute vec4 aDiffuse;', 1082 1083 // Uniforms 1084 'uniform vec3 uResolution;', 1085 'uniform vec3 uLightPosition[LIGHTS];', 1086 'uniform vec4 uLightAmbient[LIGHTS];', 1087 'uniform vec4 uLightDiffuse[LIGHTS];', 1088 1089 // Varyings 1090 'varying vec4 vColor;', 1091 1092 // Main 1093 'void main() {', 1094 1095 // Create color 1096 'vColor = vec4(0.0);', 1097 1098 // Calculate the vertex position 1099 'vec3 position = aPosition / uResolution * 2.0;', 1100 1101 // Iterate through lights 1102 'for (int i = 0; i < LIGHTS; i++) {', 1103 'vec3 lightPosition = uLightPosition[i];', 1104 'vec4 lightAmbient = uLightAmbient[i];', 1105 'vec4 lightDiffuse = uLightDiffuse[i];', 1106 1107 // Calculate illuminance 1108 'vec3 ray = normalize(lightPosition - aCentroid);', 1109 'float illuminance = dot(aNormal, ray);', 1110 'if (aSide == 0.0) {', 1111 'illuminance = max(illuminance, 0.0);', 1112 '} else if (aSide == 1.0) {', 1113 'illuminance = abs(min(illuminance, 0.0));', 1114 '} else if (aSide == 2.0) {', 1115 'illuminance = max(abs(illuminance), 0.0);', 1116 '}', 1117 1118 // Calculate ambient light 1119 'vColor += aAmbient * lightAmbient;', 1120 1121 // Calculate diffuse light
1122 'vColor += aDiffuse * lightDiffuse * illuminance;', 1123 '}', 1124 1125 // Clamp color 1126 'vColor = clamp(vColor, 0.0, 1.0);', 1127 1128 // Set gl_Position 1129 'gl_Position = vec4(position, 1.0);', 1130 1131 '}' 1132 1133 // Return the shader 1134 ].join('\n'); 1135 return shader; 1136}; 1137 1138FSS.WebGLRenderer.FS = function(lights) { 1139 var shader = [ 1140 1141 // Precision 1142 'precision mediump float;', 1143 1144 // Varyings 1145 'varying vec4 vColor;', 1146 1147 // Main 1148 'void main() {', 1149 1150 // Set gl_FragColor 1151 'gl_FragColor = vColor;', 1152 1153 '}' 1154 1155 // Return the shader 1156 ].join('\n'); 1157 return shader; 1158}; 1159 1160/** 1161 * @class SVG Renderer 1162 * @author Matthew Wagerfield 1163 */ 1164FSS.SVGRenderer = function() { 1165 FSS.Renderer.call(this); 1166 this.element = document.createElementNS(FSS.SVGNS, 'svg'); 1167 this.element.setAttribute('xmlns', FSS.SVGNS); 1168 this.element.setAttribute('version', '1.1'); 1169 this.element.style.display = 'block'; 1170 this.setSize(300, 150); 1171}; 1172 1173FSS.SVGRenderer.prototype = Object.create(FSS.Renderer.prototype); 1174 1175FSS.SVGRenderer.prototype.setSize = function(width, height) { 1176 FSS.Renderer.prototype.setSize.call(this, width, height); 1177 this.element.setAttribute('width', width); 1178 this.element.setAttribute('height', height); 1179 return this; 1180}; 1181 1182FSS.SVGRenderer.prototype.clear = function() { 1183 FSS.Renderer.prototype.clear.call(this); 1184 for (var i = this.element.childNodes.length - 1; i >= 0; i--) { 1185 this.element.removeChild(this.element.childNodes[i]); 1186 } 1187 return this; 1188}; 1189 1190FSS.SVGRenderer.prototype.render = function(scene) { 1191 FSS.Renderer.prototype.render.call(this, scene); 1192 var m,mesh, t,triangle, points, style; 1193 1194 // Update Meshes 1195 for (m = scene.meshes.length - 1; m >= 0; m--) { 1196 mesh = scene.meshes[m]; 1197 if (mesh.visible) { 1198 mesh.update(scene.lights, true); 1199 1200 // Render Triangles 1201 for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) { 1202 triangle = mesh.geometry.triangles[t]; 1203 if (triangle.polygon.parentNode !== this.element) { 1204 this.element.appendChild(triangle.polygon); 1205 } 1206 points = this.formatPoint(triangle.a)+' '; 1207 points += this.formatPoint(triangle.b)+' '; 1208 points += this.formatPoint(triangle.c); 1209 style = this.formatStyle(triangle.color.format()); 1210 triangle.polygon.setAttributeNS(null, 'points', points); 1211 triangle.polygon.setAttributeNS(null, 'style', style); 1212 } 1213 } 1214 } 1215 return this; 1216}; 1217 1218FSS.SVGRenderer.prototype.formatPoint = function(vertex) { 1219 return (this.halfWidth+vertex.position[0])+','+(this.halfHeight-vertex.position[1]); 1220}; 1221 1222FSS.SVGRenderer.prototype.formatStyle = function(color) { 1223 var style = 'fill:'+color+';'; 1224 style += 'stroke:'+color+';'; 1225 return style; 1226};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.