├── .gitignore
├── .htaccess
├── Makefile
├── README.md
├── index.html
├── lib
└── require.js
├── release.sh
├── screenshot.jpg
├── shaders
├── addForce.frag
├── advect.frag
├── boundary.vertex
├── copy.frag
├── cursor.vertex
├── divergence.frag
├── jacobi.frag
├── kernel.vertex
├── subtractPressureGradient.frag
├── velocityBoundary.frag
└── visualize.frag
├── simulation.png
├── src
├── almond.js
├── compute.js
├── dat.gui.js
├── engine
│ ├── application.js
│ ├── cameracontroller.js
│ ├── clock.js
│ ├── gl
│ │ ├── _webgl-debug.js
│ │ ├── context.js
│ │ ├── geometry.js
│ │ ├── mesh.js
│ │ ├── shader.js
│ │ └── texture.js
│ ├── input.js
│ ├── loader.js
│ ├── renderer
│ │ └── scene
│ │ │ ├── camera.js
│ │ │ ├── light.js
│ │ │ ├── model.js
│ │ │ ├── node.js
│ │ │ ├── root.js
│ │ │ └── scene.js
│ └── utils.js
├── game-shim.js
├── gl-matrix.js
└── main.js
└── style.css
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | FileETag None
2 | Header unset ETag
3 | Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
4 | Header set Pragma "no-cache"
5 | Header set Expires "Wed, 12 Jan 1984 05:00:00 GMT"
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | min:
2 | mkdir -p build
3 | r.js -o baseUrl=src name=almond include=main insertRequire=main out=build/main.js wrap=true
4 | sed 's/lib\/require\.js/main.js/g' index.html| sed 's/data-main=src\/main/ /g' > build/index.html
5 | cp -r *.png *.jpg *.css shaders build
6 |
7 | sync:
8 | rsync -vrt --copy-links --exclude release.sh build/* 29a.ch:/var/www/29a.ch/sandbox/2012/fluidwebgl/
9 |
10 | release: min sync
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a simple simulation of non compressible fluid flow using WebGL GPU Computing.
2 |
3 | Learn more about it [on my blog](http://29a.ch/2012/12/16/webgl-fluid-simulation) or go straight to the [demo](http://29a.ch/sandbox/2012/fluidwebgl/).
4 |
5 | # License
6 |
7 | This code is not released under an open source license.
8 | You are free to look at it and learn from it but you can't use it for your own projects.
9 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WebGL Fluid Dynamics based on Navier Stokes Equations
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
27 |
28 |
29 |
30 |
31 |
Your system does not support WebGL which is required for this demo. You can try to
32 | get WebGL to work or try out the older, more simple,
33 | canvas version of this demo.
34 |
35 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | rsync -vrt --copy-links --exclude release.sh --exclude .git * x.29a.ch:/var/www/29a.ch/sandbox/2012/fluidwebgl
3 |
--------------------------------------------------------------------------------
/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwagner/fluidwebgl/a567799d063c13842356cbd1a5d91e0c649529a1/screenshot.jpg
--------------------------------------------------------------------------------
/shaders/addForce.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | uniform vec2 force;
4 | uniform vec2 center;
5 | uniform vec2 scale;
6 | uniform vec2 px;
7 | varying vec2 uv;
8 |
9 | void main(){
10 | float distance_ = 1.0-min(length((uv-center)/scale), 1.0);
11 | /*vec2 force = velocity*distance_;*/
12 | gl_FragColor = vec4(force*distance_, 0, 1);
13 | /*gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);*/
14 | }
15 |
--------------------------------------------------------------------------------
/shaders/advect.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform sampler2D source;
3 | uniform sampler2D velocity;
4 | uniform float dt;
5 | uniform float scale;
6 | uniform vec2 px1;
7 | varying vec2 uv;
8 |
9 | void main(){
10 | gl_FragColor = texture2D(source, uv-texture2D(velocity, uv).xy*dt*px1)*scale;
11 | }
12 |
--------------------------------------------------------------------------------
/shaders/boundary.vertex:
--------------------------------------------------------------------------------
1 | attribute vec3 position;
2 | attribute vec3 offset;
3 | varying vec2 uv;
4 |
5 | precision highp float;
6 |
7 | void main(){
8 | uv = offset.xy*0.5+0.5;
9 | gl_Position = vec4(position, 1.0);
10 | }
11 |
--------------------------------------------------------------------------------
/shaders/copy.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform sampler2D source;
3 | varying vec2 uv;
4 |
5 | void main(){
6 | gl_FragColor = texture2D(source, uv);
7 | }
8 |
--------------------------------------------------------------------------------
/shaders/cursor.vertex:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | attribute vec3 position;
4 | uniform vec2 center;
5 | uniform vec2 px;
6 | varying vec2 uv;
7 |
8 |
9 | void main(){
10 | uv = clamp(position.xy+center, vec2(-1.0+px*2.0), vec2(1.0-px*2.0));
11 | gl_Position = vec4(uv, 0.0, 1.0);
12 | }
13 |
--------------------------------------------------------------------------------
/shaders/divergence.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform sampler2D velocity;
3 | uniform float dt;
4 | uniform vec2 px;
5 | varying vec2 uv;
6 |
7 | void main(){
8 | float x0 = texture2D(velocity, uv-vec2(px.x, 0)).x;
9 | float x1 = texture2D(velocity, uv+vec2(px.x, 0)).x;
10 | float y0 = texture2D(velocity, uv-vec2(0, px.y)).y;
11 | float y1 = texture2D(velocity, uv+vec2(0, px.y)).y;
12 | float divergence = (x1-x0 + y1-y0)*0.5;
13 | gl_FragColor = vec4(divergence);
14 | }
15 |
--------------------------------------------------------------------------------
/shaders/jacobi.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform sampler2D pressure;
3 | uniform sampler2D divergence;
4 | uniform float alpha;
5 | uniform float beta;
6 | uniform vec2 px;
7 | varying vec2 uv;
8 |
9 | void main(){
10 | float x0 = texture2D(pressure, uv-vec2(px.x, 0)).r;
11 | float x1 = texture2D(pressure, uv+vec2(px.x, 0)).r;
12 | float y0 = texture2D(pressure, uv-vec2(0, px.y)).r;
13 | float y1 = texture2D(pressure, uv+vec2(0, px.y)).r;
14 | float d = texture2D(divergence, uv).r;
15 | float relaxed = (x0 + x1 + y0 + y1 + alpha * d) * beta;
16 | gl_FragColor = vec4(relaxed);
17 | }
18 |
--------------------------------------------------------------------------------
/shaders/kernel.vertex:
--------------------------------------------------------------------------------
1 | attribute vec3 position;
2 | uniform vec2 px;
3 | varying vec2 uv;
4 |
5 | precision highp float;
6 |
7 | void main(){
8 | uv = vec2(0.5)+(position.xy)*0.5;
9 | gl_Position = vec4(position, 1.0);
10 | }
11 |
--------------------------------------------------------------------------------
/shaders/subtractPressureGradient.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform sampler2D pressure;
3 | uniform sampler2D velocity;
4 | uniform float alpha;
5 | uniform float beta;
6 | uniform float scale;
7 | uniform vec2 px;
8 | varying vec2 uv;
9 |
10 | void main(){
11 | float x0 = texture2D(pressure, uv-vec2(px.x, 0)).r;
12 | float x1 = texture2D(pressure, uv+vec2(px.x, 0)).r;
13 | float y0 = texture2D(pressure, uv-vec2(0, px.y)).r;
14 | float y1 = texture2D(pressure, uv+vec2(0, px.y)).r;
15 | vec2 v = texture2D(velocity, uv).xy;
16 | gl_FragColor = vec4((v-(vec2(x1, y1)-vec2(x0, y0))*0.5)*scale, 1.0, 1.0);
17 | }
18 |
--------------------------------------------------------------------------------
/shaders/velocityBoundary.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform sampler2D velocity;
3 | uniform float dt;
4 | uniform float scale;
5 | varying vec2 uv;
6 |
7 | vec2 velocityAt(uv){
8 | return texture2D(velocity, uv-texture2D(velocity, uv).xy*dt);
9 | }
10 |
11 | void main(){
12 | gl_FragColor = vec4(scale*velocityAt(uv), 1.0, 1.0);
13 | }
14 |
--------------------------------------------------------------------------------
/shaders/visualize.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | uniform sampler2D velocity;
3 | uniform sampler2D pressure;
4 | varying vec2 uv;
5 |
6 | void main(){
7 | gl_FragColor = vec4(
8 | (texture2D(pressure, uv)).x,
9 | (texture2D(velocity, uv)*1.5+0.5).xy,
10 | 1.0);
11 | }
12 |
--------------------------------------------------------------------------------
/simulation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwagner/fluidwebgl/a567799d063c13842356cbd1a5d91e0c649529a1/simulation.png
--------------------------------------------------------------------------------
/src/almond.js:
--------------------------------------------------------------------------------
1 | var requirejs,require,define;(function(e){function a(e,t){var n=t&&t.split("/"),i=r.map,s=i&&i["*"]||{},o,u,a,f,l,c,h,p,d,v;if(e&&e.charAt(0)==="."&&t){n=n.slice(0,n.length-1),e=n.concat(e.split("/"));for(p=0;v=e[p];p++)if(v===".")e.splice(p,1),p-=1;else if(v===".."){if(p===1&&(e[2]===".."||e[0]===".."))return!0;p>0&&(e.splice(p-1,2),p-=2)}e=e.join("/")}if((n||s)&&i){o=e.split("/");for(p=o.length;p>0;p-=1){u=o.slice(0,p).join("/");if(n)for(d=n.length;d>0;d-=1){a=i[n.slice(0,d).join("/")];if(a){a=a[u];if(a){f=a,l=p;break}}}if(f)break;!c&&s&&s[u]&&(c=s[u],h=p)}!f&&c&&(f=c,l=h),f&&(o.splice(0,l,f),e=o.join("/"))}return e}function f(t,n){return function(){return u.apply(e,s.call(arguments,0).concat([t,n]))}}function l(e){return function(t){return a(t,e)}}function c(e){return function(n){t[e]=n}}function h(r){if(n.hasOwnProperty(r)){var s=n[r];delete n[r],i[r]=!0,o.apply(e,s)}if(!t.hasOwnProperty(r))throw Error("No "+r);return t[r]}function p(e,t){var n,r,i=e.indexOf("!");return i!==-1?(n=a(e.slice(0,i),t),e=e.slice(i+1),r=h(n),r&&r.normalize?e=r.normalize(e,l(t)):e=a(e,t)):e=a(e,t),{f:n?n+"!"+e:e,n:e,p:r}}function d(e){return function(){return r&&r.config&&r.config[e]||{}}}var t={},n={},r={},i={},s=[].slice,o,u;o=function(r,s,o,u){var a=[],l,v,m,g,y,b;u=u||r;if(typeof o=="function"){s=!s.length&&o.length?["require","exports","module"]:s;for(b=0;b=0;f--)d=[c[f].apply(this,d)];return d[0]}},
15 | each:function(a,d,f){if(e&&a.forEach===e)a.forEach(d,f);else if(a.length===a.length+0)for(var b=0,n=a.length;b-1?d.length-d.indexOf(".")-1:0};c.superclass=e;a.extend(c.prototype,e.prototype,{setValue:function(a){if(this.__min!==void 0&&athis.__max)a=this.__max;this.__step!==void 0&&a%this.__step!=0&&(a=Math.round(a/this.__step)*this.__step);return c.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__step=a;return this}});return c}(dat.controllers.Controller,dat.utils.common);
29 | dat.controllers.NumberControllerBox=function(e,a,c){var d=function(f,b,e){function h(){var a=parseFloat(l.__input.value);c.isNaN(a)||l.setValue(a)}function j(a){var b=o-a.clientY;l.setValue(l.getValue()+b*l.__impliedStep);o=a.clientY}function m(){a.unbind(window,"mousemove",j);a.unbind(window,"mouseup",m)}this.__truncationSuspended=false;d.superclass.call(this,f,b,e);var l=this,o;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",h);
30 | a.bind(this.__input,"blur",function(){h();l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",j);a.bind(window,"mouseup",m);o=b.clientY});a.bind(this.__input,"keydown",function(a){if(a.keyCode===13)l.__truncationSuspended=true,this.blur(),l.__truncationSuspended=false});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype,e.prototype,{updateDisplay:function(){var a=this.__input,
31 | b;if(this.__truncationSuspended)b=this.getValue();else{b=this.getValue();var c=Math.pow(10,this.__precision);b=Math.round(b*c)/c}a.value=b;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common);
32 | dat.controllers.NumberControllerSlider=function(e,a,c,d,f){var b=function(d,c,f,e,l){function o(b){b.preventDefault();var d=a.getOffset(g.__background),c=a.getWidth(g.__background);g.setValue(g.__min+(g.__max-g.__min)*((b.clientX-d.left)/(d.left+c-d.left)));return false}function y(){a.unbind(window,"mousemove",o);a.unbind(window,"mouseup",y);g.__onFinishChange&&g.__onFinishChange.call(g,g.getValue())}b.superclass.call(this,d,c,{min:f,max:e,step:l});var g=this;this.__background=document.createElement("div");
33 | this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",o);a.bind(window,"mouseup",y);o(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};b.superclass=e;b.useDefaultStyles=function(){c.inject(f)};d.extend(b.prototype,e.prototype,{updateDisplay:function(){this.__foreground.style.width=
34 | (this.getValue()-this.__min)/(this.__max-this.__min)*100+"%";return b.superclass.prototype.updateDisplay.call(this)}});return b}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}");
35 | dat.controllers.FunctionController=function(e,a,c){var d=function(c,b,e){d.superclass.call(this,c,b);var h=this;this.__button=document.createElement("div");this.__button.innerHTML=e===void 0?"Fire":e;a.bind(this.__button,"click",function(a){a.preventDefault();h.fire();return false});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};d.superclass=e;c.extend(d.prototype,e.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.__onFinishChange&&this.__onFinishChange.call(this,
36 | this.getValue());this.getValue().call(this.object)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common);
37 | dat.controllers.BooleanController=function(e,a,c){var d=function(c,b){d.superclass.call(this,c,b);var e=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){e.setValue(!e.__prev)},false);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};d.superclass=e;c.extend(d.prototype,e.prototype,{setValue:function(a){a=d.superclass.prototype.setValue.call(this,a);this.__onFinishChange&&
38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){this.getValue()===true?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=true):this.__checkbox.checked=false;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common);
39 | dat.color.toString=function(e){return function(a){if(a.a==1||e.isUndefined(a.a)){for(a=a.hex.toString(16);a.length<6;)a="0"+a;return"#"+a}else return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common);
40 | dat.color.interpret=function(e,a){var c,d,f=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:e},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:e},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
41 | return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:e},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:e}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return a.length!=
42 | 3?false:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return a.length!=4?false:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:false},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&&
43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:false},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:false},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:false},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){d=
44 | false;var b=arguments.length>1?a.toArray(arguments):arguments[0];a.each(f,function(e){if(e.litmus(b))return a.each(e.conversions,function(e,f){c=e.read(b);if(d===false&&c!==false)return d=c,c.conversionName=f,c.conversion=e,a.BREAK}),a.BREAK});return d}}(dat.color.toString,dat.utils.common);
45 | dat.GUI=dat.gui.GUI=function(e,a,c,d,f,b,n,h,j,m,l,o,y,g,i){function q(a,b,r,c){if(b[r]===void 0)throw Error("Object "+b+' has no property "'+r+'"');c.color?b=new l(b,r):(b=[b,r].concat(c.factoryArgs),b=d.apply(a,b));if(c.before instanceof f)c.before=c.before.__li;t(a,b);g.addClass(b.domElement,"c");r=document.createElement("span");g.addClass(r,"property-name");r.innerHTML=b.property;var e=document.createElement("div");e.appendChild(r);e.appendChild(b.domElement);c=s(a,e,c.before);g.addClass(c,k.CLASS_CONTROLLER_ROW);
46 | g.addClass(c,typeof b.getValue());p(a,c,b);a.__controllers.push(b);return b}function s(a,b,d){var c=document.createElement("li");b&&c.appendChild(b);d?a.__ul.insertBefore(c,params.before):a.__ul.appendChild(c);a.onResize();return c}function p(a,d,c){c.__li=d;c.__gui=a;i.extend(c,{options:function(b){if(arguments.length>1)return c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[i.toArray(arguments)]});if(i.isArray(b)||i.isObject(b))return c.remove(),q(a,c.object,c.property,
47 | {before:c.__li.nextElementSibling,factoryArgs:[b]})},name:function(a){c.__li.firstElementChild.firstElementChild.innerHTML=a;return c},listen:function(){c.__gui.listen(c);return c},remove:function(){c.__gui.remove(c);return c}});if(c instanceof j){var e=new h(c.object,c.property,{min:c.__min,max:c.__max,step:c.__step});i.each(["updateDisplay","onChange","onFinishChange"],function(a){var b=c[a],H=e[a];c[a]=e[a]=function(){var a=Array.prototype.slice.call(arguments);b.apply(c,a);return H.apply(e,a)}});
48 | g.addClass(d,"has-slider");c.domElement.insertBefore(e.domElement,c.domElement.firstElementChild)}else if(c instanceof h){var f=function(b){return i.isNumber(c.__min)&&i.isNumber(c.__max)?(c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[c.__min,c.__max,c.__step]})):b};c.min=i.compose(f,c.min);c.max=i.compose(f,c.max)}else if(c instanceof b)g.bind(d,"click",function(){g.fakeEvent(c.__checkbox,"click")}),g.bind(c.__checkbox,"click",function(a){a.stopPropagation()});
49 | else if(c instanceof n)g.bind(d,"click",function(){g.fakeEvent(c.__button,"click")}),g.bind(d,"mouseover",function(){g.addClass(c.__button,"hover")}),g.bind(d,"mouseout",function(){g.removeClass(c.__button,"hover")});else if(c instanceof l)g.addClass(d,"color"),c.updateDisplay=i.compose(function(a){d.style.borderLeftColor=c.__color.toString();return a},c.updateDisplay),c.updateDisplay();c.setValue=i.compose(function(b){a.getRoot().__preset_select&&c.isModified()&&B(a.getRoot(),true);return b},c.setValue)}
50 | function t(a,b){var c=a.getRoot(),d=c.__rememberedObjects.indexOf(b.object);if(d!=-1){var e=c.__rememberedObjectIndecesToControllers[d];e===void 0&&(e={},c.__rememberedObjectIndecesToControllers[d]=e);e[b.property]=b;if(c.load&&c.load.remembered){c=c.load.remembered;if(c[a.preset])c=c[a.preset];else if(c[w])c=c[w];else return;if(c[d]&&c[d][b.property]!==void 0)d=c[d][b.property],b.initialValue=d,b.setValue(d)}}}function I(a){var b=a.__save_row=document.createElement("li");g.addClass(a.domElement,
51 | "has-save");a.__ul.insertBefore(b,a.__ul.firstChild);g.addClass(b,"save-row");var c=document.createElement("span");c.innerHTML=" ";g.addClass(c,"button gears");var d=document.createElement("span");d.innerHTML="Save";g.addClass(d,"button");g.addClass(d,"save");var e=document.createElement("span");e.innerHTML="New";g.addClass(e,"button");g.addClass(e,"save-as");var f=document.createElement("span");f.innerHTML="Revert";g.addClass(f,"button");g.addClass(f,"revert");var m=a.__preset_select=document.createElement("select");
52 | a.load&&a.load.remembered?i.each(a.load.remembered,function(b,c){C(a,c,c==a.preset)}):C(a,w,false);g.bind(m,"change",function(){for(var b=0;b0){a.preset=this.preset;if(!a.remembered)a.remembered={};a.remembered[this.preset]=z(this)}a.folders={};i.each(this.__folders,function(b,
69 | c){a.folders[c]=b.getSaveObject()});return a},save:function(){if(!this.load.remembered)this.load.remembered={};this.load.remembered[this.preset]=z(this);B(this,false)},saveAs:function(a){if(!this.load.remembered)this.load.remembered={},this.load.remembered[w]=z(this,true);this.load.remembered[a]=z(this);this.preset=a;C(this,a,true)},revert:function(a){i.each(this.__controllers,function(b){this.getRoot().load.remembered?t(a||this.getRoot(),b):b.setValue(b.initialValue)},this);i.each(this.__folders,
70 | function(a){a.revert(a)});a||B(this.getRoot(),false)},listen:function(a){var b=this.__listening.length==0;this.__listening.push(a);b&&E(this.__listening)}});return k}(dat.utils.css,'\n\n Here\'s the new load parameter for your
GUI
\'s constructor:\n\n
\n\n
\n\n
Automatically save\n values to
localStorage
on exit.\n\n
The values saved to localStorage
will\n override those passed to dat.GUI
\'s constructor. This makes it\n easier to work incrementally, but localStorage
is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
',
71 | ".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save ul{margin-top:27px}.dg.a.has-save ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height 0.1s ease-out;-o-transition:height 0.1s ease-out;-moz-transition:height 0.1s ease-out;transition:height 0.1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li > *{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:9px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2fa1d6}.dg .cr.number input[type=text]{color:#2fa1d6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2fa1d6}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n",
72 | dat.controllers.factory=function(e,a,c,d,f,b,n){return function(h,j,m,l){var o=h[j];if(n.isArray(m)||n.isObject(m))return new e(h,j,m);if(n.isNumber(o))return n.isNumber(m)&&n.isNumber(l)?new c(h,j,m,l):new a(h,j,{min:m,max:l});if(n.isString(o))return new d(h,j);if(n.isFunction(o))return new f(h,j,"");if(n.isBoolean(o))return new b(h,j)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(e,a,c){var d=
73 | function(c,b){function e(){h.setValue(h.__input.value)}d.superclass.call(this,c,b);var h=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",e);a.bind(this.__input,"change",e);a.bind(this.__input,"blur",function(){h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())});a.bind(this.__input,"keydown",function(a){a.keyCode===13&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype,
74 | e.prototype,{updateDisplay:function(){if(!a.isActive(this.__input))this.__input.value=this.getValue();return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController,
75 | dat.controllers.ColorController=function(e,a,c,d,f){function b(a,b,c,d){a.style.background="";f.each(j,function(e){a.style.cssText+="background: "+e+"linear-gradient("+b+", "+c+" 0%, "+d+" 100%); "})}function n(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";
76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var h=function(e,l){function o(b){q(b);a.bind(window,"mousemove",q);a.bind(window,
77 | "mouseup",j)}function j(){a.unbind(window,"mousemove",q);a.unbind(window,"mouseup",j)}function g(){var a=d(this.value);a!==false?(p.__color.__state=a,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function i(){a.unbind(window,"mousemove",s);a.unbind(window,"mouseup",i)}function q(b){b.preventDefault();var c=a.getWidth(p.__saturation_field),d=a.getOffset(p.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c,b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b=
78 | 1:b<0&&(b=0);e>1?e=1:e<0&&(e=0);p.__color.v=b;p.__color.s=e;p.setValue(p.__color.toOriginal());return false}function s(b){b.preventDefault();var c=a.getHeight(p.__hue_field),d=a.getOffset(p.__hue_field),b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b=1:b<0&&(b=0);p.__color.h=b*360;p.setValue(p.__color.toOriginal());return false}h.superclass.call(this,e,l);this.__color=new c(this.getValue());this.__temp=new c(0);var p=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,
79 | false);this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=
80 | document.createElement("input");this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){a.keyCode===13&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(){a.addClass(this,"drag").bind(window,"mouseup",function(){a.removeClass(p.__selector,"drag")})});var t=document.createElement("div");f.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});
81 | f.extend(this.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(this.__color.v<0.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});f.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});f.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});f.extend(t.style,
82 | {width:"100%",height:"100%",background:"none"});b(t,"top","rgba(0,0,0,0)","#000");f.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});n(this.__hue_field);f.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",o);a.bind(this.__field_knob,"mousedown",o);a.bind(this.__hue_field,"mousedown",
83 | function(b){s(b);a.bind(window,"mousemove",s);a.bind(window,"mouseup",i)});this.__saturation_field.appendChild(t);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};h.superclass=e;f.extend(h.prototype,e.prototype,{updateDisplay:function(){var a=d(this.getValue());
84 | if(a!==false){var e=false;f.each(c.COMPONENTS,function(b){if(!f.isUndefined(a[b])&&!f.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return e=true,{}},this);e&&f.extend(this.__color.__state,a)}f.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var h=this.__color.v<0.5||this.__color.s>0.5?255:0,j=255-h;f.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toString(),border:this.__field_knob_border+
85 | "rgb("+h+","+h+","+h+")"});this.__hue_knob.style.marginTop=(1-this.__color.h/360)*100+"px";this.__temp.s=1;this.__temp.v=1;b(this.__saturation_field,"left","#fff",this.__temp.toString());f.extend(this.__input.style,{backgroundColor:this.__input.value=this.__color.toString(),color:"rgb("+h+","+h+","+h+")",textShadow:this.__input_textShadow+"rgba("+j+","+j+","+j+",.7)"})}});var j=["-moz-","-o-","-webkit-","-ms-",""];return h}(dat.controllers.Controller,dat.dom.dom,dat.color.Color=function(e,a,c,d){function f(a,
86 | b,c){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="RGB")return this.__state[b];n(this,b,c);return this.__state[b]},set:function(a){if(this.__state.space!=="RGB")n(this,b,c),this.__state.space="RGB";this.__state[b]=a}})}function b(a,b){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="HSV")return this.__state[b];h(this);return this.__state[b]},set:function(a){if(this.__state.space!=="HSV")h(this),this.__state.space="HSV";this.__state[b]=a}})}function n(b,c,e){if(b.__state.space===
87 | "HEX")b.__state[c]=a.component_from_hex(b.__state.hex,e);else if(b.__state.space==="HSV")d.extend(b.__state,a.hsv_to_rgb(b.__state.h,b.__state.s,b.__state.v));else throw"Corrupted color state";}function h(b){var c=a.rgb_to_hsv(b.r,b.g,b.b);d.extend(b.__state,{s:c.s,v:c.v});if(d.isNaN(c.h)){if(d.isUndefined(b.__state.h))b.__state.h=0}else b.__state.h=c.h}var j=function(){this.__state=e.apply(this,arguments);if(this.__state===false)throw"Failed to interpret color arguments";this.__state.a=this.__state.a||
88 | 1};j.COMPONENTS="r,g,b,h,s,v,hex,a".split(",");d.extend(j.prototype,{toString:function(){return c(this)},toOriginal:function(){return this.__state.conversion.write(this)}});f(j.prototype,"r",2);f(j.prototype,"g",1);f(j.prototype,"b",0);b(j.prototype,"h");b(j.prototype,"s");b(j.prototype,"v");Object.defineProperty(j.prototype,"a",{get:function(){return this.__state.a},set:function(a){this.__state.a=a}});Object.defineProperty(j.prototype,"hex",{get:function(){if(!this.__state.space!=="HEX")this.__state.hex=
89 | a.rgb_to_hex(this.r,this.g,this.b);return this.__state.hex},set:function(a){this.__state.space="HEX";this.__state.hex=a}});return j}(dat.color.interpret,dat.color.math=function(){var e;return{hsv_to_rgb:function(a,c,d){var e=a/60-Math.floor(a/60),b=d*(1-c),n=d*(1-e*c),c=d*(1-(1-e)*c),a=[[d,c,b],[n,d,b],[b,d,c],[b,n,d],[c,b,d],[d,b,n]][Math.floor(a/60)%6];return{r:a[0]*255,g:a[1]*255,b:a[2]*255}},rgb_to_hsv:function(a,c,d){var e=Math.min(a,c,d),b=Math.max(a,c,d),e=b-e;if(b==0)return{h:NaN,s:0,v:0};
90 | a=a==b?(c-d)/e:c==b?2+(d-a)/e:4+(a-c)/e;a/=6;a<0&&(a+=1);return{h:a*360,s:e/b,v:b/255}},rgb_to_hex:function(a,c,d){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,c);return a=this.hex_with_component(a,0,d)},component_from_hex:function(a,c){return a>>c*8&255},hex_with_component:function(a,c,d){return d<<(e=c*8)|a&~(255< 0){
23 | this.ontick(dt);
24 | }
25 | },
26 | start: function (element) {
27 | this.running = true;
28 | var self = this, f;
29 | if(requestAnimationFrame){
30 | requestAnimationFrame(f = function () {
31 | self.tick();
32 | if(self.running){
33 | requestAnimationFrame(f, element);
34 | }
35 | }, element);
36 | }
37 | else {
38 | this.interval = window.setInterval(function() {
39 | self.tick();
40 | }, 1);
41 | }
42 | this.t0 = this.now();
43 | },
44 | stop: function() {
45 | if(this.interval){
46 | window.clearInterval(this.interval);
47 | this.interval = null;
48 | }
49 | this.running = false;
50 | },
51 | now: function(){
52 | //return new Date();
53 | return window.performance.now();
54 | },
55 | ontick: function(dt){
56 | }
57 | };
58 |
59 | clock.fixedstep = function(step, integrate, render){
60 | var accumulated = 0,
61 | t = 0;
62 | return function(dt){
63 | accumulated += dt;
64 | while(accumulated >= step){
65 | integrate(step, t);
66 | accumulated -= step;
67 | dt -= step;
68 | t += step;
69 | }
70 | render(dt, t);
71 | };
72 | };
73 |
74 | });
75 |
--------------------------------------------------------------------------------
/src/engine/gl/_webgl-debug.js:
--------------------------------------------------------------------------------
1 | /*
2 | ** Copyright (c) 2012 The Khronos Group Inc.
3 | **
4 | ** Permission is hereby granted, free of charge, to any person obtaining a
5 | ** copy of this software and/or associated documentation files (the
6 | ** "Materials"), to deal in the Materials without restriction, including
7 | ** without limitation the rights to use, copy, modify, merge, publish,
8 | ** distribute, sublicense, and/or sell copies of the Materials, and to
9 | ** permit persons to whom the Materials are furnished to do so, subject to
10 | ** the following conditions:
11 | **
12 | ** The above copyright notice and this permission notice shall be included
13 | ** in all copies or substantial portions of the Materials.
14 | **
15 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
22 | */
23 |
24 | // Various functions for helping debug WebGL apps.
25 |
26 | WebGLDebugUtils = function() {
27 |
28 | /**
29 | * Wrapped logging function.
30 | * @param {string} msg Message to log.
31 | */
32 | var log = function(msg) {
33 | if (window.console && window.console.log) {
34 | window.console.log(msg);
35 | }
36 | };
37 |
38 | /**
39 | * Wrapped error logging function.
40 | * @param {string} msg Message to log.
41 | */
42 | var error = function(msg) {
43 | if (window.console && window.console.error) {
44 | window.console.error(msg);
45 | } else {
46 | log(msg);
47 | }
48 | };
49 |
50 | /**
51 | * Which arguements are enums.
52 | * @type {!Object.}
53 | */
54 | var glValidEnumContexts = {
55 |
56 | // Generic setters and getters
57 |
58 | 'enable': { 0:true },
59 | 'disable': { 0:true },
60 | 'getParameter': { 0:true },
61 |
62 | // Rendering
63 |
64 | 'drawArrays': { 0:true },
65 | 'drawElements': { 0:true, 2:true },
66 |
67 | // Shaders
68 |
69 | 'createShader': { 0:true },
70 | 'getShaderParameter': { 1:true },
71 | 'getProgramParameter': { 1:true },
72 |
73 | // Vertex attributes
74 |
75 | 'getVertexAttrib': { 1:true },
76 | 'vertexAttribPointer': { 2:true },
77 |
78 | // Textures
79 |
80 | 'bindTexture': { 0:true },
81 | 'activeTexture': { 0:true },
82 | 'getTexParameter': { 0:true, 1:true },
83 | 'texParameterf': { 0:true, 1:true },
84 | 'texParameteri': { 0:true, 1:true, 2:true },
85 | 'texImage2D': { 0:true, 2:true, 6:true, 7:true },
86 | 'texSubImage2D': { 0:true, 6:true, 7:true },
87 | 'copyTexImage2D': { 0:true, 2:true },
88 | 'copyTexSubImage2D': { 0:true },
89 | 'generateMipmap': { 0:true },
90 |
91 | // Buffer objects
92 |
93 | 'bindBuffer': { 0:true },
94 | 'bufferData': { 0:true, 2:true },
95 | 'bufferSubData': { 0:true },
96 | 'getBufferParameter': { 0:true, 1:true },
97 |
98 | // Renderbuffers and framebuffers
99 |
100 | 'pixelStorei': { 0:true, 1:true },
101 | 'readPixels': { 4:true, 5:true },
102 | 'bindRenderbuffer': { 0:true },
103 | 'bindFramebuffer': { 0:true },
104 | 'checkFramebufferStatus': { 0:true },
105 | 'framebufferRenderbuffer': { 0:true, 1:true, 2:true },
106 | 'framebufferTexture2D': { 0:true, 1:true, 2:true },
107 | 'getFramebufferAttachmentParameter': { 0:true, 1:true, 2:true },
108 | 'getRenderbufferParameter': { 0:true, 1:true },
109 | 'renderbufferStorage': { 0:true, 1:true },
110 |
111 | // Frame buffer operations (clear, blend, depth test, stencil)
112 |
113 | 'clear': { 0:true },
114 | 'depthFunc': { 0:true },
115 | 'blendFunc': { 0:true, 1:true },
116 | 'blendFuncSeparate': { 0:true, 1:true, 2:true, 3:true },
117 | 'blendEquation': { 0:true },
118 | 'blendEquationSeparate': { 0:true, 1:true },
119 | 'stencilFunc': { 0:true },
120 | 'stencilFuncSeparate': { 0:true, 1:true },
121 | 'stencilMaskSeparate': { 0:true },
122 | 'stencilOp': { 0:true, 1:true, 2:true },
123 | 'stencilOpSeparate': { 0:true, 1:true, 2:true, 3:true },
124 |
125 | // Culling
126 |
127 | 'cullFace': { 0:true },
128 | 'frontFace': { 0:true },
129 | };
130 |
131 | /**
132 | * Map of numbers to names.
133 | * @type {Object}
134 | */
135 | var glEnums = null;
136 |
137 | /**
138 | * Initializes this module. Safe to call more than once.
139 | * @param {!WebGLRenderingContext} ctx A WebGL context. If
140 | * you have more than one context it doesn't matter which one
141 | * you pass in, it is only used to pull out constants.
142 | */
143 | function init(ctx) {
144 | if (glEnums == null) {
145 | glEnums = { };
146 | for (var propertyName in ctx) {
147 | if (typeof ctx[propertyName] == 'number') {
148 | glEnums[ctx[propertyName]] = propertyName;
149 | }
150 | }
151 | }
152 | }
153 |
154 | /**
155 | * Checks the utils have been initialized.
156 | */
157 | function checkInit() {
158 | if (glEnums == null) {
159 | throw 'WebGLDebugUtils.init(ctx) not called';
160 | }
161 | }
162 |
163 | /**
164 | * Returns true or false if value matches any WebGL enum
165 | * @param {*} value Value to check if it might be an enum.
166 | * @return {boolean} True if value matches one of the WebGL defined enums
167 | */
168 | function mightBeEnum(value) {
169 | checkInit();
170 | return (glEnums[value] !== undefined);
171 | }
172 |
173 | /**
174 | * Gets an string version of an WebGL enum.
175 | *
176 | * Example:
177 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError());
178 | *
179 | * @param {number} value Value to return an enum for
180 | * @return {string} The string version of the enum.
181 | */
182 | function glEnumToString(value) {
183 | checkInit();
184 | var name = glEnums[value];
185 | return (name !== undefined) ? name :
186 | ("*UNKNOWN WebGL ENUM (0x" + value.toString(16) + ")");
187 | }
188 |
189 | /**
190 | * Returns the string version of a WebGL argument.
191 | * Attempts to convert enum arguments to strings.
192 | * @param {string} functionName the name of the WebGL function.
193 | * @param {number} argumentIndx the index of the argument.
194 | * @param {*} value The value of the argument.
195 | * @return {string} The value as a string.
196 | */
197 | function glFunctionArgToString(functionName, argumentIndex, value) {
198 | var funcInfo = glValidEnumContexts[functionName];
199 | if (funcInfo !== undefined) {
200 | if (funcInfo[argumentIndex]) {
201 | return glEnumToString(value);
202 | }
203 | }
204 | if (value === null) {
205 | return "null";
206 | } else if (value === undefined) {
207 | return "undefined";
208 | } else {
209 | return value.toString();
210 | }
211 | }
212 |
213 | /**
214 | * Converts the arguments of a WebGL function to a string.
215 | * Attempts to convert enum arguments to strings.
216 | *
217 | * @param {string} functionName the name of the WebGL function.
218 | * @param {number} args The arguments.
219 | * @return {string} The arguments as a string.
220 | */
221 | function glFunctionArgsToString(functionName, args) {
222 | // apparently we can't do args.join(",");
223 | var argStr = "";
224 | for (var ii = 0; ii < args.length; ++ii) {
225 | argStr += ((ii == 0) ? '' : ', ') +
226 | glFunctionArgToString(functionName, ii, args[ii]);
227 | }
228 | return argStr;
229 | };
230 |
231 |
232 | function makePropertyWrapper(wrapper, original, propertyName) {
233 | //log("wrap prop: " + propertyName);
234 | wrapper.__defineGetter__(propertyName, function() {
235 | return original[propertyName];
236 | });
237 | // TODO(gmane): this needs to handle properties that take more than
238 | // one value?
239 | wrapper.__defineSetter__(propertyName, function(value) {
240 | //log("set: " + propertyName);
241 | original[propertyName] = value;
242 | });
243 | }
244 |
245 | // Makes a function that calls a function on another object.
246 | function makeFunctionWrapper(original, functionName) {
247 | //log("wrap fn: " + functionName);
248 | var f = original[functionName];
249 | return function() {
250 | //log("call: " + functionName);
251 | var result = f.apply(original, arguments);
252 | return result;
253 | };
254 | }
255 |
256 | /**
257 | * Given a WebGL context returns a wrapped context that calls
258 | * gl.getError after every command and calls a function if the
259 | * result is not gl.NO_ERROR.
260 | *
261 | * @param {!WebGLRenderingContext} ctx The webgl context to
262 | * wrap.
263 | * @param {!function(err, funcName, args): void} opt_onErrorFunc
264 | * The function to call when gl.getError returns an
265 | * error. If not specified the default function calls
266 | * console.log with a message.
267 | * @param {!function(funcName, args): void} opt_onFunc The
268 | * function to call when each webgl function is called.
269 | * You can use this to log all calls for example.
270 | */
271 | function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc) {
272 | init(ctx);
273 | opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) {
274 | // apparently we can't do args.join(",");
275 | var argStr = "";
276 | for (var ii = 0; ii < args.length; ++ii) {
277 | argStr += ((ii == 0) ? '' : ', ') +
278 | glFunctionArgToString(functionName, ii, args[ii]);
279 | }
280 | error("WebGL error "+ glEnumToString(err) + " in "+ functionName +
281 | "(" + argStr + ")");
282 | };
283 |
284 | // Holds booleans for each GL error so after we get the error ourselves
285 | // we can still return it to the client app.
286 | var glErrorShadow = { };
287 |
288 | // Makes a function that calls a WebGL function and then calls getError.
289 | function makeErrorWrapper(ctx, functionName) {
290 | return function() {
291 | if (opt_onFunc) {
292 | opt_onFunc(functionName, arguments);
293 | }
294 | var result = ctx[functionName].apply(ctx, arguments);
295 | var err = ctx.getError();
296 | if (err != 0) {
297 | glErrorShadow[err] = true;
298 | opt_onErrorFunc(err, functionName, arguments);
299 | }
300 | return result;
301 | };
302 | }
303 |
304 | // Make a an object that has a copy of every property of the WebGL context
305 | // but wraps all functions.
306 | var wrapper = {};
307 | for (var propertyName in ctx) {
308 | if (typeof ctx[propertyName] == 'function') {
309 | wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
310 | } else {
311 | makePropertyWrapper(wrapper, ctx, propertyName);
312 | }
313 | }
314 |
315 | // Override the getError function with one that returns our saved results.
316 | wrapper.getError = function() {
317 | for (var err in glErrorShadow) {
318 | if (glErrorShadow.hasOwnProperty(err)) {
319 | if (glErrorShadow[err]) {
320 | glErrorShadow[err] = false;
321 | return err;
322 | }
323 | }
324 | }
325 | return ctx.NO_ERROR;
326 | };
327 |
328 | return wrapper;
329 | }
330 |
331 | function resetToInitialState(ctx) {
332 | var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS);
333 | var tmp = ctx.createBuffer();
334 | ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp);
335 | for (var ii = 0; ii < numAttribs; ++ii) {
336 | ctx.disableVertexAttribArray(ii);
337 | ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0);
338 | ctx.vertexAttrib1f(ii, 0);
339 | }
340 | ctx.deleteBuffer(tmp);
341 |
342 | var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS);
343 | for (var ii = 0; ii < numTextureUnits; ++ii) {
344 | ctx.activeTexture(ctx.TEXTURE0 + ii);
345 | ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null);
346 | ctx.bindTexture(ctx.TEXTURE_2D, null);
347 | }
348 |
349 | ctx.activeTexture(ctx.TEXTURE0);
350 | ctx.useProgram(null);
351 | ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
352 | ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
353 | ctx.bindFramebuffer(ctx.FRAMEBUFFER, null);
354 | ctx.bindRenderbuffer(ctx.RENDERBUFFER, null);
355 | ctx.disable(ctx.BLEND);
356 | ctx.disable(ctx.CULL_FACE);
357 | ctx.disable(ctx.DEPTH_TEST);
358 | ctx.disable(ctx.DITHER);
359 | ctx.disable(ctx.SCISSOR_TEST);
360 | ctx.blendColor(0, 0, 0, 0);
361 | ctx.blendEquation(ctx.FUNC_ADD);
362 | ctx.blendFunc(ctx.ONE, ctx.ZERO);
363 | ctx.clearColor(0, 0, 0, 0);
364 | ctx.clearDepth(1);
365 | ctx.clearStencil(-1);
366 | ctx.colorMask(true, true, true, true);
367 | ctx.cullFace(ctx.BACK);
368 | ctx.depthFunc(ctx.LESS);
369 | ctx.depthMask(true);
370 | ctx.depthRange(0, 1);
371 | ctx.frontFace(ctx.CCW);
372 | ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE);
373 | ctx.lineWidth(1);
374 | ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4);
375 | ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4);
376 | ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false);
377 | ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
378 | // TODO: Delete this IF.
379 | if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) {
380 | ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL);
381 | }
382 | ctx.polygonOffset(0, 0);
383 | ctx.sampleCoverage(1, false);
384 | ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height);
385 | ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF);
386 | ctx.stencilMask(0xFFFFFFFF);
387 | ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP);
388 | ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
389 | ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
390 |
391 | // TODO: This should NOT be needed but Firefox fails with 'hint'
392 | while(ctx.getError());
393 | }
394 |
395 | function makeLostContextSimulatingCanvas(canvas) {
396 | var unwrappedContext_;
397 | var wrappedContext_;
398 | var onLost_ = [];
399 | var onRestored_ = [];
400 | var wrappedContext_ = {};
401 | var contextId_ = 1;
402 | var contextLost_ = false;
403 | var resourceId_ = 0;
404 | var resourceDb_ = [];
405 | var numCallsToLoseContext_ = 0;
406 | var numCalls_ = 0;
407 | var canRestore_ = false;
408 | var restoreTimeout_ = 0;
409 |
410 | // Holds booleans for each GL error so can simulate errors.
411 | var glErrorShadow_ = { };
412 |
413 | canvas.getContext = function(f) {
414 | return function() {
415 | var ctx = f.apply(canvas, arguments);
416 | // Did we get a context and is it a WebGL context?
417 | if (ctx instanceof WebGLRenderingContext) {
418 | if (ctx != unwrappedContext_) {
419 | if (unwrappedContext_) {
420 | throw "got different context"
421 | }
422 | unwrappedContext_ = ctx;
423 | wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_);
424 | }
425 | return wrappedContext_;
426 | }
427 | return ctx;
428 | }
429 | }(canvas.getContext);
430 |
431 | function wrapEvent(listener) {
432 | if (typeof(listener) == "function") {
433 | return listener;
434 | } else {
435 | return function(info) {
436 | listener.handleEvent(info);
437 | }
438 | }
439 | }
440 |
441 | var addOnContextLostListener = function(listener) {
442 | onLost_.push(wrapEvent(listener));
443 | };
444 |
445 | var addOnContextRestoredListener = function(listener) {
446 | onRestored_.push(wrapEvent(listener));
447 | };
448 |
449 |
450 | function wrapAddEventListener(canvas) {
451 | var f = canvas.addEventListener;
452 | canvas.addEventListener = function(type, listener, bubble) {
453 | switch (type) {
454 | case 'webglcontextlost':
455 | addOnContextLostListener(listener);
456 | break;
457 | case 'webglcontextrestored':
458 | addOnContextRestoredListener(listener);
459 | break;
460 | default:
461 | f.apply(canvas, arguments);
462 | }
463 | };
464 | }
465 |
466 | wrapAddEventListener(canvas);
467 |
468 | canvas.loseContext = function() {
469 | if (!contextLost_) {
470 | contextLost_ = true;
471 | numCallsToLoseContext_ = 0;
472 | ++contextId_;
473 | while (unwrappedContext_.getError());
474 | clearErrors();
475 | glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true;
476 | var event = makeWebGLContextEvent("context lost");
477 | var callbacks = onLost_.slice();
478 | setTimeout(function() {
479 | //log("numCallbacks:" + callbacks.length);
480 | for (var ii = 0; ii < callbacks.length; ++ii) {
481 | //log("calling callback:" + ii);
482 | callbacks[ii](event);
483 | }
484 | if (restoreTimeout_ >= 0) {
485 | setTimeout(function() {
486 | canvas.restoreContext();
487 | }, restoreTimeout_);
488 | }
489 | }, 0);
490 | }
491 | };
492 |
493 | canvas.restoreContext = function() {
494 | if (contextLost_) {
495 | if (onRestored_.length) {
496 | setTimeout(function() {
497 | if (!canRestore_) {
498 | throw "can not restore. webglcontestlost listener did not call event.preventDefault";
499 | }
500 | freeResources();
501 | resetToInitialState(unwrappedContext_);
502 | contextLost_ = false;
503 | numCalls_ = 0;
504 | canRestore_ = false;
505 | var callbacks = onRestored_.slice();
506 | var event = makeWebGLContextEvent("context restored");
507 | for (var ii = 0; ii < callbacks.length; ++ii) {
508 | callbacks[ii](event);
509 | }
510 | }, 0);
511 | }
512 | }
513 | };
514 |
515 | canvas.loseContextInNCalls = function(numCalls) {
516 | if (contextLost_) {
517 | throw "You can not ask a lost contet to be lost";
518 | }
519 | numCallsToLoseContext_ = numCalls_ + numCalls;
520 | };
521 |
522 | canvas.getNumCalls = function() {
523 | return numCalls_;
524 | };
525 |
526 | canvas.setRestoreTimeout = function(timeout) {
527 | restoreTimeout_ = timeout;
528 | };
529 |
530 | function isWebGLObject(obj) {
531 | //return false;
532 | return (obj instanceof WebGLBuffer ||
533 | obj instanceof WebGLFramebuffer ||
534 | obj instanceof WebGLProgram ||
535 | obj instanceof WebGLRenderbuffer ||
536 | obj instanceof WebGLShader ||
537 | obj instanceof WebGLTexture);
538 | }
539 |
540 | function checkResources(args) {
541 | for (var ii = 0; ii < args.length; ++ii) {
542 | var arg = args[ii];
543 | if (isWebGLObject(arg)) {
544 | return arg.__webglDebugContextLostId__ == contextId_;
545 | }
546 | }
547 | return true;
548 | }
549 |
550 | function clearErrors() {
551 | var k = Object.keys(glErrorShadow_);
552 | for (var ii = 0; ii < k.length; ++ii) {
553 | delete glErrorShadow_[k];
554 | }
555 | }
556 |
557 | function loseContextIfTime() {
558 | ++numCalls_;
559 | if (!contextLost_) {
560 | if (numCallsToLoseContext_ == numCalls_) {
561 | canvas.loseContext();
562 | }
563 | }
564 | }
565 |
566 | // Makes a function that simulates WebGL when out of context.
567 | function makeLostContextFunctionWrapper(ctx, functionName) {
568 | var f = ctx[functionName];
569 | return function() {
570 | // log("calling:" + functionName);
571 | // Only call the functions if the context is not lost.
572 | loseContextIfTime();
573 | if (!contextLost_) {
574 | //if (!checkResources(arguments)) {
575 | // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true;
576 | // return;
577 | //}
578 | var result = f.apply(ctx, arguments);
579 | return result;
580 | }
581 | };
582 | }
583 |
584 | function freeResources() {
585 | for (var ii = 0; ii < resourceDb_.length; ++ii) {
586 | var resource = resourceDb_[ii];
587 | if (resource instanceof WebGLBuffer) {
588 | unwrappedContext_.deleteBuffer(resource);
589 | } else if (resource instanceof WebGLFramebuffer) {
590 | unwrappedContext_.deleteFramebuffer(resource);
591 | } else if (resource instanceof WebGLProgram) {
592 | unwrappedContext_.deleteProgram(resource);
593 | } else if (resource instanceof WebGLRenderbuffer) {
594 | unwrappedContext_.deleteRenderbuffer(resource);
595 | } else if (resource instanceof WebGLShader) {
596 | unwrappedContext_.deleteShader(resource);
597 | } else if (resource instanceof WebGLTexture) {
598 | unwrappedContext_.deleteTexture(resource);
599 | }
600 | }
601 | }
602 |
603 | function makeWebGLContextEvent(statusMessage) {
604 | return {
605 | statusMessage: statusMessage,
606 | preventDefault: function() {
607 | canRestore_ = true;
608 | }
609 | };
610 | }
611 |
612 | return canvas;
613 |
614 | function makeLostContextSimulatingContext(ctx) {
615 | // copy all functions and properties to wrapper
616 | for (var propertyName in ctx) {
617 | if (typeof ctx[propertyName] == 'function') {
618 | wrappedContext_[propertyName] = makeLostContextFunctionWrapper(
619 | ctx, propertyName);
620 | } else {
621 | makePropertyWrapper(wrappedContext_, ctx, propertyName);
622 | }
623 | }
624 |
625 | // Wrap a few functions specially.
626 | wrappedContext_.getError = function() {
627 | loseContextIfTime();
628 | if (!contextLost_) {
629 | var err;
630 | while (err = unwrappedContext_.getError()) {
631 | glErrorShadow_[err] = true;
632 | }
633 | }
634 | for (var err in glErrorShadow_) {
635 | if (glErrorShadow_[err]) {
636 | delete glErrorShadow_[err];
637 | return err;
638 | }
639 | }
640 | return wrappedContext_.NO_ERROR;
641 | };
642 |
643 | var creationFunctions = [
644 | "createBuffer",
645 | "createFramebuffer",
646 | "createProgram",
647 | "createRenderbuffer",
648 | "createShader",
649 | "createTexture"
650 | ];
651 | for (var ii = 0; ii < creationFunctions.length; ++ii) {
652 | var functionName = creationFunctions[ii];
653 | wrappedContext_[functionName] = function(f) {
654 | return function() {
655 | loseContextIfTime();
656 | if (contextLost_) {
657 | return null;
658 | }
659 | var obj = f.apply(ctx, arguments);
660 | obj.__webglDebugContextLostId__ = contextId_;
661 | resourceDb_.push(obj);
662 | return obj;
663 | };
664 | }(ctx[functionName]);
665 | }
666 |
667 | var functionsThatShouldReturnNull = [
668 | "getActiveAttrib",
669 | "getActiveUniform",
670 | "getBufferParameter",
671 | "getContextAttributes",
672 | "getAttachedShaders",
673 | "getFramebufferAttachmentParameter",
674 | "getParameter",
675 | "getProgramParameter",
676 | "getProgramInfoLog",
677 | "getRenderbufferParameter",
678 | "getShaderParameter",
679 | "getShaderInfoLog",
680 | "getShaderSource",
681 | "getTexParameter",
682 | "getUniform",
683 | "getUniformLocation",
684 | "getVertexAttrib"
685 | ];
686 | for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) {
687 | var functionName = functionsThatShouldReturnNull[ii];
688 | wrappedContext_[functionName] = function(f) {
689 | return function() {
690 | loseContextIfTime();
691 | if (contextLost_) {
692 | return null;
693 | }
694 | return f.apply(ctx, arguments);
695 | }
696 | }(wrappedContext_[functionName]);
697 | }
698 |
699 | var isFunctions = [
700 | "isBuffer",
701 | "isEnabled",
702 | "isFramebuffer",
703 | "isProgram",
704 | "isRenderbuffer",
705 | "isShader",
706 | "isTexture"
707 | ];
708 | for (var ii = 0; ii < isFunctions.length; ++ii) {
709 | var functionName = isFunctions[ii];
710 | wrappedContext_[functionName] = function(f) {
711 | return function() {
712 | loseContextIfTime();
713 | if (contextLost_) {
714 | return false;
715 | }
716 | return f.apply(ctx, arguments);
717 | }
718 | }(wrappedContext_[functionName]);
719 | }
720 |
721 | wrappedContext_.checkFramebufferStatus = function(f) {
722 | return function() {
723 | loseContextIfTime();
724 | if (contextLost_) {
725 | return wrappedContext_.FRAMEBUFFER_UNSUPPORTED;
726 | }
727 | return f.apply(ctx, arguments);
728 | };
729 | }(wrappedContext_.checkFramebufferStatus);
730 |
731 | wrappedContext_.getAttribLocation = function(f) {
732 | return function() {
733 | loseContextIfTime();
734 | if (contextLost_) {
735 | return -1;
736 | }
737 | return f.apply(ctx, arguments);
738 | };
739 | }(wrappedContext_.getAttribLocation);
740 |
741 | wrappedContext_.getVertexAttribOffset = function(f) {
742 | return function() {
743 | loseContextIfTime();
744 | if (contextLost_) {
745 | return 0;
746 | }
747 | return f.apply(ctx, arguments);
748 | };
749 | }(wrappedContext_.getVertexAttribOffset);
750 |
751 | wrappedContext_.isContextLost = function() {
752 | return contextLost_;
753 | };
754 |
755 | return wrappedContext_;
756 | }
757 | }
758 |
759 | return {
760 | /**
761 | * Initializes this module. Safe to call more than once.
762 | * @param {!WebGLRenderingContext} ctx A WebGL context. If
763 | }
764 | * you have more than one context it doesn't matter which one
765 | * you pass in, it is only used to pull out constants.
766 | */
767 | 'init': init,
768 |
769 | /**
770 | * Returns true or false if value matches any WebGL enum
771 | * @param {*} value Value to check if it might be an enum.
772 | * @return {boolean} True if value matches one of the WebGL defined enums
773 | */
774 | 'mightBeEnum': mightBeEnum,
775 |
776 | /**
777 | * Gets an string version of an WebGL enum.
778 | *
779 | * Example:
780 | * WebGLDebugUtil.init(ctx);
781 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError());
782 | *
783 | * @param {number} value Value to return an enum for
784 | * @return {string} The string version of the enum.
785 | */
786 | 'glEnumToString': glEnumToString,
787 |
788 | /**
789 | * Converts the argument of a WebGL function to a string.
790 | * Attempts to convert enum arguments to strings.
791 | *
792 | * Example:
793 | * WebGLDebugUtil.init(ctx);
794 | * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 0, gl.TEXTURE_2D);
795 | *
796 | * would return 'TEXTURE_2D'
797 | *
798 | * @param {string} functionName the name of the WebGL function.
799 | * @param {number} argumentIndx the index of the argument.
800 | * @param {*} value The value of the argument.
801 | * @return {string} The value as a string.
802 | */
803 | 'glFunctionArgToString': glFunctionArgToString,
804 |
805 | /**
806 | * Converts the arguments of a WebGL function to a string.
807 | * Attempts to convert enum arguments to strings.
808 | *
809 | * @param {string} functionName the name of the WebGL function.
810 | * @param {number} args The arguments.
811 | * @return {string} The arguments as a string.
812 | */
813 | 'glFunctionArgsToString': glFunctionArgsToString,
814 |
815 | /**
816 | * Given a WebGL context returns a wrapped context that calls
817 | * gl.getError after every command and calls a function if the
818 | * result is not NO_ERROR.
819 | *
820 | * You can supply your own function if you want. For example, if you'd like
821 | * an exception thrown on any GL error you could do this
822 | *
823 | * function throwOnGLError(err, funcName, args) {
824 | * throw WebGLDebugUtils.glEnumToString(err) +
825 | * " was caused by call to " + funcName;
826 | * };
827 | *
828 | * ctx = WebGLDebugUtils.makeDebugContext(
829 | * canvas.getContext("webgl"), throwOnGLError);
830 | *
831 | * @param {!WebGLRenderingContext} ctx The webgl context to wrap.
832 | * @param {!function(err, funcName, args): void} opt_onErrorFunc The function
833 | * to call when gl.getError returns an error. If not specified the default
834 | * function calls console.log with a message.
835 | * @param {!function(funcName, args): void} opt_onFunc The
836 | * function to call when each webgl function is called. You
837 | * can use this to log all calls for example.
838 | */
839 | 'makeDebugContext': makeDebugContext,
840 |
841 | /**
842 | * Given a canvas element returns a wrapped canvas element that will
843 | * simulate lost context. The canvas returned adds the following functions.
844 | *
845 | * loseContext:
846 | * simulates a lost context event.
847 | *
848 | * restoreContext:
849 | * simulates the context being restored.
850 | *
851 | * lostContextInNCalls:
852 | * loses the context after N gl calls.
853 | *
854 | * getNumCalls:
855 | * tells you how many gl calls there have been so far.
856 | *
857 | * setRestoreTimeout:
858 | * sets the number of milliseconds until the context is restored
859 | * after it has been lost. Defaults to 0. Pass -1 to prevent
860 | * automatic restoring.
861 | *
862 | * @param {!Canvas} canvas The canvas element to wrap.
863 | */
864 | 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas,
865 |
866 | /**
867 | * Resets a context to the initial state.
868 | * @param {!WebGLRenderingContext} ctx The webgl context to
869 | * reset.
870 | */
871 | 'resetToInitialState': resetToInitialState
872 | };
873 |
874 | }();
875 |
876 |
--------------------------------------------------------------------------------
/src/engine/gl/context.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var mesh = require('./mesh'),
4 | texture = require('./texture'),
5 | extend = require('../utils').extend,
6 | shader = require('./shader');
7 |
8 | require('./_webgl-debug');
9 |
10 | exports.Context = function(gl, resources){
11 | this.gl = gl;
12 | this.resources = resources;
13 | this.shaderManager = new shader.Manager(resources);
14 | };
15 | exports.Context.prototype = {
16 | // factories
17 | getBuffer: function(name, target, mode) {
18 | var data = this.resources[name];
19 | new mesh.Buffer(this.gl, data, target, mode);
20 | },
21 | getFBO: function() {
22 |
23 | },
24 | getTexture: function(name, options) {
25 | var image = this.resources[name];
26 | return new texture.Texture2D(this.gl, image, options);
27 | },
28 | getShader: function(name){
29 | }
30 | };
31 |
32 | function log_error(el, msg, id){
33 | if(window.console && window.console.error) console.error(id, msg);
34 | }
35 |
36 | exports.initialize = function (canvas, options, onerror) {
37 | var upgrade = 'Try upgrading to the latest version of firefox or chrome.';
38 |
39 | onerror = onerror || log_error;
40 |
41 | if(!canvas.getContext){
42 | onerror(canvas, 'canvas is not supported by your browser. ' +
43 | upgrade, 'no-canvas');
44 | return;
45 | }
46 |
47 |
48 | var context_options = extend({
49 | alpha: false,
50 | depth: true,
51 | stencil: false,
52 | antialias: true,
53 | premultipliedAlpha: false,
54 | preserveDrawingBuffer: false
55 | }, options.context),
56 | extensions = options.extensions || {};
57 |
58 | var gl = canvas.getContext('webgl', context_options);
59 | if(gl == null){
60 | gl = canvas.getContext('experimental-webgl', context_options);
61 | if(gl == null){
62 | onerror(canvas, 'webgl is not supported by your browser. ' +
63 | upgrade, 'no-webgl');
64 | return;
65 | }
66 | }
67 |
68 | if(options.vertex_texture_units && gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) < options.vertex_texture_units){
69 | onerror(canvas, 'This application needs at least two vertex texture units which are not supported by your browser. ' +
70 | upgrade, 'no-vertext-texture-units');
71 | return;
72 | }
73 |
74 | if(extensions.texture_float && gl.getExtension('OES_texture_float') == null){
75 | onerror(canvas, 'This application needs float textures which is not supported by your browser. ' +
76 | upgrade, 'no-OES_texture_float');
77 | return;
78 | }
79 |
80 | if(extensions.standard_derivatives && gl.getExtension('OES_standard_derivatives') == null){
81 | onerror(canvas, 'This application need the standard deriviates extensions for WebGL which is not supported by your Browser.' +
82 | upgrade, 'no-OES_standard_derivatives');
83 |
84 | }
85 |
86 | if(window.WebGLDebugUtils && options.debug){
87 | if(options.log_all){
88 | gl = WebGLDebugUtils.makeDebugContext(gl, undefined, function(){
89 | console.log.apply(console, arguments);
90 | });
91 | }
92 | else {
93 | gl = WebGLDebugUtils.makeDebugContext(gl);
94 | }
95 | console.log('running in debug context');
96 | }
97 |
98 | if(context_options.depth){
99 | gl.enable(gl.DEPTH_TEST);
100 | }
101 | else {
102 | gl.disable(gl.DEPTH_TEST);
103 | }
104 | gl.enable(gl.CULL_FACE);
105 |
106 | gl.lost = false;
107 | canvas.addEventListener('webglcontextlost', function () {
108 | onerror(canvas, 'Lost webgl context!', 'context-lost');
109 | gl.lost = true;
110 | }, false);
111 | //canvas.addEventListener('webglcontextrestored', function () {
112 | //onerror(canvas, 'restored webgl context - reloading!');
113 | //window.location.reload();
114 | //}, false);
115 |
116 | return gl;
117 | };
118 |
119 | });
120 |
--------------------------------------------------------------------------------
/src/engine/gl/geometry.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | exports.grid = function(size){
4 | var buffer = new Float32Array(size*size*6*3),
5 | i = 0,
6 | half = size * 0.5;
7 |
8 | for(var y = 0; y < size; y++){
9 | for(var x = 0; x < size; x++) {
10 | buffer[i++] = x/size;
11 | buffer[i++] = 0;
12 | buffer[i++] = y/size;
13 |
14 | buffer[i++] = x/size;
15 | buffer[i++] = 0;
16 | buffer[i++] = (y+1)/size;
17 |
18 | buffer[i++] = (x+1)/size;
19 | buffer[i++] = 0;
20 | buffer[i++] = (y+1)/size;
21 |
22 | buffer[i++] = x/size;
23 | buffer[i++] = 0;
24 | buffer[i++] = y/size;
25 |
26 | buffer[i++] = (x+1)/size;
27 | buffer[i++] = 0;
28 | buffer[i++] = (y+1)/size;
29 |
30 | buffer[i++] = (x+1)/size;
31 | buffer[i++] = 0;
32 | buffer[i++] = y/size;
33 | }
34 | }
35 | return buffer;
36 | };
37 |
38 | // convert a gl.TRIANGLES mesh into a wireframe for rendering with gl.LINES
39 | exports.wireFrame = function(input){
40 | var output = new Float32Array(input.length*2),
41 | triangles = input.length/9;
42 | for(var t = 0; t < triangles; t++) {
43 | for(var v1 = 0; v1 < 3; v1++) {
44 | var v2 = (v1+1)%3;
45 | for(var i = 0; i < 3; i++) {
46 | output[t*18+v1*3+i] = input[t*9+v1*3+i];
47 | output[t*18+v1*3+9+i] = input[t*9+v2*3+i];
48 | }
49 | }
50 | }
51 | return output;
52 | };
53 |
54 | exports.screen_quad = function screen_quad(xscale, yscale) {
55 | xscale = xscale||1;
56 | yscale = yscale||xscale;
57 | return new Float32Array([
58 | -xscale, yscale, 0,
59 | -xscale, -yscale, 0,
60 | xscale, -yscale, 0,
61 |
62 | -xscale, yscale, 0,
63 | xscale, -yscale, 0,
64 | xscale, yscale, 0
65 | ]);
66 | };
67 |
68 | exports.cube = function cube(scale) {
69 | scale = scale || 1;
70 | return new Float32Array([
71 | // back
72 | scale, scale, scale,
73 | scale, -scale, scale,
74 | -scale, -scale, scale,
75 |
76 | scale, scale, scale,
77 | -scale, -scale, scale,
78 | -scale, scale, scale,
79 |
80 | // front
81 | -scale, scale, -scale,
82 | -scale, -scale, -scale,
83 | scale, scale, -scale,
84 |
85 | scale, scale, -scale,
86 | -scale, -scale, -scale,
87 | scale, -scale, -scale,
88 | // left
89 | -scale, scale, scale,
90 | -scale, -scale, -scale,
91 | -scale, scale, -scale,
92 |
93 | -scale, scale, scale,
94 | -scale, -scale, scale,
95 | -scale, -scale, -scale,
96 |
97 | // right
98 |
99 | scale, scale, scale,
100 | scale, scale, -scale,
101 | scale, -scale, -scale,
102 |
103 | scale, scale, scale,
104 | scale, -scale, -scale,
105 | scale, -scale, scale,
106 |
107 | // top
108 | scale, scale, scale,
109 | -scale, scale, scale,
110 | -scale, scale, -scale,
111 |
112 | scale, scale, -scale,
113 | scale, scale, scale,
114 | -scale, scale, -scale,
115 |
116 | // bottom
117 | -scale, -scale, -scale,
118 | -scale, -scale, scale,
119 | scale, -scale, scale,
120 |
121 | -scale, -scale, -scale,
122 | scale, -scale, scale,
123 | scale, -scale, -scale
124 | ]);
125 | };
126 |
127 | });
128 |
--------------------------------------------------------------------------------
/src/engine/gl/mesh.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | /*
4 | * Example:
5 | new exports.Mesh({
6 | index: ibo,
7 | vertex: new Float32Array(),
8 | attributes: {
9 | position: {
10 | offset: 0,
11 | size: 3,
12 | type: gl.FLOAT,
13 | stride: 0,
14 | normalized: false
15 | }
16 | },
17 | mode: gl.TRIANGLES
18 | });
19 |
20 | */
21 | var Mesh = function(gl, options) {
22 | this.gl = gl;
23 | this.ibo = options.ibo;
24 | this.vbo = options.vbo || new Buffer(gl, options.vertex);
25 | this.mode = options.mode || gl.TRIANGLES;
26 | if(this.ibo){
27 | switch(this.ibo.byteLength/this.ibo.length){
28 | case 1:
29 | this.iboType = gl.UNSIGNED_BYTE;
30 | break;
31 | case 2:
32 | this.iboType = gl.UNSIGNED_SHORT;
33 | break;
34 | case 4:
35 | this.iboType = gl.UNSIGNED_LONG;
36 | break;
37 | default:
38 | this.iboType = gl.UNSIGNED_SHORT;
39 | break;
40 | }
41 | }
42 | else{
43 | this.iboType = 0;
44 | }
45 | this.setAttributes(options.attributes);
46 | };
47 | Mesh.prototype = {
48 | setAttributes: function(attributes) {
49 | var attributeNames = Object.keys(attributes);
50 | this.attributes = [];
51 | this.vertexSize = 0;
52 | for(var i = 0; i < attributeNames.length; i++) {
53 | var name = attributeNames[i],
54 | value = attributes[name],
55 | attr = {
56 | name: name,
57 | size: value.size || 3,
58 | type: value.type || this.gl.FLOAT,
59 | stride: value.stride || 0,
60 | offset: value.offset || 0,
61 | normalized: !!value.normalized
62 | };
63 | this.vertexSize += attr.size;
64 | this.attributes.push(attr);
65 | }
66 | },
67 | bindAttributes: function(shader) {
68 | for(var i = 0; i < this.attributes.length; i++) {
69 | var attr = this.attributes[i],
70 | location = shader.getAttribLocation(attr.name);
71 | // should probably be optimized, could leak state
72 | this.gl.enableVertexAttribArray(location);
73 | this.gl.vertexAttribPointer(location, attr.size, attr.type, attr.normalized, attr.stride, attr.offset);
74 | }
75 | },
76 | draw: function(shader){
77 | shader.use();
78 | this.vbo.bind();
79 | this.bindAttributes(shader);
80 | if(this.ibo){
81 | this.ibo.bind();
82 | this.gl.drawElements(this.mode, this.ibo.length, this.iboType, 0);
83 | }
84 | else {
85 | this.gl.drawArrays(this.mode, 0, this.vbo.length/this.vertexSize);
86 | }
87 | this.vbo.unbind();
88 | }
89 | };
90 | exports.Mesh = Mesh;
91 |
92 | var Buffer = function(gl, data, target, mode){
93 | this.gl = gl;
94 | this.target = target || gl.ARRAY_BUFFER;
95 | this.buffer = gl.createBuffer();
96 | this.bind();
97 | gl.bufferData(gl.ARRAY_BUFFER, data, mode || gl.STATIC_DRAW);
98 | this.unbind();
99 | this.length = data.length;
100 | this.btyeLength = data.byteLength;
101 | };
102 | Buffer.prototype = {
103 | bind: function() {
104 | this.gl.bindBuffer(this.target, this.buffer);
105 | },
106 | unbind: function() {
107 | this.gl.bindBuffer(this.target, null);
108 | },
109 | free: function(mode) {
110 | this.gl.deleteBuffer(this.buffer);
111 | delete this.buffer;
112 | }
113 | };
114 | exports.Buffer = Buffer;
115 |
116 | });
117 |
--------------------------------------------------------------------------------
/src/engine/gl/shader.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | function keys(o){
4 | var a = [];
5 | for(var name in o){
6 | a.push(name);
7 | }
8 | return a;
9 | }
10 |
11 | function Shader(gl, vertexSource, fragmentSource) {
12 | this.gl = gl;
13 | this.program = this.makeProgram(vertexSource, fragmentSource);
14 | this.uniformLocations = {};
15 | this.uniformValues = {};
16 | this.uniformNames = [];
17 | this.attributeLocations = {};
18 | }
19 | Shader.prototype = {
20 | use: function() {
21 | this.gl.useProgram(this.program);
22 | },
23 | prepareUniforms: function(values) {
24 | this.uniformNames = keys(values);
25 | for(var i = 0; i < this.uniformNames.length; i++) {
26 | var name = this.uniformNames[i];
27 | this.uniformLocations[name] = this.gl.getUniformLocation(this.program, name);
28 | }
29 | },
30 | uniforms: function (values) {
31 | if(this.uniformNames.length === 0){
32 | this.prepareUniforms(values);
33 | }
34 | for(var i = 0; i < this.uniformNames.length; i++) {
35 | var name = this.uniformNames[i];
36 |
37 | var location = this.uniformLocations[name],
38 | value = values[name];
39 |
40 | if(location === null) continue;
41 |
42 | if(value.uniform){
43 | if(!value.equals(this.uniformValues[name])){
44 | value.uniform(location);
45 | value.set(this.uniformValues, name);
46 | }
47 | }
48 | else if(value.length){
49 | var value2 = this.uniformValues[name];
50 | if(value2 !== undefined){
51 | for(var j = 0, l = value.length; j < l; j++) {
52 | if(value[j] != value2[j]) break;
53 | }
54 | // already set
55 | if(j == l) {
56 | //continue;
57 | }
58 | else {
59 | for(j = 0, l = value.length; j < l; j++) {
60 | value2[j] = value[j];
61 | }
62 | }
63 | }
64 | else {
65 | this.uniformValues[name] = new Float32Array(value);
66 | }
67 | switch(value.length){
68 | case 2:
69 | this.gl.uniform2fv(location, value);
70 | break;
71 | case 3:
72 | this.gl.uniform3fv(location, value);
73 | break;
74 | case 4:
75 | this.gl.uniform4fv(location, value);
76 | break;
77 | case 9:
78 | this.gl.uniformMatrix3fv(location, false, value);
79 | break;
80 | case 16:
81 | this.gl.uniformMatrix4fv(location, false, value);
82 | break;
83 |
84 | }
85 | }
86 | else {
87 | if(value != this.uniformValues[name]){
88 | this.gl.uniform1f(location, value);
89 | this.uniformValues[name] = value;
90 | }
91 |
92 | }
93 | }
94 | },
95 | getUniformLocation: function(name) {
96 | if(this.uniformLocations[name] === undefined){
97 | this.uniformLocations[name] = this.gl.getUniformLocation(this.program, name);
98 | }
99 | return this.uniformLocations[name];
100 | },
101 | getAttribLocation: function(name) {
102 | if(!(name in this.attributeLocations)){
103 | var location = this.gl.getAttribLocation(this.program, name);
104 | if(location < 0){
105 | throw 'undefined attribute ' + name;
106 | }
107 | this.attributeLocations[name] = location;
108 | }
109 | return this.attributeLocations[name];
110 | },
111 | makeShader: function(shaderType, source){
112 | var shader = this.gl.createShader(shaderType);
113 | this.gl.shaderSource(shader, source);
114 | this.gl.compileShader(shader);
115 | if(!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)){
116 | console.log(this.gl.getShaderInfoLog(shader), shaderType, source);
117 | throw 'Compiler exception: "' + this.gl.getShaderInfoLog(shader) + '"';
118 | }
119 | return shader;
120 | },
121 | makeProgram: function(vertexSource, fragmentSource){
122 | var vertexShader = this.makeShader(this.gl.VERTEX_SHADER, vertexSource),
123 | fragmentShader = this.makeShader(this.gl.FRAGMENT_SHADER, fragmentSource),
124 | program = this.gl.createProgram();
125 |
126 | this.gl.attachShader(program, vertexShader);
127 | this.gl.attachShader(program, fragmentShader);
128 | this.gl.linkProgram(program);
129 |
130 | if(!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)){
131 | throw 'Linker exception: ' + this.gl.getProgramInfoLog(program);
132 | }
133 |
134 | return program;
135 | }
136 | };
137 | exports.Shader = Shader;
138 |
139 | exports.Manager = function ShaderManager(gl, resources, options){
140 | this.gl = gl;
141 | this.resources = resources;
142 | this.shaders = [];
143 | options = options || {};
144 | this.prefix = options.prefix || 'shaders/';
145 | };
146 | exports.Manager.prototype = {
147 | //prefix: 'shaders/',
148 | includeExpression: /#include "([^"]+)"/g,
149 | preprocess: function(name, content) {
150 | return content.replace(this.includeExpression, function (_, name) {
151 | return this.getSource(name);
152 | }.bind(this));
153 | },
154 | getSource: function(name) {
155 | var content = this.resources[this.prefix + name];
156 | if(content == null) {
157 | throw 'shader not found: ' + name;
158 | }
159 | return this.preprocess(name, content);
160 | },
161 | get: function(vertex, frag) {
162 | if(!frag) {
163 | frag = vertex;
164 | }
165 | frag += '.frag';
166 | vertex += '.vertex';
167 | var key = frag + ';' + vertex;
168 | if(!(key in this.shaders)){
169 | this.shaders[key] = new Shader(this.gl, this.getSource(vertex), this.getSource(frag));
170 | }
171 | return this.shaders[key];
172 | }
173 | };
174 |
175 | });
176 |
--------------------------------------------------------------------------------
/src/engine/gl/texture.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var extend = require('../utils').extend;
4 |
5 | exports.Texture2D = function Texture2D(gl, data, options) {
6 | this.gl = gl;
7 | this.texture = gl.createTexture();
8 | this.unit = -1;
9 | this.bound = false;
10 | this.bindTexture();
11 |
12 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
13 | gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
14 | gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
15 |
16 | gl.texImage2D(gl.TEXTURE_2D, 0, options.internalformat || options.format || gl.RGBA, options.format || gl.RGBA, options.type || gl.UNSIGNED_BYTE, data);
17 |
18 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, options.mag_filter || gl.LINEAR);
19 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, options.min_filter || gl.LINEAR_MIPMAP_LINEAR);
20 | gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.wrap_s || gl.REPEAT );
21 | gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.wrap_t || gl.REPEAT );
22 |
23 | if(options.mipmap !== false){
24 | gl.generateMipmap(gl.TEXTURE_2D);
25 | }
26 | };
27 | exports.Texture2D.prototype = {
28 | bindTexture: function(unit) {
29 | if(unit !== undefined){
30 | this.gl.activeTexture(this.gl.TEXTURE0+unit);
31 | this.unit = unit;
32 | }
33 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
34 | this.bound = true;
35 | },
36 | unbindTexture: function() {
37 | this.gl.activeTexture(this.gl.TEXTURE0+this.unit);
38 | this.gl.bindTexture(this.gl.TEXTURE_2D, null);
39 | this.unit = -1;
40 | this.bound = false;
41 | },
42 | uniform: function (location) {
43 | this.gl.uniform1i(location, this.unit);
44 | },
45 | equals: function(value) {
46 | return this.unit === value;
47 | },
48 | set: function(obj, name) {
49 | obj[name] = this.unit;
50 | }
51 | };
52 |
53 | exports.FBO = function FBO(gl, width, height, type, format){
54 | this.width = width;
55 | this.height = height;
56 | this.gl = gl;
57 |
58 | this.framebuffer = gl.createFramebuffer();
59 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
60 |
61 | this.texture = gl.createTexture();
62 | gl.bindTexture(gl.TEXTURE_2D, this.texture);
63 | gl.texImage2D(gl.TEXTURE_2D, 0, format || gl.RGBA, width, height, 0, format || gl.RGBA, type || gl.UNSIGNED_BYTE, null);
64 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
65 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
66 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
67 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
68 |
69 | this.depth = gl.createRenderbuffer();
70 | gl.bindRenderbuffer(gl.RENDERBUFFER, this.depth);
71 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
72 |
73 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
74 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.depth);
75 | this.supported = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
76 | console.log(arguments, this.supported);
77 |
78 | gl.bindTexture(gl.TEXTURE_2D, null);
79 | gl.bindRenderbuffer(gl.RENDERBUFFER, null);
80 | gl.bindFramebuffer(gl.FRAMEBUFFER, null);
81 |
82 | this.unit = -1;
83 | };
84 | exports.FBO.prototype = extend({}, exports.Texture2D.prototype, {
85 | bind: function () {
86 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
87 | },
88 | unbind: function() {
89 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
90 | }
91 | });
92 |
93 |
94 |
95 | });
96 |
--------------------------------------------------------------------------------
/src/engine/input.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 | var input = exports,
3 | clamp = require('engine/utils').clamp;
4 |
5 | // mapping keycodes to names
6 | var keyname = {
7 | 32: 'SPACE',
8 | 13: 'ENTER',
9 | 9: 'TAB',
10 | 8: 'BACKSPACE',
11 | 16: 'SHIFT',
12 | 17: 'CTRL',
13 | 18: 'ALT',
14 | 20: 'CAPS_LOCK',
15 | 144: 'NUM_LOCK',
16 | 145: 'SCROLL_LOCK',
17 | 37: 'LEFT',
18 | 38: 'UP',
19 | 39: 'RIGHT',
20 | 40: 'DOWN',
21 | 33: 'PAGE_UP',
22 | 34: 'PAGE_DOWN',
23 | 36: 'HOME',
24 | 35: 'END',
25 | 45: 'INSERT',
26 | 46: 'DELETE',
27 | 27: 'ESCAPE',
28 | 19: 'PAUSE'
29 | };
30 |
31 |
32 | /* User input handler using jQuery */
33 | input.Handler = function InputHandler(element) {
34 | this.bind(element);
35 | this.reset();
36 | };
37 | input.Handler.prototype = {
38 | offset: {x: 0, y: 0},
39 | onClick: null,
40 | onKeyUp: null,
41 | onKeyDown: null,
42 | hasFocus: true,
43 | bind: function(element) {
44 | var self = this;
45 | this.element = element;
46 | this.updateOffset();
47 | document.addEventListener('keydown', function(e){
48 | if(!self.keyDown(e.keyCode)){
49 | e.preventDefault();
50 | }
51 | });
52 | document.addEventListener('keyup', function(e){
53 | if(!self.keyUp(e.keyCode)){
54 | e.preventDefault();
55 | }
56 | });
57 | window.addEventListener('click', function(e){
58 | if(e.target != element){
59 | self.blur();
60 | }
61 | else {
62 | self.focus();
63 | }
64 | });
65 | window.addEventListener('blur', function (e) {
66 | self.blur();
67 | });
68 | document.addEventListener('mousemove', function(e) {
69 | self.mouseMove(e.pageX, e.pageY);
70 | });
71 | element.addEventListener('mousedown', function (e) {
72 | self.mouseDown();
73 | });
74 | element.addEventListener('mouseup', function (e) {
75 | self.mouseUp();
76 | });
77 | // prevent text selection in browsers that support it
78 | document.addEventListener('selectstart', function (e) {
79 | if(self.hasFocus) e.preventDefault();
80 | });
81 | },
82 | updateOffset: function() {
83 | var offset = this.element.getBoundingClientRect();
84 | this.offset = {x:offset.left, y:offset.top};
85 | },
86 | blur: function() {
87 | this.hasFocus = false;
88 | this.reset();
89 | },
90 | focus: function() {
91 | if(!this.hasFocus) {
92 | this.hasFocus = true;
93 | this.reset();
94 | }
95 | },
96 | reset: function() {
97 | this.keys = {};
98 | for(var i = 65; i < 128; i++) {
99 | this.keys[String.fromCharCode(i)] = false;
100 | }
101 | for(i in keyname){
102 | if(keyname.hasOwnProperty(i)){
103 | this.keys[keyname[i]] = false;
104 | }
105 | }
106 | this.mouse = {down: false, x: 0, y: 0};
107 | },
108 | keyDown: function(key) {
109 | var name = this._getKeyName(key),
110 | wasDown = this.keys[name];
111 | this.keys[name] = true;
112 | if(this.onKeyDown && !wasDown) {
113 | this.onKeyDown(name);
114 | }
115 | return this.hasFocus;
116 | },
117 | keyUp: function(key) {
118 | var name = this._getKeyName(key);
119 | this.keys[name] = false;
120 | if(this.onKeyUp) {
121 | this.onKeyUp(name);
122 | }
123 | return this.hasFocus;
124 | },
125 | mouseDown: function() {
126 | this.mouse.down = true;
127 | },
128 | mouseUp: function() {
129 | this.mouse.down = false;
130 | if(this.hasFocus && this.onClick) {
131 | this.onClick(this.mouse.x, this.mouse.y);
132 | }
133 | },
134 | mouseMove: function(x, y){
135 | this.mouse.x = clamp(x-this.offset.x, 0, this.width);
136 | this.mouse.y = clamp(y-this.offset.y, 0, this.height);
137 | },
138 | _getKeyName: function(key) {
139 | if(key in keyname) {
140 | return keyname[key];
141 | }
142 | return String.fromCharCode(key);
143 | }
144 |
145 | };
146 | });
147 |
--------------------------------------------------------------------------------
/src/engine/loader.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | function Loader(root){
4 | this.root = root || '';
5 | this.resources = {};
6 | }
7 | Loader.prototype = {
8 | load: function(resources, ready, error, progress) {
9 | var pending = 0,
10 | total = 0,
11 | failed = 0,
12 | self = this;
13 |
14 | function success_(src, data){
15 | self.resources[src] = data;
16 | pending --;
17 | if(pending === 0){
18 | if(ready){
19 | ready(self);
20 | }
21 | }
22 | else if(progress) {
23 | progress(total, pending, failed);
24 | }
25 | }
26 | function error_(src, e){
27 | pending --;
28 | failed ++;
29 | self.resources[src] = null;
30 | e.src = src;
31 | if(error){
32 | error(self, e);
33 | }
34 | }
35 |
36 | for(var i = 0; i < resources.length; i++) {
37 | var resource = resources[i];
38 | // allows loading in multiple stages
39 | if(resource in this.resources){
40 | continue;
41 | }
42 | pending ++;
43 | total ++;
44 | if(/\.(jpe?g|gif|png)$/.test(resource)){
45 | this._loadImage(resource, success_, error_);
46 | }
47 | else if(/\.(og(g|a)|mp3)$/.test(resource)){
48 | this._loadAudio(resource, success_, error_);
49 | }
50 | else if(/\.json$/.test(resource)){
51 | this._loadJSON(resource, success_, error_);
52 | }
53 | else if(/\.(bin|raw)/.test(resource)){
54 | this._loadBin(resource, success_, error_);
55 | }
56 | else {
57 | this._loadData(resource, success_, error_);
58 | }
59 | }
60 |
61 | if(pending === 0 && ready){
62 | // always call AFTER the mainloop
63 | // multiple load calls can result in
64 | // multiple ready() calls!
65 | window.setTimeout(function () {
66 | ready(this);
67 | }, 1);
68 | }
69 | else {
70 | if(progress){
71 | progress(total, pending, failed);
72 | }
73 | }
74 | },
75 | _loadImage: function(src, success, error){
76 | var self = this;
77 | var img = document.createElement('img');
78 | img.onload = function() {
79 | success(src, img);
80 | };
81 | img.onerror = function (e) {
82 | error(src, e);
83 | };
84 | img.src = this.root + src;
85 | },
86 | _loadJSON: function(src, success, error){
87 | var xhr = new XMLHttpRequest(),
88 | self = this;
89 | xhr.open('GET', src, true);
90 | xhr.onload = function() {
91 | try {
92 | var data = JSON.parse(this.response);
93 | success(src, data);
94 | }
95 | catch(ex){
96 | error(src, ex);
97 | }
98 | };
99 | xhr.onerror = function(error) { error(src, error); };
100 | xhr.send();
101 | },
102 | _loadBin: function(src, success, error){
103 | var xhr = new XMLHttpRequest(),
104 | self = this;
105 | xhr.open('GET', src, true);
106 | xhr.responseType = 'arraybuffer';
107 | xhr.onload = function(data) { success(src, this.response); };
108 | xhr.onerror = function(error) { error(src, error); };
109 | xhr.send();
110 | },
111 | _loadData: function(src, success, error){
112 | var xhr = new XMLHttpRequest(),
113 | self = this;
114 | xhr.open('GET', src, true);
115 | xhr.onload = function(data) { success(src, this.response); };
116 | xhr.onerror = function(error) { error(src, error); };
117 | xhr.send();
118 | },
119 | _success: function(src, data) {
120 | },
121 | _error: function(src, error) {
122 | }
123 | };
124 |
125 | return Loader;
126 |
127 | });
128 |
--------------------------------------------------------------------------------
/src/engine/renderer/scene/camera.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwagner/fluidwebgl/a567799d063c13842356cbd1a5d91e0c649529a1/src/engine/renderer/scene/camera.js
--------------------------------------------------------------------------------
/src/engine/renderer/scene/light.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | exports.DirectionalLight = function(color) {
4 | };
5 |
6 | });
7 |
--------------------------------------------------------------------------------
/src/engine/renderer/scene/model.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwagner/fluidwebgl/a567799d063c13842356cbd1a5d91e0c649529a1/src/engine/renderer/scene/model.js
--------------------------------------------------------------------------------
/src/engine/renderer/scene/node.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | function noop(){}
4 | function compileChild(c){
5 | return c.compile();
6 | }
7 |
8 | exports.Node = function(parent) {
9 | this.uniforms = Object.create(null);
10 | this.root = this; //this.parent = parent;
11 | this.children = [];
12 | };
13 | exports.Node.prototype = {
14 | visit: function() {
15 | //if(this.debug) debugger;
16 | this.enter();
17 | for(var i = 0; i < this.children.length; i++) {
18 | this.children[i].visit();
19 | }
20 | this.exit();
21 | },
22 | enter: function() {
23 | },
24 | exit: function() {
25 | },
26 | updateWorldTransform: function() {
27 | for(var i = 0; i < this.children.length; i++) {
28 | this.children[i].updateWorldTransform();
29 | }
30 | },
31 | updateUniforms: function() {
32 | this.uniforms = this.parent.uniforms;
33 | for(var i = 0; i < this.children.length; i++) {
34 | this.children[i].updateUniforms();
35 | }
36 | },
37 |
38 | append: function (child) {
39 | this.children.push(child);
40 | child.setParent(this);
41 | return this;
42 | },
43 | setParent: function(parent) {
44 | this.root = parent.root;
45 | this.parent = parent;
46 | this.updateUniforms();
47 | }
48 |
49 | };
50 |
51 | });
52 |
--------------------------------------------------------------------------------
/src/engine/renderer/scene/root.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 | var camera = require('./camera');
3 |
4 | exports.Root = function() {
5 | this.camera = camera;
6 | this.uniforms = Object.create(null);
7 | };
8 | exports.Root.prototype = {
9 | };
10 |
11 | });
12 |
--------------------------------------------------------------------------------
/src/engine/renderer/scene/scene.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var scene = exports,
4 | mesh = require('gl/mesh'),
5 | glUtils = require('gl/utils'),
6 | vec3 = require('gl-matrix').vec3,
7 | vec4 = require('gl-matrix').vec4,
8 | mat3 = require('gl-matrix').mat3,
9 | mat4 = require('gl-matrix').mat4,
10 | extend = require('utils').extend;
11 |
12 | scene.Node = function SceneNode(children){
13 | this.children = children || [];
14 | };
15 | scene.Node.prototype = {
16 | debug: false,
17 | children: [],
18 | visit: function(graph) {
19 | //if(this.debug) debugger;
20 | this.enter(graph);
21 | for(var i = 0; i < this.children.length; i++) {
22 | var child = this.children[i];
23 | child.visit(graph);
24 | }
25 | this.exit(graph);
26 | },
27 | append: function (child) {
28 | this.children.push(child);
29 | },
30 | enter: function(graph) {
31 | },
32 | exit: function(graph) {
33 | }
34 | };
35 |
36 | scene.Uniforms = function UniformsNode(uniforms, children) {
37 | this.uniforms = uniforms;
38 | this.children = children;
39 | };
40 | scene.Uniforms.prototype = extend({}, scene.Node.prototype, {
41 | enter: function(graph) {
42 | for(var uniform in this.uniforms){
43 | var value = this.uniforms[uniform];
44 | if(value.bindTexture){
45 | value.bindTexture(graph.pushTexture());
46 | }
47 | }
48 | graph.pushUniforms();
49 | extend(graph.uniforms, this.uniforms);
50 | },
51 | exit: function(graph) {
52 | for(var uniform in this.uniforms){
53 | var value = this.uniforms[uniform];
54 | if(value.bindTexture){
55 | value.unbindTexture();
56 | graph.popTexture();
57 | }
58 | }
59 | graph.popUniforms();
60 | }
61 | });
62 |
63 | scene.Graph = function SceneGraph(gl){
64 | this.root = new scene.Node();
65 | this.uniforms = Object.create(null);
66 | this.shaders = [];
67 | this.viewportWidth = 640;
68 | this.viewportHeight = 480;
69 | this.textureUnit = 0;
70 | this.statistics = {
71 | drawCalls: 0,
72 | vertices: 0
73 | };
74 | };
75 | scene.Graph.prototype = {
76 | draw: function() {
77 |
78 | this.statistics.drawCalls = 0;
79 | this.statistics.vertices = 0;
80 |
81 | gl.viewport(0, 0, this.viewportWidth, this.viewportHeight);
82 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
83 | // only clearing DEPTH is supposedly faster
84 | //gl.clear(gl.DEPTH_BUFFER_BIT);
85 | //gl.enable(gl.DEPTH_TEST);
86 | this.root.visit(this);
87 | },
88 | pushUniforms: function() {
89 | this.uniforms = Object.create(this.uniforms);
90 | },
91 | popUniforms: function() {
92 | this.uniforms = Object.getPrototypeOf(this.uniforms);
93 | },
94 | pushTexture: function () {
95 | return this.textureUnit++;
96 | },
97 | popTexture: function() {
98 | this.textureUnit--;
99 | },
100 | pushShader: function (shader) {
101 | this.shaders.push(shader);
102 | },
103 | popShader: function() {
104 | this.shaders.pop();
105 | },
106 | getShader: function () {
107 | return this.shaders[this.shaders.length-1];
108 | }
109 | };
110 |
111 | scene.Material = function Material(shader, uniforms, children) {
112 | this.shader = shader;
113 | this.uniforms = uniforms;
114 | this.children = children;
115 | };
116 | scene.Material.prototype = extend({}, scene.Node.prototype, {
117 | enter: function(graph){
118 | graph.pushShader(this.shader);
119 | this.shader.use();
120 | scene.Uniforms.prototype.enter.call(this, graph);
121 | },
122 | exit: function(graph) {
123 | scene.Uniforms.prototype.exit.call(this, graph);
124 | graph.popShader();
125 | }
126 | });
127 |
128 | scene.RenderTarget = function RenderTarget(fbo, children){
129 | this.fbo = fbo;
130 | this.children = children;
131 | };
132 | scene.RenderTarget.prototype = extend({}, scene.Node.prototype, {
133 | enter: function(graph) {
134 | this.fbo.bind();
135 | gl.viewport(0, 0, this.fbo.width, this.fbo.height);
136 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
137 | },
138 | exit: function(graph) {
139 | // needed?
140 | this.fbo.unbind();
141 | gl.viewport(0, 0, graph.viewportWidth, graph.viewportHeight);
142 | }
143 | });
144 |
145 | scene.Camera = function Camera(children){
146 | this.position = vec3.create([0, 0, 10]);
147 | this.pitch = 0.0;
148 | this.yaw = 0.0;
149 | this.near = 0.1;
150 | this.far = 5000;
151 | this.fov = 50;
152 |
153 | this.children = children;
154 | };
155 | scene.Camera.prototype = extend({}, scene.Node.prototype, {
156 | enter: function (graph) {
157 | var projection = this.getProjection(graph),
158 | worldView = this.getWorldView(),
159 | wvp = mat4.create();
160 |
161 | graph.pushUniforms();
162 | mat4.multiply(projection, worldView, wvp);
163 | graph.uniforms.worldViewProjection = wvp;
164 | graph.uniforms.worldView = worldView;
165 | graph.uniforms.projection = projection;
166 | graph.uniforms.eye = this.position;
167 | //this.project([0, 0, 0, 1], scene);
168 | },
169 | project: function(point, graph) {
170 | var mvp = mat4.create();
171 | mat4.multiply(this.getProjection(graph), this.getWorldView(), mvp);
172 | var projected = mat4.multiplyVec4(mvp, point, vec4.create());
173 | vec4.scale(projected, 1/projected[3]);
174 | return projected;
175 | },
176 | exit: function(graph) {
177 | graph.popUniforms();
178 | },
179 | getRay: function() {
180 | var invRot = this.getInverseRotation(),
181 | forward = vec3.create([0, 0, -1]);
182 | mat4.multiplyVec3(invRot, forward);
183 | vec3.normalize(forward);
184 | return new Float32Array([this.position[0], this.position[1], this.position[2], forward[0], forward[1], forward[2]]);
185 | },
186 | getInverseRotation: function () {
187 | return mat3.toMat4(mat4.toInverseMat3(this.getWorldView()));
188 | },
189 | getRottionOnly: function () {
190 | return mat3.toMat4(mat4.toInverseMat3(this.getWorldView()));
191 | },
192 | getProjection: function (graph) {
193 | return mat4.perspective(this.fov, graph.viewportWidth/graph.viewportHeight, this.near, this.far);
194 | },
195 | getWorldView: function(){
196 | var matrix = mat4.identity(mat4.create());
197 | mat4.rotateX(matrix, this.pitch);
198 | mat4.rotateY(matrix, this.yaw);
199 | mat4.translate(matrix, vec3.negate(this.position, vec3.create()));
200 | return matrix;
201 | }
202 | });
203 |
204 |
205 |
206 | scene.Skybox = function SkyboxNode(scale, shader, uniforms) {
207 | var mesh_ = new scene.SimpleMesh(new glUtils.VBO(mesh.cube(scale))),
208 | material = new scene.Material(shader, uniforms, [mesh_]);
209 | this.children = [material];
210 | };
211 | scene.Skybox.prototype = extend({}, scene.Node.prototype, {
212 | enter: function(graph){
213 | graph.pushUniforms();
214 | var worldViewProjection = mat4.create(),
215 | worldView = mat3.toMat4(mat4.toMat3(graph.uniforms.worldView));
216 | //mat4.identity(worldView);
217 | mat4.multiply(graph.uniforms.projection, worldView, worldViewProjection);
218 | graph.uniforms.worldViewProjection = worldViewProjection;
219 | },
220 | exit: function(graph){
221 | graph.popUniforms();
222 | }
223 | });
224 |
225 | scene.Postprocess = function PostprocessNode(shader, uniforms) {
226 | var mesh_ = new scene.SimpleMesh(new glUtils.VBO(mesh.screen_quad())),
227 | material = new scene.Material(shader, uniforms, [mesh_]);
228 | this.children = [material];
229 | };
230 | scene.Postprocess.prototype = scene.Node.prototype;
231 |
232 | scene.Transform = function Transform(children){
233 | this.children = children || [];
234 | this.matrix = mat4.create();
235 | mat4.identity(this.matrix);
236 | this.aux = mat4.create();
237 | };
238 | scene.Transform.prototype = extend({}, scene.Node, {
239 | enter: function(graph) {
240 | graph.pushUniforms();
241 | if(graph.uniforms.modelTransform){
242 | mat4.multiply(graph.uniforms.modelTransform, this.matrix, this.aux);
243 | graph.uniforms.modelTransform = this.aux;
244 | }
245 | else{
246 | graph.uniforms.modelTransform = this.matrix;
247 | }
248 | },
249 | exit: function(graph) {
250 | graph.popUniforms();
251 | }
252 | });
253 |
254 | function sign(x){
255 | return x >= 0 ? 1 : -1;
256 | }
257 |
258 |
259 | scene.Mirror = function MirrorNode(plane, children){
260 | scene.Node.call(this, children);
261 | var a = plane[0],
262 | b = plane[1],
263 | c = plane[2];
264 | this._plane = vec4.create([plane[0], plane[1], plane[2], 0]);
265 | this._viewPlane = vec4.create();
266 | this._q = vec4.create();
267 | this._c = vec4.create();
268 | this._projection = mat4.create();
269 | this._worldView = mat4.create([
270 | 1.0-(2*a*a), 0.0-(2*a*b), 0.0-(2*a*c), 0.0,
271 | 0.0-(2*a*b), 1.0-(2*b*b), 0.0-(2*b*c), 0.0,
272 | 0.0-(2*a*c), 0.0-(2*b*c), 1.0-(2*c*c), 0.0,
273 | 0.0, 0.0, 0.0, 1.0
274 | ]);
275 | //mat4.identity(this._worldView);
276 | this._worldView_ = mat4.create();
277 | this._worldViewProjection = mat4.create();
278 | };
279 | scene.Mirror.prototype = extend({}, scene.Node.prototype, {
280 | enter: function (graph) {
281 | graph.pushUniforms();
282 | gl.cullFace(gl.FRONT);
283 | //
284 |
285 | var worldView = graph.uniforms.worldView,
286 | projection = mat4.set(graph.uniforms.projection, this._projection),
287 | p = this._viewPlane,
288 | q = this._q,
289 | c = this._c,
290 | // TODO calculate proper distance
291 | w = -graph.uniforms.eye[1];
292 |
293 | mat4.multiplyVec4(worldView, this._plane, p);
294 | p[3] = w;
295 | graph.uniforms.worldView = mat4.multiply(graph.uniforms.worldView, this._worldView, this._worldView_);
296 |
297 | q[0] = (sign(p.x) + projection[8]) / projection[0];
298 | q[1] = (sign(p.y) + projection[9]) / projection[5];
299 | q[2] = -1;
300 | q[3] = (1.0+projection[10] ) / projection[14];
301 |
302 | // scaled plane
303 | var dotpq = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3];
304 | c = vec4.scale(p, 2.0/dotpq);
305 |
306 | projection[2] = c[0];
307 | projection[6] = c[1];
308 | projection[10] = c[2] + 1.0;
309 | projection[14] = c[3];
310 |
311 | graph.uniforms.worldViewProjection = mat4.multiply(projection, this._worldView_, this._worldViewProjection);
312 | graph.uniforms.projection = projection;
313 |
314 | },
315 | exit: function (graph) {
316 | graph.popUniforms();
317 | gl.cullFace(gl.BACK);
318 | }
319 | });
320 |
321 |
322 |
323 | scene.SimpleMesh = function SimpleMesh(vbo, mode){
324 | this.vbo = vbo;
325 | this.mode = mode || gl.TRIANGLES;
326 | };
327 | scene.SimpleMesh.prototype = {
328 | visit: function (graph) {
329 | var shader = graph.getShader(),
330 | location = shader.getAttribLocation('position'),
331 | stride = 0,
332 | offset = 0,
333 | normalized = false;
334 |
335 | this.vbo.bind();
336 |
337 | gl.enableVertexAttribArray(location);
338 | gl.vertexAttribPointer(location, 3, gl.FLOAT, normalized, stride, offset);
339 |
340 | shader.uniforms(graph.uniforms);
341 |
342 | graph.statistics.drawCalls ++;
343 | graph.statistics.vertices += this.vbo.length/3;
344 |
345 | this.draw();
346 |
347 | this.vbo.unbind();
348 | },
349 | draw: function(){
350 | this.vbo.draw(this.mode);
351 | }
352 | };
353 |
354 | });
355 |
--------------------------------------------------------------------------------
/src/engine/utils.js:
--------------------------------------------------------------------------------
1 | if (typeof define !== 'function') { var define = require('amdefine')(module);}
2 | define(function(require, exports, module){
3 |
4 | var utils = exports;
5 | utils.extend = function extend() {
6 | var target = arguments[0],
7 | i, argument, name, f, value;
8 | for(i = 1; i < arguments.length; i++) {
9 | argument = arguments[i];
10 | for(name in argument) {
11 | target[name] = argument[name];
12 | }
13 | }
14 | return target;
15 | };
16 |
17 | utils.debounce = function(f, delay){
18 | var arguments_, timeout, this_;
19 | function run(){
20 | f.apply(this_, arguments_);
21 | }
22 | return function(){
23 | this_ = this;
24 | arguments_ = arguments;
25 | clearTimeout(timeout);
26 | timeout = setTimeout(run, delay);
27 | };
28 | };
29 |
30 | utils.clamp = function clamp(a, b, c) {
31 | return a < b ? b : (a > c ? c : a);
32 | };
33 |
34 | utils.getHashValue = function(name, default_){
35 | var match = window.location.hash.match('[#,]+' + name + '(=([^,]*))?');
36 | if(!match){
37 | return default_;
38 | }
39 | return match.length == 3 && match[2] != null ? match[2] : true;
40 | };
41 |
42 | });
43 |
--------------------------------------------------------------------------------
/src/game-shim.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview game-shim - Shims to normalize gaming-related APIs to their respective specs
3 | * @author Brandon Jones
4 | * @version 0.9
5 | */
6 |
7 | /*
8 | * Copyright (c) 2012 Brandon Jones
9 | *
10 | * This software is provided 'as-is', without any express or implied
11 | * warranty. In no event will the authors be held liable for any damages
12 | * arising from the use of this software.
13 | *
14 | * Permission is granted to anyone to use this software for any purpose,
15 | * including commercial applications, and to alter it and redistribute it
16 | * freely, subject to the following restrictions:
17 | *
18 | * 1. The origin of this software must not be misrepresented; you must not
19 | * claim that you wrote the original software. If you use this software
20 | * in a product, an acknowledgment in the product documentation would be
21 | * appreciated but is not required.
22 | *
23 | * 2. Altered source versions must be plainly marked as such, and must not
24 | * be misrepresented as being the original software.
25 | *
26 | * 3. This notice may not be removed or altered from any source
27 | * distribution.
28 | */
29 |
30 | (function(global) {
31 |
32 | var elementPrototype = (global.HTMLElement || global.Element)["prototype"];
33 | var getter;
34 |
35 | var GameShim = global.GameShim = {
36 | supports: {
37 | fullscreen: true,
38 | pointerLock: true,
39 | gamepad: true,
40 | highResTimer: true
41 | }
42 | };
43 |
44 | //=====================
45 | // Animation
46 | //=====================
47 |
48 | // window.requestAnimaionFrame, credit: Erik Moller
49 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
50 | (function() {
51 | var lastTime = 0;
52 | var vendors = ["webkit", "moz", "ms", "o"];
53 | var x;
54 |
55 | for(x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
56 | window.requestAnimationFrame = window[vendors[x]+"RequestAnimationFrame"];
57 | }
58 |
59 | window.cancelAnimationFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame; // Check for older syntax
60 | for(x = 0; x < vendors.length && !window.cancelAnimationFrame; ++x) {
61 | window.cancelAnimationFrame = window[vendors[x]+"CancelAnimationFrame"] || window[vendors[x]+"CancelRequestAnimationFrame"];
62 | }
63 |
64 | // Manual fallbacks
65 | if (!window.requestAnimationFrame) {
66 | window.requestAnimationFrame = function(callback, element) {
67 | var currTime = Date.now();
68 | var timeToCall = Math.max(0, 16 - (currTime - lastTime));
69 | var id = window.setTimeout(function() { callback(currTime + timeToCall); },
70 | timeToCall);
71 | lastTime = currTime + timeToCall;
72 | return id;
73 | };
74 | }
75 |
76 | if (!window.cancelAnimationFrame) {
77 | window.cancelAnimationFrame = function(id) {
78 | clearTimeout(id);
79 | };
80 | }
81 |
82 | // window.animationStartTime
83 | if(!window.animationStartTime) {
84 | getter = (function() {
85 | for(x = 0; x < vendors.length; ++x) {
86 | if(window[vendors[x] + "AnimationStartTime"]) {
87 | return function() { return window[vendors[x] + "AnimationStartTime"]; };
88 | }
89 | }
90 |
91 | return function() { return Date.now(); };
92 | })();
93 |
94 | Object.defineProperty(window, "animationStartTime", {
95 | enumerable: true, configurable: false, writeable: false,
96 | get: getter
97 | });
98 | }
99 | }());
100 |
101 | //=====================
102 | // Fullscreen
103 | //=====================
104 |
105 | // document.fullscreenEnabled
106 | if(!document.hasOwnProperty("fullscreenEnabled")) {
107 | getter = (function() {
108 | // These are the functions that match the spec, and should be preferred
109 | if("webkitIsFullScreen" in document) {
110 | return function() { return webkitRequestFullScreen in document; };
111 | }
112 | if("mozFullScreenEnabled" in document) {
113 | return function() { return document.mozFullScreenEnabled; };
114 | }
115 |
116 | GameShim.supports.fullscreen = false;
117 | return function() { return false; }; // not supported, never fullscreen
118 | })();
119 |
120 | Object.defineProperty(document, "fullscreenEnabled", {
121 | enumerable: true, configurable: false, writeable: false,
122 | get: getter
123 | });
124 | }
125 |
126 | if(!document.hasOwnProperty("fullscreenElement")) {
127 | getter = (function() {
128 | // These are the functions that match the spec, and should be preferred
129 | var i=0, name=["webkitCurrentFullScreenElement", "webkitFullscreenElement", "mozFullScreenElement"];
130 | for (; i* {
50 | vertical-align: top;
51 | display: inline;
52 | }
53 |
54 | #social .fb-like * {
55 | vertical-align: top;
56 | }
57 |
58 | #header>* {
59 | display: inline-block;
60 | vertical-align: baseline;
61 | color: #fff;
62 | opacity: 0.5;
63 | }
64 |
65 | #header>*:hover {
66 | opacity: 1.0;
67 | }
68 |
69 | #header a,
70 | #header a {
71 | color: #fff;
72 | text-decoration: none;
73 | }
74 |
75 | #header h1 {
76 | padding-right: 8px;
77 | }
78 |
79 | #menu {
80 | background-color: #333;
81 | padding: 4px 0;
82 | }
83 |
84 | #menu>span {
85 | padding: 0 8px;
86 | }
87 |
88 | .fullscreen,
89 | .generate-world,
90 | .show-controls {
91 | cursor: pointer;
92 | }
93 |
94 | #footer {
95 | position: absolute;
96 | bottom: 0;
97 | left: 0;
98 | width: 100%;
99 | height: 24px;
100 | color: white;
101 | opacity: 0.5;
102 | padding-left: 8px;
103 | }
104 |
105 | #footer:hover {}
106 |
107 | #footer a {
108 | color: #ffffff;
109 | }
110 |
111 | #footer a:hover {
112 | text-shadow: white 0 0 5px;
113 | }
114 |
115 | #loading {
116 | font-size: 2em;
117 | line-height: 2em;
118 | padding: 1em;
119 | }
120 |
121 | #loading .status {
122 | font-family: Monospace, mono-space;
123 | letter-spacing: 0.3em;
124 | }
125 |
126 |
127 | #debug {
128 | z-index: 200;
129 | position: absolute;
130 | right: 50px;
131 | top: 50px;
132 | width: 200px;
133 | padding: 10px;
134 | background-color: rgba(0, 0, 0, 0.8);
135 | display: none;
136 | }
137 |
138 | #perfhub {
139 | display: none;
140 | position: absolute;
141 | z-index: 300;
142 | top: 82px;
143 | right: 0px;
144 | background-color: black;
145 | }
146 |
147 | canvas:-webkit-full-screen {
148 | width: 100% !important;
149 | height: 100% !important;
150 | }
151 |
152 | canvas:-moz-full-screen {
153 | width: 100% !important;
154 | height: 100% !important;
155 | }
156 |
157 | #controls {
158 | position: absolute;
159 | left: 50%;
160 | padding: 8px;
161 | margin-left: -256px;
162 | width: 512px;
163 | z-index: 2;
164 | background-color: rgba(0, 0, 0, 0.5);
165 | border-radius: 8px;
166 | box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.2);
167 | border: 2px solid rgba(0, 0, 0, 0.6);
168 | font-size: 14px;
169 | }
170 |
171 | .key {
172 | border-radius: 4px;
173 | min-width: 24px;
174 | display: inline-block;
175 | text-align: center;
176 | padding: 2px;
177 | color: #333;
178 | background-color: #fff;
179 | }
180 |
181 | body .dg.ac {
182 | top: 60px;
183 | }
184 |
185 | a {
186 | color: #f00;
187 | }
188 |
189 | /* michroma-regular - latin */
190 | @font-face {
191 | font-family: 'Michroma';
192 | font-style: normal;
193 | font-weight: 400;
194 | src: url('data:font/woff;base64,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');
195 | }
--------------------------------------------------------------------------------