Skip to content

Instantly share code, notes, and snippets.

@synecdocheNORTH
Created January 4, 2019 18:02
Show Gist options
  • Save synecdocheNORTH/e627cb397da46560edde2488336bde0d to your computer and use it in GitHub Desktop.
Save synecdocheNORTH/e627cb397da46560edde2488336bde0d to your computer and use it in GitHub Desktop.
WebGL plane interactions | Curtains.js
<script id="simple-plane-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// default mandatory variables
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// custom variables
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
uniform float uTime;
uniform vec2 uResolution;
uniform vec2 uMousePosition;
uniform float uMouseMoveStrength;
void main() {
vec3 vertexPosition = aVertexPosition;
// get the distance between our vertex and the mouse position
float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
// calculate our wave effect
float waveSinusoid = cos(5.0 * (distanceFromMouse - (uTime / 75.0)));
// attenuate the effect based on mouse distance
float distanceStrength = (0.4 / (distanceFromMouse + 0.4));
// calculate our distortion effect
float distortionEffect = distanceStrength * waveSinusoid * uMouseMoveStrength;
// apply it to our vertex position
vertexPosition.z += distortionEffect / 15.0;
vertexPosition.x += (distortionEffect / 15.0 * (uResolution.x / uResolution.y) * (uMousePosition.x - vertexPosition.x));
vertexPosition.y += distortionEffect / 15.0 * (uMousePosition.y - vertexPosition.y);
gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);
// varyings
vTextureCoord = aTextureCoord;
vVertexPosition = vertexPosition;
}
</script>
<script id="simple-plane-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
uniform float uTime;
uniform vec2 uResolution;
uniform vec2 uMousePosition;
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
uniform sampler2D simplePlaneTexture;
void main( void ) {
// get our texture coords
vec2 textureCoords = vec2(vTextureCoord.x, vTextureCoord.y);
// apply our texture
vec4 finalColor = texture2D(simplePlaneTexture, textureCoords);
// fake shadows based on vertex position along Z axis
finalColor.rgb -= clamp(-vVertexPosition.z, 0.0, 1.0);
// fake lights based on vertex position along Z axis
finalColor.rgb += clamp(vVertexPosition.z, 0.0, 1.0);
// handling premultiplied alpha (useful if we were using a png with transparency)
finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
gl_FragColor = finalColor;
}
</script>
<div id="js-page-wrapper">
<div id="canvas"></div>
<div class="img-container js-img-container" data-vs-id="simple-plane-vs" data-fs-id="simple-plane-fs">
<img class="img" src="https://deghq.com/wordpress/bornfight/wp-content/themes/bf/static/images/work-3.jpg" alt="" />
</div>
</div>
function Curtains(t){this.planes=[],this.drawStack=[];var e=t||"canvas";return this.container=document.getElementById(e),this.container?(this.loadingManager={texturesLoaded:0},this._init(),this):(console.warn("You must specify a valid container ID"),!1)}function Plane(t,e,i){this.wrapper=t,this.htmlElement=e,this.images=[],this.videos=[],this.canvases=[],this.textures=[],this.index=this.wrapper.planes.length,this.canDraw=!1,this.definition={width:parseInt(i.widthSegments)||1,height:parseInt(i.heightSegments)||1},this.mimicCSS=i.mimicCSS,null!==this.mimicCSS&&void 0!==this.mimicCSS||(this.mimicCSS=!0),this.imageCover=i.imageCover,null!==this.imageCover&&void 0!==this.imageCover||(this.imageCover=!0),this.crossOrigin=i.crossOrigin||"anonymous",this.fov=i.fov||75,this.shouldUseDepthTest=!0,this.size={width:this.htmlElement.clientWidth*this.wrapper.pixelRatio||this.wrapper.glCanvas.width,height:this.htmlElement.clientHeight*this.wrapper.pixelRatio||this.wrapper.glCanvas.height},this.scale={x:1,y:1},this.translation={x:0,y:0,z:0},this.rotation={x:0,y:0,z:0},this.relativeTranslation={x:0,y:0};var r,a,s=i.vertexShaderID||e.getAttribute("data-vs-id"),n=i.fragmentShaderID||e.getAttribute("data-fs-id");i.vertexShader||(s&&document.getElementById(s)?r=document.getElementById(s).innerHTML:(console.warn("No vertex shader provided, will use a default one"),r="#ifdef GL_ES\nprecision mediump float;\n#endif\nattribute vec3 aVertexPosition;\nattribute vec2 aTextureCoord;\nuniform mat4 uMVMatrix;\nuniform mat4 uPMatrix;\nvarying vec3 vVertexPosition;\nvarying vec2 vTextureCoord;\nvoid main() {vTextureCoord = aTextureCoord;vVertexPosition = aVertexPosition;gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);}")),i.fragmentShader||(n&&document.getElementById(n)?a=document.getElementById(n).innerHTML:(console.warn("No fragment shader provided, will use a default one"),a="#ifdef GL_ES\nprecision mediump float;\n#endif\nvarying vec3 vVertexPosition;\nvarying vec2 vTextureCoord;\nvoid main( void ) {gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);}")),this.shaders={},this.shaders.vertexShaderCode=i.vertexShader||r,this.shaders.fragmentShaderCode=i.fragmentShader||a,this._setupPlane(),i.uniforms||(i.uniforms={},console.warn("You are setting a plane without uniforms, you won't be able to interact with it. Please check your addPlane method for : ",this.htmlElement)),this.uniforms={};var o=this;i.uniforms&&Object.keys(i.uniforms).map(function(t,e){var r=i.uniforms[t];o.uniforms[t]={name:r.name,type:r.type,value:r.value,coreUniform:!1}}),this._setUniforms(this.uniforms);var h=Math.floor(i.widthSegments)||1,l=Math.floor(i.heightSegments)||1;return this.wrapper._stackPlane(h*l+h),this}Curtains.prototype._init=function(){this.glCanvas=document.createElement("canvas"),this.glContext=this.glCanvas.getContext("webgl",{alpha:!0})||this.glCanvas.getContext("experimental-webgl"),this.glContext?(this.pixelRatio=window.devicePixelRatio||1,this.glCanvas.style.width=Math.floor(this.container.clientWidth)+"px",this.glCanvas.style.height=Math.floor(this.container.clientHeight)+"px",this.glCanvas.width=Math.floor(this.container.clientWidth)*this.pixelRatio,this.glCanvas.height=Math.floor(this.container.clientHeight)*this.pixelRatio,this.glContext.viewport(0,0,this.glContext.drawingBufferWidth,this.glContext.drawingBufferHeight),this.glCanvas.addEventListener("webglcontextlost",this._contextLost.bind(this),!1),this.glCanvas.addEventListener("webglcontextrestored",this._contextRestored.bind(this),!1),this._readyToDraw()):console.warn("WebGL context could not be created")},Curtains.prototype._isInitialized=function(){if(!this.glCanvas||!this.glContext)return console.warn("No WebGL canvas or context"),!1},Curtains.prototype._contextLost=function(t){t.preventDefault(),this.requestAnimationFrameID&&window.cancelAnimationFrame(this.requestAnimationFrameID)},Curtains.prototype._contextRestored=function(){for(var t=0;t<this.planes.length;t++){this.planes[t]._restoreContext()}var e=this;!function t(){e._drawScene(),e.requestAnimationFrameID=window.requestAnimationFrame(t)}()},Curtains.prototype.dispose=function(){for(;this.planes.length>0;)this.removePlane(this.planes[0]);var t=this,e=setInterval(function(){0==t.planes.length&&(clearInterval(e),t.glContext.clear(t.glContext.DEPTH_BUFFER_BIT|t.glContext.COLOR_BUFFER_BIT),window.cancelAnimationFrame(t.requestAnimationFrameID),t.glContext&&t.glContext.getExtension("WEBGL_lose_context").loseContext())},100)},Curtains.prototype._createPlane=function(t,e){var i=new Plane(this,t,e);return this.planes.push(i),i},Curtains.prototype.addPlane=function(t,e){if(this.glContext){if(!t||0===t.length)return console.warn("The html element you specified does not currently exists in the DOM"),!1;for(var i=this._createPlane(t,e),r=[],a=0;a<i.htmlElement.getElementsByTagName("img").length;a++)r.push(i.htmlElement.getElementsByTagName("img")[a]);var s=[];for(a=0;a<i.htmlElement.getElementsByTagName("video").length;a++)s.push(i.htmlElement.getElementsByTagName("video")[a]);var n=[];for(a=0;a<i.htmlElement.getElementsByTagName("canvas").length;a++)n.push(i.htmlElement.getElementsByTagName("canvas")[a]);return r.length>0?i.loadImages(r):i.imagesLoaded=!0,s.length>0?i.loadVideos(s):i.videosLoaded=!0,n.length>0?i.loadCanvases(n):i.canvasesLoaded=!0,0==r.length&&0==s.length&&0==n.length&&console.warn("This plane does not contain any image, video or canvas element. You may want to add some later with the loadImages, loadVideos or loadCanvases method."),i}return console.warn("Unable to create a plane. The WebGl context couldn't be created"),null},Curtains.prototype.removePlane=function(t){t.canDraw=!1;for(var e,i,r,a=t.definition.width*t.definition.height+t.definition.width,s=this.drawStack,n=0;n<s.length;n++)s[n].definition==a&&(e=n);s[e].isReordering=!0;for(n=0;n<s[e].planesIndex.length;n++)t.index===s[e].planesIndex[n]&&(i=n);for(n=i+1;n<s[e].planesIndex.length;n++)s[e].planesIndex[n]--;this.drawStack[e].planesIndex.splice(i,1);for(n=0;n<t.textures.length;n++)"video"==t.textures[n].type&&t.videos[t.textures[n].typeIndex].updateInterval&&clearInterval(t.videos[t.textures[n].typeIndex].updateInterval),this.glContext.activeTexture(this.glContext.TEXTURE0+t.textures[n].index),this.glContext.bindTexture(this.glContext.TEXTURE_2D,null),this.glContext.deleteTexture(t.textures[n].glTexture),this.loadingManager.texturesLoaded--;this.glContext.bindBuffer(this.glContext.ARRAY_BUFFER,t.geometry.bufferInfos.id),this.glContext.bufferData(this.glContext.ARRAY_BUFFER,1,this.glContext.STATIC_DRAW),this.glContext.deleteBuffer(t.geometry.bufferInfos.id),this.glContext.bindBuffer(this.glContext.ARRAY_BUFFER,t.material.bufferInfos.id),this.glContext.bufferData(this.glContext.ARRAY_BUFFER,1,this.glContext.STATIC_DRAW),this.glContext.deleteBuffer(t.material.bufferInfos.id),this.glContext.deleteShader(t.shaders.fragmentShader),this.glContext.deleteShader(t.shaders.vertexShader),this.glContext.deleteProgram(t.program);for(n=0;n<this.planes.length;n++)t.index===this.planes[n].index&&(r=n);t=null,this.planes[r]=null,this.planes.splice(r,1),this.glContext.clear(this.glContext.DEPTH_BUFFER_BIT|this.glContext.COLOR_BUFFER_BIT),s[e].isReordering=!1},Curtains.prototype._stackPlane=function(t){if(0===this.drawStack.length){var e={definition:t,planesIndex:[this.planes.length],isReordering:!1};this.drawStack.push(e)}else{for(var i=!1,r=0;r<this.drawStack.length;r++)this.drawStack[r].definition==t&&(i=!0,this.drawStack[r].planesIndex.push(this.planes.length));if(!i){e={definition:t,planesIndex:[this.planes.length],isReordering:!1};this.drawStack.push(e)}}},Curtains.prototype._createShader=function(t,e){this._isInitialized();var i=this.glContext.createShader(e);return this.glContext.shaderSource(i,t),this.glContext.compileShader(i),this.glContext.getShaderParameter(i,this.glContext.COMPILE_STATUS)||this.glContext.isContextLost()?i:(console.warn("Errors occurred while compiling the shader:\n"+this.glContext.getShaderInfoLog(i)),this.container.classList.add("no-webgl-curtains"),null)},Curtains.prototype._reSize=function(){if(parseInt(this.glCanvas.style.width)!==Math.floor(this.container.clientWidth)||parseInt(this.glCanvas.style.height)!==Math.floor(this.container.clientHeight)){this.pixelRatio=window.devicePixelRatio||1,this.glCanvas.style.width=Math.floor(this.container.clientWidth)+"px",this.glCanvas.style.height=Math.floor(this.container.clientHeight)+"px",this.glCanvas.width=Math.floor(this.container.clientWidth)*this.pixelRatio,this.glCanvas.height=Math.floor(this.container.clientHeight)*this.pixelRatio,this.glContext.viewport(0,0,this.glContext.drawingBufferWidth,this.glContext.drawingBufferHeight);for(var t=0;t<this.planes.length;t++)this.planes[t].canDraw&&this.planes[t].planeResize()}},Curtains.prototype._handleDepth=function(t){this._isInitialized(),this._shouldHandleDepth=t,t?this.glContext.enable(this.glContext.DEPTH_TEST):this.glContext.disable(this.glContext.DEPTH_TEST)},Curtains.prototype._readyToDraw=function(){this._isInitialized(),this.container.appendChild(this.glCanvas),this.glContext.blendFunc(this.glContext.SRC_ALPHA,this.glContext.ONE_MINUS_SRC_ALPHA),this.glContext.enable(this.glContext.BLEND),this._handleDepth(!0),console.log("curtains.js - v1.5");var t=this;!function e(){t._drawScene(),t.requestAnimationFrameID=window.requestAnimationFrame(e)}()},Curtains.prototype._drawScene=function(){this._isInitialized(),this.glContext.clearColor(0,0,0,0),this.glContext.clearDepth(1),this._reSize();for(var t=0;t<this.drawStack.length;t++)if(!this.drawStack[t].isReordering)for(var e=0;e<this.drawStack[t].planesIndex.length;e++){var i=this.planes[this.drawStack[t].planesIndex[e]];i&&(i.shouldUseDepthTest&&!this._shouldHandleDepth?this._handleDepth(!0):!i.shouldUseDepthTest&&this._shouldHandleDepth&&this._handleDepth(!1),0==e?i._drawPlane(!0):i._drawPlane(!1))}},Plane.prototype._restoreContext=function(){this.canDraw=!1,this.shaders.vertexShader=null,this.shaders.fragmentShader=null,this.program=null,this.matrix=null,this.attributes=null,this.textures=[],this.geometry.bufferInfos=null,this.material.bufferInfos=null,this._setupPlane(),this._setUniforms(this.uniforms),this._createTextures("image"),this._createTextures("video")},Plane.prototype._setupPlane=function(){var t=this.wrapper.glContext;if(this.program=t.createProgram(),this.shaders.vertexShader=this.wrapper._createShader(this.shaders.vertexShaderCode,t.VERTEX_SHADER),this.shaders.fragmentShader=this.wrapper._createShader(this.shaders.fragmentShaderCode,t.FRAGMENT_SHADER),!this.shaders.vertexShader||!this.shaders.fragmentShader)return console.warn("Unable to find the vertex or fragment shader"),this.wrapper.container.classList.add("no-webgl-curtains"),!1;if(t.attachShader(this.program,this.shaders.vertexShader),t.attachShader(this.program,this.shaders.fragmentShader),t.linkProgram(this.program),!t.getProgramParameter(this.program,t.LINK_STATUS)&&!t.isContextLost())return console.warn("Unable to initialize the shader program."),this.wrapper.container.classList.add("no-webgl-curtains"),!1;t.useProgram(this.program),this.matrix={},this.matrix.mvMatrix=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]),this.matrix.pMatrix=this._setPerspectiveMatrix(this.fov,.1,2*this.fov),this.matrix.pMatrixUniform=t.getUniformLocation(this.program,"uPMatrix"),this.matrix.mvMatrixUniform=t.getUniformLocation(this.program,"uMVMatrix");this._setAttributes({vertexPosition:"aVertexPosition",textureCoord:"aTextureCoord"})},Plane.prototype._isProgramInitialized=function(){if(!this.program)return console.warn("No WebGL program for this plane"),!1},Plane.prototype.onLoading=function(t){return t&&(this.onPlaneLoadingCallback=t),this},Plane.prototype.onReady=function(t){return t&&(this._onReadyCallback=t),this},Plane.prototype.onRender=function(t){return t&&(this.onRenderCallback=t),this},Plane.prototype._setPlaneDefinition=function(t,e){var i=this.wrapper.glContext;i.useProgram(this.program);for(var r=0;r<this.textures.length;r++)if(this.textures[r].sampler){var a=this.textures[r].sampler;this.uniforms[a]={},this.uniforms[a].location=i.getUniformLocation(this.program,a),this.uniforms[a].coreUniform=!0,i.uniform1i(this.uniforms[a].location,this.textures[r].index)}else this.uniforms["sampler"+this.textures[r].index]={},this.uniforms["sampler"+this.textures[r].index].location=i.getUniformLocation(this.program,"uSampler"+r),this.uniforms["sampler"+this.textures[r].index].coreUniform=!0,i.uniform1i(this.uniforms["sampler"+this.textures[r].index].location,this.textures[r].index);this._initializeBuffers(t,e)},Plane.prototype._setPlaneVertices=function(t,e){for(var i={vertices:[],uvs:[]},r=0;r<e;++r)for(var a=r/e,s=0;s<t;++s){var n=s/t;i.uvs.push(n),i.uvs.push(a),i.uvs.push(0),i.vertices.push(2*(n-.5)),i.vertices.push(2*(a-.5)),i.vertices.push(0),i.uvs.push(n+1/t),i.uvs.push(a),i.uvs.push(0),i.vertices.push(2*(n+1/t-.5)),i.vertices.push(2*(a-.5)),i.vertices.push(0),i.uvs.push(n),i.uvs.push(a+1/e),i.uvs.push(0),i.vertices.push(2*(n-.5)),i.vertices.push(2*(a+1/e-.5)),i.vertices.push(0),i.uvs.push(n),i.uvs.push(a+1/e),i.uvs.push(0),i.vertices.push(2*(n-.5)),i.vertices.push(2*(a+1/e-.5)),i.vertices.push(0),i.uvs.push(n+1/t),i.uvs.push(a+1/e),i.uvs.push(0),i.vertices.push(2*(n+1/t-.5)),i.vertices.push(2*(a+1/e-.5)),i.vertices.push(0),i.uvs.push(n+1/t),i.uvs.push(a),i.uvs.push(0),i.vertices.push(2*(n+1/t-.5)),i.vertices.push(2*(a-.5)),i.vertices.push(0)}return i},Plane.prototype._initializeBuffers=function(t,e){this.wrapper._isInitialized(),this._isProgramInitialized(),t=Math.floor(t)||1,e=Math.floor(e)||1;var i=this.htmlElement.clientWidth*this.wrapper.pixelRatio||this.wrapper.glCanvas.width,r=this.htmlElement.clientHeight*this.wrapper.pixelRatio||this.wrapper.glCanvas.height;if(!this.geometry&&!this.material){var a=this._setPlaneVertices(t,e);this.geometry={},this.geometry.vertices=a.vertices,this.material={},this.material.uvs=a.uvs}this.geometry.innerScale={x:i/this.wrapper.glCanvas.width,y:r/this.wrapper.glCanvas.height},this.clipSpace={x:(this.geometry.innerScale.x-1)*(this.wrapper.glCanvas.width/this.wrapper.glCanvas.height/2)/this.scale.x,y:(1-this.geometry.innerScale.y)/2/this.scale.y,width:this.wrapper.glCanvas.width/this.wrapper.glCanvas.height,height:2},this.mimicCSS?this._applyCSSPositions():this.setTranslation(0,0,0);for(var s=0;s<this.textures.length;s++)this._adjustTextureSize(s);var n=this.wrapper.glContext;this.geometry.bufferInfos={},this.geometry.bufferInfos.id=n.createBuffer(),n.bindBuffer(n.ARRAY_BUFFER,this.geometry.bufferInfos.id),n.bufferData(n.ARRAY_BUFFER,new Float32Array(this.geometry.vertices),n.STATIC_DRAW),this.geometry.bufferInfos.itemSize=3,this.geometry.bufferInfos.numberOfItems=this.geometry.vertices.length/this.geometry.bufferInfos.itemSize,n.vertexAttribPointer(this.attributes.vertexPosition,this.geometry.bufferInfos.itemSize,n.FLOAT,!1,0,0),n.enableVertexAttribArray(this.attributes.vertexPosition),this.material.bufferInfos={},this.material.bufferInfos.id=n.createBuffer(),n.bindBuffer(n.ARRAY_BUFFER,this.material.bufferInfos.id),n.bufferData(n.ARRAY_BUFFER,new Float32Array(this.material.uvs),n.STATIC_DRAW),this.material.bufferInfos.itemSize=3,this.material.bufferInfos.numberOfItems=this.material.uvs.length/this.material.bufferInfos.itemSize,n.vertexAttribPointer(this.attributes.textureCoord,this.material.bufferInfos.itemSize,n.FLOAT,!1,0,0),n.enableVertexAttribArray(this.attributes.textureCoord),this.canDraw=!0,this._onReadyCallback&&this._onReadyCallback()},Plane.prototype._bindPlaneBuffers=function(){var t=this.wrapper.glContext;t.bindBuffer(t.ARRAY_BUFFER,this.geometry.bufferInfos.id),t.vertexAttribPointer(this.attributes.vertexPosition,this.geometry.bufferInfos.itemSize,t.FLOAT,!1,0,0),t.enableVertexAttribArray(this.attributes.vertexPosition),t.bindBuffer(t.ARRAY_BUFFER,this.material.bufferInfos.id),t.vertexAttribPointer(this.attributes.textureCoord,this.material.bufferInfos.itemSize,t.FLOAT,!1,0,0),t.enableVertexAttribArray(this.attributes.textureCoord)},Plane.prototype._setAttributes=function(t){this.wrapper._isInitialized(),this._isProgramInitialized(),this.attributes||(this.attributes={});var e=this;Object.keys(t).map(function(i,r){var a=t[i];e.attributes[i]=e.wrapper.glContext.getAttribLocation(e.program,a)})},Plane.prototype._handleUniformSetting=function(t,e,i){var r=this.wrapper.glContext;"1i"==t?r.uniform1i(e,i):"1iv"==t?r.uniform1iv(e,i):"1f"==t?r.uniform1f(e,i):"1fv"==t?r.uniform1fv(e,i):"2i"==t?r.uniform2i(e,i[0],i[1]):"2iv"==t?r.uniform2iv(e,i):"2f"==t?r.uniform2f(e,i[0],i[1]):"2fv"==t?r.uniform2fv(e,i):"3i"==t?r.uniform3i(e,i[0],i[1],i[2]):"3iv"==t?r.uniform3iv(e,i):"3f"==t?r.uniform3f(e,i[0],i[1],i[2]):"3fv"==t?r.uniform3fv(e,i):"4i"==t?r.uniform4i(e,i[0],i[1],i[2],i[3]):"4iv"==t?r.uniform4iv(e,i):"4f"==t?r.uniform4f(e,i[0],i[1],i[2],i[3]):"4fv"==t?r.uniform4fv(e,i):"mat2"==t?r.uniformMatrix2fv(e,!1,i):"mat3"==t?r.uniformMatrix3fv(e,!1,i):"mat4"==t?r.uniformMatrix4fv(e,!1,i):console.warn("This uniform type is not handled : ",t)},Plane.prototype._setUniforms=function(t){this.wrapper._isInitialized(),this._isProgramInitialized(),this.wrapper.glContext.useProgram(this.program);var e=this;t&&Object.keys(t).map(function(i,r){var a=t[i];a.coreUniform||(e.uniforms[i].location=e.wrapper.glContext.getUniformLocation(e.program,a.name),a.type||(Array.isArray(a.value)?4==a.value.length?(a.type="4f",console.warn("No uniform type declared for "+a.name+", applied a 4f (array of 4 floats) uniform type")):3==a.value.length?(a.type="3f",console.warn("No uniform type declared for "+a.name+", applied a 3f (array of 3 floats) uniform type")):2==a.value.length&&(a.type="2f",console.warn("No uniform type declared for "+a.name+", applied a 2f (array of 2 floats) uniform type")):a.value.constructor===Float32Array?16==a.value.length?(a.type="mat4",console.warn("No uniform type declared for "+a.name+", applied a mat4 (4x4 matrix array) uniform type")):9==a.value.length?(a.type="mat3",console.warn("No uniform type declared for "+a.name+", applied a mat3 (3x3 matrix array) uniform type")):4==a.value.length&&(a.type="mat2",console.warn("No uniform type declared for "+a.name+", applied a mat2 (2x2 matrix array) uniform type")):(a.type="1f",console.warn("No uniform type declared for "+a.name+", applied a 1f (float) uniform type"))),e._handleUniformSetting(a.type,e.uniforms[i].location,a.value))})},Plane.prototype._updateUniforms=function(){if(this.wrapper.glContext.useProgram(this.program),this.uniforms&&this.wrapper.glContext.isProgram(this.program)){var t=this.uniforms,e=this;Object.keys(t).map(function(i,r){var a=t[i];if(!a.coreUniform){var s=a.location,n=a.value,o=a.type;e._handleUniformSetting(o,s,n)}})}},Plane.prototype._multiplyMatrix=function(t,e){var i=[],r=t[0],a=t[1],s=t[2],n=t[3],o=t[4],h=t[5],l=t[6],p=t[7],u=t[8],d=t[9],m=t[10],f=t[11],g=t[12],c=t[13],x=t[14],v=t[15],y=e[0],w=e[1],C=e[2],_=e[3];return i[0]=y*r+w*o+C*u+_*g,i[1]=y*a+w*h+C*d+_*c,i[2]=y*s+w*l+C*m+_*x,i[3]=y*n+w*p+C*f+_*v,y=e[4],w=e[5],C=e[6],_=e[7],i[4]=y*r+w*o+C*u+_*g,i[5]=y*a+w*h+C*d+_*c,i[6]=y*s+w*l+C*m+_*x,i[7]=y*n+w*p+C*f+_*v,y=e[8],w=e[9],C=e[10],_=e[11],i[8]=y*r+w*o+C*u+_*g,i[9]=y*a+w*h+C*d+_*c,i[10]=y*s+w*l+C*m+_*x,i[11]=y*n+w*p+C*f+_*v,y=e[12],w=e[13],C=e[14],_=e[15],i[12]=y*r+w*o+C*u+_*g,i[13]=y*a+w*h+C*d+_*c,i[14]=y*s+w*l+C*m+_*x,i[15]=y*n+w*p+C*f+_*v,i},Plane.prototype._setPerspectiveMatrix=function(t,e,i){var r=this.wrapper.glCanvas.width/this.wrapper.glCanvas.height;return t!==this.fov&&(this.fov=t),[t/r,0,0,0,0,t,0,0,0,0,(e+i)*(1/(e-i)),-1,0,0,e*i*(1/(e-i))*2,0]},Plane.prototype.setPerspective=function(t,e,i){var r=parseInt(t)||75;r<0?r=0:r>180&&(r=180);var a=parseFloat(e)||.1,s=parseFloat(i)||100,n=this.translation.x||0,o=this.translation.y||0;this.matrix.pMatrix=this._setPerspectiveMatrix(r,a,s),this.setTranslation(n,o,0)},Plane.prototype.setScale=function(t,e){this.wrapper._isInitialized(),this._isProgramInitialized(),t=parseFloat(t)||1,t=Math.max(t,.001),e=parseFloat(e)||1,e=Math.max(e,.001),this.scale={x:t,y:e},this.matrix.mvMatrix=this._setMVMatrix()},Plane.prototype.setRotation=function(t,e,i){this.wrapper._isInitialized(),this._isProgramInitialized(),t=parseFloat(t)||0,e=parseFloat(e)||0,i=parseFloat(i)||0,this.rotation={x:t,y:e,z:i},this.matrix.mvMatrix=this._setMVMatrix()},Plane.prototype.setTranslation=function(t,e,i){this.wrapper._isInitialized(),this._isProgramInitialized(),t=t||0,e=e||0,i=i||0,this.translation={x:t,y:e,z:i},this.matrix.mvMatrix=this._setMVMatrix()},Plane.prototype.setRelativePosition=function(t,e){var i=this._documentToPlaneSpace(t,e);this.relativeTranslation={x:t,y:e},this.setTranslation(i.x,i.y,this.translation.z)},Plane.prototype._documentToPlaneSpace=function(t,e){return{x:this.clipSpace.x+t/(this.wrapper.glCanvas.width/this.wrapper.pixelRatio)*this.clipSpace.width,y:this.clipSpace.y-e/(this.wrapper.glCanvas.height/this.wrapper.pixelRatio)}},Plane.prototype.mouseToPlaneCoords=function(t,e){var i=this.htmlElement.getBoundingClientRect(),r=this.wrapper.container.getBoundingClientRect();return{x:(t-r.left-(i.left+window.pageXOffset))/(this.size.width/this.wrapper.pixelRatio)*2-1,y:1-(e-r.top-(i.top+window.pageYOffset))/(this.size.height/this.wrapper.pixelRatio)*2}},Plane.prototype._applyCSSPositions=function(){this.size.width,this.size.height;var t={top:this.htmlElement.getBoundingClientRect().top-this.wrapper.container.getBoundingClientRect().top,left:this.htmlElement.getBoundingClientRect().left-this.wrapper.container.getBoundingClientRect().left},e=this._documentToPlaneSpace(t.left,t.top);this.relativeTranslation={x:t.left,y:t.top},this.setTranslation(e.x,e.y,this.translation.z)},Plane.prototype.enableDepthTest=function(t){this.shouldUseDepthTest=t},Plane.prototype.moveToFront=function(){this.enableDepthTest(!1);for(var t,e=this.definition.width*this.definition.height+this.definition.width,i=this.wrapper.drawStack,r=0;r<i.length;r++)i[r].definition==e&&(t=i[r]);t.isReordering=!0;var a=this.index;if(t.planesIndex.length>0){for(r=0;r<t.planesIndex.length;r++)t.planesIndex[r]==a&&t.planesIndex.splice(r,1);t.planesIndex.push(this.index)}if(i.length>0){for(r=0;r<i.length;r++)i[r].definition==e&&i.splice(r,1);i.push(t)}t.isReordering=!1},Plane.prototype._setMVMatrix=function(){var t=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]),e=this.translation.z-this.fov/2,i=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,this.translation.x,this.translation.y,e,1]),r=new Float32Array([1,0,0,0,0,Math.cos(this.rotation.x),Math.sin(this.rotation.x),0,0,-Math.sin(this.rotation.x),Math.cos(this.rotation.x),0,0,0,0,1]),a=new Float32Array([Math.cos(this.rotation.y),0,-Math.sin(this.rotation.y),0,0,1,0,0,Math.sin(this.rotation.y),0,Math.cos(this.rotation.y),0,0,0,0,1]),s=new Float32Array([Math.cos(this.rotation.z),Math.sin(this.rotation.z),0,0,-Math.sin(this.rotation.z),Math.cos(this.rotation.z),0,0,0,0,1,0,0,0,0,1]),n={x:this.scale.x*(this.wrapper.glCanvas.width/this.wrapper.glCanvas.height*this.geometry.innerScale.x/2),y:this.scale.y*this.geometry.innerScale.y/2},o=new Float32Array([n.x,0,0,0,0,n.y,0,0,0,0,1,0,0,0,0,1]),h=this._multiplyMatrix(t,i);return h=this._multiplyMatrix(h,r),h=this._multiplyMatrix(h,a),h=this._multiplyMatrix(h,s),h=this._multiplyMatrix(h,o)},Plane.prototype.planeResize=function(){var t=this.wrapper.glCanvas.width/this.wrapper.glCanvas.height;this.matrix.pMatrix=this._setPerspectiveMatrix(this.fov,.1,2*this.fov);var e=this.htmlElement.clientWidth*this.wrapper.pixelRatio,i=this.htmlElement.clientHeight*this.wrapper.pixelRatio;if(!e&&!i){var r=document.getElementsByClassName(this.htmlElement.className);if(r.length>0)for(var a=0;a<r.length;a++)r[a].isEqualNode(this.htmlElement)&&(this.htmlElement=r[a],e=this.htmlElement.clientWidth*this.wrapper.pixelRatio,i=this.htmlElement.clientHeight*this.wrapper.pixelRatio)}if(e&&i){var s=!1;if(e===this.size.width&&i===this.size.height||(s=!0),this.size={width:e,height:i},this.geometry.innerScale={x:e/this.wrapper.glCanvas.width,y:i/this.wrapper.glCanvas.height},this.clipSpace={x:(this.geometry.innerScale.x-1)*(t/2)/this.scale.x,y:(1-this.geometry.innerScale.y)/2/this.scale.y,width:t,height:2},this.mimicCSS?this._applyCSSPositions():this.setTranslation(this.translation.x,this.translation.y,this.translation.z),s)for(a=0;a<this.textures.length;a++)this._adjustTextureSize(a)}},Plane.prototype.loadImages=function(t){var e,i=this;this.imagesLoaded=!1;for(var r=0;r<t.length;r++)(e=new Image).onload=function(){i.images.push(this),i.onPlaneLoadingCallback&&i.onPlaneLoadingCallback()},e.crossOrigin=i.crossOrigin,e.sampler=t[r].getAttribute("data-sampler")||null,e.src=t[r].src;var a=setInterval(function(){i.images.length==t.length&&(clearInterval(a),t.length>1?i._reorderImages(t):i._createTextures("image"))},100);return this},Plane.prototype._reorderImages=function(t){for(var e=[],i=0;i<t.length;i++)for(var r=0;r<this.images.length;r++)this.images[r].src==t[i].src&&(e[i]=this.images[r]);this.images=e,this._createTextures("image")},Plane.prototype.loadVideos=function(t){var e,i=this;function r(i,r){if((e=document.createElement("video")).preload=!0,e.muted=!0,e.loop=!0,e.width=512,e.height=512,e.sampler=t[r].getAttribute("data-sampler")||null,e.crossOrigin=this.crossOrigin,e.firstStarted=!1,e.shouldUpdate=!1,e.addEventListener("play",function(){var t=this;t.firstStarted=!0,t.updateInterval||(t.updateInterval=setInterval(function(){t.shouldUpdate=!0},33))}),t[r].src)e.src=t[r].src,e.type=t[r].type;else if(t[r].getElementsByTagName("source").length>0)for(var a=0;a<t[r].getElementsByTagName("source").length;a++){var s=document.createElement("source");s.setAttribute("src",t[r].getElementsByTagName("source")[a].src),s.setAttribute("type",t[r].getElementsByTagName("source")[a].type),e.appendChild(s)}i.videos.push(e),i.onPlaneLoadingCallback&&i.onPlaneLoadingCallback()}this.videosLoaded=!1;for(var a=0;a<t.length;a++)r(this,a);var s=setInterval(function(){i.videos.length==t.length&&(clearInterval(s),i._createTextures("video"))},100);return this},Plane.prototype.playVideos=function(){for(var t=0;t<this.textures.length;t++)if("video"==this.textures[t].type){var e=this.videos[this.textures[t].typeIndex].play();this.textures[t];void 0!==e&&e.catch(function(t){console.warn("Could not play the video : ",t)})}},Plane.prototype.loadCanvases=function(t){var e,i=this;this.canvasesLoaded=!1;for(var r=0;r<t.length;r++)(e=t[r]).sampler=t[r].getAttribute("data-sampler")||null,e.shouldUpdate=!0,this.canvases.push(e),this.onPlaneLoadingCallback&&this.onPlaneLoadingCallback();var a=setInterval(function(){i.canvases.length==t.length&&(clearInterval(a),i._createTextures("canvase"))},100);return this},Plane.prototype._createTextures=function(t){this.wrapper._isInitialized();for(var e=0;e<this[t+"s"].length;e++)r=t,a=e,s=void 0,n=void 0,s=(i=this).wrapper.glContext,(n={}).type=r,n.typeIndex=a,n.sampler=i[r+"s"][a].sampler||null,n.glTexture=s.createTexture(),s.bindTexture(s.TEXTURE_2D,n.glTexture),s.pixelStorei(s.UNPACK_FLIP_Y_WEBGL,!0),s.texParameteri(s.TEXTURE_2D,s.TEXTURE_WRAP_S,s.CLAMP_TO_EDGE),s.texParameteri(s.TEXTURE_2D,s.TEXTURE_WRAP_T,s.CLAMP_TO_EDGE),s.texParameteri(s.TEXTURE_2D,s.TEXTURE_MIN_FILTER,s.LINEAR),s.texParameteri(s.TEXTURE_2D,s.TEXTURE_MAG_FILTER,s.LINEAR),n.index=i.wrapper.loadingManager.texturesLoaded,i.textures.push(n),i.wrapper.loadingManager.texturesLoaded++;var i,r,a,s,n;this[t+"sLoaded"]=!0,this.imagesLoaded&&this.videosLoaded&&this.canvasesLoaded&&this.wrapper.glContext.getProgramParameter(this.program,this.wrapper.glContext.LINK_STATUS)&&this._setPlaneDefinition(this.definition.width,this.definition.height)},Plane.prototype._adjustTextureSize=function(t){if(this.wrapper._isInitialized(),"image"==this.textures[t].type){var e=this.images[this.textures[t].typeIndex],i=this.wrapper.glContext;if(this.imageCover){var r=document.createElement("canvas"),a=r.getContext("2d");r.width=this.size.width,r.height=this.size.height;var s=e.width/e.height,n=r.width/r.height,o=0,h=0;n>s?h=Math.min(0,(r.height-r.width*(1/s))/2):n<s&&(o=Math.min(0,(r.width-r.height*s)/2)),a.clearRect(0,0,r.width,r.height),a.drawImage(e,o,h,r.width-2*o,r.height-2*h),i.useProgram(this.program),i.activeTexture(i.TEXTURE0+this.textures[t].index),i.bindTexture(i.TEXTURE_2D,this.textures[t].glTexture),i.texImage2D(i.TEXTURE_2D,0,i.RGBA,i.RGBA,i.UNSIGNED_BYTE,r)}else i.useProgram(this.program),i.activeTexture(i.TEXTURE0+this.textures[t].index),i.bindTexture(i.TEXTURE_2D,this.textures[t].glTexture),i.texImage2D(i.TEXTURE_2D,0,i.RGBA,i.RGBA,i.UNSIGNED_BYTE,e)}},Plane.prototype._drawPlane=function(t){var e,i,r,a,s=this.wrapper.glContext;if(this.canDraw){this.onRenderCallback&&this.onRenderCallback();for(var n=0;n<this.textures.length;n++)e=s,r=n,void 0,a=(i=this).textures[r],e.activeTexture(e.TEXTURE0+a.index),e.bindTexture(e.TEXTURE_2D,a.glTexture),"video"==a.type?i.videos[a.typeIndex].shouldUpdate?(e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,i.videos[a.typeIndex]),i.videos[a.typeIndex].shouldUpdate=!1):i.videos[a.typeIndex].firstStarted||e.texImage2D(e.TEXTURE_2D,0,e.RGBA,1,1,0,e.RGBA,e.UNSIGNED_BYTE,new Uint8Array([0,0,0,255])):"canvase"==a.type&&i.canvases[a.typeIndex].shouldUpdate&&e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,i.canvases[a.typeIndex]);this._updateUniforms(),t&&this._bindPlaneBuffers(),s.uniformMatrix4fv(this.matrix.pMatrixUniform,!1,this.matrix.pMatrix),s.uniformMatrix4fv(this.matrix.mvMatrixUniform,!1,this.matrix.mvMatrix),s.drawArrays(s.TRIANGLES,0,this.geometry.bufferInfos.numberOfItems)}};
window.onload = function(){
// our canvas container
var canvasContainer = document.getElementById("canvas");
// track the mouse positions to send it to the shaders
var mousePosition = {
x: 0,
y: 0,
};
// we will keep track of the last position in order to calculate the movement strength/delta
var mouseLastPosition = {
x: 0,
y: 0,
};
var mouseDelta = 0;
// set up our WebGL context and append the canvas to our wrapper
var webGLCurtain = new Curtains("canvas");
// get our plane element
var planeElements = document.getElementsByClassName("js-img-container");
// could be useful to get pixel ratio
var pixelRatio = window.devicePixelRatio ? window.devicePixelRatio : 2.0;
// some basic parameters
// we don't need to specifiate vertexShaderID and fragmentShaderID because we already passed it via the data attributes of the plane HTML element
var params = {
widthSegments: 10,
heightSegments: 10,
uniforms: {
resolution: { // resolution of our plane
name: "uResolution",
type: "2f", // notice this is an length 2 array of floats
value: [pixelRatio * planeElements[0].clientWidth, pixelRatio * planeElements[0].clientHeight],
},
time: { // time uniform that will be updated at each draw call
name: "uTime",
type: "1f",
value: 0,
},
mousePosition: { // our mouse position
name: "uMousePosition",
type: "2f", // again an array of floats
value: [mousePosition.x, mousePosition.y],
},
mouseMoveStrength: { // the mouse move strength
name: "uMouseMoveStrength",
type: "1f",
value: 0,
}
}
}
// create our plane
var simplePlane = webGLCurtain.addPlane(planeElements[0], params);
simplePlane.onReady(function() {
// set a fov of 35 to exagerate perspective
simplePlane.setPerspective(50);
// now that our plane is ready we can listen to mouse move event
var wrapper = document.getElementById("js-page-wrapper");
wrapper.addEventListener("mousemove", function(e) {
handleMovement(e, simplePlane);
});
wrapper.addEventListener("touchmove", function(e) {
handleMovement(e, simplePlane);
});
// on resize, update the resolution uniform
window.onresize = function() {
simplePlane.uniforms.resolution.value = [pixelRatio * planeElements[0].clientWidth, pixelRatio * planeElements[0].clientHeight];
}
}).onRender(function() {
// increment our time uniform
simplePlane.uniforms.time.value++;
// send the new mouse move strength value
simplePlane.uniforms.mouseMoveStrength.value = mouseDelta;
// decrease the mouse move strenght a bit : if the user doesn't move the mouse, effect will fade away
mouseDelta = Math.max(0, mouseDelta * 0.9995);
});
// handle the mouse move event
function handleMovement(e, plane) {
if(mousePosition.x != -100000 && mousePosition.y != -100000) {
// if mouse position is defined, set mouse last position
mouseLastPosition.x = mousePosition.x;
mouseLastPosition.y = mousePosition.y;
}
// touch event
if(e.targetTouches) {
mousePosition.x = e.targetTouches[0].clientX;
mousePosition.y = e.targetTouches[0].clientY;
}
// mouse event
else {
mousePosition.x = e.clientX;
mousePosition.y = e.clientY;
}
// convert our mouse/touch position to coordinates relative to the vertices of the plane
var mouseCoords = plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y);
// update our mouse position uniform
plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];
// calculate the mouse move strength
if(mouseLastPosition.x && mouseLastPosition.y) {
var delta = Math.sqrt(Math.pow(mousePosition.x - mouseLastPosition.x, 2) + Math.pow(mousePosition.y - mouseLastPosition.y, 2)) / 100;
delta = Math.min(4, delta);
// update mouseDelta only if it increased
if(delta >= mouseDelta) {
mouseDelta = delta;
// reset our time uniform
plane.uniforms.time.value = 0;
}
}
}
}
body {
height: 100vh;
width: 100%;
position: relative;
}
#canvas {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.img-container {
display: block;
width: 80%;
height: 80vh;
margin: 10vh auto;
}
.img {
display: none;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment