A Pen by Karlo Videk on CodePen.
Created
January 4, 2019 18:02
-
-
Save synecdocheNORTH/e627cb397da46560edde2488336bde0d to your computer and use it in GitHub Desktop.
WebGL plane interactions | Curtains.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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