├── LICENSE ├── README.md ├── index.htm ├── js ├── Stats.js ├── dat.gui.min.js ├── gl-matrix-min.js ├── main.js └── shader-src.js └── shader ├── addSource.frag ├── addSource2.frag ├── advectDensityStep.frag ├── advectVelStep.frag ├── fluid.vert ├── renderVolume.frag ├── renderVolume.vert ├── setupPressure.frag ├── showTexture.frag ├── showTexture.vert ├── showTextureRGB.frag ├── solvePressure.frag └── updateVelocity.frag /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 hole 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webglSmoke 2 | Realtime smoke simulation & rendering using WebGL. 3 | 4 | Live demo: [http://kagamin.net/hole/smoke/index.htm](http://kagamin.net/hole/smoke/index.htm) 5 | 6 | YouTube: [https://www.youtube.com/watch?v=_ySiP7VCq_Q](https://www.youtube.com/watch?v=_ySiP7VCq_Q) 7 | 8 | ![screenshot](http://kagamin.net/hole/smoke/top.png) 9 | 10 | ## License 11 | 12 | The MIT License 13 | -------------------------------------------------------------------------------- /index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3D Smoke Simulation & Rendering 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /js/Stats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | var Stats = function () { 6 | 7 | var now = ( self.performance && self.performance.now ) ? self.performance.now.bind( performance ) : Date.now; 8 | 9 | var startTime = now(), prevTime = startTime; 10 | var frames = 0, mode = 0; 11 | 12 | function createElement( tag, id, css ) { 13 | 14 | var element = document.createElement( tag ); 15 | element.id = id; 16 | element.style.cssText = css; 17 | return element; 18 | 19 | } 20 | 21 | function createPanel( id, fg, bg ) { 22 | 23 | var div = createElement( 'div', id, 'padding:0 0 3px 3px;text-align:left;background:' + bg ); 24 | 25 | var text = createElement( 'div', id + 'Text', 'font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px;color:' + fg ); 26 | text.innerHTML = id.toUpperCase(); 27 | div.appendChild( text ); 28 | 29 | var graph = createElement( 'div', id + 'Graph', 'width:74px;height:30px;background:' + fg ); 30 | div.appendChild( graph ); 31 | 32 | for ( var i = 0; i < 74; i ++ ) { 33 | 34 | graph.appendChild( createElement( 'span', '', 'width:1px;height:30px;float:left;opacity:0.9;background:' + bg ) ); 35 | 36 | } 37 | 38 | return div; 39 | 40 | } 41 | 42 | function setMode( value ) { 43 | 44 | var children = container.children; 45 | 46 | for ( var i = 0; i < children.length; i ++ ) { 47 | 48 | children[ i ].style.display = i === value ? 'block' : 'none'; 49 | 50 | } 51 | 52 | mode = value; 53 | 54 | } 55 | 56 | function updateGraph( dom, value ) { 57 | 58 | var child = dom.appendChild( dom.firstChild ); 59 | child.style.height = Math.min( 30, 30 - value * 30 ) + 'px'; 60 | 61 | } 62 | 63 | // 64 | 65 | var container = createElement( 'div', 'stats', 'width:80px;opacity:0.9;cursor:pointer' ); 66 | container.addEventListener( 'mousedown', function ( event ) { 67 | 68 | event.preventDefault(); 69 | setMode( ++ mode % container.children.length ); 70 | 71 | }, false ); 72 | 73 | // FPS 74 | 75 | var fps = 0, fpsMin = Infinity, fpsMax = 0; 76 | 77 | var fpsDiv = createPanel( 'fps', '#0ff', '#002' ); 78 | var fpsText = fpsDiv.children[ 0 ]; 79 | var fpsGraph = fpsDiv.children[ 1 ]; 80 | 81 | container.appendChild( fpsDiv ); 82 | 83 | // MS 84 | 85 | var ms = 0, msMin = Infinity, msMax = 0; 86 | 87 | var msDiv = createPanel( 'ms', '#0f0', '#020' ); 88 | var msText = msDiv.children[ 0 ]; 89 | var msGraph = msDiv.children[ 1 ]; 90 | 91 | container.appendChild( msDiv ); 92 | 93 | // MEM 94 | 95 | if ( self.performance && self.performance.memory ) { 96 | 97 | var mem = 0, memMin = Infinity, memMax = 0; 98 | 99 | var memDiv = createPanel( 'mb', '#f08', '#201' ); 100 | var memText = memDiv.children[ 0 ]; 101 | var memGraph = memDiv.children[ 1 ]; 102 | 103 | container.appendChild( memDiv ); 104 | 105 | } 106 | 107 | // 108 | 109 | setMode( mode ); 110 | 111 | return { 112 | 113 | REVISION: 14, 114 | 115 | domElement: container, 116 | 117 | setMode: setMode, 118 | 119 | begin: function () { 120 | 121 | startTime = now(); 122 | 123 | }, 124 | 125 | end: function () { 126 | 127 | var time = now(); 128 | 129 | ms = time - startTime; 130 | msMin = Math.min( msMin, ms ); 131 | msMax = Math.max( msMax, ms ); 132 | 133 | msText.textContent = ( ms | 0 ) + ' MS (' + ( msMin | 0 ) + '-' + ( msMax | 0 ) + ')'; 134 | updateGraph( msGraph, ms / 200 ); 135 | 136 | frames ++; 137 | 138 | if ( time > prevTime + 1000 ) { 139 | 140 | fps = Math.round( ( frames * 1000 ) / ( time - prevTime ) ); 141 | fpsMin = Math.min( fpsMin, fps ); 142 | fpsMax = Math.max( fpsMax, fps ); 143 | 144 | fpsText.textContent = fps + ' FPS (' + fpsMin + '-' + fpsMax + ')'; 145 | updateGraph( fpsGraph, fps / 100 ); 146 | 147 | prevTime = time; 148 | frames = 0; 149 | 150 | if ( mem !== undefined ) { 151 | 152 | var heapSize = performance.memory.usedJSHeapSize; 153 | var heapSizeLimit = performance.memory.jsHeapSizeLimit; 154 | 155 | mem = Math.round( heapSize * 0.000000954 ); 156 | memMin = Math.min( memMin, mem ); 157 | memMax = Math.max( memMax, mem ); 158 | 159 | memText.textContent = mem + ' MB (' + memMin + '-' + memMax + ')'; 160 | updateGraph( memGraph, heapSize / heapSizeLimit ); 161 | 162 | } 163 | 164 | } 165 | 166 | return time; 167 | 168 | }, 169 | 170 | update: function () { 171 | 172 | startTime = this.end(); 173 | 174 | } 175 | 176 | } 177 | 178 | }; 179 | 180 | if ( typeof module === 'object' ) { 181 | 182 | module.exports = Stats; 183 | 184 | } 185 | -------------------------------------------------------------------------------- /js/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}(); 14 | dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}}, 15 | each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;bthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.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.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common); 29 | dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input, 30 | "blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b= 31 | this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); 32 | dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background= 33 | document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a= 34 | (this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.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(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object); 36 | this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 37 | dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 39 | dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); 40 | dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 41 | return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{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 3!= 42 | a.length?!1:{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 4!=a.length?!1:{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}:!1},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}:!1},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}:!1},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}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1; 44 | var b=1\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 {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n 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;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", 72 | dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e= 73 | function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype, 74 | f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(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(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(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 q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window, 77 | "mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1 78 | b&&(b=0);1e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); 79 | 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=document.createElement("input"); 80 | this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style, 81 | {position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%", 82 | background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.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",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window, 83 | "mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);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()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1; 84 | c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255<c?(u.cross(t,n,e),u.length(t)<1e-6&&u.cross(t,r,e),u.normalize(t,t),i.setAxisAngle(a,t,Math.PI),a):c>.999999?(a[0]=0,a[1]=0,a[2]=0,a[3]=1,a):(u.cross(t,e,o),a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=1+c,i.normalize(a,a))}}(),i.setAxes=function(){var t=e.create();return function(n,r,a,e){return t[0]=a[0],t[3]=a[1],t[6]=a[2],t[1]=e[0],t[4]=e[1],t[7]=e[2],t[2]=-r[0],t[5]=-r[1],t[8]=-r[2],i.normalize(n,i.fromMat3(n,t))}}(),i.clone=o.clone,i.fromValues=o.fromValues,i.copy=o.copy,i.set=o.set,i.identity=function(t){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t},i.setAxisAngle=function(t,n,r){r=.5*r;var a=Math.sin(r);return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=Math.cos(r),t},i.add=o.add,i.multiply=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],c=r[1],f=r[2],s=r[3];return t[0]=a*s+o*i+e*f-u*c,t[1]=e*s+o*c+u*i-a*f,t[2]=u*s+o*f+a*c-e*i,t[3]=o*s-a*i-e*c-u*f,t},i.mul=i.multiply,i.scale=o.scale,i.rotateX=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),c=Math.cos(r);return t[0]=a*c+o*i,t[1]=e*c+u*i,t[2]=u*c-e*i,t[3]=o*c-a*i,t},i.rotateY=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),c=Math.cos(r);return t[0]=a*c-u*i,t[1]=e*c+o*i,t[2]=u*c+a*i,t[3]=o*c-e*i,t},i.rotateZ=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),c=Math.cos(r);return t[0]=a*c+e*i,t[1]=e*c-a*i,t[2]=u*c+o*i,t[3]=o*c-u*i,t},i.calculateW=function(t,n){var r=n[0],a=n[1],e=n[2];return t[0]=r,t[1]=a,t[2]=e,t[3]=Math.sqrt(Math.abs(1-r*r-a*a-e*e)),t},i.dot=o.dot,i.lerp=o.lerp,i.slerp=function(t,n,r,a){var e,u,o,i,c,f=n[0],s=n[1],h=n[2],M=n[3],l=r[0],v=r[1],m=r[2],p=r[3];return u=f*l+s*v+h*m+M*p,0>u&&(u=-u,l=-l,v=-v,m=-m,p=-p),1-u>1e-6?(e=Math.acos(u),o=Math.sin(e),i=Math.sin((1-a)*e)/o,c=Math.sin(a*e)/o):(i=1-a,c=a),t[0]=i*f+c*l,t[1]=i*s+c*v,t[2]=i*h+c*m,t[3]=i*M+c*p,t},i.sqlerp=function(){var t=i.create(),n=i.create();return function(r,a,e,u,o,c){return i.slerp(t,a,o,c),i.slerp(n,e,u,c),i.slerp(r,t,n,2*c*(1-c)),r}}(),i.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u,i=o?1/o:0;return t[0]=-r*i,t[1]=-a*i,t[2]=-e*i,t[3]=u*i,t},i.conjugate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=n[3],t},i.length=o.length,i.len=i.length,i.squaredLength=o.squaredLength,i.sqrLen=i.squaredLength,i.normalize=o.normalize,i.fromMat3=function(t,n){var r,a=n[0]+n[4]+n[8];if(a>0)r=Math.sqrt(a+1),t[3]=.5*r,r=.5/r,t[0]=(n[5]-n[7])*r,t[1]=(n[6]-n[2])*r,t[2]=(n[1]-n[3])*r;else{var e=0;n[4]>n[0]&&(e=1),n[8]>n[3*e+e]&&(e=2);var u=(e+1)%3,o=(e+2)%3;r=Math.sqrt(n[3*e+e]-n[3*u+u]-n[3*o+o]+1),t[e]=.5*r,r=.5/r,t[3]=(n[3*u+o]-n[3*o+u])*r,t[u]=(n[3*u+e]+n[3*e+u])*r,t[o]=(n[3*o+e]+n[3*e+o])*r}return t},i.str=function(t){return"quat("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},t.exports=i},function(t,n,r){var a=r(1),e={};e.create=function(){var t=new a.ARRAY_TYPE(3);return t[0]=0,t[1]=0,t[2]=0,t},e.clone=function(t){var n=new a.ARRAY_TYPE(3);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n},e.fromValues=function(t,n,r){var e=new a.ARRAY_TYPE(3);return e[0]=t,e[1]=n,e[2]=r,e},e.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t},e.set=function(t,n,r,a){return t[0]=n,t[1]=r,t[2]=a,t},e.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t},e.subtract=function(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t},e.sub=e.subtract,e.multiply=function(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t},e.mul=e.multiply,e.divide=function(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t},e.div=e.divide,e.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t[2]=Math.min(n[2],r[2]),t},e.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t[2]=Math.max(n[2],r[2]),t},e.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t},e.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t},e.distance=function(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return Math.sqrt(r*r+a*a+e*e)},e.dist=e.distance,e.squaredDistance=function(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return r*r+a*a+e*e},e.sqrDist=e.squaredDistance,e.length=function(t){var n=t[0],r=t[1],a=t[2];return Math.sqrt(n*n+r*r+a*a)},e.len=e.length,e.squaredLength=function(t){var n=t[0],r=t[1],a=t[2];return n*n+r*r+a*a},e.sqrLen=e.squaredLength,e.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t},e.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t[2]=1/n[2],t},e.normalize=function(t,n){var r=n[0],a=n[1],e=n[2],u=r*r+a*a+e*e;return u>0&&(u=1/Math.sqrt(u),t[0]=n[0]*u,t[1]=n[1]*u,t[2]=n[2]*u),t},e.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]},e.cross=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],c=r[2];return t[0]=e*c-u*i,t[1]=u*o-a*c,t[2]=a*i-e*o,t},e.lerp=function(t,n,r,a){var e=n[0],u=n[1],o=n[2];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t},e.hermite=function(t,n,r,a,e,u){var o=u*u,i=o*(2*u-3)+1,c=o*(u-2)+u,f=o*(u-1),s=o*(3-2*u);return t[0]=n[0]*i+r[0]*c+a[0]*f+e[0]*s,t[1]=n[1]*i+r[1]*c+a[1]*f+e[1]*s,t[2]=n[2]*i+r[2]*c+a[2]*f+e[2]*s,t},e.bezier=function(t,n,r,a,e,u){var o=1-u,i=o*o,c=u*u,f=i*o,s=3*u*i,h=3*c*o,M=c*u;return t[0]=n[0]*f+r[0]*s+a[0]*h+e[0]*M,t[1]=n[1]*f+r[1]*s+a[1]*h+e[1]*M,t[2]=n[2]*f+r[2]*s+a[2]*h+e[2]*M,t},e.random=function(t,n){n=n||1;var r=2*a.RANDOM()*Math.PI,e=2*a.RANDOM()-1,u=Math.sqrt(1-e*e)*n;return t[0]=Math.cos(r)*u,t[1]=Math.sin(r)*u,t[2]=e*n,t},e.transformMat4=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[3]*a+r[7]*e+r[11]*u+r[15];return o=o||1,t[0]=(r[0]*a+r[4]*e+r[8]*u+r[12])/o,t[1]=(r[1]*a+r[5]*e+r[9]*u+r[13])/o,t[2]=(r[2]*a+r[6]*e+r[10]*u+r[14])/o,t},e.transformMat3=function(t,n,r){var a=n[0],e=n[1],u=n[2];return t[0]=a*r[0]+e*r[3]+u*r[6],t[1]=a*r[1]+e*r[4]+u*r[7],t[2]=a*r[2]+e*r[5]+u*r[8],t},e.transformQuat=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],c=r[2],f=r[3],s=f*a+i*u-c*e,h=f*e+c*a-o*u,M=f*u+o*e-i*a,l=-o*a-i*e-c*u;return t[0]=s*f+l*-o+h*-c-M*-i,t[1]=h*f+l*-i+M*-o-s*-c,t[2]=M*f+l*-c+s*-i-h*-o,t},e.rotateX=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[0],u[1]=e[1]*Math.cos(a)-e[2]*Math.sin(a),u[2]=e[1]*Math.sin(a)+e[2]*Math.cos(a),t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},e.rotateY=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[2]*Math.sin(a)+e[0]*Math.cos(a),u[1]=e[1],u[2]=e[2]*Math.cos(a)-e[0]*Math.sin(a),t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},e.rotateZ=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[0]*Math.cos(a)-e[1]*Math.sin(a),u[1]=e[0]*Math.sin(a)+e[1]*Math.cos(a),u[2]=e[2],t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},e.forEach=function(){var t=e.create();return function(n,r,a,e,u,o){var i,c;for(r||(r=3),a||(a=0),c=e?Math.min(e*r+a,n.length):n.length,i=a;c>i;i+=r)t[0]=n[i],t[1]=n[i+1],t[2]=n[i+2],u(t,t,o),n[i]=t[0],n[i+1]=t[1],n[i+2]=t[2];return n}}(),e.angle=function(t,n){var r=e.fromValues(t[0],t[1],t[2]),a=e.fromValues(n[0],n[1],n[2]);e.normalize(r,r),e.normalize(a,a);var u=e.dot(r,a);return u>1?0:Math.acos(u)},e.str=function(t){return"vec3("+t[0]+", "+t[1]+", "+t[2]+")"},t.exports=e},function(t,n,r){var a=r(1),e={};e.create=function(){var t=new a.ARRAY_TYPE(4);return t[0]=0,t[1]=0,t[2]=0,t[3]=0,t},e.clone=function(t){var n=new a.ARRAY_TYPE(4);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n},e.fromValues=function(t,n,r,e){var u=new a.ARRAY_TYPE(4);return u[0]=t,u[1]=n,u[2]=r,u[3]=e,u},e.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t},e.set=function(t,n,r,a,e){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t},e.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t},e.subtract=function(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t},e.sub=e.subtract,e.multiply=function(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t[3]=n[3]*r[3],t},e.mul=e.multiply,e.divide=function(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t[3]=n[3]/r[3],t},e.div=e.divide,e.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t[2]=Math.min(n[2],r[2]),t[3]=Math.min(n[3],r[3]),t},e.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t[2]=Math.max(n[2],r[2]),t[3]=Math.max(n[3],r[3]),t},e.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t},e.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t},e.distance=function(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2],u=n[3]-t[3];return Math.sqrt(r*r+a*a+e*e+u*u)},e.dist=e.distance,e.squaredDistance=function(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2],u=n[3]-t[3];return r*r+a*a+e*e+u*u},e.sqrDist=e.squaredDistance,e.length=function(t){var n=t[0],r=t[1],a=t[2],e=t[3];return Math.sqrt(n*n+r*r+a*a+e*e)},e.len=e.length,e.squaredLength=function(t){var n=t[0],r=t[1],a=t[2],e=t[3];return n*n+r*r+a*a+e*e},e.sqrLen=e.squaredLength,e.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=-n[3],t},e.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t[2]=1/n[2],t[3]=1/n[3],t},e.normalize=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u;return o>0&&(o=1/Math.sqrt(o),t[0]=r*o,t[1]=a*o,t[2]=e*o,t[3]=u*o),t},e.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]+t[3]*n[3]},e.lerp=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t[3]=i+a*(r[3]-i),t},e.random=function(t,n){return n=n||1,t[0]=a.RANDOM(),t[1]=a.RANDOM(),t[2]=a.RANDOM(),t[3]=a.RANDOM(),e.normalize(t,t),e.scale(t,t,n),t},e.transformMat4=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3];return t[0]=r[0]*a+r[4]*e+r[8]*u+r[12]*o,t[1]=r[1]*a+r[5]*e+r[9]*u+r[13]*o,t[2]=r[2]*a+r[6]*e+r[10]*u+r[14]*o,t[3]=r[3]*a+r[7]*e+r[11]*u+r[15]*o,t},e.transformQuat=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],c=r[2],f=r[3],s=f*a+i*u-c*e,h=f*e+c*a-o*u,M=f*u+o*e-i*a,l=-o*a-i*e-c*u;return t[0]=s*f+l*-o+h*-c-M*-i,t[1]=h*f+l*-i+M*-o-s*-c,t[2]=M*f+l*-c+s*-i-h*-o,t[3]=n[3],t},e.forEach=function(){var t=e.create();return function(n,r,a,e,u,o){var i,c;for(r||(r=4),a||(a=0),c=e?Math.min(e*r+a,n.length):n.length,i=a;c>i;i+=r)t[0]=n[i],t[1]=n[i+1],t[2]=n[i+2],t[3]=n[i+3],u(t,t,o),n[i]=t[0],n[i+1]=t[1],n[i+2]=t[2],n[i+3]=t[3];return n}}(),e.str=function(t){return"vec4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},t.exports=e},function(t,n,r){var a=r(1),e={};e.create=function(){var t=new a.ARRAY_TYPE(2);return t[0]=0,t[1]=0,t},e.clone=function(t){var n=new a.ARRAY_TYPE(2);return n[0]=t[0],n[1]=t[1],n},e.fromValues=function(t,n){var r=new a.ARRAY_TYPE(2);return r[0]=t,r[1]=n,r},e.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t},e.set=function(t,n,r){return t[0]=n,t[1]=r,t},e.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t},e.subtract=function(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t},e.sub=e.subtract,e.multiply=function(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t},e.mul=e.multiply,e.divide=function(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t},e.div=e.divide,e.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t},e.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t},e.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t},e.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t},e.distance=function(t,n){var r=n[0]-t[0],a=n[1]-t[1];return Math.sqrt(r*r+a*a)},e.dist=e.distance,e.squaredDistance=function(t,n){var r=n[0]-t[0],a=n[1]-t[1];return r*r+a*a},e.sqrDist=e.squaredDistance,e.length=function(t){var n=t[0],r=t[1];return Math.sqrt(n*n+r*r)},e.len=e.length,e.squaredLength=function(t){var n=t[0],r=t[1];return n*n+r*r},e.sqrLen=e.squaredLength,e.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t},e.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t},e.normalize=function(t,n){var r=n[0],a=n[1],e=r*r+a*a;return e>0&&(e=1/Math.sqrt(e),t[0]=n[0]*e,t[1]=n[1]*e),t},e.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]},e.cross=function(t,n,r){var a=n[0]*r[1]-n[1]*r[0];return t[0]=t[1]=0,t[2]=a,t},e.lerp=function(t,n,r,a){var e=n[0],u=n[1];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t},e.random=function(t,n){n=n||1;var r=2*a.RANDOM()*Math.PI;return t[0]=Math.cos(r)*n,t[1]=Math.sin(r)*n,t},e.transformMat2=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e,t[1]=r[1]*a+r[3]*e,t},e.transformMat2d=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e+r[4],t[1]=r[1]*a+r[3]*e+r[5],t},e.transformMat3=function(t,n,r){ 29 | var a=n[0],e=n[1];return t[0]=r[0]*a+r[3]*e+r[6],t[1]=r[1]*a+r[4]*e+r[7],t},e.transformMat4=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[4]*e+r[12],t[1]=r[1]*a+r[5]*e+r[13],t},e.forEach=function(){var t=e.create();return function(n,r,a,e,u,o){var i,c;for(r||(r=2),a||(a=0),c=e?Math.min(e*r+a,n.length):n.length,i=a;c>i;i+=r)t[0]=n[i],t[1]=n[i+1],u(t,t,o),n[i]=t[0],n[i+1]=t[1];return n}}(),e.str=function(t){return"vec2("+t[0]+", "+t[1]+")"},t.exports=e}])}); -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | 2 | var gMouseX = 0; 3 | var gMouseY = 0; 4 | 5 | var gScreenWidth = 0; 6 | var gScreenHeight = 0; 7 | 8 | var gCurrentCameraDistance = 20.0; 9 | 10 | function onMouseMove(event) { 11 | gMouseX = event.offsetX; 12 | gMouseY = event.offsetY; 13 | } 14 | 15 | var gl; 16 | function initGL(canvas) { 17 | try { 18 | gl = canvas.getContext("experimental-webgl"); 19 | var ext; 20 | ext = gl.getExtension('OES_texture_float'); 21 | if(ext == null){ 22 | alert('float texture not supported'); 23 | return; 24 | } 25 | 26 | gl.viewportWidth = canvas.width; 27 | gl.viewportHeight = canvas.height; 28 | } catch (e) { 29 | } 30 | if (!gl) { 31 | alert("Could not initialise WebGL, sorry :-("); 32 | } 33 | } 34 | 35 | 36 | // **************************************************************** // 37 | // 38 | // Initialize textures & render targets 39 | // 40 | // **************************************************************** // 41 | var gSimX = 64; 42 | var gSimY = 64; 43 | var gSimZ = 64; 44 | 45 | // 3D texture 64x64x64 -> 2D texture 512x512 46 | var gSimTexWidth = 512; 47 | var gSimTexHeight = 512; 48 | 49 | var velDensityRT = []; 50 | var densityRT = []; 51 | var pressureRT = []; 52 | 53 | var volumeHalfRT; 54 | var volumeRT; 55 | 56 | function createRenderTarget(width, height, type, data) { 57 | var rttFramebuffer = gl.createFramebuffer(); 58 | gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer); 59 | rttFramebuffer.width = width; 60 | rttFramebuffer.height = height; 61 | 62 | rttFramebuffer.texture = gl.createTexture(); 63 | gl.bindTexture(gl.TEXTURE_2D, rttFramebuffer.texture); 64 | // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 65 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, type, data); 66 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 67 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 68 | 69 | /* 70 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); 71 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 72 | */ 73 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 74 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 75 | 76 | 77 | var renderbuffer = gl.createRenderbuffer(); 78 | gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); 79 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttFramebuffer.width, rttFramebuffer.height); 80 | 81 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttFramebuffer.texture, 0); 82 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer); 83 | 84 | gl.bindTexture(gl.TEXTURE_2D, null); 85 | gl.bindRenderbuffer(gl.RENDERBUFFER, null); 86 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 87 | 88 | return rttFramebuffer; 89 | } 90 | 91 | function initTextures() { 92 | { 93 | var width = gScreenWidth / 2; 94 | var height = gScreenHeight / 2; 95 | var data = new Uint8Array(width * height * 4); 96 | volumeHalfRT = createRenderTarget(width, height, gl.UNSIGNED_BYTE, data); 97 | } 98 | { 99 | var width = gScreenWidth; 100 | var height = gScreenHeight; 101 | var data = new Uint8Array(width * height * 4); 102 | volumeRT = createRenderTarget(width, height, gl.UNSIGNED_BYTE, data); 103 | } 104 | for (var i = 0; i < 2; ++i) { 105 | var width = gSimTexWidth; 106 | var height = gSimTexHeight; 107 | var data = new Float32Array(width * height * 4); 108 | velDensityRT[i] = createRenderTarget(width, height, gl.FLOAT, data); 109 | } 110 | 111 | for (var i = 0; i < 2; ++i) { 112 | var width = gSimTexWidth; 113 | var height = gSimTexHeight; 114 | var data = new Float32Array(width * height * 4); 115 | pressureRT[i] = createRenderTarget(width, height, gl.FLOAT, data); 116 | } 117 | } 118 | 119 | // **************************************************************** // 120 | // 121 | // Shader utils 122 | // 123 | // **************************************************************** // 124 | function getShader(gl, str, type) { 125 | var shader; 126 | if (type == "fragment") { 127 | shader = gl.createShader(gl.FRAGMENT_SHADER); 128 | } else if (type == "vertex") { 129 | shader = gl.createShader(gl.VERTEX_SHADER); 130 | } else { 131 | return null; 132 | } 133 | 134 | gl.shaderSource(shader, str); 135 | gl.compileShader(shader); 136 | 137 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 138 | alert(str); 139 | alert(gl.getShaderInfoLog(shader)); 140 | return null; 141 | } 142 | 143 | return shader; 144 | } 145 | 146 | function createShader(fragmentStr, vertexStr) { 147 | var fragmentShader = getShader(gl, fragmentStr, "fragment"); 148 | var vertexShader = getShader(gl, vertexStr, "vertex"); 149 | 150 | var shader = gl.createProgram(); 151 | gl.attachShader(shader, vertexShader); 152 | gl.attachShader(shader, fragmentShader); 153 | gl.linkProgram(shader); 154 | 155 | if (!gl.getProgramParameter(shader, gl.LINK_STATUS)) { 156 | alert("Could not initialise shaders"); 157 | } 158 | return shader; 159 | } 160 | 161 | // **************************************************************** // 162 | // 163 | // Initialize shaders 164 | // 165 | // **************************************************************** // 166 | var showTextureShader; 167 | var showTextureRGBShader; 168 | function initShowTextureShader() { 169 | showTextureShader = createShader(showTexture_frag, showTexture_vert); 170 | 171 | gl.useProgram(showTextureShader); 172 | 173 | showTextureShader.vertexPositionAttribute = gl.getAttribLocation(showTextureShader, "aVertexPosition"); 174 | gl.enableVertexAttribArray(showTextureShader.vertexPositionAttribute); 175 | showTextureShader.textureCoordAttribute = gl.getAttribLocation(showTextureShader, "aTextureCoord"); 176 | gl.enableVertexAttribArray(showTextureShader.textureCoordAttribute); 177 | 178 | showTextureShader.perspectiveMatrixUniform = gl.getUniformLocation(showTextureShader, "uPerspectiveMatrix"); 179 | showTextureShader.modelMatrixUniform = gl.getUniformLocation(showTextureShader, "uModelMatrix"); 180 | showTextureShader.viewMatrixUniform = gl.getUniformLocation(showTextureShader, "uViewMatrix"); 181 | 182 | showTextureShader.samplerUniform = gl.getUniformLocation(showTextureShader, "uSampler"); 183 | 184 | showTextureRGBShader = createShader(showTextureRGB_frag, showTexture_vert); 185 | 186 | gl.useProgram(showTextureRGBShader); 187 | 188 | showTextureRGBShader.vertexPositionAttribute = gl.getAttribLocation(showTextureRGBShader, "aVertexPosition"); 189 | gl.enableVertexAttribArray(showTextureRGBShader.vertexPositionAttribute); 190 | showTextureRGBShader.textureCoordAttribute = gl.getAttribLocation(showTextureRGBShader, "aTextureCoord"); 191 | gl.enableVertexAttribArray(showTextureRGBShader.textureCoordAttribute); 192 | 193 | showTextureRGBShader.perspectiveMatrixUniform = gl.getUniformLocation(showTextureRGBShader, "uPerspectiveMatrix"); 194 | showTextureRGBShader.modelMatrixUniform = gl.getUniformLocation(showTextureRGBShader, "uModelMatrix"); 195 | showTextureRGBShader.viewMatrixUniform = gl.getUniformLocation(showTextureRGBShader, "uViewMatrix"); 196 | 197 | showTextureRGBShader.samplerUniform = gl.getUniformLocation(showTextureRGBShader, "uSampler"); 198 | } 199 | 200 | function initFluidShader(frag_str, vert_str) { 201 | var shader = createShader(frag_str, vert_str); 202 | 203 | gl.useProgram(shader); 204 | 205 | shader.vertexPositionAttribute = gl.getAttribLocation(shader, "aVertexPosition"); 206 | gl.enableVertexAttribArray(shader.vertexPositionAttribute); 207 | shader.textureCoordAttribute = gl.getAttribLocation(shader, "aTextureCoord"); 208 | gl.enableVertexAttribArray(shader.textureCoordAttribute); 209 | 210 | shader.perspectiveMatrixUniform = gl.getUniformLocation(shader, "uPerspectiveMatrix"); 211 | shader.modelMatrixUniform = gl.getUniformLocation(shader, "uModelMatrix"); 212 | shader.viewMatrixUniform = gl.getUniformLocation(shader, "uViewMatrix"); 213 | 214 | shader.velDensityTextureUniform = gl.getUniformLocation(shader, "uVelDensityTexture"); 215 | shader.pressureTextureUniform = gl.getUniformLocation(shader, "uPressureTexture"); 216 | 217 | shader.TsWsUniform = gl.getUniformLocation(shader, "uTsWs"); 218 | shader.WsTsUniform = gl.getUniformLocation(shader, "uWsTs"); 219 | shader.TsIsUniform = gl.getUniformLocation(shader, "uTsIs"); 220 | shader.IsTsUniform = gl.getUniformLocation(shader, "uIsTs"); 221 | shader.optionUniform = gl.getUniformLocation(shader, "uOption"); 222 | shader.invResolutionUniform = gl.getUniformLocation(shader, "uInvResolution"); 223 | 224 | shader.texResolutionUniform = gl.getUniformLocation(shader, "uTexResolution"); 225 | shader.simResolutionUniform = gl.getUniformLocation(shader, "uSimResolution"); 226 | shader.sliceResolutionUniform = gl.getUniformLocation(shader, "uSliceResolution"); 227 | 228 | shader.timeUniform = gl.getUniformLocation(shader, "uTime"); 229 | 230 | return shader; 231 | } 232 | 233 | function initRenderVolumeShader(frag_str, vert_str) { 234 | var shader = createShader(frag_str, vert_str); 235 | 236 | gl.useProgram(shader); 237 | 238 | shader.vertexPositionAttribute = gl.getAttribLocation(shader, "aVertexPosition"); 239 | gl.enableVertexAttribArray(shader.vertexPositionAttribute); 240 | shader.textureCoordAttribute = gl.getAttribLocation(shader, "aTextureCoord"); 241 | gl.enableVertexAttribArray(shader.textureCoordAttribute); 242 | 243 | shader.perspectiveMatrixUniform = gl.getUniformLocation(shader, "uPerspectiveMatrix"); 244 | shader.modelMatrixUniform = gl.getUniformLocation(shader, "uModelMatrix"); 245 | shader.viewMatrixUniform = gl.getUniformLocation(shader, "uViewMatrix"); 246 | shader.invViewMatrixUniform = gl.getUniformLocation(shader, "uInvViewMatrix"); 247 | shader.cameraParamsUniform = gl.getUniformLocation(shader, "uCameraParams"); 248 | 249 | shader.velDensityTextureUniform = gl.getUniformLocation(shader, "uVelDensityTexture"); 250 | 251 | 252 | shader.projParamsUniform = gl.getUniformLocation(shader, "uProjParams"); 253 | shader.texResolutionUniform = gl.getUniformLocation(shader, "uTexResolution"); 254 | shader.simResolutionUniform = gl.getUniformLocation(shader, "uSimResolution"); 255 | shader.sliceResolutionUniform = gl.getUniformLocation(shader, "uSliceResolution"); 256 | 257 | 258 | shader.optionUniform = gl.getUniformLocation(shader, "uOption"); 259 | return shader; 260 | } 261 | 262 | var advectDensityStepShader; 263 | var advectVelStepShader; 264 | var setupPressureShader; 265 | var updateVelocityShader; 266 | var solvePressureShader; 267 | 268 | var addSourceShader; 269 | var addSourceShader2; 270 | 271 | var renderVolumeShader; 272 | 273 | function initShaders() { 274 | initShowTextureShader(); 275 | 276 | advectDensityStepShader = initFluidShader(advectDensityStep_frag, fluid_vert); 277 | advectVelStepShader = initFluidShader(advectVelStep_frag, fluid_vert); 278 | setupPressureShader = initFluidShader(setupPressure_frag, fluid_vert); 279 | updateVelocityShader = initFluidShader(updateVelocity_frag, fluid_vert); 280 | solvePressureShader = initFluidShader(solvePressure_frag, fluid_vert); 281 | addSourceShader = initFluidShader(addSource_frag, fluid_vert); 282 | addSourceShader2 = initFluidShader(addSource2_frag, fluid_vert); 283 | renderVolumeShader = initRenderVolumeShader(renderVolume_frag, fluid_vert); 284 | } 285 | 286 | 287 | // **************************************************************** // 288 | // 289 | // Matrix & vertex buffers 290 | // 291 | // **************************************************************** // 292 | var modelMatrix = mat4.create(); 293 | var viewMatrix = mat4.create(); 294 | var invViewMatrix = mat4.create(); 295 | var perspectiveMatrix = mat4.create(); 296 | var orthoMatrix = mat4.create(); 297 | var translateMatrix = mat4.create(); 298 | 299 | var plainVertexBuffer; 300 | var plainTextureCoordBuffer; 301 | 302 | function initBuffers() { 303 | // plain 304 | { 305 | var vertices = [ 306 | 1, 1, 307 | -1, 1, 308 | 1, -1, 309 | -1, -1 310 | ]; 311 | var textureCoords = [ 312 | 0, 0, 313 | 1, 0, 314 | 0, 1, 315 | 1, 1 316 | ]; 317 | plainVertexBuffer = gl.createBuffer(); 318 | gl.bindBuffer(gl.ARRAY_BUFFER, plainVertexBuffer); 319 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); 320 | plainVertexBuffer.itemSize = 2; 321 | plainVertexBuffer.numItems = 4; 322 | 323 | plainTextureCoordBuffer = gl.createBuffer(); 324 | gl.bindBuffer(gl.ARRAY_BUFFER, plainTextureCoordBuffer); 325 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW); 326 | plainTextureCoordBuffer.itemSize = 2; 327 | plainTextureCoordBuffer.numItems = 4; 328 | } 329 | } 330 | 331 | function drawTexture(shader, texture, x, y, w, h) { 332 | mat4.ortho(orthoMatrix, -1, 1, -1, 1, -100, 100.0); 333 | mat4.identity(viewMatrix); 334 | gl.disable(gl.DEPTH_TEST); 335 | { 336 | gl.useProgram(shader); 337 | 338 | gl.bindBuffer(gl.ARRAY_BUFFER, plainVertexBuffer); 339 | gl.vertexAttribPointer(shader.vertexPositionAttribute, plainVertexBuffer.itemSize, gl.FLOAT, false, 0, 0); 340 | 341 | mat4.fromScaling(modelMatrix, [w, -h, 0]); 342 | mat4.fromTranslation(translateMatrix, [x, y, 0]); 343 | mat4.multiply(modelMatrix, translateMatrix, modelMatrix); 344 | // set matrix 345 | gl.uniformMatrix4fv(shader.perspectiveMatrixUniform, false, orthoMatrix); 346 | gl.uniformMatrix4fv(shader.modelMatrixUniform, false, modelMatrix); 347 | gl.uniformMatrix4fv(shader.viewMatrixUniform, false, viewMatrix); 348 | // texture 349 | gl.bindBuffer(gl.ARRAY_BUFFER, plainTextureCoordBuffer); 350 | gl.vertexAttribPointer(shader.textureCoordAttribute, plainTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0); 351 | gl.activeTexture(gl.TEXTURE0); 352 | gl.bindTexture(gl.TEXTURE_2D, texture); 353 | gl.uniform1i(shader.samplerUniform, 0); 354 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, plainVertexBuffer.numItems); 355 | } 356 | gl.enable(gl.DEPTH_TEST); 357 | } 358 | 359 | // **************************************************************** // 360 | // 361 | // Update simulation textures 362 | // 363 | // **************************************************************** // 364 | var gFrame = 0; 365 | function update(currentVelDensityRT, currentPressureRT, nextRT, shader) { 366 | gl.bindFramebuffer(gl.FRAMEBUFFER, nextRT); 367 | 368 | var width = gSimTexWidth; 369 | var height = gSimTexHeight; 370 | gl.viewport(0, 0, width, height); 371 | 372 | mat4.ortho(orthoMatrix, 1, -1, 1, -1, -100, 100.0); 373 | mat4.identity(viewMatrix); 374 | gl.disable(gl.DEPTH_TEST); 375 | { 376 | gl.useProgram(shader); 377 | 378 | gl.bindBuffer(gl.ARRAY_BUFFER, plainVertexBuffer); 379 | gl.vertexAttribPointer(shader.vertexPositionAttribute, plainVertexBuffer.itemSize, gl.FLOAT, false, 0, 0); 380 | 381 | mat4.fromTranslation(modelMatrix, [0, 0, 0]); 382 | 383 | // set matrix 384 | gl.uniformMatrix4fv(shader.perspectiveMatrixUniform, false, orthoMatrix); 385 | gl.uniformMatrix4fv(shader.modelMatrixUniform, false, modelMatrix); 386 | gl.uniformMatrix4fv(shader.viewMatrixUniform, false, viewMatrix); 387 | // texture 388 | gl.bindBuffer(gl.ARRAY_BUFFER, plainTextureCoordBuffer); 389 | gl.vertexAttribPointer(shader.textureCoordAttribute, plainTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0); 390 | 391 | if (currentVelDensityRT != null) { 392 | gl.activeTexture(gl.TEXTURE0); 393 | gl.bindTexture(gl.TEXTURE_2D, currentVelDensityRT.texture); 394 | gl.uniform1i(shader.velDensityTextureUniform, 0); 395 | } 396 | if (currentPressureRT != null) { 397 | gl.activeTexture(gl.TEXTURE1); 398 | gl.bindTexture(gl.TEXTURE_2D, currentPressureRT.texture); 399 | gl.uniform1i(shader.pressureTextureUniform, 1); 400 | } 401 | 402 | // uniform variables 403 | gl.uniform4fv(shader.timeUniform, [gFrame, 0, 0, 0]); 404 | 405 | // Texture Space -> World Space 406 | // World Space -> Texture Space 407 | gl.uniform4fv(shader.TsWsUniform, [1, 1, 1, 0]); 408 | gl.uniform4fv(shader.WsTsUniform, [1, 1, 1, 0]); 409 | 410 | // Texture Space -> Index Space 411 | // Index Space -> Texture Space 412 | gl.uniform4fv(shader.TsIsUniform, [gSimX, gSimY, gSimZ, 0]); 413 | gl.uniform4fv(shader.IsTsUniform, [1.0 / gSimX, 1.0 / gSimY, 1.0 / gSimZ, 0]); 414 | 415 | // dt: x 416 | // h: y 417 | gl.uniform4fv(shader.optionUniform, [0.01, 1.0 / gSimX, 0, 0]); 418 | 419 | gl.uniform4fv(shader.invResolutionUniform, [1.0 / gSimX, 1.0 / gSimY, 1.0 / gSimZ, 0]); 420 | 421 | gl.uniform4fv(shader.texResolutionUniform, [gSimTexWidth, gSimTexHeight, 0, 0]); 422 | gl.uniform4fv(shader.simResolutionUniform, [gSimX, gSimY, gSimZ, 0]); 423 | gl.uniform4fv(shader.sliceResolutionUniform, [gSimTexWidth / gSimX, gSimTexHeight / gSimY, 0, 0]); 424 | 425 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, plainVertexBuffer.numItems); 426 | } 427 | gl.enable(gl.DEPTH_TEST); 428 | 429 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 430 | } 431 | 432 | function clear(rt) { 433 | gl.bindFramebuffer(gl.FRAMEBUFFER, rt); 434 | 435 | var width = gScreenWidth; 436 | var height = gScreenHeight; 437 | gl.viewport(0, 0, width, height); 438 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 439 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 440 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 441 | } 442 | 443 | // **************************************************************** // 444 | // 445 | // Render volume to current render target 446 | // 447 | // **************************************************************** // 448 | var currentRT = volumeRT; 449 | function renderVolume() { 450 | gl.bindFramebuffer(gl.FRAMEBUFFER, currentRT); 451 | 452 | var width = currentRT.width; 453 | var height = currentRT.height; 454 | gl.viewport(0, 0, width, height); 455 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 456 | 457 | mat4.identity(viewMatrix); 458 | gl.disable(gl.DEPTH_TEST); 459 | { 460 | gl.useProgram(renderVolumeShader); 461 | 462 | gl.bindBuffer(gl.ARRAY_BUFFER, plainVertexBuffer); 463 | gl.vertexAttribPointer(renderVolumeShader.vertexPositionAttribute, plainVertexBuffer.itemSize, gl.FLOAT, false, 0, 0); 464 | 465 | mat4.fromTranslation(modelMatrix, [0, 0, 0]); 466 | 467 | // set matrix 468 | mat4.ortho(orthoMatrix, 1, -1, 1, -1, -100, 100.0); 469 | gl.uniformMatrix4fv(renderVolumeShader.perspectiveMatrixUniform, false, orthoMatrix); 470 | gl.uniformMatrix4fv(renderVolumeShader.modelMatrixUniform, false, modelMatrix); 471 | gl.uniformMatrix4fv(renderVolumeShader.viewMatrixUniform, false, viewMatrix); 472 | gl.uniformMatrix4fv(renderVolumeShader.invViewMatrixUniform, false, invViewMatrix); 473 | gl.uniform4fv(renderVolumeShader.cameraParamsUniform, [cameraPosition.x, cameraPosition.y, cameraPosition.z, 0]); 474 | // texture 475 | gl.bindBuffer(gl.ARRAY_BUFFER, plainTextureCoordBuffer); 476 | gl.vertexAttribPointer(renderVolumeShader.textureCoordAttribute, plainTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0); 477 | 478 | 479 | var A = perspectiveMatrix[0]; 480 | var B = perspectiveMatrix[5]; 481 | var C = perspectiveMatrix[10]; 482 | var D = perspectiveMatrix[14]; 483 | gl.uniform4fv(renderVolumeShader.projParamsUniform, [A, B, C, D]); 484 | 485 | gl.uniform4fv(renderVolumeShader.optionUniform, [gParameter.densityScale, gFrame, 0, 0]); 486 | 487 | gl.uniform4fv(renderVolumeShader.texResolutionUniform, [gSimTexWidth, gSimTexHeight, 0, 0]); 488 | gl.uniform4fv(renderVolumeShader.simResolutionUniform, [gSimX, gSimY, gSimZ, 0]); 489 | gl.uniform4fv(renderVolumeShader.sliceResolutionUniform, [gSimTexWidth / gSimX, gSimTexHeight / gSimY, 0, 0]); 490 | 491 | gl.activeTexture(gl.TEXTURE0); 492 | gl.bindTexture(gl.TEXTURE_2D, velDensityRT[0].texture); 493 | gl.uniform1i(renderVolumeShader.velDensityTextureUniform, 0); 494 | 495 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, plainVertexBuffer.numItems); 496 | } 497 | gl.enable(gl.DEPTH_TEST); 498 | 499 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 500 | 501 | } 502 | 503 | var Parameter = function() { 504 | this.iteration = 1; 505 | this.densityScale = 10.0; 506 | this.addSource = 'bottom'; 507 | this.showSimTexture = false; 508 | this.halfResolution = true; 509 | this.clear = function() { 510 | clear(velDensityRT[0]); 511 | clear(velDensityRT[1]); 512 | clear(pressureRT[0]); 513 | clear(pressureRT[1]); 514 | }; 515 | }; 516 | var gParameter = new Parameter(); 517 | 518 | 519 | // **************************************************************** // 520 | // 521 | // Simulation step (Stable fluids) 522 | // 523 | // **************************************************************** // 524 | function updateFluid() { 525 | gFrame ++; 526 | var c0 = 0; 527 | var c1 = 1; 528 | 529 | // Velocity step 530 | if (gParameter.addSource == 'bottom') { 531 | update(velDensityRT[c0], null, velDensityRT[c1], addSourceShader); 532 | } else if (gParameter.addSource == 'side') { 533 | update(velDensityRT[c0], null, velDensityRT[c1], addSourceShader2); 534 | } 535 | // Project 536 | update(velDensityRT[c1], pressureRT[c0], pressureRT[c1], setupPressureShader); 537 | for (var i = 0; i < gParameter.iteration; ++i) { 538 | update(null, pressureRT[c1], pressureRT[c0], solvePressureShader); 539 | update(null, pressureRT[c0], pressureRT[c1], solvePressureShader); 540 | } 541 | update(null, pressureRT[c1], pressureRT[c0], solvePressureShader); 542 | update(velDensityRT[c1], pressureRT[c0], velDensityRT[c0], updateVelocityShader); 543 | 544 | // Density step 545 | update(velDensityRT[c0], null, velDensityRT[c1], advectDensityStepShader); 546 | 547 | // Velocity step 548 | update(velDensityRT[c1], null, velDensityRT[c0], advectVelStepShader); 549 | } 550 | 551 | // from glMatrix-0.9.5.min.js 552 | function inverse(a,b){ 553 | b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=a[9],o=a[10],m=a[11],n=a[12],p=a[13],r=a[14],s=a[15], 554 | A=c*h-d*f,B=c*i-e*f,t=c*j-g*f,u=d*i-e*h,v=d*j-g*h,w=e*j-g*i,x=k*p-l*n,y=k*r-o*n,z=k*s-m*n,C=l*r-o*p,D=l*s-m*p,E=o*s-m*r,q=1/(A*E-B*D+t*C+u*z-v*y+w*x); 555 | b[0]=(h*E-i*D+j*C)*q;b[1]=(-d*E+e*D-g*C)*q;b[2]=(p*w-r*v+s*u)*q;b[3]=(-l*w+o*v-m*u)*q;b[4]=(-f*E+i*z-j*y)*q;b[5]=(c*E-e*z+g*y)*q;b[6]=(-n*w+r*t-s*B)*q; 556 | b[7]=(k*w-o*t+m*B)*q;b[8]=(f*D-h*z+j*x)*q; 557 | b[9]=(-c*D+d*z-g*x)*q;b[10]=(n*v-p*t+s*A)*q;b[11]=(-k*v+l*t-m*A)*q;b[12]=(-f*C+h*y-i*x)*q;b[13]=(c*C-d*y+e*x)*q;b[14]=(-n*u+p*B-r*A)*q;b[15]=(k*u-l*B+o*A)*q;return b} 558 | 559 | var cameraPosition = new Float32Array(); 560 | 561 | function drawScene() { 562 | currentRT = volumeRT; 563 | if (gParameter.halfResolution) { 564 | currentRT = volumeHalfRT; 565 | } 566 | updateFluid(); 567 | 568 | gl.viewport(0, 0, gScreenWidth, gScreenHeight); 569 | mat4.perspective(perspectiveMatrix, 60.0 / 180.0 * Math.PI, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0); 570 | 571 | // mouse position -> spherical coordinate 572 | { 573 | var u = gMouseX / gScreenWidth; 574 | var v = gMouseY / gScreenHeight; 575 | var theta = (1 - v) * Math.PI / 2.0; 576 | var phi = u * Math.PI * 2.0; 577 | var r = gCurrentCameraDistance; 578 | var sx = r * Math.sin(theta) * Math.cos(phi); 579 | var sy = r * Math.cos(theta); 580 | var sz = r * Math.sin(theta) * Math.sin(phi); 581 | 582 | cameraPosition.x = sx; 583 | cameraPosition.y = sy; 584 | cameraPosition.z = sz; 585 | 586 | mat4.lookAt(viewMatrix, [sx, sy, sz], [0, 1, 0], [0, 1, 0]); 587 | inverse(viewMatrix, invViewMatrix); 588 | } 589 | renderVolume(); 590 | 591 | gl.viewport(0, 0, gScreenWidth, gScreenHeight); 592 | drawTexture(showTextureRGBShader, currentRT.texture, 0, 0, 1, 1); 593 | 594 | if (gParameter.showSimTexture) { 595 | drawTexture(showTextureShader, velDensityRT[0].texture, 0.7, 0.0, 0.3, gScreenWidth / gScreenHeight * 0.3); 596 | } 597 | } 598 | 599 | function onWheel(e) { 600 | if(!e) e = window.event; //for legacy IE 601 | var delta = e.deltaY ? -(e.deltaY) : e.wheelDelta ? e.wheelDelta : -(e.detail); 602 | if (delta < 0){ 603 | e.preventDefault(); 604 | // down 605 | gCurrentCameraDistance -= 0.5; 606 | } else if (delta > 0){ 607 | e.preventDefault(); 608 | // up 609 | gCurrentCameraDistance += 0.5; 610 | } 611 | } 612 | 613 | function webGLStart() { 614 | // Setup canvas 615 | var canvas = document.getElementById("canvas"); 616 | canvas.onmousemove = onMouseMove; 617 | canvas.width = window.innerWidth; 618 | canvas.height = window.innerHeight; 619 | 620 | var mousewheelevent = 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'; 621 | try{ 622 | canvas.addEventListener(mousewheelevent, onWheel, false); 623 | }catch(e){ 624 | //for legacy IE 625 | canvas.attachEvent("onmousewheel", onWheel); 626 | } 627 | gScreenWidth = canvas.width; 628 | gScreenHeight = canvas.height; 629 | 630 | // Setup WebGL 631 | initGL(canvas); 632 | initShaders(); 633 | initBuffers(); 634 | initTextures(); 635 | 636 | gl.clearColor(0.0, 0.0, 0.0, 1.0); 637 | gl.clearDepth(1.0) 638 | gl.enable(gl.DEPTH_TEST); 639 | gl.depthFunc(gl.LEQUAL); 640 | 641 | // Setup GUI 642 | var gui = new dat.GUI(); 643 | gui.add(gParameter, 'iteration').min(1).max(10).step(1); 644 | gui.add(gParameter, 'densityScale').min(0.1).max(50).step(0.1); 645 | gui.add(gParameter, 'addSource', [ 'bottom', 'side'] ); 646 | gui.add(gParameter, 'clear'); 647 | gui.add(gParameter, 'showSimTexture'); 648 | gui.add(gParameter, 'halfResolution'); 649 | 650 | // Setup Stats 651 | var stats = new Stats(); 652 | stats.setMode(0); // 0: fps, 1: ms, 2: mb 653 | stats.domElement.style.position = 'absolute'; 654 | stats.domElement.style.left = '0px'; 655 | stats.domElement.style.top = '0px'; 656 | document.body.appendChild( stats.domElement ); 657 | var update = function () { 658 | stats.begin(); 659 | drawScene(); 660 | stats.end(); 661 | requestAnimationFrame(update); 662 | }; 663 | requestAnimationFrame( update ); 664 | // setInterval(drawScene, 16); 665 | } 666 | 667 | onload = function(){ 668 | webGLStart(); 669 | }; -------------------------------------------------------------------------------- /js/shader-src.js: -------------------------------------------------------------------------------- 1 | var addSource_frag="precision mediump float;\n"+ 2 | "uniform sampler2D uVelDensityTexture;\n"+ 3 | "varying vec2 vTextureCoord;\n"+ 4 | "uniform vec4 uTime;\n"+ 5 | "uniform vec4 uTexResolution;\n"+ 6 | "uniform vec4 uSimResolution;\n"+ 7 | "uniform vec4 uSliceResolution;\n"+ 8 | "vec3 uvToIndexSpace(vec2 uv) {\n"+ 9 | " \n"+ 10 | " vec2 globalIndex = uv * uTexResolution.xy;\n"+ 11 | " vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy);\n"+ 12 | " float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5);\n"+ 13 | " vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy));\n"+ 14 | " \n"+ 15 | " return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5);\n"+ 16 | "}\n"+ 17 | "void main(void) {\n"+ 18 | " vec4 value = texture2D(uVelDensityTexture, vTextureCoord);\n"+ 19 | " vec3 is = uvToIndexSpace(vTextureCoord);\n"+ 20 | " if (0.0 < is.y && is.y < 6.0 && abs(is.x - 32.0) < 8.0 && abs(is.z - 32.0) < 8.0) {\n"+ 21 | " value.w += 0.001;\n"+ 22 | " }\n"+ 23 | " if (0.0 < is.y && is.y < 3.0 && abs(is.x - 32.0) < 2.0 && abs(is.z - 32.0) < 2.0) {\n"+ 24 | " value.y += 0.05;\n"+ 25 | " }\n"+ 26 | " gl_FragColor = vec4(value);\n"+ 27 | "}\n"+ 28 | ""; 29 | var addSource2_frag="precision mediump float;\n"+ 30 | "uniform sampler2D uVelDensityTexture;\n"+ 31 | "varying vec2 vTextureCoord;\n"+ 32 | "uniform vec4 uTime;\n"+ 33 | "uniform vec4 uTexResolution;\n"+ 34 | "uniform vec4 uSimResolution;\n"+ 35 | "uniform vec4 uSliceResolution;\n"+ 36 | "vec3 uvToIndexSpace(vec2 uv) {\n"+ 37 | " \n"+ 38 | " vec2 globalIndex = uv * uTexResolution.xy;\n"+ 39 | " vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy);\n"+ 40 | " float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5);\n"+ 41 | " vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy));\n"+ 42 | " \n"+ 43 | " return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5);\n"+ 44 | "}\n"+ 45 | "void main(void) {\n"+ 46 | " vec4 value = texture2D(uVelDensityTexture, vTextureCoord);\n"+ 47 | " vec3 is = uvToIndexSpace(vTextureCoord);\n"+ 48 | " if (0.0 < is.x && is.x < 3.0 && abs(is.y - 32.0) < 2.0 && abs(is.z - 32.0) < 2.0) {\n"+ 49 | " value.w += 0.001;\n"+ 50 | " }\n"+ 51 | " if (0.0 < is.x && is.x < 3.0 && abs(is.y - 32.0) < 2.0 && abs(is.z - 32.0) < 2.0) {\n"+ 52 | " value.x += 0.05;\n"+ 53 | " }\n"+ 54 | " if (0.0 < is.y && is.y < 3.0 && abs(is.x - 32.0) < 2.0 && abs(is.z - 32.0) < 2.0) {\n"+ 55 | " value.y += 0.01;\n"+ 56 | " }\n"+ 57 | " gl_FragColor = vec4(value);\n"+ 58 | "}\n"+ 59 | ""; 60 | var advectDensityStep_frag="precision mediump float;\n"+ 61 | "uniform sampler2D uVelDensityTexture;\n"+ 62 | "uniform vec4 uTsWs;\n"+ 63 | "uniform vec4 uWsTs;\n"+ 64 | "uniform vec4 uTsIs;\n"+ 65 | "uniform vec4 uIsTs;\n"+ 66 | "uniform vec4 uOption;\n"+ 67 | "varying vec2 vTextureCoord;\n"+ 68 | "uniform vec4 uTexResolution;\n"+ 69 | "uniform vec4 uSimResolution;\n"+ 70 | "uniform vec4 uSliceResolution;\n"+ 71 | "vec3 uvToIndexSpace(vec2 uv) {\n"+ 72 | " \n"+ 73 | " vec2 globalIndex = uv * uTexResolution.xy;\n"+ 74 | " vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy);\n"+ 75 | " float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5);\n"+ 76 | " vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy));\n"+ 77 | " \n"+ 78 | " return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5);\n"+ 79 | "}\n"+ 80 | "vec2 IndexSpaceToUV(vec3 is, vec3 offset) {\n"+ 81 | " vec3 isOrg = is + offset;\n"+ 82 | " isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5));\n"+ 83 | " float iSliceX = floor(mod(isOrg.z, uSliceResolution.x));\n"+ 84 | " float iSliceY = floor(isOrg.z / uSliceResolution.x);\n"+ 85 | " \n"+ 86 | " return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy;\n"+ 87 | "}\n"+ 88 | "void main(void) {\n"+ 89 | " vec4 value = texture2D(uVelDensityTexture, vTextureCoord);\n"+ 90 | " vec3 wsPos = uvToIndexSpace(vTextureCoord) * uIsTs.xyz * uTsWs.xyz; // scaling\n"+ 91 | " float dt = uOption.x;\n"+ 92 | " vec3 wsVel = value.xyz;\n"+ 93 | " vec3 wsPrevPos = wsPos - dt * wsVel;\n"+ 94 | " vec3 ts = wsPrevPos * uWsTs.xyz;\n"+ 95 | " vec3 is = ts * uTsIs.xyz - vec3(0.5, 0.5, 0.5);\n"+ 96 | " vec3 iIs0 = floor(is);\n"+ 97 | " vec3 iIs1 = iIs0 + vec3(1, 1, 1);\n"+ 98 | " vec3 st1 = is - iIs0;\n"+ 99 | " vec3 st0 = vec3(1, 1, 1) - st1;\n"+ 100 | " iIs0 += vec3(0.5, 0.5, 0.5);\n"+ 101 | " iIs1 += vec3(0.5, 0.5, 0.5);\n"+ 102 | " vec4 v000 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs0.z), vec3(0, 0, 0)));\n"+ 103 | " vec4 v100 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs0.z), vec3(0, 0, 0)));\n"+ 104 | " vec4 v010 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs0.z), vec3(0, 0, 0)));\n"+ 105 | " vec4 v110 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs0.z), vec3(0, 0, 0)));\n"+ 106 | " vec4 v001 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs1.z), vec3(0, 0, 0)));\n"+ 107 | " vec4 v101 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs1.z), vec3(0, 0, 0)));\n"+ 108 | " vec4 v011 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs1.z), vec3(0, 0, 0)));\n"+ 109 | " vec4 v111 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs1.z), vec3(0, 0, 0)));\n"+ 110 | " \n"+ 111 | " float finalDensity = \n"+ 112 | " st0.x * (\n"+ 113 | " st0.y * (st0.z * v000.w + st1.z * v001.w) +\n"+ 114 | " st1.y * (st0.z * v010.w + st1.z * v011.w)) +\n"+ 115 | " st1.x * (\n"+ 116 | " st0.y * (st0.z * v100.w + st1.z * v101.w) +\n"+ 117 | " st1.y * (st0.z * v110.w + st1.z * v111.w));\n"+ 118 | " gl_FragColor = vec4(value.xyz, finalDensity);\n"+ 119 | "}\n"+ 120 | ""; 121 | var advectVelStep_frag="precision mediump float;\n"+ 122 | "uniform sampler2D uVelDensityTexture;\n"+ 123 | "uniform vec4 uTsWs;\n"+ 124 | "uniform vec4 uWsTs;\n"+ 125 | "uniform vec4 uTsIs;\n"+ 126 | "uniform vec4 uIsTs;\n"+ 127 | "uniform vec4 uOption;\n"+ 128 | "varying vec2 vTextureCoord;\n"+ 129 | "uniform vec4 uTexResolution;\n"+ 130 | "uniform vec4 uSimResolution;\n"+ 131 | "uniform vec4 uSliceResolution;\n"+ 132 | "vec3 uvToIndexSpace(vec2 uv) {\n"+ 133 | " \n"+ 134 | " vec2 globalIndex = uv * uTexResolution.xy;\n"+ 135 | " vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy);\n"+ 136 | " float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5);\n"+ 137 | " vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy));\n"+ 138 | " \n"+ 139 | " return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5);\n"+ 140 | "}\n"+ 141 | "vec2 IndexSpaceToUV(vec3 is, vec3 offset) {\n"+ 142 | " vec3 isOrg = is + offset;\n"+ 143 | " isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5));\n"+ 144 | " float iSliceX = floor(mod(isOrg.z, uSliceResolution.x));\n"+ 145 | " float iSliceY = floor(isOrg.z / uSliceResolution.x);\n"+ 146 | " \n"+ 147 | " return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy;\n"+ 148 | "}\n"+ 149 | "void main(void) {\n"+ 150 | " vec4 value = texture2D(uVelDensityTexture, vTextureCoord);\n"+ 151 | " vec3 wsPos = uvToIndexSpace(vTextureCoord) * uIsTs.xyz * uTsWs.xyz; // scaling\n"+ 152 | " float dt = uOption.x;\n"+ 153 | " vec3 wsVel = value.xyz;\n"+ 154 | " vec3 wsPrevPos = wsPos - dt * wsVel;\n"+ 155 | " vec3 ts = wsPrevPos * uWsTs.xyz;\n"+ 156 | " vec3 is = ts * uTsIs.xyz - vec3(0.5, 0.5, 0.5);\n"+ 157 | " vec3 iIs0 = floor(is);\n"+ 158 | " vec3 iIs1 = iIs0 + vec3(1, 1, 1);\n"+ 159 | " vec3 st1 = is - iIs0;\n"+ 160 | " vec3 st0 = vec3(1, 1, 1) - st1;\n"+ 161 | " iIs0 += vec3(0.5, 0.5, 0.5);\n"+ 162 | " iIs1 += vec3(0.5, 0.5, 0.5);\n"+ 163 | " vec4 v000 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs0.z), vec3(0, 0, 0)));\n"+ 164 | " vec4 v100 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs0.z), vec3(0, 0, 0)));\n"+ 165 | " vec4 v010 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs0.z), vec3(0, 0, 0)));\n"+ 166 | " vec4 v110 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs0.z), vec3(0, 0, 0)));\n"+ 167 | " vec4 v001 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs1.z), vec3(0, 0, 0)));\n"+ 168 | " vec4 v101 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs1.z), vec3(0, 0, 0)));\n"+ 169 | " vec4 v011 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs1.z), vec3(0, 0, 0)));\n"+ 170 | " vec4 v111 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs1.z), vec3(0, 0, 0)));\n"+ 171 | " \n"+ 172 | " vec3 finalVelocity = \n"+ 173 | " st0.x * (\n"+ 174 | " st0.y * (st0.z * v000.xyz + st1.z * v001.xyz) +\n"+ 175 | " st1.y * (st0.z * v010.xyz + st1.z * v011.xyz)) +\n"+ 176 | " st1.x * (\n"+ 177 | " st0.y * (st0.z * v100.xyz + st1.z * v101.xyz) +\n"+ 178 | " st1.y * (st0.z * v110.xyz + st1.z * v111.xyz));\n"+ 179 | " gl_FragColor = vec4(finalVelocity.xyz, value.w);\n"+ 180 | "}\n"+ 181 | ""; 182 | var fluid_vert="attribute vec2 aVertexPosition;\n"+ 183 | "attribute vec2 aTextureCoord;\n"+ 184 | "uniform mat4 uPerspectiveMatrix;\n"+ 185 | "uniform mat4 uModelMatrix;\n"+ 186 | "uniform mat4 uViewMatrix;\n"+ 187 | "varying vec2 vTextureCoord;\n"+ 188 | "void main(void) {\n"+ 189 | " vTextureCoord = aTextureCoord;\n"+ 190 | " gl_Position = uPerspectiveMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition.xy, 0, 1.0);\n"+ 191 | "}\n"+ 192 | ""; 193 | var renderVolume_frag="precision mediump float;\n"+ 194 | "varying vec2 vTextureCoord;\n"+ 195 | "uniform vec4 uOption;\n"+ 196 | "uniform vec4 uProjParams;\n"+ 197 | "uniform mat4 uInvViewMatrix;\n"+ 198 | "uniform vec4 uCameraParams;\n"+ 199 | "uniform sampler2D uVelDensityTexture;\n"+ 200 | "uniform vec4 uTexResolution;\n"+ 201 | "uniform vec4 uSimResolution;\n"+ 202 | "uniform vec4 uSliceResolution;\n"+ 203 | "vec3 uvToIndexSpace(vec2 uv) {\n"+ 204 | " \n"+ 205 | " vec2 globalIndex = uv * uTexResolution.xy;\n"+ 206 | " vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy);\n"+ 207 | " float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5);\n"+ 208 | " vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy));\n"+ 209 | " \n"+ 210 | " return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5);\n"+ 211 | "}\n"+ 212 | "vec2 IndexSpaceToUV(vec3 is, vec3 offset) {\n"+ 213 | " vec3 isOrg = is + offset;\n"+ 214 | " isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5));\n"+ 215 | " float iSliceX = floor(mod(isOrg.z, uSliceResolution.x));\n"+ 216 | " float iSliceY = floor(isOrg.z / uSliceResolution.x);\n"+ 217 | " \n"+ 218 | " return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy;\n"+ 219 | "}\n"+ 220 | "// http://bassser.tumblr.com/post/11626074256/reconstructing-position-from-depth-buffer\n"+ 221 | "vec3 reconstructPosition(in float p_depth, in vec2 p_ndc, in vec4 p_projParams)\n"+ 222 | "{ \n"+ 223 | " float depth = p_depth * 2.0 - 1.0;\n"+ 224 | " float viewDepth = p_projParams.w / (depth - p_projParams.z);\n"+ 225 | " return vec3((p_ndc * viewDepth) / p_projParams.xy, viewDepth);\n"+ 226 | "}\n"+ 227 | "float sampleDensity(vec3 is) {\n"+ 228 | " vec3 iIs0 = floor(is);\n"+ 229 | " return texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs0.z), vec3(0, 0, 0))).w;\n"+ 230 | "}\n"+ 231 | "// Very heavy!\n"+ 232 | "float sampleDensityBilinear(vec3 is) {\n"+ 233 | " vec3 iIs0 = floor(is);\n"+ 234 | " vec3 iIs1 = iIs0 + vec3(1, 1, 1);\n"+ 235 | " vec3 st1 = is - iIs0;\n"+ 236 | " vec3 st0 = vec3(1, 1, 1) - st1;\n"+ 237 | " iIs0 += vec3(0.5, 0.5, 0.5);\n"+ 238 | " iIs1 += vec3(0.5, 0.5, 0.5);\n"+ 239 | " vec4 v000 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs0.z), vec3(0, 0, 0)));\n"+ 240 | " vec4 v100 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs0.z), vec3(0, 0, 0)));\n"+ 241 | " vec4 v010 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs0.z), vec3(0, 0, 0)));\n"+ 242 | " vec4 v110 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs0.z), vec3(0, 0, 0)));\n"+ 243 | " vec4 v001 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs1.z), vec3(0, 0, 0)));\n"+ 244 | " vec4 v101 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs1.z), vec3(0, 0, 0)));\n"+ 245 | " vec4 v011 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs1.z), vec3(0, 0, 0)));\n"+ 246 | " vec4 v111 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs1.z), vec3(0, 0, 0)));\n"+ 247 | " \n"+ 248 | " float d = \n"+ 249 | " st0.x * (\n"+ 250 | " st0.y * (st0.z * v000.w + st1.z * v001.w) +\n"+ 251 | " st1.y * (st0.z * v010.w + st1.z * v011.w)) +\n"+ 252 | " st1.x * (\n"+ 253 | " st0.y * (st0.z * v100.w + st1.z * v101.w) +\n"+ 254 | " st1.y * (st0.z * v110.w + st1.z * v111.w));\n"+ 255 | " return d;\n"+ 256 | "}\n"+ 257 | "float rand(vec2 co){\n"+ 258 | " return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n"+ 259 | "}\n"+ 260 | "bool checkInside(vec3 org, vec3 pmin, vec3 pmax) {\n"+ 261 | " if (org.x < pmin.x || org.y < pmin.y || org.z < pmin.z ||\n"+ 262 | " org.x > pmax.x || org.y > pmax.y || org.z > pmax.z)\n"+ 263 | " return false;\n"+ 264 | " return true;\n"+ 265 | "}\n"+ 266 | "vec2 checkIntersect(vec3 org, vec3 dir, vec3 pmin, vec3 pmax) {\n"+ 267 | " vec2 t;\n"+ 268 | " float t0 = 0.0, t1 = 100000.0;\n"+ 269 | " for (int i = 0; i < 3; ++i) {\n"+ 270 | " float invRayDir = 1.0 / dir[i];\n"+ 271 | " float tNear = (pmin[i] - org[i]) * invRayDir;\n"+ 272 | " float tFar = (pmax[i] - org[i]) * invRayDir;\n"+ 273 | " if (tNear > tFar) {\n"+ 274 | " float tmp = tNear;\n"+ 275 | " tNear = tFar;\n"+ 276 | " tFar = tmp;\n"+ 277 | " }\n"+ 278 | " t0 = tNear > t0 ? tNear : t0;\n"+ 279 | " t1 = tFar < t1 ? tFar : t1;\n"+ 280 | " if (t0 > t1) return vec2(-1, -1);\n"+ 281 | " }\n"+ 282 | " return vec2(t0, t1);\n"+ 283 | "}\n"+ 284 | "#define N 32.0\n"+ 285 | "vec3 wsToIs(vec3 ws) {\n"+ 286 | " return ((ws / vec3(4.0, 4.0, 4.0)) + vec3(1, 0, 1)) * 0.5 * uSimResolution.xyz + vec3(0.5, 0.5, 0.5);\n"+ 287 | "}\n"+ 288 | "void main(void) {\n"+ 289 | " // Constants\n"+ 290 | " vec3 kLightVec = normalize(vec3(0.3, 0.7, 0.2));\n"+ 291 | " float densityScale = uOption.x;\n"+ 292 | " vec3 pMin = vec3(-4, 0, -4);\n"+ 293 | " vec3 pMax = vec3(4, 8, 4);\n"+ 294 | " float planeSize = 25.0;\n"+ 295 | " vec3 finalColor = vec3(0.5, 0.5, 0.5);\n"+ 296 | " float sunPower = 1.0;\n"+ 297 | " vec3 vsPos = reconstructPosition(0.0, vTextureCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), uProjParams);\n"+ 298 | " vec3 wsPos = (uInvViewMatrix * vec4(vsPos, 1)).xyz;\n"+ 299 | " vec3 wsDir = normalize(wsPos - uCameraParams.xyz);\n"+ 300 | " vec3 wsOrg = uCameraParams.xyz;\n"+ 301 | " float tPlane = -dot(wsOrg, vec3(0, 1, 0)) / dot(wsDir, vec3(0, 1, 0));\n"+ 302 | " vec3 wsInter = wsOrg + tPlane * wsDir;\n"+ 303 | " if (tPlane > 0.0 && abs(wsInter.x) < planeSize && abs(wsInter.z) < planeSize) {\n"+ 304 | " // col = vec4(wsOrg + t * wsDir, 1).xyz;\n"+ 305 | " finalColor = vec3(0.2, 0.2, 0.2);\n"+ 306 | " }\n"+ 307 | " // Shadow\n"+ 308 | " float shadowTransmittance = 1.0;\n"+ 309 | " {\n"+ 310 | " vec3 wsShadowOrg = wsOrg + tPlane * wsDir;\n"+ 311 | " vec2 shadowTp = checkIntersect(wsShadowOrg, kLightVec, pMin, pMax);\n"+ 312 | " wsShadowOrg = wsShadowOrg + shadowTp.x * kLightVec;\n"+ 313 | " float length = shadowTp.y - shadowTp.x;\n"+ 314 | " for (float j = 0.0; j < N; j += 1.0) {\n"+ 315 | " vec3 wsDPos = wsShadowOrg + (length / N) * (j + 0.5) * kLightVec;\n"+ 316 | " if (!checkInside(wsDPos, pMin, pMax) || length <= 0.0)\n"+ 317 | " break;\n"+ 318 | " vec3 isDPos = ((wsDPos / vec3(4.0, 4.0, 4.0)) + vec3(1, 0, 1)) * 0.5 * uSimResolution.xyz + vec3(0.5, 0.5, 0.5);\n"+ 319 | " float ddensity = densityScale * sampleDensity(isDPos);\n"+ 320 | " shadowTransmittance *= exp(-ddensity * (length / N));\n"+ 321 | " }\n"+ 322 | " }\n"+ 323 | " // Volume ray marching \n"+ 324 | " float sum = 0.0;\n"+ 325 | " float transmittance = 1.0;\n"+ 326 | " {\n"+ 327 | " vec2 tp = checkIntersect(wsOrg, wsDir, pMin, pMax);\n"+ 328 | " wsOrg = wsOrg + tp.x * wsDir;\n"+ 329 | " float length = tp.y - tp.x;\n"+ 330 | " for (float i = 0.0; i < N; i += 1.0) {\n"+ 331 | " float nt = length / N * (i + (1.0 + rand(vec2(i + wsPos.z, uOption.y + wsPos.x + wsPos.y))) * 0.5);\n"+ 332 | " vec3 wsCurrentPos = wsOrg + nt * wsDir;\n"+ 333 | " if (!checkInside(wsCurrentPos, pMin, pMax) || length <= 0.0)\n"+ 334 | " break;\n"+ 335 | " vec3 isPos = wsToIs(wsCurrentPos);\n"+ 336 | " float density = densityScale * sampleDensity(isPos);\n"+ 337 | " transmittance *= exp(-density * (length / N));\n"+ 338 | " float sunTransmittance = 1.0;\n"+ 339 | " vec3 wsSunOrg = wsCurrentPos;\n"+ 340 | " vec2 sunTp = checkIntersect(wsSunOrg, kLightVec, pMin, pMax);\n"+ 341 | " wsSunOrg = wsSunOrg + sunTp.x * kLightVec;\n"+ 342 | " float sunLength = sunTp.y - sunTp.x;\n"+ 343 | " for (float j = 0.0; j < N; j += 1.0) {\n"+ 344 | " vec3 wsDPos = wsSunOrg + (sunLength / N) * (j + 0.5) * kLightVec;\n"+ 345 | " vec3 isDPos = wsToIs(wsDPos);\n"+ 346 | " float ddensity = densityScale * sampleDensity(isDPos);\n"+ 347 | " sunTransmittance *= exp(-ddensity * (sunLength / N));\n"+ 348 | " }\n"+ 349 | " sum += (transmittance * sunPower * sunTransmittance * density) * (length / N);\n"+ 350 | " }\n"+ 351 | " }\n"+ 352 | " vec3 result = vec3(shadowTransmittance * transmittance * finalColor) + vec3(sum, sum, sum);\n"+ 353 | " result = pow(result, vec3(0.45, 0.45, 0.45));\n"+ 354 | " gl_FragColor = vec4(result, 1);\n"+ 355 | "}\n"+ 356 | ""; 357 | var renderVolume_vert="attribute vec2 aVertexPosition;\n"+ 358 | "uniform mat4 uPerspectiveMatrix;\n"+ 359 | "uniform mat4 uModelMatrix;\n"+ 360 | "uniform mat4 uViewMatrix;\n"+ 361 | "uniform sampler2D uSampler;\n"+ 362 | "varying vec4 vColor;\n"+ 363 | "void main(void) {\n"+ 364 | " vColor = vec4(1.0, 1.0, 1.0, 1.0);\n"+ 365 | " vec3 value = texture2D(uSampler, aVertexPosition.xy).xyz;\n"+ 366 | "/* vec3 value = vec3(aVertexPosition.xy, 0);*/\n"+ 367 | " gl_Position = uPerspectiveMatrix * uViewMatrix * uModelMatrix * vec4(value, 1.0);\n"+ 368 | " gl_PointSize = 2.0;\n"+ 369 | "}\n"+ 370 | ""; 371 | var setupPressure_frag="precision mediump float;\n"+ 372 | "uniform sampler2D uVelDensityTexture;\n"+ 373 | "uniform sampler2D uPressureTexture;\n"+ 374 | "uniform vec4 uOption;\n"+ 375 | "uniform vec4 uInvResolution;\n"+ 376 | "varying vec2 vTextureCoord;\n"+ 377 | "uniform vec4 uTsIs;\n"+ 378 | "uniform vec4 uTexResolution;\n"+ 379 | "uniform vec4 uSimResolution;\n"+ 380 | "uniform vec4 uSliceResolution;\n"+ 381 | "vec3 uvToIndexSpace(vec2 uv) {\n"+ 382 | " \n"+ 383 | " vec2 globalIndex = uv * uTexResolution.xy;\n"+ 384 | " vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy);\n"+ 385 | " float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5);\n"+ 386 | " vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy));\n"+ 387 | " \n"+ 388 | " return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5);\n"+ 389 | "}\n"+ 390 | "vec2 IndexSpaceToUV(vec3 is, vec3 offset) {\n"+ 391 | " vec3 isOrg = is + offset;\n"+ 392 | " isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5));\n"+ 393 | " float iSliceX = floor(mod(isOrg.z, uSliceResolution.x));\n"+ 394 | " float iSliceY = floor(isOrg.z / uSliceResolution.x);\n"+ 395 | " \n"+ 396 | " return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy;\n"+ 397 | "}\n"+ 398 | "void main(void) {\n"+ 399 | " vec3 is = uvToIndexSpace(vTextureCoord);\n"+ 400 | " \n"+ 401 | " vec4 vx0 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 1, 0, 0)));\n"+ 402 | " vec4 vx1 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3(-1, 0, 0)));\n"+ 403 | " vec4 vy0 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 0, 1, 0)));\n"+ 404 | " vec4 vy1 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 0,-1, 0)));\n"+ 405 | " vec4 vz0 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 0, 0, 1)));\n"+ 406 | " vec4 vz1 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 0, 0,-1)));\n"+ 407 | " float h = uOption.y;\n"+ 408 | " float div = -0.5 * h * (\n"+ 409 | " vx0.x - vx1.x + \n"+ 410 | " vy0.y - vy1.y + \n"+ 411 | " vz0.z - vz1.z);\n"+ 412 | " float prevPressure = texture2D(uPressureTexture, vTextureCoord).y;\n"+ 413 | " gl_FragColor = vec4(div, prevPressure, 0, 0);\n"+ 414 | "}\n"+ 415 | ""; 416 | var showTexture_frag="precision mediump float;\n"+ 417 | "uniform sampler2D uSampler;\n"+ 418 | "varying vec2 vTextureCoord;\n"+ 419 | "void main(void) {\n"+ 420 | " gl_FragColor = vec4(abs(texture2D(uSampler, vTextureCoord).www) * 1.0, 1);\n"+ 421 | "}\n"+ 422 | ""; 423 | var showTexture_vert="attribute vec2 aVertexPosition;\n"+ 424 | "attribute vec2 aTextureCoord;\n"+ 425 | "uniform mat4 uPerspectiveMatrix;\n"+ 426 | "uniform mat4 uModelMatrix;\n"+ 427 | "uniform mat4 uViewMatrix;\n"+ 428 | "varying vec2 vTextureCoord;\n"+ 429 | "void main(void) {\n"+ 430 | " vTextureCoord = aTextureCoord;\n"+ 431 | " gl_Position = uPerspectiveMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition.xy, 0, 1.0);\n"+ 432 | "}\n"+ 433 | ""; 434 | var showTextureRGB_frag="precision mediump float;\n"+ 435 | "uniform sampler2D uSampler;\n"+ 436 | "varying vec2 vTextureCoord;\n"+ 437 | "void main(void) {\n"+ 438 | " gl_FragColor = vec4(abs(texture2D(uSampler, vTextureCoord).rgb) * 1.0, 1);\n"+ 439 | "}\n"+ 440 | ""; 441 | var solvePressure_frag="precision mediump float;\n"+ 442 | "uniform sampler2D uPressureTexture;\n"+ 443 | "uniform vec4 uOption;\n"+ 444 | "uniform vec4 uInvResolution;\n"+ 445 | "varying vec2 vTextureCoord;\n"+ 446 | "uniform vec4 uTexResolution;\n"+ 447 | "uniform vec4 uSimResolution;\n"+ 448 | "uniform vec4 uSliceResolution;\n"+ 449 | "vec3 uvToIndexSpace(vec2 uv) {\n"+ 450 | " \n"+ 451 | " vec2 globalIndex = uv * uTexResolution.xy;\n"+ 452 | " vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy);\n"+ 453 | " float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5);\n"+ 454 | " vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy));\n"+ 455 | " \n"+ 456 | " return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5);\n"+ 457 | "}\n"+ 458 | "vec2 IndexSpaceToUV(vec3 is, vec3 offset) {\n"+ 459 | " vec3 isOrg = is + offset;\n"+ 460 | " isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5));\n"+ 461 | " float iSliceX = floor(mod(isOrg.z, uSliceResolution.x));\n"+ 462 | " float iSliceY = floor(isOrg.z / uSliceResolution.x);\n"+ 463 | " \n"+ 464 | " return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy;\n"+ 465 | "}\n"+ 466 | "void main(void) {\n"+ 467 | " vec3 is = uvToIndexSpace(vTextureCoord);\n"+ 468 | " float p0 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 1, 0, 0))).y;\n"+ 469 | " float p1 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3(-1, 0, 0))).y;\n"+ 470 | " float p2 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 1, 0))).y;\n"+ 471 | " float p3 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0,-1, 0))).y;\n"+ 472 | " float p4 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 0, 1))).y;\n"+ 473 | " float p5 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 0,-1))).y;\n"+ 474 | " float div = texture2D(uPressureTexture, vTextureCoord).x;\n"+ 475 | " float newp = (div + p0 + p1 + p2 + p3 + p4 + p5) / 6.0;\n"+ 476 | " gl_FragColor = vec4(div, newp, 0, 0);\n"+ 477 | "}\n"+ 478 | ""; 479 | var updateVelocity_frag="precision mediump float;\n"+ 480 | "uniform sampler2D uVelDensityTexture;\n"+ 481 | "uniform sampler2D uPressureTexture;\n"+ 482 | "uniform vec4 uOption;\n"+ 483 | "uniform vec4 uInvResolution;\n"+ 484 | "varying vec2 vTextureCoord;\n"+ 485 | "uniform vec4 uTexResolution;\n"+ 486 | "uniform vec4 uSimResolution;\n"+ 487 | "uniform vec4 uSliceResolution;\n"+ 488 | "vec3 uvToIndexSpace(vec2 uv) {\n"+ 489 | " \n"+ 490 | " vec2 globalIndex = uv * uTexResolution.xy;\n"+ 491 | " vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy);\n"+ 492 | " float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5);\n"+ 493 | " vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy));\n"+ 494 | " \n"+ 495 | " return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5);\n"+ 496 | "}\n"+ 497 | "vec2 IndexSpaceToUV(vec3 is, vec3 offset) {\n"+ 498 | " vec3 isOrg = is + offset;\n"+ 499 | " isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5));\n"+ 500 | " float iSliceX = floor(mod(isOrg.z, uSliceResolution.x));\n"+ 501 | " float iSliceY = floor(isOrg.z / uSliceResolution.x);\n"+ 502 | " \n"+ 503 | " return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy;\n"+ 504 | "}\n"+ 505 | "void main(void) {\n"+ 506 | " vec3 is = uvToIndexSpace(vTextureCoord);\n"+ 507 | " float p0 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 1, 0, 0))).y;\n"+ 508 | " float p1 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3(-1, 0, 0))).y;\n"+ 509 | " float p2 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 1, 0))).y;\n"+ 510 | " float p3 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0,-1, 0))).y;\n"+ 511 | " float p4 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 0, 1))).y;\n"+ 512 | " float p5 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 0,-1))).y;\n"+ 513 | " float h = uOption.y;\n"+ 514 | " vec4 vel = texture2D(uVelDensityTexture, vTextureCoord);\n"+ 515 | " vel.x -= 0.5 * (p0 - p1) / h;\n"+ 516 | " vel.y -= 0.5 * (p2 - p3) / h;\n"+ 517 | " vel.z -= 0.5 * (p4 - p5) / h;\n"+ 518 | " gl_FragColor = vec4(vel.xyz, vel.w);\n"+ 519 | "}\n"+ 520 | ""; 521 | -------------------------------------------------------------------------------- /shader/addSource.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uVelDensityTexture; 4 | varying vec2 vTextureCoord; 5 | 6 | uniform vec4 uTime; 7 | 8 | uniform vec4 uTexResolution; 9 | uniform vec4 uSimResolution; 10 | uniform vec4 uSliceResolution; 11 | vec3 uvToIndexSpace(vec2 uv) { 12 | 13 | vec2 globalIndex = uv * uTexResolution.xy; 14 | vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy); 15 | 16 | float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5); 17 | vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy)); 18 | 19 | return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5); 20 | } 21 | 22 | void main(void) { 23 | vec4 value = texture2D(uVelDensityTexture, vTextureCoord); 24 | vec3 is = uvToIndexSpace(vTextureCoord); 25 | 26 | if (0.0 < is.y && is.y < 6.0 && abs(is.x - 32.0) < 8.0 && abs(is.z - 32.0) < 8.0) { 27 | value.w += 0.001; 28 | } 29 | if (0.0 < is.y && is.y < 3.0 && abs(is.x - 32.0) < 2.0 && abs(is.z - 32.0) < 2.0) { 30 | value.y += 0.05; 31 | } 32 | 33 | gl_FragColor = vec4(value); 34 | } 35 | -------------------------------------------------------------------------------- /shader/addSource2.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uVelDensityTexture; 4 | varying vec2 vTextureCoord; 5 | 6 | uniform vec4 uTime; 7 | 8 | uniform vec4 uTexResolution; 9 | uniform vec4 uSimResolution; 10 | uniform vec4 uSliceResolution; 11 | vec3 uvToIndexSpace(vec2 uv) { 12 | 13 | vec2 globalIndex = uv * uTexResolution.xy; 14 | vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy); 15 | 16 | float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5); 17 | vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy)); 18 | 19 | return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5); 20 | } 21 | 22 | void main(void) { 23 | vec4 value = texture2D(uVelDensityTexture, vTextureCoord); 24 | vec3 is = uvToIndexSpace(vTextureCoord); 25 | 26 | if (0.0 < is.x && is.x < 3.0 && abs(is.y - 32.0) < 2.0 && abs(is.z - 32.0) < 2.0) { 27 | value.w += 0.001; 28 | } 29 | if (0.0 < is.x && is.x < 3.0 && abs(is.y - 32.0) < 2.0 && abs(is.z - 32.0) < 2.0) { 30 | value.x += 0.05; 31 | } 32 | if (0.0 < is.y && is.y < 3.0 && abs(is.x - 32.0) < 2.0 && abs(is.z - 32.0) < 2.0) { 33 | value.y += 0.01; 34 | } 35 | 36 | gl_FragColor = vec4(value); 37 | } 38 | -------------------------------------------------------------------------------- /shader/advectDensityStep.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uVelDensityTexture; 4 | 5 | uniform vec4 uTsWs; 6 | uniform vec4 uWsTs; 7 | uniform vec4 uTsIs; 8 | uniform vec4 uIsTs; 9 | uniform vec4 uOption; 10 | 11 | varying vec2 vTextureCoord; 12 | 13 | uniform vec4 uTexResolution; 14 | uniform vec4 uSimResolution; 15 | uniform vec4 uSliceResolution; 16 | 17 | vec3 uvToIndexSpace(vec2 uv) { 18 | 19 | vec2 globalIndex = uv * uTexResolution.xy; 20 | vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy); 21 | 22 | float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5); 23 | vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy)); 24 | 25 | return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5); 26 | } 27 | 28 | vec2 IndexSpaceToUV(vec3 is, vec3 offset) { 29 | vec3 isOrg = is + offset; 30 | isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5)); 31 | 32 | float iSliceX = floor(mod(isOrg.z, uSliceResolution.x)); 33 | float iSliceY = floor(isOrg.z / uSliceResolution.x); 34 | 35 | return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy; 36 | } 37 | 38 | void main(void) { 39 | vec4 value = texture2D(uVelDensityTexture, vTextureCoord); 40 | 41 | vec3 wsPos = uvToIndexSpace(vTextureCoord) * uIsTs.xyz * uTsWs.xyz; // scaling 42 | float dt = uOption.x; 43 | 44 | vec3 wsVel = value.xyz; 45 | vec3 wsPrevPos = wsPos - dt * wsVel; 46 | 47 | vec3 ts = wsPrevPos * uWsTs.xyz; 48 | 49 | 50 | vec3 is = ts * uTsIs.xyz - vec3(0.5, 0.5, 0.5); 51 | vec3 iIs0 = floor(is); 52 | vec3 iIs1 = iIs0 + vec3(1, 1, 1); 53 | vec3 st1 = is - iIs0; 54 | vec3 st0 = vec3(1, 1, 1) - st1; 55 | 56 | iIs0 += vec3(0.5, 0.5, 0.5); 57 | iIs1 += vec3(0.5, 0.5, 0.5); 58 | 59 | vec4 v000 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs0.z), vec3(0, 0, 0))); 60 | vec4 v100 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs0.z), vec3(0, 0, 0))); 61 | vec4 v010 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs0.z), vec3(0, 0, 0))); 62 | vec4 v110 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs0.z), vec3(0, 0, 0))); 63 | vec4 v001 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs1.z), vec3(0, 0, 0))); 64 | vec4 v101 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs1.z), vec3(0, 0, 0))); 65 | vec4 v011 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs1.z), vec3(0, 0, 0))); 66 | vec4 v111 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs1.z), vec3(0, 0, 0))); 67 | 68 | float finalDensity = 69 | st0.x * ( 70 | st0.y * (st0.z * v000.w + st1.z * v001.w) + 71 | st1.y * (st0.z * v010.w + st1.z * v011.w)) + 72 | st1.x * ( 73 | st0.y * (st0.z * v100.w + st1.z * v101.w) + 74 | st1.y * (st0.z * v110.w + st1.z * v111.w)); 75 | 76 | gl_FragColor = vec4(value.xyz, finalDensity); 77 | } 78 | -------------------------------------------------------------------------------- /shader/advectVelStep.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uVelDensityTexture; 4 | 5 | uniform vec4 uTsWs; 6 | uniform vec4 uWsTs; 7 | uniform vec4 uTsIs; 8 | uniform vec4 uIsTs; 9 | uniform vec4 uOption; 10 | 11 | varying vec2 vTextureCoord; 12 | 13 | 14 | uniform vec4 uTexResolution; 15 | uniform vec4 uSimResolution; 16 | uniform vec4 uSliceResolution; 17 | 18 | vec3 uvToIndexSpace(vec2 uv) { 19 | 20 | vec2 globalIndex = uv * uTexResolution.xy; 21 | vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy); 22 | 23 | float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5); 24 | vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy)); 25 | 26 | return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5); 27 | } 28 | 29 | vec2 IndexSpaceToUV(vec3 is, vec3 offset) { 30 | vec3 isOrg = is + offset; 31 | isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5)); 32 | 33 | float iSliceX = floor(mod(isOrg.z, uSliceResolution.x)); 34 | float iSliceY = floor(isOrg.z / uSliceResolution.x); 35 | 36 | return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy; 37 | } 38 | void main(void) { 39 | vec4 value = texture2D(uVelDensityTexture, vTextureCoord); 40 | 41 | vec3 wsPos = uvToIndexSpace(vTextureCoord) * uIsTs.xyz * uTsWs.xyz; // scaling 42 | float dt = uOption.x; 43 | 44 | vec3 wsVel = value.xyz; 45 | vec3 wsPrevPos = wsPos - dt * wsVel; 46 | 47 | vec3 ts = wsPrevPos * uWsTs.xyz; 48 | 49 | 50 | vec3 is = ts * uTsIs.xyz - vec3(0.5, 0.5, 0.5); 51 | vec3 iIs0 = floor(is); 52 | vec3 iIs1 = iIs0 + vec3(1, 1, 1); 53 | vec3 st1 = is - iIs0; 54 | vec3 st0 = vec3(1, 1, 1) - st1; 55 | 56 | iIs0 += vec3(0.5, 0.5, 0.5); 57 | iIs1 += vec3(0.5, 0.5, 0.5); 58 | vec4 v000 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs0.z), vec3(0, 0, 0))); 59 | vec4 v100 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs0.z), vec3(0, 0, 0))); 60 | vec4 v010 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs0.z), vec3(0, 0, 0))); 61 | vec4 v110 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs0.z), vec3(0, 0, 0))); 62 | vec4 v001 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs1.z), vec3(0, 0, 0))); 63 | vec4 v101 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs1.z), vec3(0, 0, 0))); 64 | vec4 v011 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs1.z), vec3(0, 0, 0))); 65 | vec4 v111 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs1.z), vec3(0, 0, 0))); 66 | 67 | vec3 finalVelocity = 68 | st0.x * ( 69 | st0.y * (st0.z * v000.xyz + st1.z * v001.xyz) + 70 | st1.y * (st0.z * v010.xyz + st1.z * v011.xyz)) + 71 | st1.x * ( 72 | st0.y * (st0.z * v100.xyz + st1.z * v101.xyz) + 73 | st1.y * (st0.z * v110.xyz + st1.z * v111.xyz)); 74 | 75 | gl_FragColor = vec4(finalVelocity.xyz, value.w); 76 | } 77 | -------------------------------------------------------------------------------- /shader/fluid.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aVertexPosition; 2 | attribute vec2 aTextureCoord; 3 | 4 | uniform mat4 uPerspectiveMatrix; 5 | uniform mat4 uModelMatrix; 6 | uniform mat4 uViewMatrix; 7 | 8 | varying vec2 vTextureCoord; 9 | 10 | void main(void) { 11 | vTextureCoord = aTextureCoord; 12 | gl_Position = uPerspectiveMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition.xy, 0, 1.0); 13 | } -------------------------------------------------------------------------------- /shader/renderVolume.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec2 vTextureCoord; 4 | 5 | uniform vec4 uOption; 6 | 7 | uniform vec4 uProjParams; 8 | uniform mat4 uInvViewMatrix; 9 | uniform vec4 uCameraParams; 10 | 11 | uniform sampler2D uVelDensityTexture; 12 | 13 | uniform vec4 uTexResolution; 14 | uniform vec4 uSimResolution; 15 | uniform vec4 uSliceResolution; 16 | vec3 uvToIndexSpace(vec2 uv) { 17 | 18 | vec2 globalIndex = uv * uTexResolution.xy; 19 | vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy); 20 | 21 | float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5); 22 | vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy)); 23 | 24 | return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5); 25 | } 26 | 27 | vec2 IndexSpaceToUV(vec3 is, vec3 offset) { 28 | vec3 isOrg = is + offset; 29 | isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5)); 30 | 31 | float iSliceX = floor(mod(isOrg.z, uSliceResolution.x)); 32 | float iSliceY = floor(isOrg.z / uSliceResolution.x); 33 | 34 | return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy; 35 | } 36 | 37 | // http://bassser.tumblr.com/post/11626074256/reconstructing-position-from-depth-buffer 38 | vec3 reconstructPosition(in float p_depth, in vec2 p_ndc, in vec4 p_projParams) 39 | { 40 | float depth = p_depth * 2.0 - 1.0; 41 | float viewDepth = p_projParams.w / (depth - p_projParams.z); 42 | 43 | return vec3((p_ndc * viewDepth) / p_projParams.xy, viewDepth); 44 | } 45 | 46 | float sampleDensity(vec3 is) { 47 | vec3 iIs0 = floor(is); 48 | return texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs0.z), vec3(0, 0, 0))).w; 49 | } 50 | 51 | // Very heavy! 52 | float sampleDensityBilinear(vec3 is) { 53 | vec3 iIs0 = floor(is); 54 | vec3 iIs1 = iIs0 + vec3(1, 1, 1); 55 | vec3 st1 = is - iIs0; 56 | vec3 st0 = vec3(1, 1, 1) - st1; 57 | 58 | iIs0 += vec3(0.5, 0.5, 0.5); 59 | iIs1 += vec3(0.5, 0.5, 0.5); 60 | 61 | vec4 v000 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs0.z), vec3(0, 0, 0))); 62 | vec4 v100 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs0.z), vec3(0, 0, 0))); 63 | vec4 v010 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs0.z), vec3(0, 0, 0))); 64 | vec4 v110 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs0.z), vec3(0, 0, 0))); 65 | vec4 v001 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs0.y, iIs1.z), vec3(0, 0, 0))); 66 | vec4 v101 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs0.y, iIs1.z), vec3(0, 0, 0))); 67 | vec4 v011 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs0.x, iIs1.y, iIs1.z), vec3(0, 0, 0))); 68 | vec4 v111 = texture2D(uVelDensityTexture, IndexSpaceToUV(vec3(iIs1.x, iIs1.y, iIs1.z), vec3(0, 0, 0))); 69 | 70 | float d = 71 | st0.x * ( 72 | st0.y * (st0.z * v000.w + st1.z * v001.w) + 73 | st1.y * (st0.z * v010.w + st1.z * v011.w)) + 74 | st1.x * ( 75 | st0.y * (st0.z * v100.w + st1.z * v101.w) + 76 | st1.y * (st0.z * v110.w + st1.z * v111.w)); 77 | return d; 78 | } 79 | 80 | float rand(vec2 co){ 81 | return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); 82 | } 83 | 84 | bool checkInside(vec3 org, vec3 pmin, vec3 pmax) { 85 | if (org.x < pmin.x || org.y < pmin.y || org.z < pmin.z || 86 | org.x > pmax.x || org.y > pmax.y || org.z > pmax.z) 87 | return false; 88 | return true; 89 | } 90 | 91 | vec2 checkIntersect(vec3 org, vec3 dir, vec3 pmin, vec3 pmax) { 92 | vec2 t; 93 | float t0 = 0.0, t1 = 100000.0; 94 | 95 | for (int i = 0; i < 3; ++i) { 96 | float invRayDir = 1.0 / dir[i]; 97 | float tNear = (pmin[i] - org[i]) * invRayDir; 98 | float tFar = (pmax[i] - org[i]) * invRayDir; 99 | 100 | if (tNear > tFar) { 101 | float tmp = tNear; 102 | tNear = tFar; 103 | tFar = tmp; 104 | } 105 | t0 = tNear > t0 ? tNear : t0; 106 | t1 = tFar < t1 ? tFar : t1; 107 | if (t0 > t1) return vec2(-1, -1); 108 | } 109 | return vec2(t0, t1); 110 | } 111 | 112 | #define N 32.0 113 | 114 | vec3 wsToIs(vec3 ws) { 115 | return ((ws / vec3(4.0, 4.0, 4.0)) + vec3(1, 0, 1)) * 0.5 * uSimResolution.xyz + vec3(0.5, 0.5, 0.5); 116 | } 117 | 118 | void main(void) { 119 | // Constants 120 | vec3 kLightVec = normalize(vec3(0.3, 0.7, 0.2)); 121 | float densityScale = uOption.x; 122 | vec3 pMin = vec3(-4, 0, -4); 123 | vec3 pMax = vec3(4, 8, 4); 124 | float planeSize = 25.0; 125 | vec3 finalColor = vec3(0.5, 0.5, 0.5); 126 | float sunPower = 1.0; 127 | 128 | vec3 vsPos = reconstructPosition(0.0, vTextureCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), uProjParams); 129 | vec3 wsPos = (uInvViewMatrix * vec4(vsPos, 1)).xyz; 130 | vec3 wsDir = normalize(wsPos - uCameraParams.xyz); 131 | vec3 wsOrg = uCameraParams.xyz; 132 | float tPlane = -dot(wsOrg, vec3(0, 1, 0)) / dot(wsDir, vec3(0, 1, 0)); 133 | 134 | vec3 wsInter = wsOrg + tPlane * wsDir; 135 | if (tPlane > 0.0 && abs(wsInter.x) < planeSize && abs(wsInter.z) < planeSize) { 136 | // col = vec4(wsOrg + t * wsDir, 1).xyz; 137 | finalColor = vec3(0.2, 0.2, 0.2); 138 | } 139 | 140 | // Shadow 141 | float shadowTransmittance = 1.0; 142 | { 143 | vec3 wsShadowOrg = wsOrg + tPlane * wsDir; 144 | vec2 shadowTp = checkIntersect(wsShadowOrg, kLightVec, pMin, pMax); 145 | wsShadowOrg = wsShadowOrg + shadowTp.x * kLightVec; 146 | 147 | float length = shadowTp.y - shadowTp.x; 148 | for (float j = 0.0; j < N; j += 1.0) { 149 | vec3 wsDPos = wsShadowOrg + (length / N) * (j + 0.5) * kLightVec; 150 | 151 | if (!checkInside(wsDPos, pMin, pMax) || length <= 0.0) 152 | break; 153 | 154 | vec3 isDPos = ((wsDPos / vec3(4.0, 4.0, 4.0)) + vec3(1, 0, 1)) * 0.5 * uSimResolution.xyz + vec3(0.5, 0.5, 0.5); 155 | float ddensity = densityScale * sampleDensity(isDPos); 156 | 157 | shadowTransmittance *= exp(-ddensity * (length / N)); 158 | } 159 | } 160 | 161 | // Volume ray marching 162 | float sum = 0.0; 163 | float transmittance = 1.0; 164 | { 165 | vec2 tp = checkIntersect(wsOrg, wsDir, pMin, pMax); 166 | wsOrg = wsOrg + tp.x * wsDir; 167 | 168 | float length = tp.y - tp.x; 169 | for (float i = 0.0; i < N; i += 1.0) { 170 | float nt = length / N * (i + (1.0 + rand(vec2(i + wsPos.z, uOption.y + wsPos.x + wsPos.y))) * 0.5); 171 | vec3 wsCurrentPos = wsOrg + nt * wsDir; 172 | 173 | if (!checkInside(wsCurrentPos, pMin, pMax) || length <= 0.0) 174 | break; 175 | 176 | vec3 isPos = wsToIs(wsCurrentPos); 177 | float density = densityScale * sampleDensity(isPos); 178 | 179 | transmittance *= exp(-density * (length / N)); 180 | 181 | float sunTransmittance = 1.0; 182 | vec3 wsSunOrg = wsCurrentPos; 183 | vec2 sunTp = checkIntersect(wsSunOrg, kLightVec, pMin, pMax); 184 | wsSunOrg = wsSunOrg + sunTp.x * kLightVec; 185 | float sunLength = sunTp.y - sunTp.x; 186 | for (float j = 0.0; j < N; j += 1.0) { 187 | vec3 wsDPos = wsSunOrg + (sunLength / N) * (j + 0.5) * kLightVec; 188 | vec3 isDPos = wsToIs(wsDPos); 189 | float ddensity = densityScale * sampleDensity(isDPos); 190 | 191 | sunTransmittance *= exp(-ddensity * (sunLength / N)); 192 | } 193 | sum += (transmittance * sunPower * sunTransmittance * density) * (length / N); 194 | } 195 | } 196 | 197 | vec3 result = vec3(shadowTransmittance * transmittance * finalColor) + vec3(sum, sum, sum); 198 | result = pow(result, vec3(0.45, 0.45, 0.45)); 199 | gl_FragColor = vec4(result, 1); 200 | } -------------------------------------------------------------------------------- /shader/renderVolume.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aVertexPosition; 2 | 3 | uniform mat4 uPerspectiveMatrix; 4 | uniform mat4 uModelMatrix; 5 | uniform mat4 uViewMatrix; 6 | uniform sampler2D uSampler; 7 | 8 | varying vec4 vColor; 9 | 10 | void main(void) { 11 | vColor = vec4(1.0, 1.0, 1.0, 1.0); 12 | 13 | vec3 value = texture2D(uSampler, aVertexPosition.xy).xyz; 14 | /* vec3 value = vec3(aVertexPosition.xy, 0);*/ 15 | 16 | gl_Position = uPerspectiveMatrix * uViewMatrix * uModelMatrix * vec4(value, 1.0); 17 | gl_PointSize = 2.0; 18 | } -------------------------------------------------------------------------------- /shader/setupPressure.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uVelDensityTexture; 4 | uniform sampler2D uPressureTexture; 5 | 6 | uniform vec4 uOption; 7 | uniform vec4 uInvResolution; 8 | 9 | varying vec2 vTextureCoord; 10 | 11 | 12 | uniform vec4 uTsIs; 13 | 14 | uniform vec4 uTexResolution; 15 | uniform vec4 uSimResolution; 16 | uniform vec4 uSliceResolution; 17 | 18 | vec3 uvToIndexSpace(vec2 uv) { 19 | 20 | vec2 globalIndex = uv * uTexResolution.xy; 21 | vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy); 22 | 23 | float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5); 24 | vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy)); 25 | 26 | return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5); 27 | } 28 | 29 | vec2 IndexSpaceToUV(vec3 is, vec3 offset) { 30 | vec3 isOrg = is + offset; 31 | isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5)); 32 | 33 | float iSliceX = floor(mod(isOrg.z, uSliceResolution.x)); 34 | float iSliceY = floor(isOrg.z / uSliceResolution.x); 35 | 36 | return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy; 37 | } 38 | 39 | void main(void) { 40 | vec3 is = uvToIndexSpace(vTextureCoord); 41 | 42 | vec4 vx0 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 1, 0, 0))); 43 | vec4 vx1 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3(-1, 0, 0))); 44 | vec4 vy0 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 0, 1, 0))); 45 | vec4 vy1 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 0,-1, 0))); 46 | vec4 vz0 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 0, 0, 1))); 47 | vec4 vz1 = texture2D(uVelDensityTexture, IndexSpaceToUV(is, vec3( 0, 0,-1))); 48 | float h = uOption.y; 49 | float div = -0.5 * h * ( 50 | vx0.x - vx1.x + 51 | vy0.y - vy1.y + 52 | vz0.z - vz1.z); 53 | float prevPressure = texture2D(uPressureTexture, vTextureCoord).y; 54 | 55 | gl_FragColor = vec4(div, prevPressure, 0, 0); 56 | } 57 | -------------------------------------------------------------------------------- /shader/showTexture.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uSampler; 4 | 5 | varying vec2 vTextureCoord; 6 | 7 | void main(void) { 8 | gl_FragColor = vec4(abs(texture2D(uSampler, vTextureCoord).www) * 1.0, 1); 9 | } -------------------------------------------------------------------------------- /shader/showTexture.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aVertexPosition; 2 | attribute vec2 aTextureCoord; 3 | 4 | uniform mat4 uPerspectiveMatrix; 5 | uniform mat4 uModelMatrix; 6 | uniform mat4 uViewMatrix; 7 | 8 | varying vec2 vTextureCoord; 9 | 10 | void main(void) { 11 | vTextureCoord = aTextureCoord; 12 | gl_Position = uPerspectiveMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition.xy, 0, 1.0); 13 | } -------------------------------------------------------------------------------- /shader/showTextureRGB.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uSampler; 4 | 5 | varying vec2 vTextureCoord; 6 | 7 | void main(void) { 8 | gl_FragColor = vec4(abs(texture2D(uSampler, vTextureCoord).rgb) * 1.0, 1); 9 | } -------------------------------------------------------------------------------- /shader/solvePressure.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uPressureTexture; 4 | 5 | uniform vec4 uOption; 6 | uniform vec4 uInvResolution; 7 | 8 | varying vec2 vTextureCoord; 9 | 10 | uniform vec4 uTexResolution; 11 | uniform vec4 uSimResolution; 12 | uniform vec4 uSliceResolution; 13 | 14 | vec3 uvToIndexSpace(vec2 uv) { 15 | 16 | vec2 globalIndex = uv * uTexResolution.xy; 17 | vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy); 18 | 19 | float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5); 20 | vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy)); 21 | 22 | return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5); 23 | } 24 | 25 | vec2 IndexSpaceToUV(vec3 is, vec3 offset) { 26 | vec3 isOrg = is + offset; 27 | isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5)); 28 | 29 | float iSliceX = floor(mod(isOrg.z, uSliceResolution.x)); 30 | float iSliceY = floor(isOrg.z / uSliceResolution.x); 31 | 32 | return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy; 33 | } 34 | void main(void) { 35 | vec3 is = uvToIndexSpace(vTextureCoord); 36 | 37 | float p0 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 1, 0, 0))).y; 38 | float p1 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3(-1, 0, 0))).y; 39 | float p2 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 1, 0))).y; 40 | float p3 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0,-1, 0))).y; 41 | float p4 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 0, 1))).y; 42 | float p5 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 0,-1))).y; 43 | float div = texture2D(uPressureTexture, vTextureCoord).x; 44 | 45 | float newp = (div + p0 + p1 + p2 + p3 + p4 + p5) / 6.0; 46 | 47 | gl_FragColor = vec4(div, newp, 0, 0); 48 | } 49 | -------------------------------------------------------------------------------- /shader/updateVelocity.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D uVelDensityTexture; 4 | uniform sampler2D uPressureTexture; 5 | 6 | uniform vec4 uOption; 7 | uniform vec4 uInvResolution; 8 | 9 | varying vec2 vTextureCoord; 10 | 11 | 12 | uniform vec4 uTexResolution; 13 | uniform vec4 uSimResolution; 14 | uniform vec4 uSliceResolution; 15 | vec3 uvToIndexSpace(vec2 uv) { 16 | 17 | vec2 globalIndex = uv * uTexResolution.xy; 18 | vec2 iSliceIndex = floor(globalIndex / uSimResolution.xy); 19 | 20 | float iIndexZ = floor(iSliceIndex.y * uSliceResolution.x + iSliceIndex.x + 0.5); 21 | vec2 localIndex = floor(mod(globalIndex, uSimResolution.xy)); 22 | 23 | return vec3(localIndex, float(iIndexZ)) + vec3(0.5, 0.5, 0.5); 24 | } 25 | 26 | vec2 IndexSpaceToUV(vec3 is, vec3 offset) { 27 | vec3 isOrg = is + offset; 28 | isOrg = clamp(isOrg, vec3(0.5, 0.5, 0.5), uSimResolution.xyz - vec3(0.5, 0.5, 0.5)); 29 | 30 | float iSliceX = floor(mod(isOrg.z, uSliceResolution.x)); 31 | float iSliceY = floor(isOrg.z / uSliceResolution.x); 32 | 33 | return (uSimResolution.xy * vec2(iSliceX, iSliceY) + vec2(isOrg.x, isOrg.y)) / uTexResolution.xy; 34 | } 35 | 36 | void main(void) { 37 | vec3 is = uvToIndexSpace(vTextureCoord); 38 | 39 | float p0 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 1, 0, 0))).y; 40 | float p1 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3(-1, 0, 0))).y; 41 | float p2 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 1, 0))).y; 42 | float p3 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0,-1, 0))).y; 43 | float p4 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 0, 1))).y; 44 | float p5 = texture2D(uPressureTexture, IndexSpaceToUV(is, vec3( 0, 0,-1))).y; 45 | 46 | float h = uOption.y; 47 | vec4 vel = texture2D(uVelDensityTexture, vTextureCoord); 48 | vel.x -= 0.5 * (p0 - p1) / h; 49 | vel.y -= 0.5 * (p2 - p3) / h; 50 | vel.z -= 0.5 * (p4 - p5) / h; 51 | 52 | gl_FragColor = vec4(vel.xyz, vel.w); 53 | } 54 | --------------------------------------------------------------------------------