├── .gitignore ├── README.md ├── docs ├── index.html ├── main.a6c128a5.js ├── matcap.6a80787e.jpg └── social.jpg ├── package-lock.json ├── package.json └── src ├── assets ├── matcap.jpg ├── matcap.png └── social.jpg ├── index.html └── js ├── assets.js ├── camera.js ├── main.js ├── postfx ├── postfx.frag ├── postfx.js └── postfx.vert ├── renderer.js ├── scene.js ├── settings.js ├── tube ├── position.frag ├── tube.frag ├── tube.js ├── tube.vert └── velocity.frag └── utils ├── deferred.js ├── fbo.js └── pointer.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | .cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TUBBBO - https://luruke.github.io/tubbbo/ 2 | 3 | [![Alt text](src/assets/social.jpg?raw=true "Title")](https://luruke.github.io/tubbbo/) 4 | 5 | A little WebGL experiment. 6 | 7 | > << Non capisci proprio un tubo >> -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Tubbbo – WebGL experiment by @luruke | luruke.com -------------------------------------------------------------------------------- /docs/matcap.6a80787e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruke/tubbbo/e85be5b107b805659939d3170b61074a32869541/docs/matcap.6a80787e.jpg -------------------------------------------------------------------------------- /docs/social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruke/tubbbo/e85be5b107b805659939d3170b61074a32869541/docs/social.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boilerthree", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "parcel src/index.html --no-autoinstall --open", 9 | "build": "rm -rf ./docs; parcel build src/index.html -d docs --public-url '.' --no-source-maps; cp ./src/assets/social.jpg ./docs/social.jpg" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/luruke/boilerthree.git" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/luruke/boilerthree/issues" 19 | }, 20 | "homepage": "https://github.com/luruke/boilerthree#readme", 21 | "devDependencies": { 22 | "bidello": "0.0.6", 23 | "detect-gpu": "^1.0.0", 24 | "glsl-curl-noise": "0.0.4", 25 | "glsl-fxaa": "^3.0.0", 26 | "glslify-bundle": "^5.1.1", 27 | "glslify-deps": "^1.3.1", 28 | "magicshader": "^0.1.2", 29 | "math-toolbox": "^1.12.0", 30 | "orbit-controls-es6": "^2.0.0", 31 | "parcel-bundler": "^1.12.3", 32 | "resource-loader": "^2.2.4", 33 | "three": "^0.105.2" 34 | }, 35 | "dependencies": {} 36 | } 37 | -------------------------------------------------------------------------------- /src/assets/matcap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruke/tubbbo/e85be5b107b805659939d3170b61074a32869541/src/assets/matcap.jpg -------------------------------------------------------------------------------- /src/assets/matcap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruke/tubbbo/e85be5b107b805659939d3170b61074a32869541/src/assets/matcap.png -------------------------------------------------------------------------------- /src/assets/social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruke/tubbbo/e85be5b107b805659939d3170b61074a32869541/src/assets/social.jpg -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tubbbo – WebGL experiment by @luruke | luruke.com 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/js/assets.js: -------------------------------------------------------------------------------- 1 | import Loader from 'resource-loader'; 2 | import bidello from 'bidello'; 3 | import deferred from './utils/deferred'; 4 | 5 | const Resource = Loader.Resource; 6 | const RESOURCES = [ 7 | { 8 | name: 'matcap', 9 | url: require('/assets/matcap.jpg') 10 | }, 11 | 12 | // { 13 | // name: 'photo', 14 | // url: require('/assets/photo.glb'), 15 | // loadType: Resource.LOAD_TYPE.XHR, 16 | // xhrType: Resource.XHR_RESPONSE_TYPE.BLOB, 17 | // }, 18 | ]; 19 | 20 | /* 21 | assets.resources.photo.loading.then(res => { 22 | console.log(res.meta.data); 23 | }); 24 | */ 25 | 26 | class Assets { 27 | constructor() { 28 | this.resources = {}; 29 | 30 | RESOURCES.forEach(entry => { 31 | this.resources[entry.name] = entry; 32 | this.resources[entry.name].loading = deferred(); 33 | }); 34 | } 35 | 36 | load() { 37 | this.deferred = deferred(); 38 | this.loader = new Loader(); 39 | 40 | bidello.trigger({ name: 'loadStart' }); 41 | 42 | RESOURCES.forEach(res => { 43 | this.loader.add(res); 44 | }); 45 | 46 | this.loader.onProgress.add(this.onProgress.bind(this)); 47 | this.loader.load(this.finish.bind(this)); 48 | 49 | return deferred; 50 | } 51 | 52 | onProgress(loader, meta) { 53 | bidello.trigger({ name: 'loadProgress' }, { progress: this.loader.progress }); 54 | const res = this.resources[meta.name]; 55 | res.meta = meta; 56 | res.loading.resolve(res); 57 | } 58 | 59 | finish() { 60 | this.deferred.resolve(); 61 | bidello.trigger({ name: 'loadEnd' }, { resources: this.resources }); 62 | } 63 | } 64 | 65 | export default new Assets(); 66 | -------------------------------------------------------------------------------- /src/js/camera.js: -------------------------------------------------------------------------------- 1 | import { PerspectiveCamera, Vector3 } from 'three'; 2 | import { component } from 'bidello'; 3 | import OrbitControls from 'orbit-controls-es6'; 4 | import renderer from './renderer'; 5 | import pointer from './utils/pointer'; 6 | 7 | import { map, lerp } from 'math-toolbox'; 8 | 9 | class Camera extends component(PerspectiveCamera) { 10 | constructor() { 11 | super(35, 0, 0.1, 1000); 12 | 13 | this.position.set(0, 0, 80); 14 | this.lookAt(new Vector3(0, 0, 0)); 15 | 16 | // this.targetRot = new Vector3().copy(this.rotation); 17 | } 18 | 19 | initOrbitControl() { 20 | const controls = new OrbitControls(this, renderer.domElement); 21 | 22 | controls.enabled = true; 23 | controls.maxDistance = 900; 24 | controls.minDistance = 30; 25 | } 26 | 27 | onResize({ ratio }) { 28 | this.aspect = ratio; 29 | this.updateProjectionMatrix(); 30 | } 31 | 32 | onRaf({ delta }) { 33 | const x = map(pointer.normalized.y, -1, 1, -0.2, 0.2); 34 | const y = map(pointer.normalized.x, -1, 1, 0.2, -0.2); 35 | 36 | this.rotation.x = lerp(this.rotation.x, x, .01) 37 | this.rotation.y = lerp(this.rotation.y, y, .01) 38 | } 39 | } 40 | 41 | export default new Camera(); -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | import * as helpers from 'bidello/helpers'; 2 | import pointer from './utils/pointer'; 3 | 4 | import renderer from './renderer'; 5 | import camera from './camera'; 6 | import scene from './scene'; 7 | import { component } from 'bidello'; 8 | import settings from './settings'; 9 | import postfx from './postfx/postfx'; 10 | import assets from './assets'; 11 | 12 | class Site extends component() { 13 | init() { 14 | assets.load(); 15 | document.body.appendChild(renderer.domElement); 16 | } 17 | 18 | onRaf() { 19 | // renderer.render(scene, camera); 20 | postfx.render(scene, camera); 21 | } 22 | 23 | onLoadEnd() { 24 | console.log('finished loader!'); 25 | } 26 | } 27 | 28 | new Site(); -------------------------------------------------------------------------------- /src/js/postfx/postfx.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | uniform sampler2D uScene; 3 | uniform vec2 uResolution; 4 | 5 | #ifdef FXAA 6 | #pragma glslify: fxaa = require(glsl-fxaa) 7 | #endif 8 | 9 | float gamma = 2.2; 10 | 11 | vec3 linearToneMapping(vec3 color) 12 | { 13 | float exposure = 1.; 14 | color = clamp(exposure * color, 0., 1.); 15 | color = pow(color, vec3(1. / gamma)); 16 | return color; 17 | } 18 | 19 | vec3 simpleReinhardToneMapping(vec3 color) 20 | { 21 | float exposure = 1.5; 22 | color *= exposure/(1. + color / exposure); 23 | color = pow(color, vec3(1. / gamma)); 24 | return color; 25 | } 26 | 27 | vec3 lumaBasedReinhardToneMapping(vec3 color) 28 | { 29 | float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); 30 | float toneMappedLuma = luma / (1. + luma); 31 | color *= toneMappedLuma / luma; 32 | color = pow(color, vec3(1. / gamma)); 33 | return color; 34 | } 35 | 36 | vec3 whitePreservingLumaBasedReinhardToneMapping(vec3 color) 37 | { 38 | float white = 2.; 39 | float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); 40 | float toneMappedLuma = luma * (1. + luma / (white*white)) / (1. + luma); 41 | color *= toneMappedLuma / luma; 42 | color = pow(color, vec3(1. / gamma)); 43 | return color; 44 | } 45 | 46 | vec3 RomBinDaHouseToneMapping(vec3 color) 47 | { 48 | color = exp( -1.0 / ( 2.72*color + 0.15 ) ); 49 | color = pow(color, vec3(1. / gamma)); 50 | return color; 51 | } 52 | 53 | vec3 filmicToneMapping(vec3 color) 54 | { 55 | color = max(vec3(0.), color - vec3(0.004)); 56 | color = (color * (6.2 * color + .5)) / (color * (6.2 * color + 1.7) + 0.06); 57 | return color; 58 | } 59 | 60 | vec3 Uncharted2ToneMapping(vec3 color) 61 | { 62 | float A = 0.15; 63 | float B = 0.50; 64 | float C = 0.10; 65 | float D = 0.20; 66 | float E = 0.02; 67 | float F = 0.30; 68 | float W = 11.2; 69 | float exposure = 2.; 70 | color *= exposure; 71 | color = ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F; 72 | float white = ((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F; 73 | color /= white; 74 | color = pow(color, vec3(1. / gamma)); 75 | return color; 76 | } 77 | 78 | 79 | void main() { 80 | #ifdef FXAA 81 | vec3 color = fxaa(uScene, gl_FragCoord.xy, uResolution).rgb; 82 | #else 83 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 84 | vec3 color = texture2D(uScene, uv).rgb; 85 | #endif 86 | 87 | color = linearToneMapping(color); 88 | 89 | gl_FragColor = vec4(color, 1.0); 90 | } -------------------------------------------------------------------------------- /src/js/postfx/postfx.js: -------------------------------------------------------------------------------- 1 | import { 2 | WebGLRenderTarget, 3 | Camera, 4 | RGBFormat, 5 | BufferGeometry, 6 | BufferAttribute, 7 | Mesh, 8 | Scene, 9 | RawShaderMaterial, 10 | Vector2, 11 | } from 'three'; 12 | 13 | import renderer from '../renderer'; 14 | import settings from '../settings'; 15 | import { component } from 'bidello'; 16 | import vertexShader from './postfx.vert'; 17 | import fragmentShader from './postfx.frag'; 18 | 19 | class PostFX extends component() { 20 | init() { 21 | this.renderer = renderer; 22 | this.scene = new Scene(); 23 | this.dummyCamera = new Camera(); 24 | this.geometry = new BufferGeometry(); 25 | 26 | const vertices = new Float32Array([ 27 | -1.0, -1.0, 28 | 3.0, -1.0, 29 | -1.0, 3.0 30 | ]); 31 | 32 | this.geometry.addAttribute('position', new BufferAttribute(vertices, 2)); 33 | this.resolution = new Vector2(); 34 | this.renderer.getDrawingBufferSize(this.resolution); 35 | 36 | this.target = new WebGLRenderTarget(this.resolution.x, this.resolution.y, { 37 | format: RGBFormat, 38 | stencilBuffer: false, 39 | depthBuffer: true, 40 | }); 41 | 42 | const defines = {}; 43 | 44 | settings.fxaa && (defines.FXAA = true); 45 | 46 | this.material = new RawShaderMaterial({ 47 | defines, 48 | fragmentShader, 49 | vertexShader, 50 | uniforms: { 51 | uScene: { value: this.target.texture }, 52 | uResolution: { value: this.resolution }, 53 | }, 54 | }); 55 | 56 | this.triangle = new Mesh(this.geometry, this.material); 57 | this.triangle.frustumCulled = false; 58 | this.scene.add(this.triangle); 59 | } 60 | 61 | onResize() { 62 | this.renderer.getDrawingBufferSize(this.resolution); 63 | this.target.setSize(this.resolution.x, this.resolution.y); 64 | } 65 | 66 | render(scene, camera) { 67 | this.renderer.setRenderTarget(this.target); 68 | this.renderer.render(scene, camera); 69 | this.renderer.setRenderTarget(null); 70 | this.renderer.render(this.scene, this.dummyCamera); 71 | } 72 | } 73 | 74 | export default new PostFX(); -------------------------------------------------------------------------------- /src/js/postfx/postfx.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | attribute vec2 position; 3 | 4 | void main() { 5 | gl_Position = vec4(position, 1.0, 1.0); 6 | } -------------------------------------------------------------------------------- /src/js/renderer.js: -------------------------------------------------------------------------------- 1 | import { WebGLRenderer } from 'three'; 2 | import { component } from 'bidello'; 3 | import settings from './settings'; 4 | 5 | class Renderer extends component(WebGLRenderer) { 6 | constructor() { 7 | super({ 8 | powerPreference: 'high-performance', 9 | antialiasing: false, 10 | }) 11 | 12 | this.setPixelRatio(settings.dpr); 13 | } 14 | 15 | onResize({ width, height }) { 16 | this.setSize(width, height); 17 | } 18 | } 19 | 20 | export default new Renderer(); 21 | -------------------------------------------------------------------------------- /src/js/scene.js: -------------------------------------------------------------------------------- 1 | // import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; 2 | import { Scene } from 'three'; 3 | import { component } from 'bidello'; 4 | import Tube from './tube/tube'; 5 | 6 | class Stage extends component(Scene) { 7 | init() { 8 | this.add(new Tube()); 9 | } 10 | } 11 | 12 | export default new Stage(); -------------------------------------------------------------------------------- /src/js/settings.js: -------------------------------------------------------------------------------- 1 | import { getGPUTier } from 'detect-gpu'; 2 | 3 | const tier = getGPUTier({ 4 | mobileBenchmarkPercentages: [10, 40, 30, 20], // (Default) [TIER_0, TIER_1, TIER_2, TIER_3] 5 | desktopBenchmarkPercentages: [10, 40, 30, 20], // (Default) [TIER_0, TIER_1, TIER_2, TIER_3] 6 | // forceRendererString: 'Apple A11 GPU', // (Development) Force a certain renderer string 7 | // forceMobile: true, // (Development) Force the use of mobile benchmarking scores 8 | }); 9 | 10 | // const dpr = Math.min(1.5, window.devicePixelRatio || 1); 11 | const dpr = Math.min(1.5, window.devicePixelRatio || 1); 12 | 13 | const settings = { 14 | tier, 15 | dpr, 16 | fxaa: dpr <= 1, 17 | }; 18 | 19 | console.log(`⚙️ settings`, settings); 20 | 21 | export default settings; 22 | -------------------------------------------------------------------------------- /src/js/tube/position.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D texture; 4 | uniform sampler2D velocity; 5 | uniform float uTime; 6 | uniform float uMouseAngle; 7 | uniform vec3 uMousePos; 8 | 9 | // float qinticOut(float t) { 10 | // return 1.0 - (pow(t - 1.0, 5.0)); 11 | // } 12 | 13 | // https://gist.github.com/yiwenl/3f804e80d0930e34a0b33359259b556c 14 | mat4 rotationMatrix(vec3 axis, float angle) { 15 | axis = normalize(axis); 16 | float s = sin(angle); 17 | float c = cos(angle); 18 | float oc = 1.0 - c; 19 | 20 | return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, 21 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, 22 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 23 | 0.0, 0.0, 0.0, 1.0); 24 | } 25 | 26 | vec3 rotate(vec3 v, vec3 axis, float angle) { 27 | mat4 m = rotationMatrix(axis, angle); 28 | return (m * vec4(v, 1.0)).xyz; 29 | } 30 | 31 | 32 | float rand(vec2 co){ 33 | return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); 34 | } 35 | 36 | void main() { 37 | float pixelWidth = 1.0 / RESOLUTION.x; 38 | vec2 uv = gl_FragCoord.xy / RESOLUTION.xy; 39 | vec4 oldValues = texture2D(texture, uv); 40 | 41 | vec4 head = texture2D(velocity, vec2(0.0, uv.y)); 42 | 43 | if (uv.x <= pixelWidth) { 44 | // vec4 velocityData = texture2D(velocity, uv); 45 | // float speed = clamp(smoothstep(0.2, 0.5, velocityData.a), 0.0, 1.0); 46 | 47 | if (head.a >= 1.0) { 48 | // float speed = 1.0; 49 | 50 | // speed += 1.0 - smoothstep(0.0, 50.0, head.a) * 4.0; 51 | oldValues.xyz += head.xyz;// * speed; 52 | } 53 | 54 | // if (velocityData.a > RESOLUTION.x * 2.0) { 55 | // // oldValues.xyz = vec3(0.0); 56 | // } else { 57 | // // oldValues.xyz = vec3(0.0); 58 | // } 59 | 60 | } else { 61 | vec3 toFollow = texture2D(texture, uv - vec2(pixelWidth, 0.0)).xyz; 62 | 63 | // length of tube 64 | // float speed = .8 * uv.y; 65 | // float t = .1 + speed; 66 | 67 | float t = 0.2; 68 | 69 | // float time = (sin(uTime + uv.y * 40.0) + 1.0) / 2.0; 70 | 71 | // t += qinticOut(1.0 - uv.x) * 0.8; 72 | 73 | oldValues.xyz = mix(oldValues.xyz, toFollow, t); 74 | } 75 | 76 | if (head.a <= 0.0) { 77 | // oldValues.xyz = vec3(0.0); 78 | oldValues.xyz = uMousePos - vec3(0.0, 15.0, 0.0); 79 | oldValues.y += uv.x * 20.0; 80 | 81 | oldValues.x += rand(vec2(uv.y, uTime)) * 10.0; 82 | oldValues.y += rand(vec2(uv.y * 321.3, uTime * 0.2)) * 10.0; 83 | oldValues.z += rand(vec2(uMousePos + uTime)); 84 | 85 | // vec3 pivot = vec3(0.0, -10.0, 0.0); 86 | // oldValues.xyz -= pivot; 87 | oldValues.xyz -= uMousePos; 88 | oldValues.xyz = rotate(oldValues.xyz, vec3(0.0, 0.0, 1.0), -(3.14 / 2.0) + uMouseAngle); 89 | oldValues.xyz += uMousePos; 90 | // oldValues.xyz += pivot; 91 | } 92 | 93 | // oldValues.y += .01; 94 | 95 | oldValues.a = head.a; 96 | 97 | gl_FragColor = oldValues; 98 | } -------------------------------------------------------------------------------- /src/js/tube/tube.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D uMatcap; 4 | //uniform vec3 cameraPosition; 5 | uniform vec3 uMousePos; 6 | 7 | uniform vec3 color;// ms({ value: '#ff0000' }) 8 | varying vec3 vNormal; 9 | varying vec3 vViewPosition; 10 | varying float vAo; 11 | varying float vProgress; 12 | varying vec3 wPos; 13 | varying float vLife; 14 | 15 | vec4 sRGBToLinear( in vec4 value ) { 16 | return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a ); 17 | } 18 | // https://github.com/hughsk/glsl-fog/blob/master/exp.glsl 19 | float fogFactorExp( 20 | const float dist, 21 | const float density 22 | ) { 23 | return 1.0 - clamp(exp(-density * dist), 0.0, 1.0); 24 | } 25 | 26 | float fogFactorExp2( 27 | const float dist, 28 | const float density 29 | ) { 30 | const float LOG2 = -1.442695; 31 | float d = density * dist; 32 | return 1.0 - clamp(exp2(d * d * LOG2), 0.0, 1.0); 33 | } 34 | 35 | void main(){ 36 | 37 | vec3 normal = normalize(vNormal); 38 | 39 | 40 | // if (vProgress >= 0.75) { 41 | // normal = vec3(0.0, 1.0, 0.0); 42 | // // vec3 vPos = vViewPosition; 43 | // // vec3 fdx = vec3( dFdx( vPos.x ), dFdx( vPos.y ), dFdx( vPos.z ) ); 44 | // // vec3 fdy = vec3( dFdy( vPos.x ), dFdy( vPos.y ), dFdy( vPos.z ) ); 45 | // // normal = normalize( cross( fdx, fdy ) ); 46 | // } 47 | 48 | vec3 viewDir = normalize( vViewPosition ); 49 | vec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) ); 50 | vec3 y = cross( viewDir, x ); 51 | vec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5; 52 | 53 | vec4 color = sRGBToLinear(texture2D(uMatcap, uv)); 54 | 55 | // fake "AO" 56 | float ao = pow(1.0 - vAo, 2.0); 57 | color.rgb = mix(color.rgb * 0.2, color.rgb, ao); 58 | 59 | // SSS - https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/ 60 | 61 | // vec3 lightPos = vec3(0.0, 4.0, 0.0); 62 | vec3 lightPos = uMousePos; 63 | // lightPos.z -= 40.0; 64 | 65 | vec3 ssLight = vec3(0.7, 0.2, 0.2) * 0.07; 66 | vec3 fLTThickness = ssLight * pow(1.0 - vAo, 6.0); 67 | float fLTScale = 20.6; 68 | float fLTDistortion = 0.18; 69 | float fLTAmbient = 0.0; 70 | float iLTPower = 40.0; 71 | float dist = distance(lightPos, wPos); 72 | float fLightAttenuation = (1.0 - clamp(pow(dist / 20.0, 4.0), 0.0, 1.0)); 73 | vec3 vLight = normalize(-vViewPosition - lightPos); 74 | vec3 vLTLight = normalize(vLight + (normal * fLTDistortion)); 75 | float fLTDot = pow(clamp(dot(viewDir, -vLTLight), 0.0, 1.0), iLTPower) * fLTScale; 76 | vec3 fLT = fLightAttenuation * (fLTDot + fLTAmbient) * fLTThickness; 77 | color.rgb += fLT; 78 | 79 | // Fog 80 | color.rgb = mix(color.rgb, color.rgb * 0.1, smoothstep(0.0, 150.0, vViewPosition.z)); 81 | 82 | 83 | gl_FragColor = color; 84 | } -------------------------------------------------------------------------------- /src/js/tube/tube.js: -------------------------------------------------------------------------------- 1 | import { component } from 'bidello'; 2 | 3 | import { 4 | Object3D, 5 | CylinderBufferGeometry, 6 | Mesh, 7 | DoubleSide, 8 | Vector4, 9 | BufferAttribute, 10 | Vector2, 11 | InstancedBufferGeometry, 12 | InstancedBufferAttribute, 13 | Texture, 14 | RepeatWrapping, 15 | MeshBasicMaterial, 16 | Vector3, 17 | AdditiveBlending, 18 | } from 'three'; 19 | 20 | import { 21 | lerp 22 | } from 'math-toolbox'; 23 | 24 | import MagicShader, { gui } from 'magicshader'; 25 | import FBO from '../utils/fbo'; 26 | import assets from '../assets'; 27 | import pointer from '../utils/pointer'; 28 | 29 | export default class extends component(Object3D) { 30 | init() { 31 | const LIFE = 1500; 32 | const INSTANCES = 100; 33 | const WIDTH = 64; 34 | const HEIGHT = INSTANCES; 35 | 36 | gui.destroy(); 37 | 38 | this.mousePos = new Vector3(); 39 | this.oldMousePos = new Vector2(); 40 | this.angle = 0; 41 | this.stop = false; 42 | 43 | const velocityData = new Float32Array(1 * HEIGHT * 4); 44 | 45 | for (let i = 0; i < velocityData.length; i += 4) { 46 | // velocityData[i + 3] = i / velocityData.length; // take the alpha part 47 | velocityData[i + 3] = (i / velocityData.length) * LIFE; // take the alpha part 48 | } 49 | 50 | this.velocity = new FBO({ 51 | data: velocityData, 52 | width: 1, // Only the head needs velocity 53 | height: HEIGHT, 54 | name: 'velocity', 55 | shader: require('./velocity.frag'), 56 | uniforms: { 57 | uTime: { value: 0 }, 58 | uLife: { value: LIFE } 59 | // uMousePos: { value: this.mousePos } 60 | }, 61 | }); 62 | 63 | this.curvepos = new FBO({ 64 | width: WIDTH, 65 | height: HEIGHT, 66 | name: 'position', 67 | shader: require('./position.frag'), 68 | uniforms: { 69 | uTime: { value: 0 }, 70 | velocity: { value: this.velocity.target }, 71 | uMousePos: { value: this.mousePos }, 72 | uMouseAngle: { value: this.angle }, 73 | }, 74 | }); 75 | 76 | this.velocity.material.gui.add(this, 'stop'); 77 | 78 | this.velocity.uniforms.uPosition = { 79 | value: this.curvepos.target 80 | }; 81 | 82 | // this.curvepos.update(); 83 | 84 | const radialSegment = 20;// 50; 85 | const heightSegment = 150;// 50; 86 | 87 | const cylinder = new CylinderBufferGeometry(1, 1, 1, radialSegment, heightSegment, false); 88 | cylinder.rotateZ(Math.PI / 2); 89 | 90 | this.geometry = new InstancedBufferGeometry().copy(cylinder); 91 | 92 | // const pp = this.geometry.attributes.position.array; 93 | // let min = undefined; 94 | // let max = undefined; 95 | // for (let i = 0; i < pp.length; i+=4) { 96 | // let x = pp[i + 0] 97 | // let y = pp[i + 1] 98 | // let z = pp[i + 2] 99 | 100 | // let t = y; 101 | 102 | // if (t < min || typeof min === 'undefined') { 103 | // min = t; 104 | // } 105 | 106 | // if (t > max || typeof max === 'undefined') { 107 | // max = t; 108 | // } 109 | // } 110 | 111 | // console.log('min ', min); 112 | // console.log('max ', max); 113 | 114 | const tmp = new Vector2(); 115 | const angles = []; 116 | const indexes = []; 117 | const dat = this.geometry.attributes.position.array; 118 | 119 | for (let i = 0; i < this.geometry.attributes.position.count; i++) { 120 | const index = i * 3; 121 | const x = dat[index + 0]; // x 122 | const y = dat[index + 1]; // y 123 | const z = dat[index + 2]; // z 124 | 125 | tmp.set(y, z).normalize(); 126 | angles.push(Math.atan2(tmp.y, tmp.x)); 127 | } 128 | 129 | for (let i = 0; i < INSTANCES; i++) { 130 | indexes.push(i / INSTANCES); 131 | } 132 | 133 | this.geometry.addAttribute('aAngle', new BufferAttribute(new Float32Array(angles), 1)); 134 | this.geometry.addAttribute('aIndex', new InstancedBufferAttribute(new Float32Array(indexes), 1)); 135 | 136 | this.matcap = new Texture(); 137 | 138 | assets.resources.matcap.loading.then(res => { 139 | const tex = new Texture(res.meta.data, RepeatWrapping, RepeatWrapping); 140 | tex.needsUpdate = true; 141 | this.material.uniforms.uMatcap.value = tex; 142 | }); 143 | 144 | this.material = new MagicShader({ 145 | // wireframe: true, 146 | transparent: true, 147 | name: 'Tube', 148 | extensions: { 149 | derivatives: true, 150 | }, 151 | defines: { 152 | RESOLUTION: `vec2(${WIDTH.toFixed(1)}, ${HEIGHT.toFixed(1)})`, 153 | LIFE: LIFE.toFixed(1), 154 | }, 155 | uniforms: { 156 | uData: { value: this.curvepos.target }, 157 | uMatcap: { value: this.matcap }, 158 | uMousePos: { value: this.mousePos }, 159 | uMouseAngle: { value: this.angle }, 160 | }, 161 | // side: DoubleSide, 162 | vertexShader: require('./tube.vert'), 163 | fragmentShader: require('./tube.frag') 164 | }); 165 | 166 | this.mesh = new Mesh(this.geometry, this.material); 167 | this.mesh.frustumCulled = false; 168 | 169 | this.add(this.mesh); 170 | } 171 | 172 | stop() { 173 | this.stop = !this.stop; 174 | } 175 | 176 | // onPointerMove({ pointer }) { 177 | 178 | // } 179 | 180 | onRaf({ delta }) { 181 | this.mousePos.lerp(pointer.world, .1); 182 | 183 | if (pointer.y !== this.oldMousePos.y && pointer.x !== this.oldMousePos.x) { 184 | this.angle = Math.atan2(pointer.y - this.oldMousePos.y, pointer.x - this.oldMousePos.x); 185 | } 186 | 187 | this.curvepos.uniforms.uMouseAngle.value = lerp(this.curvepos.uniforms.uMouseAngle.value, this.angle, .1); 188 | this.material.uniforms.uMouseAngle.value = this.curvepos.uniforms.uMouseAngle.value; 189 | 190 | this.oldMousePos.set(pointer.x, pointer.y); 191 | 192 | if (this.stop) { 193 | return; 194 | } 195 | 196 | this.curvepos.uniforms.uTime.value += delta * 2; 197 | this.velocity.uniforms.uTime.value += delta * 2; 198 | 199 | this.velocity.uniforms.uPosition.value = this.curvepos.target; 200 | this.velocity.update(); 201 | 202 | this.curvepos.uniforms.velocity.value = this.velocity.target; 203 | this.curvepos.update(); 204 | } 205 | } -------------------------------------------------------------------------------- /src/js/tube/tube.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | attribute vec3 position; 3 | attribute vec2 uv; 4 | attribute vec3 normal; 5 | attribute float aAngle; 6 | attribute float aIndex; 7 | 8 | uniform mat4 modelMatrix; 9 | uniform mat4 viewMatrix; 10 | uniform vec3 cameraPosition; 11 | uniform mat4 modelViewMatrix; 12 | uniform mat4 projectionMatrix; 13 | uniform mat3 normalMatrix; 14 | uniform sampler2D uData; 15 | uniform float uMouseAngle; 16 | 17 | varying float vProgress; 18 | varying vec3 vNormal; 19 | varying vec3 vViewPosition; 20 | varying vec3 wPos; 21 | varying float vAo; 22 | varying float vLife; 23 | 24 | const float pixelWidth = 1.0 / (RESOLUTION.x); 25 | 26 | float qinticOut(float t) { 27 | return 1.0 - (pow(t - 1.0, 5.0)); 28 | } 29 | 30 | float cubicOut(float t) { 31 | float f = t - 1.0; 32 | return f * f * f + 1.0; 33 | } 34 | 35 | float map(float value, float min1, float max1, float min2, float max2) { 36 | return min2 + (value - min1) * (max2 - min2) / (max1 - min1); 37 | } 38 | 39 | void main(){ 40 | float progress = 1.0 - (position.x + 1.0) / 2.0; 41 | vProgress = progress; 42 | 43 | // https://mattdesl.svbtle.com/shaping-curves-with-parametric-equations 44 | 45 | vec4 data = texture2D(uData, vec2(progress, aIndex)); 46 | vLife = data.a; 47 | 48 | vec2 volume = vec2(0.8); 49 | // float volume = 0.8; 50 | 51 | volume *= cubicOut(smoothstep(50.0, 200.0, vLife)); 52 | volume *= cubicOut(clamp(smoothstep(LIFE, LIFE - 100.0, vLife), 0.0, 1.0)); 53 | 54 | if (vLife <= 0.0) { 55 | volume = vec2(0.0); 56 | } 57 | 58 | vec3 cur = data.xyz; 59 | vec3 next = texture2D(uData, vec2(progress - pixelWidth, aIndex)).xyz; 60 | vec3 next2 = texture2D(uData, vec2(progress - pixelWidth * 2.0, aIndex)).xyz; 61 | 62 | float introMul = cubicOut(clamp(smoothstep(LIFE, LIFE - 100.0, vLife), 0.0, 1.0)); 63 | 64 | float val = map(introMul, 0.0, 1.0, 0.88, 1.0); 65 | 66 | cur *= val; 67 | next *= val; 68 | next2 *= val; 69 | 70 | // compute the Frenet-Serret frame 71 | vec3 T = normalize(next - cur); 72 | vec3 B = normalize(cross(T, next + cur)); 73 | vec3 N = -normalize(cross(B, T)); 74 | 75 | // extrude outward to create a tube 76 | float tubeAngle = aAngle; 77 | float circX = cos(tubeAngle); 78 | float circY = sin(tubeAngle); 79 | 80 | vec3 calculatedNormal = normalize(B * circX + N * circY); 81 | vNormal = normalize(normalMatrix * calculatedNormal); 82 | 83 | // vec3 pos = cur + B * volume.x * circX + N * volume.y * circY; 84 | vec3 pos = cur + calculatedNormal * volume.x; 85 | 86 | // pos.xyz -= cur; 87 | // pos.yz *= cubicOut(clamp(smoothstep(LIFE, LIFE - 100.0, vLife), 0.0, 1.0)); 88 | // pos.xyz += cur; 89 | 90 | vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0); 91 | vViewPosition = - mvPosition.xyz; 92 | 93 | vAo = length(abs(cross( 94 | normalize(cur - next), 95 | normalize(next - next2) 96 | ))); 97 | 98 | if (position.x > 0.49) { 99 | vNormal = normalize(cur - next); 100 | } 101 | // } else if (vProgress == 1.0) { 102 | // vNormal = normalize(next - cur); 103 | // } 104 | 105 | wPos = (modelMatrix * vec4(pos, 1.0)).xyz; 106 | 107 | 108 | gl_Position = projectionMatrix * mvPosition; 109 | } -------------------------------------------------------------------------------- /src/js/tube/velocity.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D texture; 4 | uniform sampler2D uPosition; 5 | uniform float uTime; 6 | uniform float uLife; 7 | 8 | const int OCTAVES = 2; 9 | 10 | vec4 mod289(vec4 x) { 11 | return x - floor(x * (1.0 / 289.0)) * 289.0; 12 | } 13 | float mod289(float x) { 14 | return x - floor(x * (1.0 / 289.0)) * 289.0; 15 | } 16 | vec4 permute(vec4 x) { 17 | return mod289(((x*34.0)+1.0)*x); 18 | } 19 | float permute(float x) { 20 | return mod289(((x*34.0)+1.0)*x); 21 | } 22 | vec4 taylorInvSqrt(vec4 r) { 23 | return 1.79284291400159 - 0.85373472095314 * r; 24 | } 25 | float taylorInvSqrt(float r) { 26 | return 1.79284291400159 - 0.85373472095314 * r; 27 | } 28 | vec4 grad4(float j, vec4 ip) { 29 | const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); 30 | vec4 p, s; 31 | p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; 32 | p.w = 1.5 - dot(abs(p.xyz), ones.xyz); 33 | s = vec4(lessThan(p, vec4(0.0))); 34 | p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www; 35 | return p; 36 | } 37 | #define F4 0.309016994374947451 38 | 39 | vec4 simplexNoiseDerivatives (vec4 v) { 40 | const vec4 C = vec4( 0.138196601125011, 0.276393202250021, 0.414589803375032, -0.447213595499958); 41 | vec4 i = floor(v + dot(v, vec4(F4)) ); 42 | vec4 x0 = v - i + dot(i, C.xxxx); 43 | vec4 i0; 44 | vec3 isX = step( x0.yzw, x0.xxx ); 45 | vec3 isYZ = step( x0.zww, x0.yyz ); 46 | i0.x = isX.x + isX.y + isX.z; 47 | i0.yzw = 1.0 - isX; 48 | i0.y += isYZ.x + isYZ.y; 49 | i0.zw += 1.0 - isYZ.xy; 50 | i0.z += isYZ.z; 51 | i0.w += 1.0 - isYZ.z; 52 | vec4 i3 = clamp( i0, 0.0, 1.0 ); 53 | vec4 i2 = clamp( i0-1.0, 0.0, 1.0 ); 54 | vec4 i1 = clamp( i0-2.0, 0.0, 1.0 ); 55 | vec4 x1 = x0 - i1 + C.xxxx; 56 | vec4 x2 = x0 - i2 + C.yyyy; 57 | vec4 x3 = x0 - i3 + C.zzzz; 58 | vec4 x4 = x0 + C.wwww; 59 | i = mod289(i); 60 | float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x); 61 | vec4 j1 = permute( permute( permute( permute ( 62 | i.w + vec4(i1.w, i2.w, i3.w, 1.0 )) 63 | + i.z + vec4(i1.z, i2.z, i3.z, 1.0 )) 64 | + i.y + vec4(i1.y, i2.y, i3.y, 1.0 )) 65 | + i.x + vec4(i1.x, i2.x, i3.x, 1.0 )); 66 | vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ; 67 | vec4 p0 = grad4(j0, ip); 68 | vec4 p1 = grad4(j1.x, ip); 69 | vec4 p2 = grad4(j1.y, ip); 70 | vec4 p3 = grad4(j1.z, ip); 71 | vec4 p4 = grad4(j1.w, ip); 72 | vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); 73 | p0 *= norm.x; 74 | p1 *= norm.y; 75 | p2 *= norm.z; 76 | p3 *= norm.w; 77 | p4 *= taylorInvSqrt(dot(p4, p4)); 78 | vec3 values0 = vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2)); //value of contributions from each corner at point 79 | 80 | vec2 values1 = vec2(dot(p3, x3), dot(p4, x4)); 81 | vec3 m0 = max(0.5 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0); //(0.5 - x^2) where x is the distance 82 | 83 | vec2 m1 = max(0.5 - vec2(dot(x3, x3), dot(x4, x4)), 0.0); 84 | vec3 temp0 = -6.0 * m0 * m0 * values0; 85 | vec2 temp1 = -6.0 * m1 * m1 * values1; 86 | vec3 mmm0 = m0 * m0 * m0; 87 | vec2 mmm1 = m1 * m1 * m1; 88 | float dx = temp0[0] * x0.x + temp0[1] * x1.x + temp0[2] * x2.x + temp1[0] * x3.x + temp1[1] * x4.x + mmm0[0] * p0.x + mmm0[1] * p1.x + mmm0[2] * p2.x + mmm1[0] * p3.x + mmm1[1] * p4.x; 89 | float dy = temp0[0] * x0.y + temp0[1] * x1.y + temp0[2] * x2.y + temp1[0] * x3.y + temp1[1] * x4.y + mmm0[0] * p0.y + mmm0[1] * p1.y + mmm0[2] * p2.y + mmm1[0] * p3.y + mmm1[1] * p4.y; 90 | float dz = temp0[0] * x0.z + temp0[1] * x1.z + temp0[2] * x2.z + temp1[0] * x3.z + temp1[1] * x4.z + mmm0[0] * p0.z + mmm0[1] * p1.z + mmm0[2] * p2.z + mmm1[0] * p3.z + mmm1[1] * p4.z; 91 | float dw = temp0[0] * x0.w + temp0[1] * x1.w + temp0[2] * x2.w + temp1[0] * x3.w + temp1[1] * x4.w + mmm0[0] * p0.w + mmm0[1] * p1.w + mmm0[2] * p2.w + mmm1[0] * p3.w + mmm1[1] * p4.w; 92 | return vec4(dx, dy, dz, dw) * 49.0; 93 | } 94 | 95 | vec3 curl(in vec3 p, in float noiseTime, in float persistence) { 96 | vec4 xNoisePotentialDerivatives = vec4(0.0); 97 | vec4 yNoisePotentialDerivatives = vec4(0.0); 98 | vec4 zNoisePotentialDerivatives = vec4(0.0); 99 | for(int i = 0; i < OCTAVES; ++i) { 100 | float twoPowI = pow(2.0, float(i)); 101 | float scale = 0.5 * twoPowI * pow(persistence, float(i)); 102 | 103 | xNoisePotentialDerivatives += simplexNoiseDerivatives(vec4(p * twoPowI, noiseTime)) * scale; 104 | yNoisePotentialDerivatives += simplexNoiseDerivatives(vec4((p + vec3(123.4, 129845.6, -1239.1)) * twoPowI, noiseTime)) * scale; 105 | zNoisePotentialDerivatives += simplexNoiseDerivatives(vec4((p + vec3(-9519.0, 9051.0, -123.0)) * twoPowI, noiseTime)) * scale; 106 | } 107 | return vec3( 108 | zNoisePotentialDerivatives[1] - yNoisePotentialDerivatives[2], xNoisePotentialDerivatives[2] - zNoisePotentialDerivatives[0], yNoisePotentialDerivatives[0] - xNoisePotentialDerivatives[1] 109 | ); 110 | } 111 | 112 | void main() { 113 | float pixelWidth = 1.0 / RESOLUTION.x; 114 | vec2 uv = gl_FragCoord.xy / RESOLUTION.xy; 115 | 116 | vec4 oldValue = texture2D(texture, uv); 117 | vec4 currentPosition = texture2D(uPosition, uv); 118 | vec4 newValue = oldValue; 119 | 120 | float curlSize = 0.001; 121 | float speed = 0.02; 122 | float spacing = uv.y * 20.0; 123 | 124 | newValue.xyz += curl(currentPosition.xyz * curlSize, uTime + spacing, 0.3) * speed; 125 | newValue.xyz = mix(newValue.xyz, -newValue.xyz, length(newValue.xyz) * 0.3); 126 | 127 | 128 | if (newValue.a <= 0.0) { 129 | newValue.a += uLife; 130 | } else { 131 | newValue.a -= 1.0; 132 | } 133 | 134 | gl_FragColor = newValue; 135 | } -------------------------------------------------------------------------------- /src/js/utils/deferred.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | let _resolve = undefined; 3 | let _reject = undefined; 4 | const promise = new Promise((resolve, reject) => { 5 | _resolve = resolve; 6 | _reject = reject; 7 | }); 8 | 9 | promise.resolve = _resolve; 10 | promise.reject = _reject; 11 | 12 | return promise; 13 | }; 14 | -------------------------------------------------------------------------------- /src/js/utils/fbo.js: -------------------------------------------------------------------------------- 1 | import { 2 | WebGLRenderTarget, 3 | NearestFilter, 4 | DataTexture, 5 | RGBAFormat, 6 | FloatType, 7 | HalfFloatType, 8 | Camera, 9 | BufferGeometry, 10 | BufferAttribute, 11 | Scene, 12 | Mesh, 13 | } from 'three'; 14 | 15 | import renderer from '../renderer'; 16 | import MagicShader from 'magicshader'; 17 | 18 | export const isAvailable = (() => { 19 | const gl = renderer.getContext(); 20 | 21 | if (!gl.getExtension('OES_texture_float')) { 22 | return false; 23 | } 24 | 25 | if (gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) === 0) { 26 | return false; 27 | } 28 | 29 | return true; 30 | })(); 31 | 32 | const iOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent); 33 | const type = iOS ? HalfFloatType : FloatType; 34 | 35 | export default class FBO { 36 | constructor({ 37 | width, 38 | height, 39 | data, 40 | name, 41 | shader, 42 | uniforms = {} 43 | }) { 44 | this.options = arguments[0]; 45 | 46 | const vertices = new Float32Array([ 47 | -1.0, -1.0, 48 | 3.0, -1.0, 49 | -1.0, 3.0 50 | ]); 51 | 52 | this.renderer = renderer; 53 | this.camera = new Camera(); 54 | this.scene = new Scene(); 55 | this.index = 0; 56 | this.copyData = true; 57 | this.texture = new DataTexture( 58 | data || new Float32Array((width * height * 4)), 59 | width, 60 | height, 61 | RGBAFormat, 62 | FloatType, 63 | // type, 64 | ); 65 | this.texture.needsUpdate = true; 66 | 67 | this.rt = [this.createRT(), this.createRT()]; 68 | 69 | this.geometry = new BufferGeometry(); 70 | this.geometry.addAttribute('position', new BufferAttribute(vertices, 2)); 71 | 72 | this.material = new MagicShader({ 73 | name: name || 'FBO', 74 | defines: { 75 | RESOLUTION: `vec2(${width.toFixed(1)}, ${height.toFixed(1)})` 76 | }, 77 | uniforms: { 78 | ...uniforms, 79 | texture: { value: this.texture } 80 | }, 81 | vertexShader: ` 82 | precision highp float; 83 | attribute vec3 position; 84 | 85 | void main() { 86 | gl_Position = vec4(position, 1.0); 87 | } 88 | `, 89 | fragmentShader: shader || ` 90 | precision highp float; 91 | uniform sampler2D texture; 92 | void main() { 93 | vec2 uv = gl_FragCoord.xy / RESOLUTION.xy; 94 | gl_FragColor = texture2D(texture, uv); 95 | } 96 | `, 97 | }); 98 | 99 | this.mesh = new Mesh(this.geometry, this.material); 100 | this.mesh.frustumCulled = false; 101 | this.scene.add(this.mesh); 102 | } 103 | 104 | createRT() { 105 | return new WebGLRenderTarget(this.options.width, this.options.height, { 106 | minFilter: NearestFilter, 107 | magFilter: NearestFilter, 108 | stencilBuffer: false, 109 | depthBuffer: false, 110 | type, 111 | }); 112 | } 113 | 114 | get target() { 115 | return this.rt[this.index].texture; 116 | } 117 | 118 | get uniforms() { 119 | return this.material.uniforms; 120 | } 121 | 122 | update(switchBack = true) { 123 | const destIndex = this.index === 0 ? 1 : 0; 124 | const old = this.rt[this.index]; 125 | const dest = this.rt[destIndex]; 126 | 127 | this.material.uniforms.texture.value = this.copyData ? this.texture : old.texture; 128 | 129 | const oldMainTarget = this.renderer.getRenderTarget(); 130 | this.renderer.setRenderTarget(dest); 131 | this.renderer.render(this.scene, this.camera); 132 | switchBack && this.renderer.setRenderTarget(oldMainTarget); 133 | 134 | this.index = destIndex; 135 | this.copyData = false; 136 | } 137 | } -------------------------------------------------------------------------------- /src/js/utils/pointer.js: -------------------------------------------------------------------------------- 1 | import { 2 | Vector2, 3 | Vector3 4 | } from 'three'; 5 | 6 | import { clamp } from 'math-toolbox'; 7 | import bidello from 'bidello'; 8 | import { viewport } from 'bidello/helpers'; 9 | import camera from '../camera'; 10 | 11 | // TODO use pointer velocity/direction to push noise tubes. 12 | 13 | class Pointer { 14 | constructor() { 15 | this.x = 0; 16 | this.y = 0; 17 | this.isTouching = true; 18 | this.distance = 0; 19 | 20 | this.hold = new Vector2(); 21 | this.last = new Vector2(); 22 | this.delta = new Vector2(); 23 | this.move = new Vector2(); 24 | this.world = new Vector3(); 25 | this.normalized = new Vector2(); 26 | this._tmp = new Vector3(); 27 | 28 | this.bind(); 29 | } 30 | 31 | bind() { 32 | const container = window; 33 | 34 | container.addEventListener('touchstart', this.onStart.bind(this), { passive: false }); 35 | container.addEventListener('touchmove', this.onMove.bind(this), { passive: false }); 36 | container.addEventListener('touchend', this.onEnd.bind(this), { passive: false }); 37 | container.addEventListener('touchcancel', this.onEnd.bind(this), { passive: false }); 38 | 39 | container.addEventListener('mousedown', this.onStart.bind(this)); 40 | container.addEventListener('mousemove', this.onMove.bind(this)); 41 | container.addEventListener('mouseup', this.onEnd.bind(this)); 42 | container.addEventListener('contextmenu', this.onEnd.bind(this)); 43 | } 44 | 45 | convertEvent(e) { 46 | e.preventDefault(); 47 | e.stopPropagation(); 48 | 49 | const t = { 50 | x: 0, 51 | y: 0, 52 | }; 53 | 54 | if (!e) { 55 | return t; 56 | } 57 | 58 | if (e.windowsPointer) { 59 | return e; 60 | } 61 | 62 | if (e.touches || e.changedTouches) { 63 | if (e.touches.length) { 64 | t.x = e.touches[0].pageX; 65 | t.y = e.touches[0].pageY; 66 | } else { 67 | t.x = e.changedTouches[0].pageX; 68 | t.y = e.changedTouches[0].pageY; 69 | } 70 | } else { 71 | t.x = e.pageX; 72 | t.y = e.pageY; 73 | } 74 | 75 | t.x = clamp(0, viewport.width, t.x); 76 | t.y = clamp(0, viewport.height, t.y); 77 | 78 | return t; 79 | } 80 | 81 | onStart(event) { 82 | const e = this.convertEvent(event); 83 | 84 | this.isTouching = true; 85 | this.x = e.x; 86 | this.y = e.y; 87 | 88 | this.hold.set(e.x, e.y); 89 | this.last.set(e.x, e.y); 90 | this.delta.set(0, 0); 91 | this.move.set(0, 0); 92 | 93 | this.normalized.x = ((this.x / viewport.width) * 2) - 1; 94 | this.normalized.y = (-(this.y / viewport.height) * 2) + 1; 95 | this.distance = 0; 96 | 97 | bidello.trigger({ name: 'pointerStart' }, { 98 | pointer: this, 99 | }); 100 | } 101 | 102 | onMove(event) { 103 | const e = this.convertEvent(event); 104 | 105 | if (this.isTouching) { 106 | this.move.x = e.x - this.hold.x; 107 | this.move.y = e.y - this.hold.y; 108 | } 109 | 110 | // if (this.last.x !== e.x || this.last.y !== e.y) { 111 | // this.last.set(this.x, this.y); 112 | // } 113 | 114 | this.x = e.x; 115 | this.y = e.y; 116 | this.delta.x = e.x - this.last.x; 117 | this.delta.y = e.y - this.last.y; 118 | 119 | this.distance += this.delta.length(); 120 | 121 | this.normalized.x = ((this.x / viewport.width) * 2) - 1; 122 | this.normalized.y = (-(this.y / viewport.height) * 2) + 1; 123 | 124 | this._tmp.x = this.normalized.x; 125 | this._tmp.y = this.normalized.y; 126 | this._tmp.z = 0.5; 127 | this._tmp.unproject(camera); 128 | const dir = this._tmp.sub(camera.position).normalize(); 129 | const dist = -camera.position.z / dir.z; 130 | this.world.copy(camera.position).add(dir.multiplyScalar(dist)); 131 | 132 | bidello.trigger({ name: 'pointerMove' }, { 133 | pointer: this, 134 | }); 135 | 136 | if (this.isTouching) { 137 | bidello.trigger({ name: 'pointerDrag' }, { 138 | pointer: this, 139 | }); 140 | } 141 | } 142 | 143 | onEnd() { 144 | this.isTouching = false; 145 | this.move.set(0, 0); 146 | 147 | bidello.trigger({ name: 'pointerEnd' }, { 148 | pointer: this, 149 | }); 150 | } 151 | } 152 | 153 | export default new Pointer(); --------------------------------------------------------------------------------