├── .gitignore
├── .npmignore
├── LICENSE.md
├── README.md
├── assets
├── ballet.mp4
├── ballet2.mp4
└── ballet2.webm
├── index.html
├── index.js
├── index2.js
├── package.json
├── particles.js
└── src
├── audio.js
├── clear.js
├── engine.js
├── image.js
├── intro.js
├── shaders
├── debug.frag
├── logic.frag
├── mouse.glsl
├── particles.frag
├── particles.vert
├── pass.vert
├── quad.frag
├── quad.vert
└── size.glsl
├── three-video.js
├── three-viewer.js
└── video.js
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | *.log
4 | .DS_Store
5 | bundle.js
6 | assets/
7 | !assets/ballet.mp4
8 | !assets/ballet.webm
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | *.log
4 | .DS_Store
5 | bundle.js
6 | test
7 | test.js
8 | demo/
9 | .npmignore
10 | LICENSE.md
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2015 Matt DesLauriers
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
20 | OR OTHER DEALINGS IN THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # motion
2 |
3 | [](http://github.com/badges/stability-badges)
4 |
5 | experiments
6 |
7 | ## Usage
8 |
9 | [](https://www.npmjs.com/package/motion)
10 |
11 | ## License
12 |
13 | MIT, see [LICENSE.md](http://github.com/mattdesl/motion/blob/master/LICENSE.md) for details.
14 |
--------------------------------------------------------------------------------
/assets/ballet.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdesl/motion/fa81a9a51f948d60a37cf1d7f3000e0b2927ec26/assets/ballet.mp4
--------------------------------------------------------------------------------
/assets/ballet2.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdesl/motion/fa81a9a51f948d60a37cf1d7f3000e0b2927ec26/assets/ballet2.mp4
--------------------------------------------------------------------------------
/assets/ballet2.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdesl/motion/fa81a9a51f948d60a37cf1d7f3000e0b2927ec26/assets/ballet2.webm
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @mattdesl
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import fitter from 'canvas-fit'
2 | import createContext from 'webgl-context'
3 | import createLoop from 'raf-loop'
4 | import createEngine from './src/engine'
5 | import createVideo from './src/video'
6 | import createImage from './src/image'
7 | import createAudio from './src/audio'
8 | import renderIntro from './src/intro'
9 |
10 | import assign from 'object-assign'
11 | import colorStyle from 'color-style'
12 | import parallel from 'run-parallel'
13 |
14 | const Promise = global.Promise || require('es6-promise').Promise
15 | const promisify = require('es6-denodeify')(Promise)
16 |
17 | const clear = require('./src/clear')
18 | const background = clear.rgb.map(x => x*255)
19 |
20 | const gl = createContext({
21 | alpha: false, antialias: false
22 | })
23 | const canvas = gl.canvas
24 |
25 | const fit = fitter(canvas, () => {
26 | return [ window.innerWidth, window.innerHeight ]
27 | .map(x => Math.min(x, 500))
28 | }, 1)
29 |
30 | const engine = createEngine(gl)
31 | const resolution = [ 1, 1 ]
32 | const deviceResolution = [ 1, 1 ]
33 |
34 | require('domready')(() => {
35 | window.addEventListener('touchstart', ev => {
36 | ev.preventDefault()
37 | })
38 |
39 | const resize = () => {
40 | fit()
41 |
42 | assign(canvas.style, {
43 | margin: 'auto',
44 | top: '0',
45 | left: '0',
46 | bottom: '0',
47 | right: '0',
48 | position: 'absolute'
49 | })
50 |
51 | const scale = fit.scale
52 | deviceResolution[0] = gl.drawingBufferWidth
53 | deviceResolution[1] = gl.drawingBufferHeight
54 | resolution[0] = deviceResolution[0] / scale
55 | resolution[1] = deviceResolution[1] / scale
56 | engine.resolution = resolution
57 | }
58 | window.addEventListener('resize', resize, false)
59 | resize()
60 |
61 | assign(document.body.style, {
62 | background: colorStyle(background),
63 | margin: '0'
64 | })
65 | document.body.appendChild(canvas)
66 | const loop = createLoop(draw)
67 | clear(gl)
68 |
69 | Promise.all([
70 | promisify(createAudio)(),
71 | promisify(createVideo.bind(null, gl))()
72 | ]).then(([audio, texture]) => {
73 | engine.motion = texture
74 | console.log("Playing")
75 |
76 | audio.play()
77 | renderIntro()
78 | setTimeout(function() {
79 | texture.video.play()
80 | }, 2000)
81 |
82 | loop.on('tick', texture.update.bind(texture))
83 | loop.start()
84 | })
85 | })
86 |
87 |
88 | function draw(dt) {
89 | gl.viewport(0, 0, deviceResolution[0], deviceResolution[1])
90 |
91 | gl.disable(gl.DEPTH_TEST)
92 | gl.disable(gl.CULL_FACE)
93 | gl.disable(gl.BLEND)
94 | engine(dt)
95 | }
--------------------------------------------------------------------------------
/index2.js:
--------------------------------------------------------------------------------
1 | import THREE from 'three'
2 | import domready from 'domready'
3 | import viewer from './src/viewer'
4 | import assign from 'object-assign'
5 |
6 | import createVideo from './src/video'
7 |
8 | domready(() => {
9 | const app = viewer({
10 | alpha: false,
11 | preserveDrawingBuffer: false,
12 | antialias: true
13 | })
14 | document.body.appendChild(app.canvas)
15 | assign(document.body.style, {
16 | background: '#000',
17 | overflow: 'hidden'
18 | })
19 |
20 | const video = createVideo(app)
21 |
22 | app.start()
23 | })
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "motion",
3 | "version": "1.0.0",
4 | "description": "experiments",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "author": {
8 | "name": "Matt DesLauriers",
9 | "email": "dave.des@gmail.com",
10 | "url": "https://github.com/mattdesl"
11 | },
12 | "dependencies": {
13 | "a-big-triangle": "^1.0.0",
14 | "async-each": "^0.1.6",
15 | "canvas-fit": "^1.4.0",
16 | "color-style": "^1.0.0",
17 | "dom-events": "^0.1.1",
18 | "domify": "^1.3.3",
19 | "domready": "^1.0.7",
20 | "es6-denodeify": "^0.1.1",
21 | "es6-promise": "^2.1.0",
22 | "gl-context": "^0.1.1",
23 | "gl-particles": "^1.1.0",
24 | "gl-shader": "^4.0.1",
25 | "gl-texture2d": "^2.0.8",
26 | "gl-texture2d-pixels": "^1.0.2",
27 | "gl-vec2": "^1.0.0",
28 | "glsl-hash-blur": "^1.0.2",
29 | "glsl-hsl2rgb": "^1.1.0",
30 | "glsl-noise": "0.0.0",
31 | "glsl-random": "0.0.5",
32 | "glslify-hex": "^2.0.1",
33 | "img": "^1.0.0",
34 | "load-json-xhr": "^3.0.1",
35 | "object-assign": "^2.0.0",
36 | "once": "^1.3.1",
37 | "raf-loop": "^1.0.1",
38 | "run-parallel": "^1.1.0",
39 | "soundcloud-badge": "0.0.0",
40 | "three": "^0.70.0",
41 | "three-orbit-controls": "^69.0.4",
42 | "touch-position": "^1.0.3",
43 | "tweenr": "^2.1.3",
44 | "webgl-context": "^2.1.2",
45 | "xtend": "^4.0.0"
46 | },
47 | "devDependencies": {
48 | "babelify": "^6.0.2",
49 | "browserify": "^9.0.8",
50 | "budo": "^3.0.4",
51 | "errorify": "^0.2.4",
52 | "garnish": "^2.1.3",
53 | "glslify": "^2.1.2",
54 | "uglify": "^0.1.3",
55 | "uglify-js": "^2.4.20",
56 | "watchify": "^3.1.1"
57 | },
58 | "scripts": {
59 | "build": "browserify index.js -t babelify -t glslify | uglifyjs -cm > bundle.js",
60 | "start": "budo index.js:bundle.js --live -v -t babelify -t glslify -p errorify | garnish"
61 | },
62 | "keywords": [],
63 | "repository": {
64 | "type": "git",
65 | "url": "git://github.com/mattdesl/motion.git"
66 | },
67 | "homepage": "https://github.com/mattdesl/motion",
68 | "bugs": {
69 | "url": "https://github.com/mattdesl/motion/issues"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/particles.js:
--------------------------------------------------------------------------------
1 | const canvas = document.body.appendChild(document.createElement('canvas'))
2 | const gl = require('gl-context')(canvas, render)
3 | const fit = require('canvas-fit')
4 | const glslify = require('glslify')
5 | const Particles = require('gl-particles')
6 |
7 | const logics = [
8 | glslify(`
9 | precision mediump float;
10 | #define PI 3.14159265359
11 | uniform sampler2D data;
12 | uniform float time;
13 | uniform vec2 resolution;
14 | #pragma glslify: noise = require('glsl-noise/simplex/3d')
15 | void main() {
16 | vec2 uv = gl_FragCoord.xy / resolution;
17 | vec4 tData = texture2D(data, uv);
18 | vec2 position = tData.xy;
19 | vec2 speed = tData.zw;
20 | speed.x += noise(vec3(position * 2.125, uv.x + time)) * 0.000225;
21 | speed.y += noise(vec3(position * 2.125, uv.y + time + 1000.0)) * 0.000225;
22 | float r = length(position);
23 | float a;
24 | if (r > 0.001) {
25 | a = atan(position.y, position.x);
26 | } else {
27 | a = 0.0;
28 | }
29 | // position.x += cos(a + PI * 0.5) * 0.005;
30 | // position.y += sin(a + PI * 0.5) * 0.005;
31 | // position += speed;
32 | // speed *= 0.975;
33 | // position *= 0.995;
34 | gl_FragColor = vec4(position, speed);
35 | }
36 | `, { inline: true })
37 | ]
38 |
39 | const particles = Particles(gl, {
40 | shape: [64, 64],
41 | logic: logics[0],
42 | vert: `
43 | precision mediump float;
44 | uniform sampler2D data;
45 | uniform vec2 resolution;
46 | attribute vec2 uv;
47 | void main() {
48 | vec4 tData = texture2D(data, uv);
49 | vec2 position = tData.xy;
50 | position.x *= resolution.y / resolution.x;
51 | gl_PointSize = 5.0;
52 | gl_Position = vec4(position, 1, 1);
53 | }
54 | `,
55 | frag: `
56 | precision mediump float;
57 | void main() {
58 | vec2 p = (gl_PointCoord.xy - 0.5) * 2.0;
59 | float d = 1.0 - length(p);
60 | gl_FragColor = vec4(d * vec3(1.0, 1.0, 1.0), 1);
61 | }
62 | `
63 | })
64 |
65 | particles.populate(function(u, v, data) {
66 | var a = Math.random() * Math.PI * 2
67 | var l = Math.random() * 0.04
68 | data[0] = Math.random() * 2 - 1
69 | data[1] = Math.random() * 2 - 1
70 | data[2] = Math.cos(a) * l
71 | data[3] = Math.sin(a) * l
72 | })
73 |
74 | const start = Date.now()
75 |
76 | function render() {
77 | const width = gl.drawingBufferWidth
78 | const height = gl.drawingBufferHeight
79 |
80 | // Disabling blending here is important – if it's still
81 | // enabled your simulation will behave differently
82 | // to what you'd expect.
83 | gl.disable(gl.BLEND)
84 | particles.step(function(uniforms) {
85 | uniforms.time = (Date.now() - start) / 1000
86 | })
87 |
88 | gl.enable(gl.BLEND)
89 | gl.blendFunc(gl.ONE, gl.ONE)
90 | gl.clearColor(0.045, 0.02, 0.095, 1)
91 | gl.clear(gl.COLOR_BUFFER_BIT)
92 | gl.viewport(0, 0, width, height)
93 |
94 | particles.draw(function(uniforms) {
95 | uniforms.resolution = [width, height]
96 | })
97 | }
98 |
99 | window.addEventListener('resize', fit(canvas), false)
100 |
101 | logics.forEach(function(source, i) {
102 | var el = document.body.appendChild(document.createElement('div'))
103 |
104 | el.innerHTML = i + 1
105 | el.style.position = 'absolute'
106 | el.style.cursor = 'pointer'
107 | el.style.color = '#66c4ff'
108 | el.style.zIndex = 9999
109 | el.style.left = (1.25 + i * 1.25) + 'em'
110 | el.style.top = '1.25em'
111 | el.style.fontFamily = '"Ubuntu Mono", monospace'
112 |
113 | el.addEventListener('click', function(e) {
114 | particles.setLogicShader(logics[i])
115 | }, false)
116 | })
--------------------------------------------------------------------------------
/src/audio.js:
--------------------------------------------------------------------------------
1 | import badge from 'soundcloud-badge'
2 |
3 | const noop = () => {}
4 |
5 | export default function(cb) {
6 | cb = cb || noop
7 |
8 | badge({
9 | client_id: 'b95f61a90da961736c03f659c03cb0cc',
10 | song: 'https://soundcloud.com/richardxbm/annie-anthonio-berlin',
11 | dark: false,
12 | getFonts: true
13 | }, function(err, src) {
14 | if (err) return cb(err)
15 |
16 | const audio = new Audio()
17 | const ready = () => {
18 | cb(null, audio)
19 | }
20 |
21 | audio.addEventListener('canplay', ready)
22 |
23 | if (audio.readyState > audio.HAVE_FUTURE_DATA)
24 | ready()
25 | audio.src = src
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/src/clear.js:
--------------------------------------------------------------------------------
1 | module.exports = function(gl) {
2 | var rgb = module.exports.rgb
3 | gl.clearColor(rgb[0], rgb[1], rgb[2], 1.0)
4 | gl.clear(gl.COLOR_BUFFER_BIT)
5 | }
6 |
7 | module.exports.rgb = [0.13, 0.13, 0.13]
8 | // module.exports.rgb = [25/255, 50/255, 55/255]
--------------------------------------------------------------------------------
/src/engine.js:
--------------------------------------------------------------------------------
1 | const clear = require('./clear')
2 | const glslify = require('glslify')
3 | const Particles = require('gl-particles')
4 | const random = require('gl-vec2/random')
5 | const getPixels = require('gl-texture2d-pixels')
6 | const triangle = require('a-big-triangle')
7 | const createShader = require('gl-shader')
8 | const mouse = require('touch-position')()
9 |
10 | const logic = glslify('./shaders/logic.frag')
11 | const pointVert = glslify('./shaders/particles.vert')
12 | const pointFrag = glslify('./shaders/particles.frag')
13 | const quadVert = glslify('./shaders/quad.vert')
14 | const quadFrag = glslify('./shaders/quad.frag')
15 |
16 | function copy(out, x, y, z, w) {
17 | out[0] = x
18 | out[1] = y
19 | out[2] = z
20 | out[3] = w
21 | return out
22 | }
23 |
24 | export default function(gl) {
25 | const quadShader = createShader(gl, quadVert, quadFrag)
26 |
27 | const particles = Particles(gl, {
28 | shape: [500, 500],
29 | logic: logic,
30 | vert: pointVert,
31 | frag: pointFrag
32 | })
33 |
34 | const r = [0, 0]
35 | const mouseVec = [0, 0]
36 | let time = 0
37 | let motion
38 | let resolution
39 | particles.populate(repopulate)
40 |
41 | Object.defineProperties(render, {
42 | motion: {
43 | get() {
44 | return motion
45 | },
46 | set(tex) {
47 | motion = tex
48 | }
49 | },
50 | resolution: {
51 | get() {
52 | return resolution
53 | },
54 | set(res) {
55 | resolution = res
56 | }
57 | }
58 | })
59 |
60 | return render
61 |
62 | function repopulate(u, v, data) {
63 | random(r, Math.random())
64 | data[0] = r[0]
65 | data[1] = r[1]
66 | }
67 |
68 | function render(dt) {
69 | time += dt / 1000
70 | const width = gl.drawingBufferWidth
71 | const height = gl.drawingBufferHeight
72 | const deviceResolution = [width, height]
73 |
74 | gl.disable(gl.BLEND)
75 | if (motion)
76 | motion.bind(1)
77 |
78 |
79 | const midx = window.innerWidth/2
80 | const midy = window.innerHeight/2
81 | mouseVec[0] = ((mouse[0] - midx) / (resolution[0]/2))
82 | mouseVec[1] = ((mouse[1] - midy) / (-resolution[1]/2))
83 |
84 | // mouseVec[0] = (((mouse[0] + window.innerWidth/2) / resolution[0]) * 2 - 1)
85 | // mouseVec[0] += (deviceResolution[0]/deviceResolution[1])
86 | // mouseVec[1] = (mouse[1] / window.innerHeight) * -2 + 1
87 | // mouseVec[0] *=
88 |
89 | // uv.x -= aspect/2.0 - 0.5;
90 | // mouseVec[1] *= 2
91 |
92 | particles.step(function(uniforms) {
93 | uniforms.motion = 1
94 | uniforms.mouse = mouseVec
95 | uniforms.motionResolution = particles.shape
96 | uniforms.resolution = deviceResolution
97 | uniforms.time = time
98 | })
99 |
100 | gl.enable(gl.BLEND)
101 | gl.blendFunc(gl.ONE, gl.ONE)
102 | gl.viewport(0, 0, width, height)
103 |
104 | clear(gl)
105 |
106 | quadShader.bind()
107 | quadShader.uniforms.time = time
108 | quadShader.uniforms.motion = 1
109 | quadShader.uniforms.resolution = deviceResolution
110 | triangle(gl)
111 |
112 | particles.draw(function(uniforms) {
113 | uniforms.mouse = mouseVec
114 | uniforms.motion = 1
115 | uniforms.time = time
116 | uniforms.resolution = deviceResolution
117 | })
118 | }
119 |
120 | }
--------------------------------------------------------------------------------
/src/image.js:
--------------------------------------------------------------------------------
1 | import loadImage from 'img'
2 | import createTexture from 'gl-texture2d'
3 | const noop = () => {}
4 |
5 | export default function(gl, cb) {
6 | cb = cb || noop
7 |
8 | loadImage('assets/test.png', (err, img) => {
9 | if (err) return cb(err)
10 | const texture = createTexture(gl, img)
11 | texture.minFilter = gl.LINEAR
12 | texture.update = noop
13 | cb(null, texture)
14 | })
15 | }
--------------------------------------------------------------------------------
/src/intro.js:
--------------------------------------------------------------------------------
1 | import domify from 'domify'
2 | import assign from 'object-assign'
3 |
4 | const tweenr = require('tweenr')()
5 |
6 | export default function() {
7 | const text = `
8 | all particles are rendered in real-time
9 | `.trim()
10 | const html = domify(`${text}
`)
11 |
12 | assign(html.style, {
13 | position: 'absolute',
14 | top: '0',
15 | left: '0',
16 | right: '0',
17 | bottom: '0',
18 | margin: 'auto',
19 | width: '400px',
20 | 'text-align': 'center',
21 | height: '40px',
22 | color: 'white',
23 | 'font-size': '12px',
24 | 'font-family': '"Open Sans", "Helvetica", sans-serif',
25 | 'font-weight': 300,
26 | })
27 |
28 | document.body.appendChild(html)
29 |
30 | const anim = { opacity: 1 }
31 | tweenr.to(anim, {
32 | duration: 1,
33 | ease: 'quadOut',
34 | opacity: 0,
35 | delay: 1.5
36 | })
37 | .on('update', () => {
38 | html.style.opacity = anim.opacity.toFixed(5)
39 | })
40 | .on('complete', () => {
41 | html.style.display = 'none'
42 | })
43 | }
--------------------------------------------------------------------------------
/src/shaders/debug.frag:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 | uniform sampler2D iChannel0;
3 | // uniform float iGlobalTime;
4 | // uniform vec2 iResolution;
5 |
6 | void main() {
7 |
8 | gl_FragColor.rgb = texture2D(iChannel0, vUv).rrr;
9 | gl_FragColor.a = 1.0;
10 | }
--------------------------------------------------------------------------------
/src/shaders/logic.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | #define PI 3.14159265359
4 | #define MAX_DISPLACE 2048.0
5 |
6 | uniform sampler2D data;
7 | uniform sampler2D motion;
8 | uniform float time;
9 | uniform vec2 resolution;
10 | uniform vec2 motionResolution;
11 | uniform vec2 mouse;
12 |
13 | #pragma glslify: size = require('./size')
14 | #pragma glslify: random = require('glsl-random')
15 | #pragma glslify: mouseEffect = require('./mouse')
16 |
17 | float insideBox(vec2 v, vec2 bottomLeft, vec2 topRight) {
18 | vec2 s = step(bottomLeft, v) - step(topRight, v);
19 | return s.x * s.y;
20 | }
21 |
22 | void main() {
23 | vec2 uv = gl_FragCoord.xy / motionResolution;
24 |
25 | vec4 tData = texture2D(data, uv);
26 |
27 | //get RG in bytes
28 | vec2 position = tData.xy;
29 | vec2 uv2 = clamp(position * 0.5 + 0.5, 0.0, 1.0);
30 | uv2.y = 1.0 - uv2.y;
31 |
32 | vec2 velocity = tData.zw;
33 | vec2 motion_rg = texture2D(motion, uv2).rg;
34 | motion_rg.xy = 1.0 - motion_rg.xy;
35 | vec2 movement = vec2(motion_rg) * 2.0 - 1.0;
36 |
37 |
38 | // float inside = insideBox(position, vec2(-1.0), vec2(1.0));
39 |
40 | float dist = length((position*0.5+0.5) - 0.5);
41 | float r = random(uv);
42 | if (dist < 0.5) {
43 | velocity += movement.xy * 0.0005;
44 | } else { //re-birth the particles
45 | // float r = 1.0;
46 | float rd = smoothstep(0.5, 0.49, length(uv - 0.5));
47 | vec2 page = (uv * 2.0 - 1.0) * rd;
48 | position.xy = vec2(page * r);
49 | velocity.xy = vec2(0.0);
50 | }
51 |
52 |
53 |
54 |
55 | position += velocity;
56 |
57 | // velocity.x -= dir.x*0.0001*mouseOff;
58 | vec2 effect = mouseEffect(position, mouse, resolution);
59 | velocity += 0.0008*effect;
60 | // velocity.y -= 0.0008 * mouseOff;
61 |
62 |
63 | //how much to affect gravity
64 | // float gAffect = smoothstep(0.4, 0.91, abs(movement.x));
65 | // velocity -= vec2(mouseOff) * gAffect;
66 | velocity *= 0.99;
67 | // if (position.y < -1.0) {
68 | // position.y = 1.0;
69 | // velocity *= 0.0;
70 | // velocity.y = -velocity.y*1.0;
71 | // }
72 |
73 | // vec2 dir = normalize(position.xy);
74 | // velocity.xy -= dir.xy*0.0001;
75 |
76 | gl_FragColor = vec4(position, velocity);
77 | }
--------------------------------------------------------------------------------
/src/shaders/mouse.glsl:
--------------------------------------------------------------------------------
1 | vec2 mouseEffect(vec2 position, vec2 mouse, vec2 resolution) {
2 | vec2 aspect = vec2(resolution.x / resolution.y, 1.0);
3 | vec2 mpos = position * aspect;
4 | vec2 dir = normalize(mpos - mouse);
5 | float mouseOff = distance(mpos, mouse);
6 | mouseOff = smoothstep(0.05, 0.0, mouseOff);
7 | return dir*mouseOff;
8 | }
9 |
10 | #pragma glslify: export(mouseEffect)
--------------------------------------------------------------------------------
/src/shaders/particles.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 | uniform float time;
3 | uniform sampler2D motion;
4 | varying float vSize;
5 |
6 | #pragma glslify: hsl2rgb = require('glsl-hsl2rgb')
7 |
8 | void main() {
9 | vec2 p = (gl_PointCoord.xy - 0.5) * 2.0;
10 |
11 | float d = 1.0 - length(p);
12 | d = smoothstep(0.6, 0.8, d);
13 |
14 | float tmin = 0.0;
15 | float tmax = 0.6;
16 | float hue = mix(tmin, tmax, vSize) + sin(time);
17 | vec3 rgb = hsl2rgb(hue*0.2 + 0.0, 1.0, mix(0.0, 0.45, vSize));
18 |
19 | gl_FragColor = vec4(d * vec3(rgb * 0.4), 1.0);
20 | // gl_FragColor = vec4(texture2D(motion, p).rgb, 1.0);
21 | }
--------------------------------------------------------------------------------
/src/shaders/particles.vert:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 | uniform sampler2D data;
3 | // uniform vec2 mouse;
4 | uniform vec2 resolution;
5 | attribute vec2 uv;
6 | varying float vSize;
7 |
8 | #pragma glslify: size = require('./size')
9 |
10 | void main() {
11 | vec4 tData = texture2D(data, uv);
12 | vec2 position = tData.xy;
13 | float dist = length((position*0.5+0.5) - 0.5);
14 | dist = smoothstep(0.5, 0.0, dist);
15 |
16 | float aspect = resolution.y / resolution.x;
17 | position.x *= aspect;
18 | float motion = size(vec2(tData.z, tData.w*0.5));
19 |
20 | float pSize = dist * motion;
21 |
22 | gl_PointSize = pSize * 3.0;
23 | gl_Position = vec4(position, 1, 1);
24 | vSize = pSize;
25 | }
--------------------------------------------------------------------------------
/src/shaders/pass.vert:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 | void main() {
3 | vUv = uv;
4 | gl_Position = projectionMatrix *
5 | modelViewMatrix *
6 | vec4(position, 1.0);
7 | }
--------------------------------------------------------------------------------
/src/shaders/quad.frag:
--------------------------------------------------------------------------------
1 | precision mediump float;
2 |
3 | uniform vec2 resolution;
4 | uniform sampler2D motion;
5 | varying vec2 screenPosition;
6 |
7 | vec3 sample(vec2 uv);
8 | #pragma glslify: blur = require('glsl-hash-blur', iterations=5, sample=sample)
9 |
10 | vec3 sample(vec2 uv) {
11 | return texture2D(motion, uv).bbb;
12 | }
13 |
14 | void main() {
15 | vec2 uv = (gl_FragCoord.xy / resolution.xy);
16 | uv.y = 1.0 - uv.y;
17 | float aspect = resolution.x/resolution.y;
18 | uv.x *= aspect;
19 | uv.x -= aspect/2.0 - 0.5;
20 |
21 | // float gray = texture2D(motion, uv).b;
22 | float texel = 1.0 / resolution.x;
23 | float gray = blur(uv, texel * 7.0, aspect).r;
24 | float vignette = length(uv - 0.5);
25 |
26 | gray = smoothstep(0.01, 0.82, gray) * 1.0;
27 | gray *= smoothstep(0.5, 0.0, vignette);
28 | gl_FragColor = vec4(vec3(gray), 1.0);
29 | }
--------------------------------------------------------------------------------
/src/shaders/quad.vert:
--------------------------------------------------------------------------------
1 | attribute vec2 position;
2 | varying vec2 screenPosition;
3 |
4 | void main() {
5 | screenPosition = (position + 1.0) * 0.5;
6 | gl_Position = vec4(position, 1.0, 1.0);
7 | }
--------------------------------------------------------------------------------
/src/shaders/size.glsl:
--------------------------------------------------------------------------------
1 | float size(vec2 velocity) {
2 | float d = length(velocity)*1000.0;
3 | return smoothstep(0.5, 2.0, d);
4 | }
5 |
6 | #pragma glslify: export(size)
--------------------------------------------------------------------------------
/src/three-video.js:
--------------------------------------------------------------------------------
1 | import THREE from 'three'
2 | import events from 'dom-events'
3 | const glslify = require('glslify')
4 |
5 | const noop = () => {}
6 |
7 | module.exports = function(app, cb) {
8 | cb = cb || noop
9 |
10 | const video = document.createElement('video')
11 | video.setAttribute('loop', true)
12 | video.setAttribute('muted', 'muted')
13 | addSource('video/mp4', 'assets/motion2.mp4')
14 | video.load()
15 |
16 | const texture = new THREE.Texture(video)
17 | texture.minFilter = THREE.LinearFilter
18 | texture.generateMipmaps = false
19 | const result = {
20 | video, texture
21 | }
22 |
23 | events.on(video, 'error', err => {
24 | cb(new Error(err))
25 | cb = noop
26 | })
27 | events.on(video, 'canplay', () => {
28 | texture.needsUpdate = true
29 | video.play()
30 | cb(null, result)
31 | cb = noop
32 | })
33 |
34 | function addSource(type, path) {
35 | var source = document.createElement('source')
36 | source.src = path
37 | source.type = type
38 | return video.appendChild(source)
39 | }
40 |
41 | app.on('tick', () => {
42 | if (video.readyState !== video.HAVE_ENOUGH_DATA)
43 | return
44 | texture.needsUpdate = true
45 | })
46 |
47 | const vert = glslify('./shaders/pass.vert')
48 | const frag = glslify('./shaders/debug.frag')
49 |
50 | const mat = new THREE.ShaderMaterial({
51 | uniforms: {
52 | iChannel0: { type: 't', value: texture }
53 | },
54 | vertexShader: vert,
55 | fragmentShader: frag,
56 | defines: {
57 | 'USE_MAP': ''
58 | }
59 | })
60 | const geo = new THREE.BoxGeometry(1,1,1)
61 | const mesh = new THREE.Mesh(geo, mat)
62 | app.scene.add(mesh)
63 |
64 | return result
65 | }
--------------------------------------------------------------------------------
/src/three-viewer.js:
--------------------------------------------------------------------------------
1 | import THREE from 'three'
2 | import loop from 'raf-loop'
3 |
4 | import assign from 'object-assign'
5 | import domready from 'domready'
6 | import fitter from 'canvas-fit'
7 |
8 |
9 | const OrbitControls = require('three-orbit-controls')(THREE)
10 |
11 | export default function(opt) {
12 | opt = assign({}, opt)
13 |
14 | const dpr = Math.min(2, window.devicePixelRatio)
15 | const canvas = opt.canvas || document.createElement('canvas')
16 | const fit = fitter(canvas, window, dpr)
17 |
18 | const renderer = new THREE.WebGLRenderer(assign({
19 | canvas: canvas
20 | }, opt))
21 |
22 | const gl = renderer.getContext()
23 |
24 | const app = loop(draw)
25 | const scene = new THREE.Scene()
26 | const camera = new THREE.PerspectiveCamera(65, 1, 0.1, 1000)
27 | const controls = new OrbitControls(camera, canvas)
28 | camera.position.copy(new THREE.Vector3(0, 0, -1.6))
29 | camera.lookAt(new THREE.Vector3())
30 |
31 | app.render = renderer.render.bind(renderer, scene, camera)
32 |
33 | //render each frame unless user wants to do it manually
34 | if (opt.rendering !== false)
35 | app.on('render', app.render.bind(app))
36 |
37 | assign(app, {
38 | renderer,
39 | scene,
40 | camera,
41 | controls,
42 | gl,
43 | canvas
44 | })
45 |
46 | window.addEventListener('resize', resize, false)
47 | renderer.setClearColor(0xf7f7f7, 1)
48 | process.nextTick(resize)
49 | return app
50 |
51 | function draw() {
52 | app.emit('render')
53 | }
54 |
55 | function resize() {
56 | fit()
57 | const width = window.innerWidth
58 | const height = window.innerHeight
59 | const size = { width, height }
60 |
61 | renderer.setSize(width, height)
62 | camera.aspect = width / height
63 | camera.updateProjectionMatrix()
64 | assign(app, size)
65 | app.emit('resize', size)
66 | }
67 | }
--------------------------------------------------------------------------------
/src/video.js:
--------------------------------------------------------------------------------
1 | import createTexture from 'gl-texture2d'
2 | import events from 'dom-events'
3 | import once from 'once'
4 | const noop = () => {}
5 |
6 | module.exports = function(gl, cb) {
7 | cb = cb || noop
8 |
9 | const video = document.createElement('video')
10 | video.setAttribute('loop', true)
11 | video.setAttribute('muted', 'muted')
12 | addSource('video/webm', 'assets/ballet2.webm')
13 | // addSource('video/mp4', 'assets/ballet2.mp4')
14 | video.load()
15 |
16 | const ready = once(() => {
17 | const texture = createTexture(gl, video)
18 | texture.minFilter = gl.LINEAR
19 | texture.update = update.bind(null, texture)
20 | texture.video = video
21 | console.log("Ready.")
22 |
23 | cb(null, texture)
24 | cb = noop
25 | })
26 |
27 | if (video.readyState > video.HAVE_FUTURE_DATA)
28 | ready()
29 |
30 | events.on(video, 'error', err => {
31 | cb(new Error(err))
32 | cb = noop
33 | })
34 |
35 | // const ff = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
36 | // if (ff) {
37 | // fixLoop(video)
38 | // }
39 | events.on(video, 'canplay', ready)
40 |
41 | function fixLoop(video) {
42 | // events.on(video, 'ended', () => {
43 | // console.log("END")
44 | // video.play()
45 | // })
46 | // events.on(video, 'timeupdate', () => {
47 | // //grr.. firefox 'ended' and 'loop' not working
48 | // if (Math.round(video.currentTime) >= Math.round(video.duration)) {
49 | // video.src = src
50 | // video.play()
51 | // }
52 | // })
53 | }
54 |
55 | function addSource(type, path) {
56 | var source = document.createElement('source')
57 | source.src = path
58 | source.type = type
59 | return video.appendChild(source)
60 | }
61 |
62 | function update(texture) {
63 | if (video.readyState !== video.HAVE_ENOUGH_DATA)
64 | return
65 | texture.setPixels(video)
66 | }
67 | }
--------------------------------------------------------------------------------