├── .babelrc ├── .editorconfig ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── docs ├── dist │ ├── vue.fss.js │ └── vue.fss.js.map └── index.html ├── package-lock.json ├── package.json ├── src ├── FSS.vue └── main.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | !docs/dist/ 5 | npm-debug.log 6 | yarn-error.log 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | docs/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 grzhan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-flat-surface-shader 2 | 3 | > A Vue component for [flat surface shader](https://github.com/wagerfield/flat-surface-shader) 4 | 5 | ![license badge](https://img.shields.io/npm/l/vue-flat-surface-shader.svg?style=flat-square) ![npm badge](https://img.shields.io/npm/v/vue-flat-surface-shader.svg?style=flat-square) 6 | 7 | DEMO: [Github Pages](https://grzhan.github.io/vue-flat-surface-shader/) 8 | 9 | ![demo screenshot](https://ws3.sinaimg.cn/large/006tKfTcgy1fjxbf31cp5j30m80eiq3p.jpg) 10 | 11 | 12 | 13 | ## How to use 14 | 15 | ```bash 16 | npm install --save vue-flat-surface-shader 17 | ``` 18 | 19 | #### Main.js 20 | 21 | ```javascript 22 | import Vue from 'vue' 23 | import FlatSurfaceShader from 'vue-flat-surface-shader' 24 | 25 | Vue.use(FlatSurfaceShader) 26 | ``` 27 | 28 | #### App.vue file (simple example) 29 | 30 | ```vue 31 | 40 | ``` 41 | 42 | #### App.vue file (advanced example) 43 | 44 | ```vue 45 | 54 | 64 | ``` 65 | 66 | ### Props 67 | 68 | #### 1.Type 69 | 70 | + The type of shader's renderer, avaliable values are `webgl`, `canvas`, `svg`. 71 | + Prop type: `String` 72 | + Default value: `webgl` 73 | 74 | #### 2. Light 75 | 76 | A **Light** is composed of a 3D position **Vector** and 2 **Color** objects defining its **ambient** & **diffuse** emissions. These color channels interact with the **Material** of a **Mesh** to calculate the color of a **Triangle**. 77 | 78 | For detailed explanation, see [http://wagerfield.github.io/flat-surf…](http://wagerfield.github.io/flat-surface-shader/) and [https://github.com/wagerfield/flat-surface-shader](https://github.com/wagerfield/flat-surface-shader) 79 | 80 | | Name | Type | Default | 81 | | ----------- | ------- | --------- | 82 | | count | Number | 2 | 83 | | xyScalar | Number | 1 | 84 | | zOffset | Number | 100 | 85 | | ambient | String | '#880066' | 86 | | diffuse | String | '#FF8800' | 87 | | speed | Number | 0.001 | 88 | | gravity | Number | 1200 | 89 | | dampening | Number | 0.95 | 90 | | minLimit | Number | 10 | 91 | | maxLimit | Number | null | 92 | | minDistance | Number | 20 | 93 | | maxDistance | Number | 400 | 94 | | autopilot | Boolean | false | 95 | | draw | Boolean | true | 96 | 97 | #### 3. Mesh 98 | 99 | A **Mesh** is constructed from a **Geometry** object and a **Material** object. All the **Triangles** within the **Geometry** are rendered using the properties of the **Material**: 100 | 101 | + **Geometry** is simply a collection of triangles – nothing more. 102 | + A **Material** is composed of 2 **Color** objects which define the **ambient** & **diffuse** properties of a *surface*. 103 | + A **Triangle** is constructed from **3 Vertices** which each define the **x, y** and **z** coordinates of a corner. 104 | 105 | For detailed explanation, see [http://wagerfield.github.io/flat-surf…](http://wagerfield.github.io/flat-surface-shader/) and [https://github.com/wagerfield/flat-surface-shader](https://github.com/wagerfield/flat-surface-shader) 106 | 107 | 108 | 109 | | Name | Type | Default | 110 | | -------- | ------ | --------- | 111 | | width | Number | 1.2 | 112 | | height | Number | 1.2 | 113 | | depth | Number | 10 | 114 | | segments | Number | 16 | 115 | | slices | Number | 8 | 116 | | xRange | Number | 0.8 | 117 | | yRange | Number | 0.1 | 118 | | zRange | Number | 1.0 | 119 | | ambient | String | '#555555' | 120 | | diffuse | String | '#FFFFFF' | 121 | | speed | Number | 0.002 | 122 | 123 | ## Development Build Setup 124 | 125 | ``` bash 126 | # install dependencies 127 | npm install 128 | 129 | # serve with hot reload at localhost:8080 130 | npm run dev 131 | 132 | # build for production with minification 133 | npm run build 134 | ``` 135 | 136 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 137 | 138 | ## License 139 | 140 | Licensed under [MIT](http://www.opensource.org/licenses/mit-license.php). -------------------------------------------------------------------------------- /docs/dist/vue.fss.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("vue-flat-surface-shader",[],e):"object"==typeof exports?exports["vue-flat-surface-shader"]=e():t["vue-flat-surface-shader"]=e()}(this,function(){return function(t){function e(r){if(i[r])return i[r].exports;var n=i[r]={i:r,l:!1,exports:{}};return t[r].call(n.exports,n,n.exports,e),n.l=!0,n.exports}var i={};return e.m=t,e.c=i,e.i=function(t){return t},e.d=function(t,i,r){e.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="/dist/",e(e.s=2)}([function(t,e,i){function r(t){i(8)}var n=i(6)(i(1),i(7),r,null,null);t.exports=n.exports},function(t,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=i(5),n=i.n(r),s=function(t){throw new Error("[vue-flat-surface-shader]: "+t)};e.default={name:"flat-surface-shader",props:{type:{type:String,default:"svg"},mesh:{type:Object,default:function(){return{}}},light:{type:Object,default:function(){return{}}}},mounted:function(){this.initialize()},data:function(){return{start:Date.now(),scene:new n.a.Scene,renderer:null,meshIns:null,MESH:{width:1.2,height:1.2,depth:10,segments:16,slices:8,xRange:.8,yRange:.1,zRange:1,ambient:"#555555",diffuse:"#FFFFFF",speed:.002},LIGHT:{count:2,xyScalar:1,zOffset:100,ambient:"#880066",diffuse:"#FF8800",speed:.001,gravity:1200,dampening:.95,minLimit:10,maxLimit:null,minDistance:20,maxDistance:400,autopilot:!1,draw:!0,bounds:n.a.Vector3.create(),step:n.a.Vector3.create(Math.randomInRange(.2,1),Math.randomInRange(.2,1),Math.randomInRange(.2,1))},geometry:new n.a.Plane,material:new n.a.Material,center:n.a.Vector3.create(),attractor:n.a.Vector3.create(),svgRenderer:new n.a.SVGRenderer,canvasRenderer:new n.a.CanvasRenderer,webglRenderer:new n.a.WebGLRenderer}},methods:{initialize:function(){var t=this.$refs.shader;this.setRenderer(),this.scene=new n.a.Scene,this.createMesh(),this.createLights(),this.addEventListeners(),this.resize(t.offsetWidth,t.offsetHeight),this.animate()},setRenderer:function(){var t=this.$refs.shader;switch(this.renderer&&t.removeChild(this.renderer.element),this.type){case"webgl":this.renderer=this.webglRenderer;break;case"canvas":this.renderer=this.canvasRenderer;break;case"svg":this.renderer=this.svgRenderer;break;default:s("Invalid renderer type - "+this.type)}this.renderer.setSize(t.offsetWidth,t.offsetHeight),t.appendChild(this.renderer.element)},createMesh:function(){this.MESH=Object.assign(this.MESH,this.mesh),this.scene.remove(this.meshIns),this.renderer.clear();var t=this.MESH;this.geometry=new n.a.Plane(t.width*this.renderer.width,t.height*this.renderer.height,t.segments,t.slices),this.material=new n.a.Material(t.ambient,t.diffuse),this.meshIns=new n.a.Mesh(this.geometry,this.material),this.scene.add(this.meshIns);for(var e=this.geometry.vertices.length-1;e>=0;e--){var i=this.geometry.vertices[e];i.anchor=n.a.Vector3.clone(i.position),i.step=n.a.Vector3.create(Math.randomInRange(.2,1),Math.randomInRange(.2,1),Math.randomInRange(.2,1)),i.time=Math.randomInRange(0,Math.PIM2)}},createLights:function(){this.LIGHT=Object.assign(this.LIGHT,this.light);for(var t=this.scene.lights.length-1;t>=0;t--){var e=this.scene.lights[t];this.scene.remove(e)}this.renderer.clear();for(var i=0;i=0;o--){var a=this.scene.lights[o];n.a.Vector3.setZ(a.position,this.LIGHT.zOffset);var h=Math.clamp(n.a.Vector3.distanceSquared(a.position,this.attractor),this.LIGHT.minDistance,this.LIGHT.maxDistance),c=this.LIGHT.gravity*a.mass/h;n.a.Vector3.subtractVectors(a.force,this.attractor,a.position),n.a.Vector3.normalise(a.force),n.a.Vector3.multiplyScalar(a.force,c),n.a.Vector3.set(a.acceleration),n.a.Vector3.add(a.acceleration,a.force),n.a.Vector3.add(a.velocity,a.acceleration),n.a.Vector3.multiplyScalar(a.velocity,this.LIGHT.dampening),n.a.Vector3.limit(a.velocity,this.LIGHT.minLimit,this.LIGHT.maxLimit),n.a.Vector3.add(a.position,a.velocity)}for(var l=this.geometry.vertices.length-1;l>=0;l--){var u=this.geometry.vertices[l];e=Math.sin(u.time+u.step[0]*t*this.MESH.speed),i=Math.cos(u.time+u.step[1]*t*this.MESH.speed),r=Math.sin(u.time+u.step[2]*t*this.MESH.speed),n.a.Vector3.set(u.position,this.MESH.xRange*this.geometry.segmentWidth*e,this.MESH.yRange*this.geometry.sliceHeight*i,this.MESH.zRange*s*r-s),n.a.Vector3.add(u.position,u.anchor)}this.geometry.dirty=!0},shaderRender:function(){if(this.MESH=Object.assign(this.MESH,this.mesh),this.LIGHT=Object.assign(this.LIGHT,this.light),this.renderer.render(this.scene),this.LIGHT.draw)for(var t=this.scene.lights.length-1;t>=0;t--){var e=this.scene.lights[t],i=e.position[0],r=e.position[1];switch(this.type){case"canvas":this.renderer.context.lineWidth=.5,this.renderer.context.beginPath(),this.renderer.context.arc(i,r,10,0,Math.PIM2),this.renderer.context.strokeStyle=e.ambientHex,this.renderer.context.stroke(),this.renderer.context.beginPath(),this.renderer.context.arc(i,r,4,0,Math.PIM2),this.renderer.context.fillStyle=e.diffuseHex,this.renderer.context.fill();break;case"svg":i+=this.renderer.halfWidth,r=this.renderer.halfHeight-r,e.core.setAttributeNS(null,"fill",e.diffuseHex),e.core.setAttributeNS(null,"cx",i),e.core.setAttributeNS(null,"cy",r),this.renderer.element.appendChild(e.core),e.ring.setAttributeNS(null,"stroke",e.ambientHex),e.ring.setAttributeNS(null,"cx",i),e.ring.setAttributeNS(null,"cy",r),this.renderer.element.appendChild(e.ring)}}},addEventListeners:function(){var t=this.$refs.shader;window.addEventListener("resize",this.onWindowResize),t.addEventListener("mousemove",this.onMouseMove)},onWindowResize:function(t){var e=this.$refs.shader;this.resize(e.offsetWidth,e.offsetHeight),this.shaderRender()},onMouseMove:function(t){n.a.Vector3.set(this.attractor,t.x,this.renderer.height-t.y),n.a.Vector3.subtract(this.attractor,this.center)}}}},function(t,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=i(0),n=i.n(r);i.d(e,"FlatSurfaceShader",function(){return n.a});var s=function(t){t.component("flat-surface-shader",n.a)};e.default={install:s}},function(t,e,i){e=t.exports=i(4)(),e.push([t.i,"",""])},function(t,e){t.exports=function(){var t=[];return t.toString=function(){for(var t=[],e=0;ee&&(t[0]=e),t[1]>e&&(t[1]=e),t[2]>e&&(t[2]=e),this},clamp:function(t,e,i){return this.min(t,e),this.max(t,i),this},limit:function(t,e,i){var r=this.length(t);return null!==e&&ri&&this.setLength(t,i),this},dot:function(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]},normalise:function(t){return this.divideScalar(t,this.length(t))},negate:function(t){return this.multiplyScalar(t,-1)},distanceSquared:function(t,e){var i=t[0]-e[0],r=t[1]-e[1],n=t[2]-e[2];return i*i+r*r+n*n},distance:function(t,e){return Math.sqrt(this.distanceSquared(t,e))},lengthSquared:function(t){return t[0]*t[0]+t[1]*t[1]+t[2]*t[2]},length:function(t){return Math.sqrt(this.lengthSquared(t))},setLength:function(t,e){var i=this.length(t);return 0!==i&&e!==i&&this.multiplyScalar(t,e/i),this}},FSS.Vector4={create:function(t,e,i,r){var n=new FSS.Array(4);return this.set(n,t,e,i),n},set:function(t,e,i,r,n){return t[0]=e||0,t[1]=i||0,t[2]=r||0,t[3]=n||0,this},setX:function(t,e){return t[0]=e||0,this},setY:function(t,e){return t[1]=e||0,this},setZ:function(t,e){return t[2]=e||0,this},setW:function(t,e){return t[3]=e||0,this},add:function(t,e){return t[0]+=e[0],t[1]+=e[1],t[2]+=e[2],t[3]+=e[3],this},multiplyVectors:function(t,e,i){return t[0]=e[0]*i[0],t[1]=e[1]*i[1],t[2]=e[2]*i[2],t[3]=e[3]*i[3],this},multiplyScalar:function(t,e){return t[0]*=e,t[1]*=e,t[2]*=e,t[3]*=e,this},min:function(t,e){return t[0]e&&(t[0]=e),t[1]>e&&(t[1]=e),t[2]>e&&(t[2]=e),t[3]>e&&(t[3]=e),this},clamp:function(t,e,i){return this.min(t,e),this.max(t,i),this}},FSS.Color=function(t,e){this.rgba=FSS.Vector4.create(),this.hex=t||"#000000",this.opacity=FSS.Utils.isNumber(e)?e:1,this.set(this.hex,this.opacity)},FSS.Color.prototype={set:function(t,e){t=t.replace("#","");var i=t.length/3;return this.rgba[0]=parseInt(t.substring(0*i,1*i),16)/255,this.rgba[1]=parseInt(t.substring(1*i,2*i),16)/255,this.rgba[2]=parseInt(t.substring(2*i,3*i),16)/255,this.rgba[3]=FSS.Utils.isNumber(e)?e:this.rgba[3],this},hexify:function(t){var e=Math.ceil(255*t).toString(16);return 1===e.length&&(e="0"+e),e},format:function(){var t=this.hexify(this.rgba[0]),e=this.hexify(this.rgba[1]),i=this.hexify(this.rgba[2]);return this.hex="#"+t+e+i,this.hex}},FSS.Object=function(){this.position=FSS.Vector3.create()},FSS.Object.prototype={setPosition:function(t,e,i){return FSS.Vector3.set(this.position,t,e,i),this}},FSS.Light=function(t,e){FSS.Object.call(this),this.ambient=new FSS.Color(t||"#FFFFFF"),this.diffuse=new FSS.Color(e||"#FFFFFF"),this.ray=FSS.Vector3.create()},FSS.Light.prototype=Object.create(FSS.Object.prototype),FSS.Vertex=function(t,e,i){this.position=FSS.Vector3.create(t,e,i)},FSS.Vertex.prototype={setPosition:function(t,e,i){return FSS.Vector3.set(this.position,t,e,i),this}},FSS.Triangle=function(t,e,i){this.a=t||new FSS.Vertex,this.b=e||new FSS.Vertex,this.c=i||new FSS.Vertex,this.vertices=[this.a,this.b,this.c],this.u=FSS.Vector3.create(),this.v=FSS.Vector3.create(),this.centroid=FSS.Vector3.create(),this.normal=FSS.Vector3.create(),this.color=new FSS.Color,this.polygon=document.createElementNS(FSS.SVGNS,"polygon"),this.polygon.setAttributeNS(null,"stroke-linejoin","round"),this.polygon.setAttributeNS(null,"stroke-miterlimit","1"),this.polygon.setAttributeNS(null,"stroke-width","1"),this.computeCentroid(),this.computeNormal()},FSS.Triangle.prototype={computeCentroid:function(){return this.centroid[0]=this.a.position[0]+this.b.position[0]+this.c.position[0],this.centroid[1]=this.a.position[1]+this.b.position[1]+this.c.position[1],this.centroid[2]=this.a.position[2]+this.b.position[2]+this.c.position[2],FSS.Vector3.divideScalar(this.centroid,3),this},computeNormal:function(){return FSS.Vector3.subtractVectors(this.u,this.b.position,this.a.position),FSS.Vector3.subtractVectors(this.v,this.c.position,this.a.position),FSS.Vector3.crossVectors(this.normal,this.u,this.v),FSS.Vector3.normalise(this.normal),this}},FSS.Geometry=function(){this.vertices=[],this.triangles=[],this.dirty=!1},FSS.Geometry.prototype={update:function(){if(this.dirty){var t,e;for(t=this.triangles.length-1;t>=0;t--)e=this.triangles[t],e.computeCentroid(),e.computeNormal();this.dirty=!1}return this}},FSS.Plane=function(t,e,i,r){FSS.Geometry.call(this),this.width=t||100,this.height=e||100,this.segments=i||4,this.slices=r||4,this.segmentWidth=this.width/this.segments,this.sliceHeight=this.height/this.slices;var n,s,o,a,h,c,l,u=[],f=-.5*this.width,d=.5*this.height;for(n=0;n<=this.segments;n++)for(u.push([]),s=0;s<=this.slices;s++)l=new FSS.Vertex(f+n*this.segmentWidth,d-s*this.sliceHeight),u[n].push(l),this.vertices.push(l);for(n=0;n=0;i--){for(r=this.geometry.triangles[i],FSS.Vector4.set(r.color.rgba),n=t.length-1;n>=0;n--)s=t[n],FSS.Vector3.subtractVectors(s.ray,s.position,r.centroid),FSS.Vector3.normalise(s.ray),o=FSS.Vector3.dot(r.normal,s.ray),this.side===FSS.FRONT?o=Math.max(o,0):this.side===FSS.BACK?o=Math.abs(Math.min(o,0)):this.side===FSS.DOUBLE&&(o=Math.max(Math.abs(o),0)),FSS.Vector4.multiplyVectors(this.material.slave.rgba,this.material.ambient.rgba,s.ambient.rgba),FSS.Vector4.add(r.color.rgba,this.material.slave.rgba),FSS.Vector4.multiplyVectors(this.material.slave.rgba,this.material.diffuse.rgba,s.diffuse.rgba),FSS.Vector4.multiplyScalar(this.material.slave.rgba,o),FSS.Vector4.add(r.color.rgba,this.material.slave.rgba);FSS.Vector4.clamp(r.color.rgba,0,1)}return this},FSS.Scene=function(){this.meshes=[],this.lights=[]},FSS.Scene.prototype={add:function(t){return t instanceof FSS.Mesh&&!~this.meshes.indexOf(t)?this.meshes.push(t):t instanceof FSS.Light&&!~this.lights.indexOf(t)&&this.lights.push(t),this},remove:function(t){return t instanceof FSS.Mesh&&~this.meshes.indexOf(t)?this.meshes.splice(this.meshes.indexOf(t),1):t instanceof FSS.Light&&~this.lights.indexOf(t)&&this.lights.splice(this.lights.indexOf(t),1),this}},FSS.Renderer=function(){this.width=0,this.height=0,this.halfWidth=0,this.halfHeight=0},FSS.Renderer.prototype={setSize:function(t,e){if(this.width!==t||this.height!==e)return this.width=t,this.height=e,this.halfWidth=.5*this.width,this.halfHeight=.5*this.height,this},clear:function(){return this},render:function(t){return this}},FSS.CanvasRenderer=function(){FSS.Renderer.call(this),this.element=document.createElement("canvas"),this.element.style.display="block",this.context=this.element.getContext("2d"),this.setSize(this.element.width,this.element.height)},FSS.CanvasRenderer.prototype=Object.create(FSS.Renderer.prototype),FSS.CanvasRenderer.prototype.setSize=function(t,e){return FSS.Renderer.prototype.setSize.call(this,t,e),this.element.width=t,this.element.height=e,this.context.setTransform(1,0,0,-1,this.halfWidth,this.halfHeight),this},FSS.CanvasRenderer.prototype.clear=function(){return FSS.Renderer.prototype.clear.call(this),this.context.clearRect(-this.halfWidth,-this.halfHeight,this.width,this.height),this},FSS.CanvasRenderer.prototype.render=function(t){FSS.Renderer.prototype.render.call(this,t);var e,i,r,n,s;for(this.clear(),this.context.lineJoin="round",this.context.lineWidth=1,e=t.meshes.length-1;e>=0;e--)if(i=t.meshes[e],i.visible)for(i.update(t.lights,!0),r=i.geometry.triangles.length-1;r>=0;r--)n=i.geometry.triangles[r],s=n.color.format(),this.context.beginPath(),this.context.moveTo(n.a.position[0],n.a.position[1]),this.context.lineTo(n.b.position[0],n.b.position[1]),this.context.lineTo(n.c.position[0],n.c.position[1]),this.context.closePath(),this.context.strokeStyle=s,this.context.fillStyle=s,this.context.stroke(),this.context.fill();return this},FSS.WebGLRenderer=function(){FSS.Renderer.call(this),this.element=document.createElement("canvas"),this.element.style.display="block",this.vertices=null,this.lights=null;var t={preserveDrawingBuffer:!1,premultipliedAlpha:!0,antialias:!0,stencil:!0,alpha:!0};if(this.gl=this.getContext(this.element,t),this.unsupported=!this.gl,this.unsupported)return"WebGL is not supported by your browser.";this.gl.clearColor(0,0,0,0),this.gl.enable(this.gl.DEPTH_TEST),this.setSize(this.element.width,this.element.height)},FSS.WebGLRenderer.prototype=Object.create(FSS.Renderer.prototype),FSS.WebGLRenderer.prototype.getContext=function(t,e){var i=!1;try{if(!(i=t.getContext("experimental-webgl",e)))throw"Error creating WebGL context."}catch(t){console.error(t)}return i},FSS.WebGLRenderer.prototype.setSize=function(t,e){if(FSS.Renderer.prototype.setSize.call(this,t,e),!this.unsupported)return this.element.width=t,this.element.height=e,this.gl.viewport(0,0,t,e),this},FSS.WebGLRenderer.prototype.clear=function(){if(FSS.Renderer.prototype.clear.call(this),!this.unsupported)return this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT),this},FSS.WebGLRenderer.prototype.render=function(t){if(FSS.Renderer.prototype.render.call(this,t),!this.unsupported){var e,i,r,n,s,o,a,h,c,l,u,f,d,S,m,g=!1,p=t.lights.length,b=0;if(this.clear(),this.lights!==p){if(this.lights=p,!(this.lights>0))return;this.buildProgram(p)}if(this.program){for(e=t.meshes.length-1;e>=0;e--)i=t.meshes[e],i.geometry.dirty&&(g=!0),i.update(t.lights,!1),b+=3*i.geometry.triangles.length;if(g||this.vertices!==b){this.vertices=b;for(h in this.program.attributes){for(l=this.program.attributes[h],l.data=new FSS.Array(b*l.size),d=0,e=t.meshes.length-1;e>=0;e--)for(i=t.meshes[e],r=0,n=i.geometry.triangles.length;r=0;o--)a=t.lights[o],this.setBufferData(o,this.program.uniforms.lightPosition,a.position),this.setBufferData(o,this.program.uniforms.lightAmbient,a.ambient.rgba),this.setBufferData(o,this.program.uniforms.lightDiffuse,a.diffuse.rgba);for(c in this.program.uniforms)switch(l=this.program.uniforms[c],f=l.location,u=l.data,l.structure){case"3f":this.gl.uniform3f(f,u[0],u[1],u[2]);break;case"3fv":this.gl.uniform3fv(f,u);break;case"4fv":this.gl.uniform4fv(f,u)}}return this.gl.drawArrays(this.gl.TRIANGLES,0,this.vertices),this}},FSS.WebGLRenderer.prototype.setBufferData=function(t,e,i){if(FSS.Utils.isNumber(i))e.data[t*e.size]=i;else for(var r=i.length-1;r>=0;r--)e.data[t*e.size+r]=i[r]},FSS.WebGLRenderer.prototype.buildProgram=function(t){if(!this.unsupported){var e=FSS.WebGLRenderer.VS(t),i=FSS.WebGLRenderer.FS(t),r=e+i;if(!this.program||this.program.code!==r){var n=this.gl.createProgram(),s=this.buildShader(this.gl.VERTEX_SHADER,e),o=this.buildShader(this.gl.FRAGMENT_SHADER,i);if(this.gl.attachShader(n,s),this.gl.attachShader(n,o),this.gl.linkProgram(n),!this.gl.getProgramParameter(n,this.gl.LINK_STATUS)){var a=this.gl.getError(),h=this.gl.getProgramParameter(n,this.gl.VALIDATE_STATUS);return console.error("Could not initialise shader.\nVALIDATE_STATUS: "+h+"\nERROR: "+a),null}return this.gl.deleteShader(o),this.gl.deleteShader(s),n.code=r,n.attributes={side:this.buildBuffer(n,"attribute","aSide",1,"f"),position:this.buildBuffer(n,"attribute","aPosition",3,"v3"),centroid:this.buildBuffer(n,"attribute","aCentroid",3,"v3"),normal:this.buildBuffer(n,"attribute","aNormal",3,"v3"),ambient:this.buildBuffer(n,"attribute","aAmbient",4,"v4"),diffuse:this.buildBuffer(n,"attribute","aDiffuse",4,"v4")},n.uniforms={resolution:this.buildBuffer(n,"uniform","uResolution",3,"3f",1),lightPosition:this.buildBuffer(n,"uniform","uLightPosition",3,"3fv",t),lightAmbient:this.buildBuffer(n,"uniform","uLightAmbient",4,"4fv",t),lightDiffuse:this.buildBuffer(n,"uniform","uLightDiffuse",4,"4fv",t)},this.program=n,this.gl.useProgram(this.program),n}}},FSS.WebGLRenderer.prototype.buildShader=function(t,e){if(!this.unsupported){var i=this.gl.createShader(t);return this.gl.shaderSource(i,e),this.gl.compileShader(i),this.gl.getShaderParameter(i,this.gl.COMPILE_STATUS)?i:(console.error(this.gl.getShaderInfoLog(i)),null)}},FSS.WebGLRenderer.prototype.buildBuffer=function(t,e,i,r,n,s){var o={buffer:this.gl.createBuffer(),size:r,structure:n,data:null};switch(e){case"attribute":o.location=this.gl.getAttribLocation(t,i);break;case"uniform":o.location=this.gl.getUniformLocation(t,i)}return s&&(o.data=new FSS.Array(s*r)),o},FSS.WebGLRenderer.VS=function(t){return["precision mediump float;","#define LIGHTS "+t,"attribute float aSide;","attribute vec3 aPosition;","attribute vec3 aCentroid;","attribute vec3 aNormal;","attribute vec4 aAmbient;","attribute vec4 aDiffuse;","uniform vec3 uResolution;","uniform vec3 uLightPosition[LIGHTS];","uniform vec4 uLightAmbient[LIGHTS];","uniform vec4 uLightDiffuse[LIGHTS];","varying vec4 vColor;","void main() {","vColor = vec4(0.0);","vec3 position = aPosition / uResolution * 2.0;","for (int i = 0; i < LIGHTS; i++) {","vec3 lightPosition = uLightPosition[i];","vec4 lightAmbient = uLightAmbient[i];","vec4 lightDiffuse = uLightDiffuse[i];","vec3 ray = normalize(lightPosition - aCentroid);","float illuminance = dot(aNormal, ray);","if (aSide == 0.0) {","illuminance = max(illuminance, 0.0);","} else if (aSide == 1.0) {","illuminance = abs(min(illuminance, 0.0));","} else if (aSide == 2.0) {","illuminance = max(abs(illuminance), 0.0);","}","vColor += aAmbient * lightAmbient;","vColor += aDiffuse * lightDiffuse * illuminance;","}","vColor = clamp(vColor, 0.0, 1.0);","gl_Position = vec4(position, 1.0);","}"].join("\n")},FSS.WebGLRenderer.FS=function(t){return["precision mediump float;","varying vec4 vColor;","void main() {","gl_FragColor = vColor;","}"].join("\n")},FSS.SVGRenderer=function(){FSS.Renderer.call(this),this.element=document.createElementNS(FSS.SVGNS,"svg"),this.element.setAttribute("xmlns",FSS.SVGNS),this.element.setAttribute("version","1.1"),this.element.style.display="block",this.setSize(300,150)},FSS.SVGRenderer.prototype=Object.create(FSS.Renderer.prototype),FSS.SVGRenderer.prototype.setSize=function(t,e){return FSS.Renderer.prototype.setSize.call(this,t,e),this.element.setAttribute("width",t),this.element.setAttribute("height",e),this},FSS.SVGRenderer.prototype.clear=function(){FSS.Renderer.prototype.clear.call(this);for(var t=this.element.childNodes.length-1;t>=0;t--)this.element.removeChild(this.element.childNodes[t]);return this},FSS.SVGRenderer.prototype.render=function(t){FSS.Renderer.prototype.render.call(this,t);var e,i,r,n,s,o;for(e=t.meshes.length-1;e>=0;e--)if(i=t.meshes[e],i.visible)for(i.update(t.lights,!0),r=i.geometry.triangles.length-1;r>=0;r--)n=i.geometry.triangles[r],n.polygon.parentNode!==this.element&&this.element.appendChild(n.polygon),s=this.formatPoint(n.a)+" ",s+=this.formatPoint(n.b)+" ",s+=this.formatPoint(n.c),o=this.formatStyle(n.color.format()),n.polygon.setAttributeNS(null,"points",s),n.polygon.setAttributeNS(null,"style",o);return this},FSS.SVGRenderer.prototype.formatPoint=function(t){return this.halfWidth+t.position[0]+","+(this.halfHeight-t.position[1])},FSS.SVGRenderer.prototype.formatStyle=function(t){var e="fill:"+t+";";return e+="stroke:"+t+";"},t.exports=FSS},function(t,e){t.exports=function(t,e,i,r,n){var s,o=t=t||{},a=typeof t.default;"object"!==a&&"function"!==a||(s=t,o=t.default);var h="function"==typeof o?o.options:o;e&&(h.render=e.render,h.staticRenderFns=e.staticRenderFns),r&&(h._scopeId=r);var c;if(n?(c=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),i&&i.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(n)},h._ssrRegister=c):i&&(c=i),c){var l=h.functional,u=l?h.render:h.beforeCreate;l?h.render=function(t,e){return c.call(e),u(t,e)}:h.beforeCreate=u?[].concat(u,c):[c]}return{esModule:s,exports:o,options:h}}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement;return(t._self._c||e)("div",{ref:"shader"},[t._t("default")],2)},staticRenderFns:[]}},function(t,e,i){var r=i(3);"string"==typeof r&&(r=[[t.i,r,""]]),r.locals&&(t.exports=r.locals);i(9)("03a00953",r,!0)},function(t,e,i){function r(t){for(var e=0;ei.parts.length&&(r.parts.length=i.parts.length)}else{for(var o=[],n=0;n= 0; v--) {\n var vertex = this.geometry.vertices[v];\n vertex.anchor = __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.clone(vertex.position);\n vertex.step = __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.create(Math.randomInRange(0.2, 1.0), Math.randomInRange(0.2, 1.0), Math.randomInRange(0.2, 1.0));\n vertex.time = Math.randomInRange(0, Math.PIM2);\n }\n },\n\n createLights: function createLights() {\n this.LIGHT = Object.assign(this.LIGHT, this.light);\n for (var l = this.scene.lights.length - 1; l >= 0; l--) {\n var light = this.scene.lights[l];\n this.scene.remove(light);\n }\n this.renderer.clear();\n for (var _l = 0; _l < this.LIGHT.count; _l++) {\n var _light = new __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Light(this.LIGHT.ambient, this.LIGHT.diffuse);\n _light.ambientHex = _light.ambient.format();\n _light.diffuseHex = _light.diffuse.format();\n this.scene.add(_light);\n // Augment light for animation\n _light.mass = Math.randomInRange(0.5, 1);\n _light.velocity = __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.create();\n _light.acceleration = __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.create();\n _light.force = __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.create();\n\n // Ring SVG Circle\n _light.ring = document.createElementNS(__WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.SVGNS, 'circle');\n _light.ring.setAttributeNS(null, 'stroke', _light.ambientHex);\n _light.ring.setAttributeNS(null, 'stroke-width', '0.5');\n _light.ring.setAttributeNS(null, 'fill', 'none');\n _light.ring.setAttributeNS(null, 'r', '10');\n\n // Core SVG Circle\n _light.core = document.createElementNS(__WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.SVGNS, 'circle');\n _light.core.setAttributeNS(null, 'fill', _light.diffuseHex);\n _light.core.setAttributeNS(null, 'r', '4');\n }\n },\n\n resize: function resize(width, height) {\n this.renderer.setSize(width, height);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.set(this.center, this.renderer.halfWidth, this.renderer.halfHeight);\n this.createMesh();\n },\n\n animate: function animate() {\n this.update();\n this.shaderRender();\n requestAnimationFrame(this.animate);\n },\n\n update: function update() {\n this.MESH = Object.assign(this.MESH, this.mesh);\n this.LIGHT = Object.assign(this.LIGHT, this.light);\n var now = Date.now() - this.start;\n var ox = void 0,\n oy = void 0,\n oz = void 0;\n var offset = this.MESH.depth / 2;\n // Update Bounds\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.copy(this.LIGHT.bounds, this.center);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.multiplyScalar(this.LIGHT.bounds, this.LIGHT.xyScalar);\n // Upadte Attractor\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.setZ(this.attractor, this.LIGHT.zOffset);\n // Overwrite the Attractor position\n if (this.LIGHT.autopilot) {\n ox = Math.sin(this.LIGHT.step[0] * now * this.LIGHT.speed);\n oy = Math.cos(this.LIGHT.step[1] * now * this.LIGHT.speed);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.set(this.attractor, this.LIGHT.bounds[0] * ox, this.LIGHT.bounds[1] * oy, this.LIGHT.zOffset);\n }\n // Animate Lights\n for (var l = this.scene.lights.length - 1; l >= 0; l--) {\n var light = this.scene.lights[l];\n // Reset the z position of the light\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.setZ(light.position, this.LIGHT.zOffset);\n // Calculate the force Luke!\n var D = Math.clamp(__WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.distanceSquared(light.position, this.attractor), this.LIGHT.minDistance, this.LIGHT.maxDistance);\n var F = this.LIGHT.gravity * light.mass / D;\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.subtractVectors(light.force, this.attractor, light.position);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.normalise(light.force);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.multiplyScalar(light.force, F);\n // Update the light position\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.set(light.acceleration);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.add(light.acceleration, light.force);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.add(light.velocity, light.acceleration);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.multiplyScalar(light.velocity, this.LIGHT.dampening);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.limit(light.velocity, this.LIGHT.minLimit, this.LIGHT.maxLimit);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.add(light.position, light.velocity);\n }\n // Animate Vertices\n for (var v = this.geometry.vertices.length - 1; v >= 0; v--) {\n var vertex = this.geometry.vertices[v];\n ox = Math.sin(vertex.time + vertex.step[0] * now * this.MESH.speed);\n oy = Math.cos(vertex.time + vertex.step[1] * now * this.MESH.speed);\n oz = Math.sin(vertex.time + vertex.step[2] * now * this.MESH.speed);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.set(vertex.position, this.MESH.xRange * this.geometry.segmentWidth * ox, this.MESH.yRange * this.geometry.sliceHeight * oy, this.MESH.zRange * offset * oz - offset);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.add(vertex.position, vertex.anchor);\n }\n // Set the Geometry to dirty\n this.geometry.dirty = true;\n },\n\n shaderRender: function shaderRender() {\n this.MESH = Object.assign(this.MESH, this.mesh);\n this.LIGHT = Object.assign(this.LIGHT, this.light);\n this.renderer.render(this.scene);\n // Draw Lights\n if (this.LIGHT.draw) {\n for (var l = this.scene.lights.length - 1; l >= 0; l--) {\n var light = this.scene.lights[l];\n var lx = light.position[0],\n ly = light.position[1];\n switch (this.type) {\n case 'canvas':\n this.renderer.context.lineWidth = 0.5;\n this.renderer.context.beginPath();\n this.renderer.context.arc(lx, ly, 10, 0, Math.PIM2);\n this.renderer.context.strokeStyle = light.ambientHex;\n this.renderer.context.stroke();\n this.renderer.context.beginPath();\n this.renderer.context.arc(lx, ly, 4, 0, Math.PIM2);\n this.renderer.context.fillStyle = light.diffuseHex;\n this.renderer.context.fill();\n break;\n case 'svg':\n lx += this.renderer.halfWidth;\n ly = this.renderer.halfHeight - ly;\n light.core.setAttributeNS(null, 'fill', light.diffuseHex);\n light.core.setAttributeNS(null, 'cx', lx);\n light.core.setAttributeNS(null, 'cy', ly);\n this.renderer.element.appendChild(light.core);\n light.ring.setAttributeNS(null, 'stroke', light.ambientHex);\n light.ring.setAttributeNS(null, 'cx', lx);\n light.ring.setAttributeNS(null, 'cy', ly);\n this.renderer.element.appendChild(light.ring);\n break;\n }\n }\n }\n },\n\n addEventListeners: function addEventListeners() {\n var container = this.$refs.shader;\n window.addEventListener('resize', this.onWindowResize);\n container.addEventListener('mousemove', this.onMouseMove);\n },\n\n onWindowResize: function onWindowResize(event) {\n var container = this.$refs.shader;\n this.resize(container.offsetWidth, container.offsetHeight);\n this.shaderRender();\n },\n onMouseMove: function onMouseMove(event) {\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.set(this.attractor, event.x, this.renderer.height - event.y);\n __WEBPACK_IMPORTED_MODULE_0_flat_surface_shader_unofficial___default.a.Vector3.subtract(this.attractor, this.center);\n }\n }\n\n});\n\n/***/ }),\n/* 2 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\nObject.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__FSS_vue__ = __webpack_require__(0);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__FSS_vue___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__FSS_vue__);\n/* harmony reexport (default from non-hamory) */ __webpack_require__.d(__webpack_exports__, \"FlatSurfaceShader\", function() { return __WEBPACK_IMPORTED_MODULE_0__FSS_vue___default.a; });\n\n\nvar install = function install(Vue) {\n Vue.component(\"flat-surface-shader\", __WEBPACK_IMPORTED_MODULE_0__FSS_vue___default.a);\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n install: install\n});\n\n\n\n/***/ }),\n/* 3 */\n/***/ (function(module, exports, __webpack_require__) {\n\nexports = module.exports = __webpack_require__(4)();\n// imports\n\n\n// module\nexports.push([module.i, \"\", \"\"]);\n\n// exports\n\n\n/***/ }),\n/* 4 */\n/***/ (function(module, exports) {\n\n/*\r\n\tMIT License http://www.opensource.org/licenses/mit-license.php\r\n\tAuthor Tobias Koppers @sokra\r\n*/\r\n// css base code, injected by the css-loader\r\nmodule.exports = function() {\r\n\tvar list = [];\r\n\r\n\t// return the list of modules as css string\r\n\tlist.toString = function toString() {\r\n\t\tvar result = [];\r\n\t\tfor(var i = 0; i < this.length; i++) {\r\n\t\t\tvar item = this[i];\r\n\t\t\tif(item[2]) {\r\n\t\t\t\tresult.push(\"@media \" + item[2] + \"{\" + item[1] + \"}\");\r\n\t\t\t} else {\r\n\t\t\t\tresult.push(item[1]);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn result.join(\"\");\r\n\t};\r\n\r\n\t// import a list of modules into the list\r\n\tlist.i = function(modules, mediaQuery) {\r\n\t\tif(typeof modules === \"string\")\r\n\t\t\tmodules = [[null, modules, \"\"]];\r\n\t\tvar alreadyImportedModules = {};\r\n\t\tfor(var i = 0; i < this.length; i++) {\r\n\t\t\tvar id = this[i][0];\r\n\t\t\tif(typeof id === \"number\")\r\n\t\t\t\talreadyImportedModules[id] = true;\r\n\t\t}\r\n\t\tfor(i = 0; i < modules.length; i++) {\r\n\t\t\tvar item = modules[i];\r\n\t\t\t// skip already imported module\r\n\t\t\t// this implementation is not 100% perfect for weird media query combinations\r\n\t\t\t// when a module is imported multiple times with different media queries.\r\n\t\t\t// I hope this will never occur (Hey this way we have smaller bundles)\r\n\t\t\tif(typeof item[0] !== \"number\" || !alreadyImportedModules[item[0]]) {\r\n\t\t\t\tif(mediaQuery && !item[2]) {\r\n\t\t\t\t\titem[2] = mediaQuery;\r\n\t\t\t\t} else if(mediaQuery) {\r\n\t\t\t\t\titem[2] = \"(\" + item[2] + \") and (\" + mediaQuery + \")\";\r\n\t\t\t\t}\r\n\t\t\t\tlist.push(item);\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\treturn list;\r\n};\r\n\n\n/***/ }),\n/* 5 */\n/***/ (function(module, exports) {\n\n//============================================================\n//\n// Copyright (C) 2014 Matthew Wagerfield\n//\n// Twitter: https://twitter.com/wagerfield\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the\n// Software without restriction, including without limitation\n// the rights to use, copy, modify, merge, publish, distribute,\n// sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do\n// so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice\n// shall be included in all copies or substantial portions\n// of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY\n// OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT\n// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO\n// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN\n// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE\n// OR OTHER DEALINGS IN THE SOFTWARE.\n//\n//============================================================\n\n/**\n * Defines the Flat Surface Shader namespace for all the awesomeness to exist upon.\n * @author Matthew Wagerfield\n */\nFSS = {\n FRONT : 0,\n BACK : 1,\n DOUBLE : 2,\n SVGNS : 'http://www.w3.org/2000/svg'\n};\n\n/**\n * @class Array\n * @author Matthew Wagerfield\n */\nFSS.Array = typeof Float32Array === 'function' ? Float32Array : Array;\n\n/**\n * @class Utils\n * @author Matthew Wagerfield\n */\nFSS.Utils = {\n isNumber: function(value) {\n return !isNaN(parseFloat(value)) && isFinite(value);\n }\n};\n\n/**\n * Request Animation Frame Polyfill.\n * @author Paul Irish\n * @see https://gist.github.com/paulirish/1579671\n */\n(function() {\n\n var lastTime = 0;\n var vendors = ['ms', 'moz', 'webkit', 'o'];\n\n for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {\n window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];\n window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];\n }\n\n if (!window.requestAnimationFrame) {\n window.requestAnimationFrame = function(callback, element) {\n var currentTime = new Date().getTime();\n var timeToCall = Math.max(0, 16 - (currentTime - lastTime));\n var id = window.setTimeout(function() {\n callback(currentTime + timeToCall);\n }, timeToCall);\n lastTime = currentTime + timeToCall;\n return id;\n };\n }\n\n if (!window.cancelAnimationFrame) {\n window.cancelAnimationFrame = function(id) {\n clearTimeout(id);\n };\n }\n\n}());\n\n/**\n * @object Math Augmentation\n * @author Matthew Wagerfield\n */\nMath.PIM2 = Math.PI*2;\nMath.PID2 = Math.PI/2;\nMath.randomInRange = function(min, max) {\n return min + (max - min) * Math.random();\n};\nMath.clamp = function(value, min, max) {\n value = Math.max(value, min);\n value = Math.min(value, max);\n return value;\n};\n\n/**\n * @object Vector3\n * @author Matthew Wagerfield\n */\nFSS.Vector3 = {\n create: function(x, y, z) {\n var vector = new FSS.Array(3);\n this.set(vector, x, y, z);\n return vector;\n },\n clone: function(a) {\n var vector = this.create();\n this.copy(vector, a);\n return vector;\n },\n set: function(target, x, y, z) {\n target[0] = x || 0;\n target[1] = y || 0;\n target[2] = z || 0;\n return this;\n },\n setX: function(target, x) {\n target[0] = x || 0;\n return this;\n },\n setY: function(target, y) {\n target[1] = y || 0;\n return this;\n },\n setZ: function(target, z) {\n target[2] = z || 0;\n return this;\n },\n copy: function(target, a) {\n target[0] = a[0];\n target[1] = a[1];\n target[2] = a[2];\n return this;\n },\n add: function(target, a) {\n target[0] += a[0];\n target[1] += a[1];\n target[2] += a[2];\n return this;\n },\n addVectors: function(target, a, b) {\n target[0] = a[0] + b[0];\n target[1] = a[1] + b[1];\n target[2] = a[2] + b[2];\n return this;\n },\n addScalar: function(target, s) {\n target[0] += s;\n target[1] += s;\n target[2] += s;\n return this;\n },\n subtract: function(target, a) {\n target[0] -= a[0];\n target[1] -= a[1];\n target[2] -= a[2];\n return this;\n },\n subtractVectors: function(target, a, b) {\n target[0] = a[0] - b[0];\n target[1] = a[1] - b[1];\n target[2] = a[2] - b[2];\n return this;\n },\n subtractScalar: function(target, s) {\n target[0] -= s;\n target[1] -= s;\n target[2] -= s;\n return this;\n },\n multiply: function(target, a) {\n target[0] *= a[0];\n target[1] *= a[1];\n target[2] *= a[2];\n return this;\n },\n multiplyVectors: function(target, a, b) {\n target[0] = a[0] * b[0];\n target[1] = a[1] * b[1];\n target[2] = a[2] * b[2];\n return this;\n },\n multiplyScalar: function(target, s) {\n target[0] *= s;\n target[1] *= s;\n target[2] *= s;\n return this;\n },\n divide: function(target, a) {\n target[0] /= a[0];\n target[1] /= a[1];\n target[2] /= a[2];\n return this;\n },\n divideVectors: function(target, a, b) {\n target[0] = a[0] / b[0];\n target[1] = a[1] / b[1];\n target[2] = a[2] / b[2];\n return this;\n },\n divideScalar: function(target, s) {\n if (s !== 0) {\n target[0] /= s;\n target[1] /= s;\n target[2] /= s;\n } else {\n target[0] = 0;\n target[1] = 0;\n target[2] = 0;\n }\n return this;\n },\n cross: function(target, a) {\n var x = target[0];\n var y = target[1];\n var z = target[2];\n target[0] = y*a[2] - z*a[1];\n target[1] = z*a[0] - x*a[2];\n target[2] = x*a[1] - y*a[0];\n return this;\n },\n crossVectors: function(target, a, b) {\n target[0] = a[1]*b[2] - a[2]*b[1];\n target[1] = a[2]*b[0] - a[0]*b[2];\n target[2] = a[0]*b[1] - a[1]*b[0];\n return this;\n },\n min: function(target, value) {\n if (target[0] < value) { target[0] = value; }\n if (target[1] < value) { target[1] = value; }\n if (target[2] < value) { target[2] = value; }\n return this;\n },\n max: function(target, value) {\n if (target[0] > value) { target[0] = value; }\n if (target[1] > value) { target[1] = value; }\n if (target[2] > value) { target[2] = value; }\n return this;\n },\n clamp: function(target, min, max) {\n this.min(target, min);\n this.max(target, max);\n return this;\n },\n limit: function(target, min, max) {\n var length = this.length(target);\n if (min !== null && length < min) {\n this.setLength(target, min);\n } else if (max !== null && length > max) {\n this.setLength(target, max);\n }\n return this;\n },\n dot: function(a, b) {\n return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];\n },\n normalise: function(target) {\n return this.divideScalar(target, this.length(target));\n },\n negate: function(target) {\n return this.multiplyScalar(target, -1);\n },\n distanceSquared: function(a, b) {\n var dx = a[0] - b[0];\n var dy = a[1] - b[1];\n var dz = a[2] - b[2];\n return dx*dx + dy*dy + dz*dz;\n },\n distance: function(a, b) {\n return Math.sqrt(this.distanceSquared(a, b));\n },\n lengthSquared: function(a) {\n return a[0]*a[0] + a[1]*a[1] + a[2]*a[2];\n },\n length: function(a) {\n return Math.sqrt(this.lengthSquared(a));\n },\n setLength: function(target, l) {\n var length = this.length(target);\n if (length !== 0 && l !== length) {\n this.multiplyScalar(target, l / length);\n }\n return this;\n }\n};\n\n/**\n * @object Vector4\n * @author Matthew Wagerfield\n */\nFSS.Vector4 = {\n create: function(x, y, z, w) {\n var vector = new FSS.Array(4);\n this.set(vector, x, y, z);\n return vector;\n },\n set: function(target, x, y, z, w) {\n target[0] = x || 0;\n target[1] = y || 0;\n target[2] = z || 0;\n target[3] = w || 0;\n return this;\n },\n setX: function(target, x) {\n target[0] = x || 0;\n return this;\n },\n setY: function(target, y) {\n target[1] = y || 0;\n return this;\n },\n setZ: function(target, z) {\n target[2] = z || 0;\n return this;\n },\n setW: function(target, w) {\n target[3] = w || 0;\n return this;\n },\n add: function(target, a) {\n target[0] += a[0];\n target[1] += a[1];\n target[2] += a[2];\n target[3] += a[3];\n return this;\n },\n multiplyVectors: function(target, a, b) {\n target[0] = a[0] * b[0];\n target[1] = a[1] * b[1];\n target[2] = a[2] * b[2];\n target[3] = a[3] * b[3];\n return this;\n },\n multiplyScalar: function(target, s) {\n target[0] *= s;\n target[1] *= s;\n target[2] *= s;\n target[3] *= s;\n return this;\n },\n min: function(target, value) {\n if (target[0] < value) { target[0] = value; }\n if (target[1] < value) { target[1] = value; }\n if (target[2] < value) { target[2] = value; }\n if (target[3] < value) { target[3] = value; }\n return this;\n },\n max: function(target, value) {\n if (target[0] > value) { target[0] = value; }\n if (target[1] > value) { target[1] = value; }\n if (target[2] > value) { target[2] = value; }\n if (target[3] > value) { target[3] = value; }\n return this;\n },\n clamp: function(target, min, max) {\n this.min(target, min);\n this.max(target, max);\n return this;\n }\n};\n\n/**\n * @class Color\n * @author Matthew Wagerfield\n */\nFSS.Color = function(hex, opacity) {\n this.rgba = FSS.Vector4.create();\n this.hex = hex || '#000000';\n this.opacity = FSS.Utils.isNumber(opacity) ? opacity : 1;\n this.set(this.hex, this.opacity);\n};\n\nFSS.Color.prototype = {\n set: function(hex, opacity) {\n hex = hex.replace('#', '');\n var size = hex.length / 3;\n this.rgba[0] = parseInt(hex.substring(size*0, size*1), 16) / 255;\n this.rgba[1] = parseInt(hex.substring(size*1, size*2), 16) / 255;\n this.rgba[2] = parseInt(hex.substring(size*2, size*3), 16) / 255;\n this.rgba[3] = FSS.Utils.isNumber(opacity) ? opacity : this.rgba[3];\n return this;\n },\n hexify: function(channel) {\n var hex = Math.ceil(channel*255).toString(16);\n if (hex.length === 1) { hex = '0' + hex; }\n return hex;\n },\n format: function() {\n var r = this.hexify(this.rgba[0]);\n var g = this.hexify(this.rgba[1]);\n var b = this.hexify(this.rgba[2]);\n this.hex = '#' + r + g + b;\n return this.hex;\n }\n};\n\n/**\n * @class Object\n * @author Matthew Wagerfield\n */\nFSS.Object = function() {\n this.position = FSS.Vector3.create();\n};\n\nFSS.Object.prototype = {\n setPosition: function(x, y, z) {\n FSS.Vector3.set(this.position, x, y, z);\n return this;\n }\n};\n\n/**\n * @class Light\n * @author Matthew Wagerfield\n */\nFSS.Light = function(ambient, diffuse) {\n FSS.Object.call(this);\n this.ambient = new FSS.Color(ambient || '#FFFFFF');\n this.diffuse = new FSS.Color(diffuse || '#FFFFFF');\n this.ray = FSS.Vector3.create();\n};\n\nFSS.Light.prototype = Object.create(FSS.Object.prototype);\n\n/**\n * @class Vertex\n * @author Matthew Wagerfield\n */\nFSS.Vertex = function(x, y, z) {\n this.position = FSS.Vector3.create(x, y, z);\n};\n\nFSS.Vertex.prototype = {\n setPosition: function(x, y, z) {\n FSS.Vector3.set(this.position, x, y, z);\n return this;\n }\n};\n\n/**\n * @class Triangle\n * @author Matthew Wagerfield\n */\nFSS.Triangle = function(a, b, c) {\n this.a = a || new FSS.Vertex();\n this.b = b || new FSS.Vertex();\n this.c = c || new FSS.Vertex();\n this.vertices = [this.a, this.b, this.c];\n this.u = FSS.Vector3.create();\n this.v = FSS.Vector3.create();\n this.centroid = FSS.Vector3.create();\n this.normal = FSS.Vector3.create();\n this.color = new FSS.Color();\n this.polygon = document.createElementNS(FSS.SVGNS, 'polygon');\n this.polygon.setAttributeNS(null, 'stroke-linejoin', 'round');\n this.polygon.setAttributeNS(null, 'stroke-miterlimit', '1');\n this.polygon.setAttributeNS(null, 'stroke-width', '1');\n this.computeCentroid();\n this.computeNormal();\n};\n\nFSS.Triangle.prototype = {\n computeCentroid: function() {\n this.centroid[0] = this.a.position[0] + this.b.position[0] + this.c.position[0];\n this.centroid[1] = this.a.position[1] + this.b.position[1] + this.c.position[1];\n this.centroid[2] = this.a.position[2] + this.b.position[2] + this.c.position[2];\n FSS.Vector3.divideScalar(this.centroid, 3);\n return this;\n },\n computeNormal: function() {\n FSS.Vector3.subtractVectors(this.u, this.b.position, this.a.position);\n FSS.Vector3.subtractVectors(this.v, this.c.position, this.a.position);\n FSS.Vector3.crossVectors(this.normal, this.u, this.v);\n FSS.Vector3.normalise(this.normal);\n return this;\n }\n};\n\n/**\n * @class Geometry\n * @author Matthew Wagerfield\n */\nFSS.Geometry = function() {\n this.vertices = [];\n this.triangles = [];\n this.dirty = false;\n};\n\nFSS.Geometry.prototype = {\n update: function() {\n if (this.dirty) {\n var t,triangle;\n for (t = this.triangles.length - 1; t >= 0; t--) {\n triangle = this.triangles[t];\n triangle.computeCentroid();\n triangle.computeNormal();\n }\n this.dirty = false;\n }\n return this;\n }\n};\n\n/**\n * @class Plane\n * @author Matthew Wagerfield\n */\nFSS.Plane = function(width, height, segments, slices) {\n FSS.Geometry.call(this);\n this.width = width || 100;\n this.height = height || 100;\n this.segments = segments || 4;\n this.slices = slices || 4;\n this.segmentWidth = this.width / this.segments;\n this.sliceHeight = this.height / this.slices;\n\n // Cache Variables\n var x, y, v0, v1, v2, v3,\n vertex, triangle, vertices = [],\n offsetX = this.width * -0.5,\n offsetY = this.height * 0.5;\n\n // Add Vertices\n for (x = 0; x <= this.segments; x++) {\n vertices.push([]);\n for (y = 0; y <= this.slices; y++) {\n vertex = new FSS.Vertex(offsetX + x*this.segmentWidth, offsetY - y*this.sliceHeight);\n vertices[x].push(vertex);\n this.vertices.push(vertex);\n }\n }\n\n // Add Triangles\n for (x = 0; x < this.segments; x++) {\n for (y = 0; y < this.slices; y++) {\n v0 = vertices[x+0][y+0];\n v1 = vertices[x+0][y+1];\n v2 = vertices[x+1][y+0];\n v3 = vertices[x+1][y+1];\n t0 = new FSS.Triangle(v0, v1, v2);\n t1 = new FSS.Triangle(v2, v1, v3);\n this.triangles.push(t0, t1);\n }\n }\n};\n\nFSS.Plane.prototype = Object.create(FSS.Geometry.prototype);\n\n/**\n * @class Material\n * @author Matthew Wagerfield\n */\nFSS.Material = function(ambient, diffuse) {\n this.ambient = new FSS.Color(ambient || '#444444');\n this.diffuse = new FSS.Color(diffuse || '#FFFFFF');\n this.slave = new FSS.Color();\n};\n\n/**\n * @class Mesh\n * @author Matthew Wagerfield\n */\nFSS.Mesh = function(geometry, material) {\n FSS.Object.call(this);\n this.geometry = geometry || new FSS.Geometry();\n this.material = material || new FSS.Material();\n this.side = FSS.FRONT;\n this.visible = true;\n};\n\nFSS.Mesh.prototype = Object.create(FSS.Object.prototype);\n\nFSS.Mesh.prototype.update = function(lights, calculate) {\n var t,triangle, l,light, illuminance;\n\n // Update Geometry\n this.geometry.update();\n\n // Calculate the triangle colors\n if (calculate) {\n\n // Iterate through Triangles\n for (t = this.geometry.triangles.length - 1; t >= 0; t--) {\n triangle = this.geometry.triangles[t];\n\n // Reset Triangle Color\n FSS.Vector4.set(triangle.color.rgba);\n\n // Iterate through Lights\n for (l = lights.length - 1; l >= 0; l--) {\n light = lights[l];\n\n // Calculate Illuminance\n FSS.Vector3.subtractVectors(light.ray, light.position, triangle.centroid);\n FSS.Vector3.normalise(light.ray);\n illuminance = FSS.Vector3.dot(triangle.normal, light.ray);\n if (this.side === FSS.FRONT) {\n illuminance = Math.max(illuminance, 0);\n } else if (this.side === FSS.BACK) {\n illuminance = Math.abs(Math.min(illuminance, 0));\n } else if (this.side === FSS.DOUBLE) {\n illuminance = Math.max(Math.abs(illuminance), 0);\n }\n\n // Calculate Ambient Light\n FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.ambient.rgba, light.ambient.rgba);\n FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba);\n\n // Calculate Diffuse Light\n FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.diffuse.rgba, light.diffuse.rgba);\n FSS.Vector4.multiplyScalar(this.material.slave.rgba, illuminance);\n FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba);\n }\n\n // Clamp & Format Color\n FSS.Vector4.clamp(triangle.color.rgba, 0, 1);\n }\n }\n return this;\n};\n\n/**\n * @class Scene\n * @author Matthew Wagerfield\n */\nFSS.Scene = function() {\n this.meshes = [];\n this.lights = [];\n};\n\nFSS.Scene.prototype = {\n add: function(object) {\n if (object instanceof FSS.Mesh && !~this.meshes.indexOf(object)) {\n this.meshes.push(object);\n } else if (object instanceof FSS.Light && !~this.lights.indexOf(object)) {\n this.lights.push(object);\n }\n return this;\n },\n remove: function(object) {\n if (object instanceof FSS.Mesh && ~this.meshes.indexOf(object)) {\n this.meshes.splice(this.meshes.indexOf(object), 1);\n } else if (object instanceof FSS.Light && ~this.lights.indexOf(object)) {\n this.lights.splice(this.lights.indexOf(object), 1);\n }\n return this;\n }\n};\n\n/**\n * @class Renderer\n * @author Matthew Wagerfield\n */\nFSS.Renderer = function() {\n this.width = 0;\n this.height = 0;\n this.halfWidth = 0;\n this.halfHeight = 0;\n};\n\nFSS.Renderer.prototype = {\n setSize: function(width, height) {\n if (this.width === width && this.height === height) return;\n this.width = width;\n this.height = height;\n this.halfWidth = this.width * 0.5;\n this.halfHeight = this.height * 0.5;\n return this;\n },\n clear: function() {\n return this;\n },\n render: function(scene) {\n return this;\n }\n};\n\n/**\n * @class Canvas Renderer\n * @author Matthew Wagerfield\n */\nFSS.CanvasRenderer = function() {\n FSS.Renderer.call(this);\n this.element = document.createElement('canvas');\n this.element.style.display = 'block';\n this.context = this.element.getContext('2d');\n this.setSize(this.element.width, this.element.height);\n};\n\nFSS.CanvasRenderer.prototype = Object.create(FSS.Renderer.prototype);\n\nFSS.CanvasRenderer.prototype.setSize = function(width, height) {\n FSS.Renderer.prototype.setSize.call(this, width, height);\n this.element.width = width;\n this.element.height = height;\n this.context.setTransform(1, 0, 0, -1, this.halfWidth, this.halfHeight);\n return this;\n};\n\nFSS.CanvasRenderer.prototype.clear = function() {\n FSS.Renderer.prototype.clear.call(this);\n this.context.clearRect(-this.halfWidth, -this.halfHeight, this.width, this.height);\n return this;\n};\n\nFSS.CanvasRenderer.prototype.render = function(scene) {\n FSS.Renderer.prototype.render.call(this, scene);\n var m,mesh, t,triangle, color;\n\n // Clear Context\n this.clear();\n\n // Configure Context\n this.context.lineJoin = 'round';\n this.context.lineWidth = 1;\n\n // Update Meshes\n for (m = scene.meshes.length - 1; m >= 0; m--) {\n mesh = scene.meshes[m];\n if (mesh.visible) {\n mesh.update(scene.lights, true);\n\n // Render Triangles\n for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) {\n triangle = mesh.geometry.triangles[t];\n color = triangle.color.format();\n this.context.beginPath();\n this.context.moveTo(triangle.a.position[0], triangle.a.position[1]);\n this.context.lineTo(triangle.b.position[0], triangle.b.position[1]);\n this.context.lineTo(triangle.c.position[0], triangle.c.position[1]);\n this.context.closePath();\n this.context.strokeStyle = color;\n this.context.fillStyle = color;\n this.context.stroke();\n this.context.fill();\n }\n }\n }\n return this;\n};\n\n/**\n * @class WebGL Renderer\n * @author Matthew Wagerfield\n */\nFSS.WebGLRenderer = function() {\n FSS.Renderer.call(this);\n this.element = document.createElement('canvas');\n this.element.style.display = 'block';\n\n // Set initial vertex and light count\n this.vertices = null;\n this.lights = null;\n\n // Create parameters object\n var parameters = {\n preserveDrawingBuffer: false,\n premultipliedAlpha: true,\n antialias: true,\n stencil: true,\n alpha: true\n };\n\n // Create and configure the gl context\n this.gl = this.getContext(this.element, parameters);\n\n // Set the internal support flag\n this.unsupported = !this.gl;\n\n // Setup renderer\n if (this.unsupported) {\n return 'WebGL is not supported by your browser.';\n } else {\n this.gl.clearColor(0.0, 0.0, 0.0, 0.0);\n this.gl.enable(this.gl.DEPTH_TEST);\n this.setSize(this.element.width, this.element.height);\n }\n};\n\nFSS.WebGLRenderer.prototype = Object.create(FSS.Renderer.prototype);\n\nFSS.WebGLRenderer.prototype.getContext = function(canvas, parameters) {\n var context = false;\n try {\n if (!(context = canvas.getContext('experimental-webgl', parameters))) {\n throw 'Error creating WebGL context.';\n }\n } catch (error) {\n console.error(error);\n }\n return context;\n};\n\nFSS.WebGLRenderer.prototype.setSize = function(width, height) {\n FSS.Renderer.prototype.setSize.call(this, width, height);\n if (this.unsupported) return;\n\n // Set the size of the canvas element\n this.element.width = width;\n this.element.height = height;\n\n // Set the size of the gl viewport\n this.gl.viewport(0, 0, width, height);\n return this;\n};\n\nFSS.WebGLRenderer.prototype.clear = function() {\n FSS.Renderer.prototype.clear.call(this);\n if (this.unsupported) return;\n this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);\n return this;\n};\n\nFSS.WebGLRenderer.prototype.render = function(scene) {\n FSS.Renderer.prototype.render.call(this, scene);\n if (this.unsupported) return;\n var m,mesh, t,tl,triangle, l,light,\n attribute, uniform, buffer, data, location,\n update = false, lights = scene.lights.length,\n index, v,vl,vetex,vertices = 0;\n\n // Clear context\n this.clear();\n\n // Build the shader program\n if (this.lights !== lights) {\n this.lights = lights;\n if (this.lights > 0) {\n this.buildProgram(lights);\n } else {\n return;\n }\n }\n\n // Update program\n if (!!this.program) {\n\n // Increment vertex counter\n for (m = scene.meshes.length - 1; m >= 0; m--) {\n mesh = scene.meshes[m];\n if (mesh.geometry.dirty) update = true;\n mesh.update(scene.lights, false);\n vertices += mesh.geometry.triangles.length*3;\n }\n\n // Compare vertex counter\n if (update || this.vertices !== vertices) {\n this.vertices = vertices;\n\n // Build buffers\n for (attribute in this.program.attributes) {\n buffer = this.program.attributes[attribute];\n buffer.data = new FSS.Array(vertices*buffer.size);\n\n // Reset vertex index\n index = 0;\n\n // Update attribute buffer data\n for (m = scene.meshes.length - 1; m >= 0; m--) {\n mesh = scene.meshes[m];\n\n for (t = 0, tl = mesh.geometry.triangles.length; t < tl; t++) {\n triangle = mesh.geometry.triangles[t];\n\n for (v = 0, vl = triangle.vertices.length; v < vl; v++) {\n vertex = triangle.vertices[v];\n switch (attribute) {\n case 'side':\n this.setBufferData(index, buffer, mesh.side);\n break;\n case 'position':\n this.setBufferData(index, buffer, vertex.position);\n break;\n case 'centroid':\n this.setBufferData(index, buffer, triangle.centroid);\n break;\n case 'normal':\n this.setBufferData(index, buffer, triangle.normal);\n break;\n case 'ambient':\n this.setBufferData(index, buffer, mesh.material.ambient.rgba);\n break;\n case 'diffuse':\n this.setBufferData(index, buffer, mesh.material.diffuse.rgba);\n break;\n }\n index++;\n }\n }\n }\n\n // Upload attribute buffer data\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer.buffer);\n this.gl.bufferData(this.gl.ARRAY_BUFFER, buffer.data, this.gl.DYNAMIC_DRAW);\n this.gl.enableVertexAttribArray(buffer.location);\n this.gl.vertexAttribPointer(buffer.location, buffer.size, this.gl.FLOAT, false, 0, 0);\n }\n }\n\n // Build uniform buffers\n this.setBufferData(0, this.program.uniforms.resolution, [this.width, this.height, this.width]);\n for (l = lights-1; l >= 0; l--) {\n light = scene.lights[l];\n this.setBufferData(l, this.program.uniforms.lightPosition, light.position);\n this.setBufferData(l, this.program.uniforms.lightAmbient, light.ambient.rgba);\n this.setBufferData(l, this.program.uniforms.lightDiffuse, light.diffuse.rgba);\n }\n\n // Update uniforms\n for (uniform in this.program.uniforms) {\n buffer = this.program.uniforms[uniform];\n location = buffer.location;\n data = buffer.data;\n switch (buffer.structure) {\n case '3f':\n this.gl.uniform3f(location, data[0], data[1], data[2]);\n break;\n case '3fv':\n this.gl.uniform3fv(location, data);\n break;\n case '4fv':\n this.gl.uniform4fv(location, data);\n break;\n }\n }\n }\n\n // Draw those lovely triangles\n this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertices);\n return this;\n};\n\nFSS.WebGLRenderer.prototype.setBufferData = function(index, buffer, value) {\n if (FSS.Utils.isNumber(value)) {\n buffer.data[index*buffer.size] = value;\n } else {\n for (var i = value.length - 1; i >= 0; i--) {\n buffer.data[index*buffer.size+i] = value[i];\n }\n }\n};\n\n/**\n * Concepts taken from three.js WebGLRenderer\n * @see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js\n */\nFSS.WebGLRenderer.prototype.buildProgram = function(lights) {\n if (this.unsupported) return;\n\n // Create shader source\n var vs = FSS.WebGLRenderer.VS(lights);\n var fs = FSS.WebGLRenderer.FS(lights);\n\n // Derive the shader fingerprint\n var code = vs + fs;\n\n // Check if the program has already been compiled\n if (!!this.program && this.program.code === code) return;\n\n // Create the program and shaders\n var program = this.gl.createProgram();\n var vertexShader = this.buildShader(this.gl.VERTEX_SHADER, vs);\n var fragmentShader = this.buildShader(this.gl.FRAGMENT_SHADER, fs);\n\n // Attach an link the shader\n this.gl.attachShader(program, vertexShader);\n this.gl.attachShader(program, fragmentShader);\n this.gl.linkProgram(program);\n\n // Add error handling\n if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {\n var error = this.gl.getError();\n var status = this.gl.getProgramParameter(program, this.gl.VALIDATE_STATUS);\n console.error('Could not initialise shader.\\nVALIDATE_STATUS: '+status+'\\nERROR: '+error);\n return null;\n }\n\n // Delete the shader\n this.gl.deleteShader(fragmentShader);\n this.gl.deleteShader(vertexShader);\n\n // Set the program code\n program.code = code;\n\n // Add the program attributes\n program.attributes = {\n side: this.buildBuffer(program, 'attribute', 'aSide', 1, 'f' ),\n position: this.buildBuffer(program, 'attribute', 'aPosition', 3, 'v3'),\n centroid: this.buildBuffer(program, 'attribute', 'aCentroid', 3, 'v3'),\n normal: this.buildBuffer(program, 'attribute', 'aNormal', 3, 'v3'),\n ambient: this.buildBuffer(program, 'attribute', 'aAmbient', 4, 'v4'),\n diffuse: this.buildBuffer(program, 'attribute', 'aDiffuse', 4, 'v4')\n };\n\n // Add the program uniforms\n program.uniforms = {\n resolution: this.buildBuffer(program, 'uniform', 'uResolution', 3, '3f', 1 ),\n lightPosition: this.buildBuffer(program, 'uniform', 'uLightPosition', 3, '3fv', lights),\n lightAmbient: this.buildBuffer(program, 'uniform', 'uLightAmbient', 4, '4fv', lights),\n lightDiffuse: this.buildBuffer(program, 'uniform', 'uLightDiffuse', 4, '4fv', lights)\n };\n\n // Set the renderer program\n this.program = program;\n\n // Enable program\n this.gl.useProgram(this.program);\n\n // Return the program\n return program;\n};\n\nFSS.WebGLRenderer.prototype.buildShader = function(type, source) {\n if (this.unsupported) return;\n\n // Create and compile shader\n var shader = this.gl.createShader(type);\n this.gl.shaderSource(shader, source);\n this.gl.compileShader(shader);\n\n // Add error handling\n if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {\n console.error(this.gl.getShaderInfoLog(shader));\n return null;\n }\n\n // Return the shader\n return shader;\n};\n\nFSS.WebGLRenderer.prototype.buildBuffer = function(program, type, identifier, size, structure, count) {\n var buffer = {buffer:this.gl.createBuffer(), size:size, structure:structure, data:null};\n\n // Set the location\n switch (type) {\n case 'attribute':\n buffer.location = this.gl.getAttribLocation(program, identifier);\n break;\n case 'uniform':\n buffer.location = this.gl.getUniformLocation(program, identifier);\n break;\n }\n\n // Create the buffer if count is provided\n if (!!count) {\n buffer.data = new FSS.Array(count*size);\n }\n\n // Return the buffer\n return buffer;\n};\n\nFSS.WebGLRenderer.VS = function(lights) {\n var shader = [\n\n // Precision\n 'precision mediump float;',\n\n // Lights\n '#define LIGHTS ' + lights,\n\n // Attributes\n 'attribute float aSide;',\n 'attribute vec3 aPosition;',\n 'attribute vec3 aCentroid;',\n 'attribute vec3 aNormal;',\n 'attribute vec4 aAmbient;',\n 'attribute vec4 aDiffuse;',\n\n // Uniforms\n 'uniform vec3 uResolution;',\n 'uniform vec3 uLightPosition[LIGHTS];',\n 'uniform vec4 uLightAmbient[LIGHTS];',\n 'uniform vec4 uLightDiffuse[LIGHTS];',\n\n // Varyings\n 'varying vec4 vColor;',\n\n // Main\n 'void main() {',\n\n // Create color\n 'vColor = vec4(0.0);',\n\n // Calculate the vertex position\n 'vec3 position = aPosition / uResolution * 2.0;',\n\n // Iterate through lights\n 'for (int i = 0; i < LIGHTS; i++) {',\n 'vec3 lightPosition = uLightPosition[i];',\n 'vec4 lightAmbient = uLightAmbient[i];',\n 'vec4 lightDiffuse = uLightDiffuse[i];',\n\n // Calculate illuminance\n 'vec3 ray = normalize(lightPosition - aCentroid);',\n 'float illuminance = dot(aNormal, ray);',\n 'if (aSide == 0.0) {',\n 'illuminance = max(illuminance, 0.0);',\n '} else if (aSide == 1.0) {',\n 'illuminance = abs(min(illuminance, 0.0));',\n '} else if (aSide == 2.0) {',\n 'illuminance = max(abs(illuminance), 0.0);',\n '}',\n\n // Calculate ambient light\n 'vColor += aAmbient * lightAmbient;',\n\n // Calculate diffuse light\n 'vColor += aDiffuse * lightDiffuse * illuminance;',\n '}',\n\n // Clamp color\n 'vColor = clamp(vColor, 0.0, 1.0);',\n\n // Set gl_Position\n 'gl_Position = vec4(position, 1.0);',\n\n '}'\n\n // Return the shader\n ].join('\\n');\n return shader;\n};\n\nFSS.WebGLRenderer.FS = function(lights) {\n var shader = [\n\n // Precision\n 'precision mediump float;',\n\n // Varyings\n 'varying vec4 vColor;',\n\n // Main\n 'void main() {',\n\n // Set gl_FragColor\n 'gl_FragColor = vColor;',\n\n '}'\n\n // Return the shader\n ].join('\\n');\n return shader;\n};\n\n/**\n * @class SVG Renderer\n * @author Matthew Wagerfield\n */\nFSS.SVGRenderer = function() {\n FSS.Renderer.call(this);\n this.element = document.createElementNS(FSS.SVGNS, 'svg');\n this.element.setAttribute('xmlns', FSS.SVGNS);\n this.element.setAttribute('version', '1.1');\n this.element.style.display = 'block';\n this.setSize(300, 150);\n};\n\nFSS.SVGRenderer.prototype = Object.create(FSS.Renderer.prototype);\n\nFSS.SVGRenderer.prototype.setSize = function(width, height) {\n FSS.Renderer.prototype.setSize.call(this, width, height);\n this.element.setAttribute('width', width);\n this.element.setAttribute('height', height);\n return this;\n};\n\nFSS.SVGRenderer.prototype.clear = function() {\n FSS.Renderer.prototype.clear.call(this);\n for (var i = this.element.childNodes.length - 1; i >= 0; i--) {\n this.element.removeChild(this.element.childNodes[i]);\n }\n return this;\n};\n\nFSS.SVGRenderer.prototype.render = function(scene) {\n FSS.Renderer.prototype.render.call(this, scene);\n var m,mesh, t,triangle, points, style;\n\n // Update Meshes\n for (m = scene.meshes.length - 1; m >= 0; m--) {\n mesh = scene.meshes[m];\n if (mesh.visible) {\n mesh.update(scene.lights, true);\n\n // Render Triangles\n for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) {\n triangle = mesh.geometry.triangles[t];\n if (triangle.polygon.parentNode !== this.element) {\n this.element.appendChild(triangle.polygon);\n }\n points = this.formatPoint(triangle.a)+' ';\n points += this.formatPoint(triangle.b)+' ';\n points += this.formatPoint(triangle.c);\n style = this.formatStyle(triangle.color.format());\n triangle.polygon.setAttributeNS(null, 'points', points);\n triangle.polygon.setAttributeNS(null, 'style', style);\n }\n }\n }\n return this;\n};\n\nFSS.SVGRenderer.prototype.formatPoint = function(vertex) {\n return (this.halfWidth+vertex.position[0])+','+(this.halfHeight-vertex.position[1]);\n};\n\nFSS.SVGRenderer.prototype.formatStyle = function(color) {\n var style = 'fill:'+color+';';\n style += 'stroke:'+color+';';\n return style;\n};\n\n\n/*** EXPORTS FROM exports-loader ***/\nmodule.exports = FSS;\n\n/***/ }),\n/* 6 */\n/***/ (function(module, exports) {\n\n/* globals __VUE_SSR_CONTEXT__ */\n\n// this module is a runtime utility for cleaner component module output and will\n// be included in the final webpack user bundle\n\nmodule.exports = function normalizeComponent (\n rawScriptExports,\n compiledTemplate,\n injectStyles,\n scopeId,\n moduleIdentifier /* server only */\n) {\n var esModule\n var scriptExports = rawScriptExports = rawScriptExports || {}\n\n // ES6 modules interop\n var type = typeof rawScriptExports.default\n if (type === 'object' || type === 'function') {\n esModule = rawScriptExports\n scriptExports = rawScriptExports.default\n }\n\n // Vue.extend constructor export interop\n var options = typeof scriptExports === 'function'\n ? scriptExports.options\n : scriptExports\n\n // render functions\n if (compiledTemplate) {\n options.render = compiledTemplate.render\n options.staticRenderFns = compiledTemplate.staticRenderFns\n }\n\n // scopedId\n if (scopeId) {\n options._scopeId = scopeId\n }\n\n var hook\n if (moduleIdentifier) { // server build\n hook = function (context) {\n // 2.3 injection\n context =\n context || // cached call\n (this.$vnode && this.$vnode.ssrContext) || // stateful\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional\n // 2.2 with runInNewContext: true\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\n context = __VUE_SSR_CONTEXT__\n }\n // inject component styles\n if (injectStyles) {\n injectStyles.call(this, context)\n }\n // register component module identifier for async chunk inferrence\n if (context && context._registeredComponents) {\n context._registeredComponents.add(moduleIdentifier)\n }\n }\n // used by ssr in case component is cached and beforeCreate\n // never gets called\n options._ssrRegister = hook\n } else if (injectStyles) {\n hook = injectStyles\n }\n\n if (hook) {\n var functional = options.functional\n var existing = functional\n ? options.render\n : options.beforeCreate\n if (!functional) {\n // inject component registration as beforeCreate hook\n options.beforeCreate = existing\n ? [].concat(existing, hook)\n : [hook]\n } else {\n // register for functioal component in vue file\n options.render = function renderWithStyleInjection (h, context) {\n hook.call(context)\n return existing(h, context)\n }\n }\n }\n\n return {\n esModule: esModule,\n exports: scriptExports,\n options: options\n }\n}\n\n\n/***/ }),\n/* 7 */\n/***/ (function(module, exports) {\n\nmodule.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;\n return _c('div', {\n ref: \"shader\"\n }, [_vm._t(\"default\")], 2)\n},staticRenderFns: []}\n\n/***/ }),\n/* 8 */\n/***/ (function(module, exports, __webpack_require__) {\n\n// style-loader: Adds some css to the DOM by adding a \n\n\n\n// WEBPACK FOOTER //\n// FSS.vue?2f3ba2ec","import FlatSurfaceShader from './FSS.vue'\n\nconst install = (Vue) => {\n Vue.component(\"flat-surface-shader\", FlatSurfaceShader)\n}\n\nexport default {\n install,\n}\n\nexport {\n FlatSurfaceShader\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/main.js","exports = module.exports = require(\"../node_modules/css-loader/lib/css-base.js\")();\n// imports\n\n\n// module\nexports.push([module.id, \"\", \"\"]);\n\n// exports\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/css-loader?minimize!./~/vue-loader/lib/style-compiler?{\"vue\":true,\"id\":\"data-v-47119860\",\"scoped\":false,\"hasInlineConfig\":false}!./~/vue-loader/lib/selector.js?type=styles&index=0!./src/FSS.vue\n// module id = 3\n// module chunks = 0","/*\r\n\tMIT License http://www.opensource.org/licenses/mit-license.php\r\n\tAuthor Tobias Koppers @sokra\r\n*/\r\n// css base code, injected by the css-loader\r\nmodule.exports = function() {\r\n\tvar list = [];\r\n\r\n\t// return the list of modules as css string\r\n\tlist.toString = function toString() {\r\n\t\tvar result = [];\r\n\t\tfor(var i = 0; i < this.length; i++) {\r\n\t\t\tvar item = this[i];\r\n\t\t\tif(item[2]) {\r\n\t\t\t\tresult.push(\"@media \" + item[2] + \"{\" + item[1] + \"}\");\r\n\t\t\t} else {\r\n\t\t\t\tresult.push(item[1]);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn result.join(\"\");\r\n\t};\r\n\r\n\t// import a list of modules into the list\r\n\tlist.i = function(modules, mediaQuery) {\r\n\t\tif(typeof modules === \"string\")\r\n\t\t\tmodules = [[null, modules, \"\"]];\r\n\t\tvar alreadyImportedModules = {};\r\n\t\tfor(var i = 0; i < this.length; i++) {\r\n\t\t\tvar id = this[i][0];\r\n\t\t\tif(typeof id === \"number\")\r\n\t\t\t\talreadyImportedModules[id] = true;\r\n\t\t}\r\n\t\tfor(i = 0; i < modules.length; i++) {\r\n\t\t\tvar item = modules[i];\r\n\t\t\t// skip already imported module\r\n\t\t\t// this implementation is not 100% perfect for weird media query combinations\r\n\t\t\t// when a module is imported multiple times with different media queries.\r\n\t\t\t// I hope this will never occur (Hey this way we have smaller bundles)\r\n\t\t\tif(typeof item[0] !== \"number\" || !alreadyImportedModules[item[0]]) {\r\n\t\t\t\tif(mediaQuery && !item[2]) {\r\n\t\t\t\t\titem[2] = mediaQuery;\r\n\t\t\t\t} else if(mediaQuery) {\r\n\t\t\t\t\titem[2] = \"(\" + item[2] + \") and (\" + mediaQuery + \")\";\r\n\t\t\t\t}\r\n\t\t\t\tlist.push(item);\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\treturn list;\r\n};\r\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/css-loader/lib/css-base.js\n// module id = 4\n// module chunks = 0","//============================================================\n//\n// Copyright (C) 2014 Matthew Wagerfield\n//\n// Twitter: https://twitter.com/wagerfield\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the\n// Software without restriction, including without limitation\n// the rights to use, copy, modify, merge, publish, distribute,\n// sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do\n// so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice\n// shall be included in all copies or substantial portions\n// of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY\n// OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT\n// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO\n// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN\n// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE\n// OR OTHER DEALINGS IN THE SOFTWARE.\n//\n//============================================================\n\n/**\n * Defines the Flat Surface Shader namespace for all the awesomeness to exist upon.\n * @author Matthew Wagerfield\n */\nFSS = {\n FRONT : 0,\n BACK : 1,\n DOUBLE : 2,\n SVGNS : 'http://www.w3.org/2000/svg'\n};\n\n/**\n * @class Array\n * @author Matthew Wagerfield\n */\nFSS.Array = typeof Float32Array === 'function' ? Float32Array : Array;\n\n/**\n * @class Utils\n * @author Matthew Wagerfield\n */\nFSS.Utils = {\n isNumber: function(value) {\n return !isNaN(parseFloat(value)) && isFinite(value);\n }\n};\n\n/**\n * Request Animation Frame Polyfill.\n * @author Paul Irish\n * @see https://gist.github.com/paulirish/1579671\n */\n(function() {\n\n var lastTime = 0;\n var vendors = ['ms', 'moz', 'webkit', 'o'];\n\n for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {\n window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];\n window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];\n }\n\n if (!window.requestAnimationFrame) {\n window.requestAnimationFrame = function(callback, element) {\n var currentTime = new Date().getTime();\n var timeToCall = Math.max(0, 16 - (currentTime - lastTime));\n var id = window.setTimeout(function() {\n callback(currentTime + timeToCall);\n }, timeToCall);\n lastTime = currentTime + timeToCall;\n return id;\n };\n }\n\n if (!window.cancelAnimationFrame) {\n window.cancelAnimationFrame = function(id) {\n clearTimeout(id);\n };\n }\n\n}());\n\n/**\n * @object Math Augmentation\n * @author Matthew Wagerfield\n */\nMath.PIM2 = Math.PI*2;\nMath.PID2 = Math.PI/2;\nMath.randomInRange = function(min, max) {\n return min + (max - min) * Math.random();\n};\nMath.clamp = function(value, min, max) {\n value = Math.max(value, min);\n value = Math.min(value, max);\n return value;\n};\n\n/**\n * @object Vector3\n * @author Matthew Wagerfield\n */\nFSS.Vector3 = {\n create: function(x, y, z) {\n var vector = new FSS.Array(3);\n this.set(vector, x, y, z);\n return vector;\n },\n clone: function(a) {\n var vector = this.create();\n this.copy(vector, a);\n return vector;\n },\n set: function(target, x, y, z) {\n target[0] = x || 0;\n target[1] = y || 0;\n target[2] = z || 0;\n return this;\n },\n setX: function(target, x) {\n target[0] = x || 0;\n return this;\n },\n setY: function(target, y) {\n target[1] = y || 0;\n return this;\n },\n setZ: function(target, z) {\n target[2] = z || 0;\n return this;\n },\n copy: function(target, a) {\n target[0] = a[0];\n target[1] = a[1];\n target[2] = a[2];\n return this;\n },\n add: function(target, a) {\n target[0] += a[0];\n target[1] += a[1];\n target[2] += a[2];\n return this;\n },\n addVectors: function(target, a, b) {\n target[0] = a[0] + b[0];\n target[1] = a[1] + b[1];\n target[2] = a[2] + b[2];\n return this;\n },\n addScalar: function(target, s) {\n target[0] += s;\n target[1] += s;\n target[2] += s;\n return this;\n },\n subtract: function(target, a) {\n target[0] -= a[0];\n target[1] -= a[1];\n target[2] -= a[2];\n return this;\n },\n subtractVectors: function(target, a, b) {\n target[0] = a[0] - b[0];\n target[1] = a[1] - b[1];\n target[2] = a[2] - b[2];\n return this;\n },\n subtractScalar: function(target, s) {\n target[0] -= s;\n target[1] -= s;\n target[2] -= s;\n return this;\n },\n multiply: function(target, a) {\n target[0] *= a[0];\n target[1] *= a[1];\n target[2] *= a[2];\n return this;\n },\n multiplyVectors: function(target, a, b) {\n target[0] = a[0] * b[0];\n target[1] = a[1] * b[1];\n target[2] = a[2] * b[2];\n return this;\n },\n multiplyScalar: function(target, s) {\n target[0] *= s;\n target[1] *= s;\n target[2] *= s;\n return this;\n },\n divide: function(target, a) {\n target[0] /= a[0];\n target[1] /= a[1];\n target[2] /= a[2];\n return this;\n },\n divideVectors: function(target, a, b) {\n target[0] = a[0] / b[0];\n target[1] = a[1] / b[1];\n target[2] = a[2] / b[2];\n return this;\n },\n divideScalar: function(target, s) {\n if (s !== 0) {\n target[0] /= s;\n target[1] /= s;\n target[2] /= s;\n } else {\n target[0] = 0;\n target[1] = 0;\n target[2] = 0;\n }\n return this;\n },\n cross: function(target, a) {\n var x = target[0];\n var y = target[1];\n var z = target[2];\n target[0] = y*a[2] - z*a[1];\n target[1] = z*a[0] - x*a[2];\n target[2] = x*a[1] - y*a[0];\n return this;\n },\n crossVectors: function(target, a, b) {\n target[0] = a[1]*b[2] - a[2]*b[1];\n target[1] = a[2]*b[0] - a[0]*b[2];\n target[2] = a[0]*b[1] - a[1]*b[0];\n return this;\n },\n min: function(target, value) {\n if (target[0] < value) { target[0] = value; }\n if (target[1] < value) { target[1] = value; }\n if (target[2] < value) { target[2] = value; }\n return this;\n },\n max: function(target, value) {\n if (target[0] > value) { target[0] = value; }\n if (target[1] > value) { target[1] = value; }\n if (target[2] > value) { target[2] = value; }\n return this;\n },\n clamp: function(target, min, max) {\n this.min(target, min);\n this.max(target, max);\n return this;\n },\n limit: function(target, min, max) {\n var length = this.length(target);\n if (min !== null && length < min) {\n this.setLength(target, min);\n } else if (max !== null && length > max) {\n this.setLength(target, max);\n }\n return this;\n },\n dot: function(a, b) {\n return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];\n },\n normalise: function(target) {\n return this.divideScalar(target, this.length(target));\n },\n negate: function(target) {\n return this.multiplyScalar(target, -1);\n },\n distanceSquared: function(a, b) {\n var dx = a[0] - b[0];\n var dy = a[1] - b[1];\n var dz = a[2] - b[2];\n return dx*dx + dy*dy + dz*dz;\n },\n distance: function(a, b) {\n return Math.sqrt(this.distanceSquared(a, b));\n },\n lengthSquared: function(a) {\n return a[0]*a[0] + a[1]*a[1] + a[2]*a[2];\n },\n length: function(a) {\n return Math.sqrt(this.lengthSquared(a));\n },\n setLength: function(target, l) {\n var length = this.length(target);\n if (length !== 0 && l !== length) {\n this.multiplyScalar(target, l / length);\n }\n return this;\n }\n};\n\n/**\n * @object Vector4\n * @author Matthew Wagerfield\n */\nFSS.Vector4 = {\n create: function(x, y, z, w) {\n var vector = new FSS.Array(4);\n this.set(vector, x, y, z);\n return vector;\n },\n set: function(target, x, y, z, w) {\n target[0] = x || 0;\n target[1] = y || 0;\n target[2] = z || 0;\n target[3] = w || 0;\n return this;\n },\n setX: function(target, x) {\n target[0] = x || 0;\n return this;\n },\n setY: function(target, y) {\n target[1] = y || 0;\n return this;\n },\n setZ: function(target, z) {\n target[2] = z || 0;\n return this;\n },\n setW: function(target, w) {\n target[3] = w || 0;\n return this;\n },\n add: function(target, a) {\n target[0] += a[0];\n target[1] += a[1];\n target[2] += a[2];\n target[3] += a[3];\n return this;\n },\n multiplyVectors: function(target, a, b) {\n target[0] = a[0] * b[0];\n target[1] = a[1] * b[1];\n target[2] = a[2] * b[2];\n target[3] = a[3] * b[3];\n return this;\n },\n multiplyScalar: function(target, s) {\n target[0] *= s;\n target[1] *= s;\n target[2] *= s;\n target[3] *= s;\n return this;\n },\n min: function(target, value) {\n if (target[0] < value) { target[0] = value; }\n if (target[1] < value) { target[1] = value; }\n if (target[2] < value) { target[2] = value; }\n if (target[3] < value) { target[3] = value; }\n return this;\n },\n max: function(target, value) {\n if (target[0] > value) { target[0] = value; }\n if (target[1] > value) { target[1] = value; }\n if (target[2] > value) { target[2] = value; }\n if (target[3] > value) { target[3] = value; }\n return this;\n },\n clamp: function(target, min, max) {\n this.min(target, min);\n this.max(target, max);\n return this;\n }\n};\n\n/**\n * @class Color\n * @author Matthew Wagerfield\n */\nFSS.Color = function(hex, opacity) {\n this.rgba = FSS.Vector4.create();\n this.hex = hex || '#000000';\n this.opacity = FSS.Utils.isNumber(opacity) ? opacity : 1;\n this.set(this.hex, this.opacity);\n};\n\nFSS.Color.prototype = {\n set: function(hex, opacity) {\n hex = hex.replace('#', '');\n var size = hex.length / 3;\n this.rgba[0] = parseInt(hex.substring(size*0, size*1), 16) / 255;\n this.rgba[1] = parseInt(hex.substring(size*1, size*2), 16) / 255;\n this.rgba[2] = parseInt(hex.substring(size*2, size*3), 16) / 255;\n this.rgba[3] = FSS.Utils.isNumber(opacity) ? opacity : this.rgba[3];\n return this;\n },\n hexify: function(channel) {\n var hex = Math.ceil(channel*255).toString(16);\n if (hex.length === 1) { hex = '0' + hex; }\n return hex;\n },\n format: function() {\n var r = this.hexify(this.rgba[0]);\n var g = this.hexify(this.rgba[1]);\n var b = this.hexify(this.rgba[2]);\n this.hex = '#' + r + g + b;\n return this.hex;\n }\n};\n\n/**\n * @class Object\n * @author Matthew Wagerfield\n */\nFSS.Object = function() {\n this.position = FSS.Vector3.create();\n};\n\nFSS.Object.prototype = {\n setPosition: function(x, y, z) {\n FSS.Vector3.set(this.position, x, y, z);\n return this;\n }\n};\n\n/**\n * @class Light\n * @author Matthew Wagerfield\n */\nFSS.Light = function(ambient, diffuse) {\n FSS.Object.call(this);\n this.ambient = new FSS.Color(ambient || '#FFFFFF');\n this.diffuse = new FSS.Color(diffuse || '#FFFFFF');\n this.ray = FSS.Vector3.create();\n};\n\nFSS.Light.prototype = Object.create(FSS.Object.prototype);\n\n/**\n * @class Vertex\n * @author Matthew Wagerfield\n */\nFSS.Vertex = function(x, y, z) {\n this.position = FSS.Vector3.create(x, y, z);\n};\n\nFSS.Vertex.prototype = {\n setPosition: function(x, y, z) {\n FSS.Vector3.set(this.position, x, y, z);\n return this;\n }\n};\n\n/**\n * @class Triangle\n * @author Matthew Wagerfield\n */\nFSS.Triangle = function(a, b, c) {\n this.a = a || new FSS.Vertex();\n this.b = b || new FSS.Vertex();\n this.c = c || new FSS.Vertex();\n this.vertices = [this.a, this.b, this.c];\n this.u = FSS.Vector3.create();\n this.v = FSS.Vector3.create();\n this.centroid = FSS.Vector3.create();\n this.normal = FSS.Vector3.create();\n this.color = new FSS.Color();\n this.polygon = document.createElementNS(FSS.SVGNS, 'polygon');\n this.polygon.setAttributeNS(null, 'stroke-linejoin', 'round');\n this.polygon.setAttributeNS(null, 'stroke-miterlimit', '1');\n this.polygon.setAttributeNS(null, 'stroke-width', '1');\n this.computeCentroid();\n this.computeNormal();\n};\n\nFSS.Triangle.prototype = {\n computeCentroid: function() {\n this.centroid[0] = this.a.position[0] + this.b.position[0] + this.c.position[0];\n this.centroid[1] = this.a.position[1] + this.b.position[1] + this.c.position[1];\n this.centroid[2] = this.a.position[2] + this.b.position[2] + this.c.position[2];\n FSS.Vector3.divideScalar(this.centroid, 3);\n return this;\n },\n computeNormal: function() {\n FSS.Vector3.subtractVectors(this.u, this.b.position, this.a.position);\n FSS.Vector3.subtractVectors(this.v, this.c.position, this.a.position);\n FSS.Vector3.crossVectors(this.normal, this.u, this.v);\n FSS.Vector3.normalise(this.normal);\n return this;\n }\n};\n\n/**\n * @class Geometry\n * @author Matthew Wagerfield\n */\nFSS.Geometry = function() {\n this.vertices = [];\n this.triangles = [];\n this.dirty = false;\n};\n\nFSS.Geometry.prototype = {\n update: function() {\n if (this.dirty) {\n var t,triangle;\n for (t = this.triangles.length - 1; t >= 0; t--) {\n triangle = this.triangles[t];\n triangle.computeCentroid();\n triangle.computeNormal();\n }\n this.dirty = false;\n }\n return this;\n }\n};\n\n/**\n * @class Plane\n * @author Matthew Wagerfield\n */\nFSS.Plane = function(width, height, segments, slices) {\n FSS.Geometry.call(this);\n this.width = width || 100;\n this.height = height || 100;\n this.segments = segments || 4;\n this.slices = slices || 4;\n this.segmentWidth = this.width / this.segments;\n this.sliceHeight = this.height / this.slices;\n\n // Cache Variables\n var x, y, v0, v1, v2, v3,\n vertex, triangle, vertices = [],\n offsetX = this.width * -0.5,\n offsetY = this.height * 0.5;\n\n // Add Vertices\n for (x = 0; x <= this.segments; x++) {\n vertices.push([]);\n for (y = 0; y <= this.slices; y++) {\n vertex = new FSS.Vertex(offsetX + x*this.segmentWidth, offsetY - y*this.sliceHeight);\n vertices[x].push(vertex);\n this.vertices.push(vertex);\n }\n }\n\n // Add Triangles\n for (x = 0; x < this.segments; x++) {\n for (y = 0; y < this.slices; y++) {\n v0 = vertices[x+0][y+0];\n v1 = vertices[x+0][y+1];\n v2 = vertices[x+1][y+0];\n v3 = vertices[x+1][y+1];\n t0 = new FSS.Triangle(v0, v1, v2);\n t1 = new FSS.Triangle(v2, v1, v3);\n this.triangles.push(t0, t1);\n }\n }\n};\n\nFSS.Plane.prototype = Object.create(FSS.Geometry.prototype);\n\n/**\n * @class Material\n * @author Matthew Wagerfield\n */\nFSS.Material = function(ambient, diffuse) {\n this.ambient = new FSS.Color(ambient || '#444444');\n this.diffuse = new FSS.Color(diffuse || '#FFFFFF');\n this.slave = new FSS.Color();\n};\n\n/**\n * @class Mesh\n * @author Matthew Wagerfield\n */\nFSS.Mesh = function(geometry, material) {\n FSS.Object.call(this);\n this.geometry = geometry || new FSS.Geometry();\n this.material = material || new FSS.Material();\n this.side = FSS.FRONT;\n this.visible = true;\n};\n\nFSS.Mesh.prototype = Object.create(FSS.Object.prototype);\n\nFSS.Mesh.prototype.update = function(lights, calculate) {\n var t,triangle, l,light, illuminance;\n\n // Update Geometry\n this.geometry.update();\n\n // Calculate the triangle colors\n if (calculate) {\n\n // Iterate through Triangles\n for (t = this.geometry.triangles.length - 1; t >= 0; t--) {\n triangle = this.geometry.triangles[t];\n\n // Reset Triangle Color\n FSS.Vector4.set(triangle.color.rgba);\n\n // Iterate through Lights\n for (l = lights.length - 1; l >= 0; l--) {\n light = lights[l];\n\n // Calculate Illuminance\n FSS.Vector3.subtractVectors(light.ray, light.position, triangle.centroid);\n FSS.Vector3.normalise(light.ray);\n illuminance = FSS.Vector3.dot(triangle.normal, light.ray);\n if (this.side === FSS.FRONT) {\n illuminance = Math.max(illuminance, 0);\n } else if (this.side === FSS.BACK) {\n illuminance = Math.abs(Math.min(illuminance, 0));\n } else if (this.side === FSS.DOUBLE) {\n illuminance = Math.max(Math.abs(illuminance), 0);\n }\n\n // Calculate Ambient Light\n FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.ambient.rgba, light.ambient.rgba);\n FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba);\n\n // Calculate Diffuse Light\n FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.diffuse.rgba, light.diffuse.rgba);\n FSS.Vector4.multiplyScalar(this.material.slave.rgba, illuminance);\n FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba);\n }\n\n // Clamp & Format Color\n FSS.Vector4.clamp(triangle.color.rgba, 0, 1);\n }\n }\n return this;\n};\n\n/**\n * @class Scene\n * @author Matthew Wagerfield\n */\nFSS.Scene = function() {\n this.meshes = [];\n this.lights = [];\n};\n\nFSS.Scene.prototype = {\n add: function(object) {\n if (object instanceof FSS.Mesh && !~this.meshes.indexOf(object)) {\n this.meshes.push(object);\n } else if (object instanceof FSS.Light && !~this.lights.indexOf(object)) {\n this.lights.push(object);\n }\n return this;\n },\n remove: function(object) {\n if (object instanceof FSS.Mesh && ~this.meshes.indexOf(object)) {\n this.meshes.splice(this.meshes.indexOf(object), 1);\n } else if (object instanceof FSS.Light && ~this.lights.indexOf(object)) {\n this.lights.splice(this.lights.indexOf(object), 1);\n }\n return this;\n }\n};\n\n/**\n * @class Renderer\n * @author Matthew Wagerfield\n */\nFSS.Renderer = function() {\n this.width = 0;\n this.height = 0;\n this.halfWidth = 0;\n this.halfHeight = 0;\n};\n\nFSS.Renderer.prototype = {\n setSize: function(width, height) {\n if (this.width === width && this.height === height) return;\n this.width = width;\n this.height = height;\n this.halfWidth = this.width * 0.5;\n this.halfHeight = this.height * 0.5;\n return this;\n },\n clear: function() {\n return this;\n },\n render: function(scene) {\n return this;\n }\n};\n\n/**\n * @class Canvas Renderer\n * @author Matthew Wagerfield\n */\nFSS.CanvasRenderer = function() {\n FSS.Renderer.call(this);\n this.element = document.createElement('canvas');\n this.element.style.display = 'block';\n this.context = this.element.getContext('2d');\n this.setSize(this.element.width, this.element.height);\n};\n\nFSS.CanvasRenderer.prototype = Object.create(FSS.Renderer.prototype);\n\nFSS.CanvasRenderer.prototype.setSize = function(width, height) {\n FSS.Renderer.prototype.setSize.call(this, width, height);\n this.element.width = width;\n this.element.height = height;\n this.context.setTransform(1, 0, 0, -1, this.halfWidth, this.halfHeight);\n return this;\n};\n\nFSS.CanvasRenderer.prototype.clear = function() {\n FSS.Renderer.prototype.clear.call(this);\n this.context.clearRect(-this.halfWidth, -this.halfHeight, this.width, this.height);\n return this;\n};\n\nFSS.CanvasRenderer.prototype.render = function(scene) {\n FSS.Renderer.prototype.render.call(this, scene);\n var m,mesh, t,triangle, color;\n\n // Clear Context\n this.clear();\n\n // Configure Context\n this.context.lineJoin = 'round';\n this.context.lineWidth = 1;\n\n // Update Meshes\n for (m = scene.meshes.length - 1; m >= 0; m--) {\n mesh = scene.meshes[m];\n if (mesh.visible) {\n mesh.update(scene.lights, true);\n\n // Render Triangles\n for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) {\n triangle = mesh.geometry.triangles[t];\n color = triangle.color.format();\n this.context.beginPath();\n this.context.moveTo(triangle.a.position[0], triangle.a.position[1]);\n this.context.lineTo(triangle.b.position[0], triangle.b.position[1]);\n this.context.lineTo(triangle.c.position[0], triangle.c.position[1]);\n this.context.closePath();\n this.context.strokeStyle = color;\n this.context.fillStyle = color;\n this.context.stroke();\n this.context.fill();\n }\n }\n }\n return this;\n};\n\n/**\n * @class WebGL Renderer\n * @author Matthew Wagerfield\n */\nFSS.WebGLRenderer = function() {\n FSS.Renderer.call(this);\n this.element = document.createElement('canvas');\n this.element.style.display = 'block';\n\n // Set initial vertex and light count\n this.vertices = null;\n this.lights = null;\n\n // Create parameters object\n var parameters = {\n preserveDrawingBuffer: false,\n premultipliedAlpha: true,\n antialias: true,\n stencil: true,\n alpha: true\n };\n\n // Create and configure the gl context\n this.gl = this.getContext(this.element, parameters);\n\n // Set the internal support flag\n this.unsupported = !this.gl;\n\n // Setup renderer\n if (this.unsupported) {\n return 'WebGL is not supported by your browser.';\n } else {\n this.gl.clearColor(0.0, 0.0, 0.0, 0.0);\n this.gl.enable(this.gl.DEPTH_TEST);\n this.setSize(this.element.width, this.element.height);\n }\n};\n\nFSS.WebGLRenderer.prototype = Object.create(FSS.Renderer.prototype);\n\nFSS.WebGLRenderer.prototype.getContext = function(canvas, parameters) {\n var context = false;\n try {\n if (!(context = canvas.getContext('experimental-webgl', parameters))) {\n throw 'Error creating WebGL context.';\n }\n } catch (error) {\n console.error(error);\n }\n return context;\n};\n\nFSS.WebGLRenderer.prototype.setSize = function(width, height) {\n FSS.Renderer.prototype.setSize.call(this, width, height);\n if (this.unsupported) return;\n\n // Set the size of the canvas element\n this.element.width = width;\n this.element.height = height;\n\n // Set the size of the gl viewport\n this.gl.viewport(0, 0, width, height);\n return this;\n};\n\nFSS.WebGLRenderer.prototype.clear = function() {\n FSS.Renderer.prototype.clear.call(this);\n if (this.unsupported) return;\n this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);\n return this;\n};\n\nFSS.WebGLRenderer.prototype.render = function(scene) {\n FSS.Renderer.prototype.render.call(this, scene);\n if (this.unsupported) return;\n var m,mesh, t,tl,triangle, l,light,\n attribute, uniform, buffer, data, location,\n update = false, lights = scene.lights.length,\n index, v,vl,vetex,vertices = 0;\n\n // Clear context\n this.clear();\n\n // Build the shader program\n if (this.lights !== lights) {\n this.lights = lights;\n if (this.lights > 0) {\n this.buildProgram(lights);\n } else {\n return;\n }\n }\n\n // Update program\n if (!!this.program) {\n\n // Increment vertex counter\n for (m = scene.meshes.length - 1; m >= 0; m--) {\n mesh = scene.meshes[m];\n if (mesh.geometry.dirty) update = true;\n mesh.update(scene.lights, false);\n vertices += mesh.geometry.triangles.length*3;\n }\n\n // Compare vertex counter\n if (update || this.vertices !== vertices) {\n this.vertices = vertices;\n\n // Build buffers\n for (attribute in this.program.attributes) {\n buffer = this.program.attributes[attribute];\n buffer.data = new FSS.Array(vertices*buffer.size);\n\n // Reset vertex index\n index = 0;\n\n // Update attribute buffer data\n for (m = scene.meshes.length - 1; m >= 0; m--) {\n mesh = scene.meshes[m];\n\n for (t = 0, tl = mesh.geometry.triangles.length; t < tl; t++) {\n triangle = mesh.geometry.triangles[t];\n\n for (v = 0, vl = triangle.vertices.length; v < vl; v++) {\n vertex = triangle.vertices[v];\n switch (attribute) {\n case 'side':\n this.setBufferData(index, buffer, mesh.side);\n break;\n case 'position':\n this.setBufferData(index, buffer, vertex.position);\n break;\n case 'centroid':\n this.setBufferData(index, buffer, triangle.centroid);\n break;\n case 'normal':\n this.setBufferData(index, buffer, triangle.normal);\n break;\n case 'ambient':\n this.setBufferData(index, buffer, mesh.material.ambient.rgba);\n break;\n case 'diffuse':\n this.setBufferData(index, buffer, mesh.material.diffuse.rgba);\n break;\n }\n index++;\n }\n }\n }\n\n // Upload attribute buffer data\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer.buffer);\n this.gl.bufferData(this.gl.ARRAY_BUFFER, buffer.data, this.gl.DYNAMIC_DRAW);\n this.gl.enableVertexAttribArray(buffer.location);\n this.gl.vertexAttribPointer(buffer.location, buffer.size, this.gl.FLOAT, false, 0, 0);\n }\n }\n\n // Build uniform buffers\n this.setBufferData(0, this.program.uniforms.resolution, [this.width, this.height, this.width]);\n for (l = lights-1; l >= 0; l--) {\n light = scene.lights[l];\n this.setBufferData(l, this.program.uniforms.lightPosition, light.position);\n this.setBufferData(l, this.program.uniforms.lightAmbient, light.ambient.rgba);\n this.setBufferData(l, this.program.uniforms.lightDiffuse, light.diffuse.rgba);\n }\n\n // Update uniforms\n for (uniform in this.program.uniforms) {\n buffer = this.program.uniforms[uniform];\n location = buffer.location;\n data = buffer.data;\n switch (buffer.structure) {\n case '3f':\n this.gl.uniform3f(location, data[0], data[1], data[2]);\n break;\n case '3fv':\n this.gl.uniform3fv(location, data);\n break;\n case '4fv':\n this.gl.uniform4fv(location, data);\n break;\n }\n }\n }\n\n // Draw those lovely triangles\n this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertices);\n return this;\n};\n\nFSS.WebGLRenderer.prototype.setBufferData = function(index, buffer, value) {\n if (FSS.Utils.isNumber(value)) {\n buffer.data[index*buffer.size] = value;\n } else {\n for (var i = value.length - 1; i >= 0; i--) {\n buffer.data[index*buffer.size+i] = value[i];\n }\n }\n};\n\n/**\n * Concepts taken from three.js WebGLRenderer\n * @see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js\n */\nFSS.WebGLRenderer.prototype.buildProgram = function(lights) {\n if (this.unsupported) return;\n\n // Create shader source\n var vs = FSS.WebGLRenderer.VS(lights);\n var fs = FSS.WebGLRenderer.FS(lights);\n\n // Derive the shader fingerprint\n var code = vs + fs;\n\n // Check if the program has already been compiled\n if (!!this.program && this.program.code === code) return;\n\n // Create the program and shaders\n var program = this.gl.createProgram();\n var vertexShader = this.buildShader(this.gl.VERTEX_SHADER, vs);\n var fragmentShader = this.buildShader(this.gl.FRAGMENT_SHADER, fs);\n\n // Attach an link the shader\n this.gl.attachShader(program, vertexShader);\n this.gl.attachShader(program, fragmentShader);\n this.gl.linkProgram(program);\n\n // Add error handling\n if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {\n var error = this.gl.getError();\n var status = this.gl.getProgramParameter(program, this.gl.VALIDATE_STATUS);\n console.error('Could not initialise shader.\\nVALIDATE_STATUS: '+status+'\\nERROR: '+error);\n return null;\n }\n\n // Delete the shader\n this.gl.deleteShader(fragmentShader);\n this.gl.deleteShader(vertexShader);\n\n // Set the program code\n program.code = code;\n\n // Add the program attributes\n program.attributes = {\n side: this.buildBuffer(program, 'attribute', 'aSide', 1, 'f' ),\n position: this.buildBuffer(program, 'attribute', 'aPosition', 3, 'v3'),\n centroid: this.buildBuffer(program, 'attribute', 'aCentroid', 3, 'v3'),\n normal: this.buildBuffer(program, 'attribute', 'aNormal', 3, 'v3'),\n ambient: this.buildBuffer(program, 'attribute', 'aAmbient', 4, 'v4'),\n diffuse: this.buildBuffer(program, 'attribute', 'aDiffuse', 4, 'v4')\n };\n\n // Add the program uniforms\n program.uniforms = {\n resolution: this.buildBuffer(program, 'uniform', 'uResolution', 3, '3f', 1 ),\n lightPosition: this.buildBuffer(program, 'uniform', 'uLightPosition', 3, '3fv', lights),\n lightAmbient: this.buildBuffer(program, 'uniform', 'uLightAmbient', 4, '4fv', lights),\n lightDiffuse: this.buildBuffer(program, 'uniform', 'uLightDiffuse', 4, '4fv', lights)\n };\n\n // Set the renderer program\n this.program = program;\n\n // Enable program\n this.gl.useProgram(this.program);\n\n // Return the program\n return program;\n};\n\nFSS.WebGLRenderer.prototype.buildShader = function(type, source) {\n if (this.unsupported) return;\n\n // Create and compile shader\n var shader = this.gl.createShader(type);\n this.gl.shaderSource(shader, source);\n this.gl.compileShader(shader);\n\n // Add error handling\n if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {\n console.error(this.gl.getShaderInfoLog(shader));\n return null;\n }\n\n // Return the shader\n return shader;\n};\n\nFSS.WebGLRenderer.prototype.buildBuffer = function(program, type, identifier, size, structure, count) {\n var buffer = {buffer:this.gl.createBuffer(), size:size, structure:structure, data:null};\n\n // Set the location\n switch (type) {\n case 'attribute':\n buffer.location = this.gl.getAttribLocation(program, identifier);\n break;\n case 'uniform':\n buffer.location = this.gl.getUniformLocation(program, identifier);\n break;\n }\n\n // Create the buffer if count is provided\n if (!!count) {\n buffer.data = new FSS.Array(count*size);\n }\n\n // Return the buffer\n return buffer;\n};\n\nFSS.WebGLRenderer.VS = function(lights) {\n var shader = [\n\n // Precision\n 'precision mediump float;',\n\n // Lights\n '#define LIGHTS ' + lights,\n\n // Attributes\n 'attribute float aSide;',\n 'attribute vec3 aPosition;',\n 'attribute vec3 aCentroid;',\n 'attribute vec3 aNormal;',\n 'attribute vec4 aAmbient;',\n 'attribute vec4 aDiffuse;',\n\n // Uniforms\n 'uniform vec3 uResolution;',\n 'uniform vec3 uLightPosition[LIGHTS];',\n 'uniform vec4 uLightAmbient[LIGHTS];',\n 'uniform vec4 uLightDiffuse[LIGHTS];',\n\n // Varyings\n 'varying vec4 vColor;',\n\n // Main\n 'void main() {',\n\n // Create color\n 'vColor = vec4(0.0);',\n\n // Calculate the vertex position\n 'vec3 position = aPosition / uResolution * 2.0;',\n\n // Iterate through lights\n 'for (int i = 0; i < LIGHTS; i++) {',\n 'vec3 lightPosition = uLightPosition[i];',\n 'vec4 lightAmbient = uLightAmbient[i];',\n 'vec4 lightDiffuse = uLightDiffuse[i];',\n\n // Calculate illuminance\n 'vec3 ray = normalize(lightPosition - aCentroid);',\n 'float illuminance = dot(aNormal, ray);',\n 'if (aSide == 0.0) {',\n 'illuminance = max(illuminance, 0.0);',\n '} else if (aSide == 1.0) {',\n 'illuminance = abs(min(illuminance, 0.0));',\n '} else if (aSide == 2.0) {',\n 'illuminance = max(abs(illuminance), 0.0);',\n '}',\n\n // Calculate ambient light\n 'vColor += aAmbient * lightAmbient;',\n\n // Calculate diffuse light\n 'vColor += aDiffuse * lightDiffuse * illuminance;',\n '}',\n\n // Clamp color\n 'vColor = clamp(vColor, 0.0, 1.0);',\n\n // Set gl_Position\n 'gl_Position = vec4(position, 1.0);',\n\n '}'\n\n // Return the shader\n ].join('\\n');\n return shader;\n};\n\nFSS.WebGLRenderer.FS = function(lights) {\n var shader = [\n\n // Precision\n 'precision mediump float;',\n\n // Varyings\n 'varying vec4 vColor;',\n\n // Main\n 'void main() {',\n\n // Set gl_FragColor\n 'gl_FragColor = vColor;',\n\n '}'\n\n // Return the shader\n ].join('\\n');\n return shader;\n};\n\n/**\n * @class SVG Renderer\n * @author Matthew Wagerfield\n */\nFSS.SVGRenderer = function() {\n FSS.Renderer.call(this);\n this.element = document.createElementNS(FSS.SVGNS, 'svg');\n this.element.setAttribute('xmlns', FSS.SVGNS);\n this.element.setAttribute('version', '1.1');\n this.element.style.display = 'block';\n this.setSize(300, 150);\n};\n\nFSS.SVGRenderer.prototype = Object.create(FSS.Renderer.prototype);\n\nFSS.SVGRenderer.prototype.setSize = function(width, height) {\n FSS.Renderer.prototype.setSize.call(this, width, height);\n this.element.setAttribute('width', width);\n this.element.setAttribute('height', height);\n return this;\n};\n\nFSS.SVGRenderer.prototype.clear = function() {\n FSS.Renderer.prototype.clear.call(this);\n for (var i = this.element.childNodes.length - 1; i >= 0; i--) {\n this.element.removeChild(this.element.childNodes[i]);\n }\n return this;\n};\n\nFSS.SVGRenderer.prototype.render = function(scene) {\n FSS.Renderer.prototype.render.call(this, scene);\n var m,mesh, t,triangle, points, style;\n\n // Update Meshes\n for (m = scene.meshes.length - 1; m >= 0; m--) {\n mesh = scene.meshes[m];\n if (mesh.visible) {\n mesh.update(scene.lights, true);\n\n // Render Triangles\n for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) {\n triangle = mesh.geometry.triangles[t];\n if (triangle.polygon.parentNode !== this.element) {\n this.element.appendChild(triangle.polygon);\n }\n points = this.formatPoint(triangle.a)+' ';\n points += this.formatPoint(triangle.b)+' ';\n points += this.formatPoint(triangle.c);\n style = this.formatStyle(triangle.color.format());\n triangle.polygon.setAttributeNS(null, 'points', points);\n triangle.polygon.setAttributeNS(null, 'style', style);\n }\n }\n }\n return this;\n};\n\nFSS.SVGRenderer.prototype.formatPoint = function(vertex) {\n return (this.halfWidth+vertex.position[0])+','+(this.halfHeight-vertex.position[1]);\n};\n\nFSS.SVGRenderer.prototype.formatStyle = function(color) {\n var style = 'fill:'+color+';';\n style += 'stroke:'+color+';';\n return style;\n};\n\n\n/*** EXPORTS FROM exports-loader ***/\nmodule.exports = FSS;\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/flat-surface-shader-unofficial/dist/fss.js\n// module id = 5\n// module chunks = 0","/* globals __VUE_SSR_CONTEXT__ */\n\n// this module is a runtime utility for cleaner component module output and will\n// be included in the final webpack user bundle\n\nmodule.exports = function normalizeComponent (\n rawScriptExports,\n compiledTemplate,\n injectStyles,\n scopeId,\n moduleIdentifier /* server only */\n) {\n var esModule\n var scriptExports = rawScriptExports = rawScriptExports || {}\n\n // ES6 modules interop\n var type = typeof rawScriptExports.default\n if (type === 'object' || type === 'function') {\n esModule = rawScriptExports\n scriptExports = rawScriptExports.default\n }\n\n // Vue.extend constructor export interop\n var options = typeof scriptExports === 'function'\n ? scriptExports.options\n : scriptExports\n\n // render functions\n if (compiledTemplate) {\n options.render = compiledTemplate.render\n options.staticRenderFns = compiledTemplate.staticRenderFns\n }\n\n // scopedId\n if (scopeId) {\n options._scopeId = scopeId\n }\n\n var hook\n if (moduleIdentifier) { // server build\n hook = function (context) {\n // 2.3 injection\n context =\n context || // cached call\n (this.$vnode && this.$vnode.ssrContext) || // stateful\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional\n // 2.2 with runInNewContext: true\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\n context = __VUE_SSR_CONTEXT__\n }\n // inject component styles\n if (injectStyles) {\n injectStyles.call(this, context)\n }\n // register component module identifier for async chunk inferrence\n if (context && context._registeredComponents) {\n context._registeredComponents.add(moduleIdentifier)\n }\n }\n // used by ssr in case component is cached and beforeCreate\n // never gets called\n options._ssrRegister = hook\n } else if (injectStyles) {\n hook = injectStyles\n }\n\n if (hook) {\n var functional = options.functional\n var existing = functional\n ? options.render\n : options.beforeCreate\n if (!functional) {\n // inject component registration as beforeCreate hook\n options.beforeCreate = existing\n ? [].concat(existing, hook)\n : [hook]\n } else {\n // register for functioal component in vue file\n options.render = function renderWithStyleInjection (h, context) {\n hook.call(context)\n return existing(h, context)\n }\n }\n }\n\n return {\n esModule: esModule,\n exports: scriptExports,\n options: options\n }\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/vue-loader/lib/component-normalizer.js\n// module id = 6\n// module chunks = 0","module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;\n return _c('div', {\n ref: \"shader\"\n }, [_vm._t(\"default\")], 2)\n},staticRenderFns: []}\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/vue-loader/lib/template-compiler?{\"id\":\"data-v-47119860\",\"hasScoped\":false}!./~/vue-loader/lib/selector.js?type=template&index=0!./src/FSS.vue\n// module id = 7\n// module chunks = 0","// style-loader: Adds some css to the DOM by adding a 42 | 43 | 44 | 45 |
46 | 47 | 48 |
49 |
50 | VUE-FLAT-SURFACE-SHADER 51 |
52 |
53 | A Vue component for Flat Surface Shader 54 |
55 |
56 | Star 57 |
58 |
59 |
60 | 61 | 62 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-flat-surface-shader", 3 | "description": "A Vue component for flat surface shader", 4 | "version": "1.0.5", 5 | "main": "dist/vue.fss.js", 6 | "author": "grzhan ", 7 | "license": "MIT", 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 11 | }, 12 | "dependencies": { 13 | "flat-surface-shader-unofficial": "^0.1.0" 14 | }, 15 | "devDependencies": { 16 | "babel-core": "^6.0.0", 17 | "babel-loader": "^6.0.0", 18 | "babel-preset-env": "^1.5.1", 19 | "cross-env": "^3.0.0", 20 | "css-loader": "^0.25.0", 21 | "exports-loader": "^0.6.4", 22 | "file-loader": "^0.9.0", 23 | "vue": "^2.4.2", 24 | "vue-loader": "^12.1.0", 25 | "vue-template-compiler": "^2.3.3", 26 | "webpack": "^2.6.1", 27 | "webpack-dev-server": "^2.4.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/FSS.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 305 | 306 | 308 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import FlatSurfaceShader from './FSS.vue' 2 | 3 | const install = (Vue) => { 4 | Vue.component("flat-surface-shader", FlatSurfaceShader) 5 | } 6 | 7 | export default { 8 | install, 9 | } 10 | 11 | export { 12 | FlatSurfaceShader 13 | } 14 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'vue.fss.js', 10 | library: 'vue-flat-surface-shader', 11 | libraryTarget: 'umd', 12 | umdNamedDefine: true 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.vue$/, 18 | loader: 'vue-loader', 19 | options: { 20 | loaders: { 21 | } 22 | // other vue-loader options go here 23 | } 24 | }, 25 | { 26 | test: /\.js$/, 27 | loader: 'babel-loader', 28 | exclude: /node_modules/ 29 | }, 30 | { 31 | test: /\.(png|jpg|gif|svg)$/, 32 | loader: 'file-loader', 33 | options: { 34 | name: '[name].[ext]?[hash]' 35 | } 36 | }, 37 | { 38 | test: require.resolve('flat-surface-shader-unofficial'), 39 | loader: "exports-loader?FSS" 40 | } 41 | ] 42 | }, 43 | resolve: { 44 | alias: { 45 | 'vue$': 'vue/dist/vue.esm.js' 46 | } 47 | }, 48 | devServer: { 49 | historyApiFallback: true, 50 | noInfo: true 51 | }, 52 | performance: { 53 | hints: false 54 | }, 55 | devtool: '#eval-source-map' 56 | } 57 | 58 | if (process.env.NODE_ENV === 'production') { 59 | module.exports.devtool = '#source-map' 60 | // http://vue-loader.vuejs.org/en/workflow/production.html 61 | module.exports.plugins = (module.exports.plugins || []).concat([ 62 | new webpack.DefinePlugin({ 63 | 'process.env': { 64 | NODE_ENV: '"production"' 65 | } 66 | }), 67 | new webpack.optimize.UglifyJsPlugin({ 68 | sourceMap: true, 69 | compress: { 70 | warnings: false 71 | } 72 | }), 73 | new webpack.LoaderOptionsPlugin({ 74 | minimize: true 75 | }) 76 | ]) 77 | } 78 | --------------------------------------------------------------------------------