PageSourceSearch

https://www.grepixit.com/assets/libs/flat-surface-shader/deploy/fss.js

js grepixit.com collected 2026-09-24 10:27:38 UTC 33,597 bytes, 1,226 lines download raw bytes

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.