├── grunt ├── tasks │ ├── .gitkeep │ └── shaderChunks.js └── config │ ├── clean.js │ ├── notify.js │ ├── connect.js │ ├── jshint.js │ ├── uglify.js │ ├── handlebars.js │ ├── haychtml.js │ ├── autoprefixer.js │ ├── neuter.js │ ├── sass.js │ ├── copy.js │ └── watch.js ├── static ├── scss │ ├── helpers │ │ ├── _mixins.scss │ │ ├── _colors.scss │ │ └── _type.scss │ ├── style.scss │ ├── components │ │ ├── _graph.scss │ │ ├── _color.scss │ │ └── _controls.scss │ ├── apps │ │ ├── _application.scss │ │ └── _index.scss │ └── tests.scss ├── img │ └── favicon.ico ├── audio │ ├── bg-loop.mp3 │ ├── bg-loop.ogg │ ├── bubbles-1.mp3 │ ├── bubbles-1.ogg │ ├── bubbles-2.mp3 │ ├── bubbles-2.ogg │ ├── buzz-wave-2.mp3 │ ├── buzz-wave-2.ogg │ ├── buzz-wave-4.mp3 │ └── buzz-wave-4.ogg ├── glsl │ ├── shader-chunks │ │ ├── lerp_pos_pars_vertex.glsl │ │ └── lerp_pos_vertex.glsl │ └── shaders │ │ ├── uvs-frag.glsl │ │ ├── gel-vert.glsl │ │ ├── tentacle-vert.glsl │ │ ├── tentacle-frag.glsl │ │ ├── alpha-vert.glsl │ │ ├── gel-frag.glsl │ │ ├── lerp-vert.glsl │ │ ├── lerp-point-vert.glsl │ │ ├── normal-vert.glsl │ │ ├── dust-frag.glsl │ │ ├── dust-vert.glsl │ │ ├── basic-point-frag.glsl │ │ ├── alpha-frag.glsl │ │ ├── basic-vert.glsl │ │ ├── basic-frag.glsl │ │ ├── tail-frag.glsl │ │ └── bulb-frag.glsl ├── js │ ├── controllers │ │ ├── TestController.js │ │ ├── IndexController.js │ │ └── AudioController.js │ ├── application │ │ └── App.js │ ├── utils │ │ ├── Geometry.js │ │ ├── Format.js │ │ ├── KeyDelegator.js │ │ ├── Dispatcher.js │ │ ├── Links.js │ │ ├── Faces.js │ │ ├── Tweens.js │ │ ├── Looper.js │ │ └── Features.js │ ├── app.js │ ├── materials │ │ ├── AlphaMaterial.js │ │ ├── UVMaterial.js │ │ ├── GelMaterial.js │ │ ├── LerpMaterial.js │ │ ├── DustMaterial.js │ │ ├── TentacleMaterial.js │ │ ├── LerpPointMaterial.js │ │ ├── BulbMaterial.js │ │ ├── TailMaterial.js │ │ └── ShaderMaterial.js │ ├── libs.js │ ├── components │ │ ├── ModalComponent.js │ │ ├── ColorComponent.js │ │ ├── ToggleComponent.js │ │ └── GraphComponent.js │ ├── forces │ │ └── PointRepulsorForce.js │ ├── constraints │ │ └── LocalPlaneConstraint.js │ ├── items │ │ └── Dust.js │ └── post-processing │ │ ├── LensDirtTexture.js │ │ └── LensDirtPass.js ├── lib-extras │ └── three │ │ ├── .editorconfig │ │ ├── shaders │ │ ├── BasicShader.js │ │ ├── CopyShader.js │ │ ├── LuminosityShader.js │ │ ├── NormalMapShader.js │ │ ├── BleachBypassShader.js │ │ ├── VignetteShader.js │ │ ├── HueSaturationShader.js │ │ ├── VerticalBlurShader.js │ │ ├── HorizontalBlurShader.js │ │ ├── HorizontalTiltShiftShader.js │ │ ├── VerticalTiltShiftShader.js │ │ ├── ConvolutionShader.js │ │ ├── FXAAShader.js │ │ ├── BokehShader.js │ │ └── SSAOShader.js │ │ ├── postprocessing │ │ ├── TexturePass.js │ │ ├── RenderPass.js │ │ ├── ShaderPass.js │ │ ├── SavePass.js │ │ ├── FilmPass.js │ │ ├── MaskPass.js │ │ ├── BokehPass.js │ │ ├── EffectComposer.js │ │ └── BloomPass.js │ │ ├── geometries │ │ ├── PlaneBufferGeometry.js │ │ └── SphereBufferGeometry.js │ │ └── controls │ │ └── TrackballControls.js └── tests │ ├── assert │ ├── range.js │ ├── equal-array.js │ └── close-enough.js │ ├── post-processing │ └── LensDirtPass.js │ ├── .jshintrc │ ├── tests.js │ └── constraints │ └── LocalPlaneConstraint.js ├── .bowerrc ├── pages ├── index.html └── _base.html ├── .editorconfig ├── postinstall.sh ├── .gitattributes ├── bower.json ├── .jshintrc ├── .gitignore ├── package.json ├── Gruntfile.js ├── index.html ├── README.md └── LICENSE /grunt/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/scss/helpers/_mixins.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "static/lib" 3 | } 4 | -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/img/favicon.ico -------------------------------------------------------------------------------- /static/audio/bg-loop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/bg-loop.mp3 -------------------------------------------------------------------------------- /static/audio/bg-loop.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/bg-loop.ogg -------------------------------------------------------------------------------- /static/audio/bubbles-1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/bubbles-1.mp3 -------------------------------------------------------------------------------- /static/audio/bubbles-1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/bubbles-1.ogg -------------------------------------------------------------------------------- /static/audio/bubbles-2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/bubbles-2.mp3 -------------------------------------------------------------------------------- /static/audio/bubbles-2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/bubbles-2.ogg -------------------------------------------------------------------------------- /static/glsl/shader-chunks/lerp_pos_pars_vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform float stepProgress; 2 | attribute vec3 positionPrev; 3 | -------------------------------------------------------------------------------- /static/audio/buzz-wave-2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/buzz-wave-2.mp3 -------------------------------------------------------------------------------- /static/audio/buzz-wave-2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/buzz-wave-2.ogg -------------------------------------------------------------------------------- /static/audio/buzz-wave-4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/buzz-wave-4.mp3 -------------------------------------------------------------------------------- /static/audio/buzz-wave-4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milcktoast/particulate-medusae/HEAD/static/audio/buzz-wave-4.ogg -------------------------------------------------------------------------------- /static/scss/helpers/_colors.scss: -------------------------------------------------------------------------------- 1 | $red : #f00; 2 | $black : #222; 3 | $white : #fefefe; 4 | 5 | $link-color : $red; -------------------------------------------------------------------------------- /static/js/controllers/TestController.js: -------------------------------------------------------------------------------- 1 | App.register('tests', function tests() { 2 | document.body.className = 'testing'; 3 | }); 4 | -------------------------------------------------------------------------------- /static/glsl/shader-chunks/lerp_pos_vertex.glsl: -------------------------------------------------------------------------------- 1 | vec4 mvPosition = modelViewMatrix * vec4(mix(positionPrev, position, stepProgress), 1.0); 2 | gl_Position = projectionMatrix * mvPosition; 3 | -------------------------------------------------------------------------------- /static/scss/helpers/_type.scss: -------------------------------------------------------------------------------- 1 | body { 2 | color: #fff; 3 | font: 12px/1.2 Monaco, monospace; 4 | -webkit-font-smoothing: antialiased; 5 | } 6 | 7 | h1 { 8 | font-size: 14px; 9 | } 10 | -------------------------------------------------------------------------------- /static/lib-extras/three/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "_base.html" %} 2 | 3 | {% block page_name %}index{% endblock page_name %} 4 | {% block title %}Home{% endblock title %} 5 | 6 | {% block content %} 7 |

Home

8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /static/tests/assert/range.js: -------------------------------------------------------------------------------- 1 | Test.assert.range = assert_range; 2 | function assert_range(actual, min, max, message) { 3 | var passes = actual >= min && actual <= max; 4 | var expected = [min, max]; 5 | QUnit.push(passes, actual, expected, message); 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /static/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import "red-sass/red-sass"; 2 | @include reset; 3 | 4 | @import 5 | "helpers/colors", 6 | "helpers/type", 7 | "helpers/mixins", 8 | 9 | "components/controls", 10 | "components/color", 11 | "components/graph", 12 | 13 | "apps/application", 14 | "apps/index"; 15 | -------------------------------------------------------------------------------- /grunt/config/clean.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/gruntjs/grunt-contrib-clean 5 | 6 | // Cleans folders and files. 7 | 8 | module.exports = function (config) { 9 | return { 10 | build: config.deploy + '*', 11 | temp: '.temp' 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /postinstall.sh: -------------------------------------------------------------------------------- 1 | bower install 2 | 3 | NODE=./node_modules 4 | BOWER=./static/lib 5 | PACKAGES="three" 6 | 7 | for PACKAGE in $PACKAGES 8 | do 9 | if [ ! -e "$NODE/$PACKAGE" ] 10 | then 11 | continue 12 | fi 13 | rm -rf $BOWER/$PACKAGE 14 | mv -v $NODE/$PACKAGE $BOWER/$PACKAGE 15 | done 16 | -------------------------------------------------------------------------------- /static/glsl/shaders/uvs-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform float opacity; 3 | 4 | {{{chunks.common}}} 5 | {{{chunks.map_pars_fragment}}} 6 | 7 | void main() { 8 | vec4 diffuseColor = vec4(diffuse, opacity); 9 | 10 | {{{chunks.map_fragment}}} 11 | 12 | gl_FragColor = vec4(vUv.xy, 0.0, opacity); 13 | } 14 | -------------------------------------------------------------------------------- /static/glsl/shaders/gel-vert.glsl: -------------------------------------------------------------------------------- 1 | {{{chunks.common}}} 2 | {{{chunks.lerp_pos_pars_vertex}}} 3 | {{{chunks.color_pars_vertex}}} 4 | 5 | varying vec3 vNormal; 6 | 7 | void main() { 8 | {{{chunks.color_vertex}}} 9 | {{{chunks.lerp_pos_vertex}}} 10 | {{{chunks.worldpos_vertex}}} 11 | 12 | vNormal = normalize(position); 13 | } 14 | -------------------------------------------------------------------------------- /grunt/config/notify.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/dylang/grunt-notify 5 | 6 | // Desktop notifications for grunt errors and warnings. 7 | 8 | module.exports = function () { 9 | return { 10 | build: { 11 | options: { 12 | message: 'Build complete.' 13 | } 14 | } 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /static/glsl/shaders/tentacle-vert.glsl: -------------------------------------------------------------------------------- 1 | uniform float area; 2 | varying float centerDist; 3 | 4 | {{{chunks.common}}} 5 | {{{chunks.lerp_pos_pars_vertex}}} 6 | {{{chunks.color_pars_vertex}}} 7 | 8 | void main() { 9 | {{{chunks.color_vertex}}} 10 | 11 | centerDist = length(position); 12 | 13 | {{{chunks.lerp_pos_vertex}}} 14 | {{{chunks.worldpos_vertex}}} 15 | } 16 | -------------------------------------------------------------------------------- /static/scss/components/_graph.scss: -------------------------------------------------------------------------------- 1 | .graph { 2 | position: relative; 3 | margin: 10px 0; 4 | 5 | > .label { 6 | position: absolute; 7 | bottom: 0; 8 | right: 0; 9 | padding: 2px 4px 0; 10 | 11 | color: #fff; 12 | background: rgba(#111, 0.5); 13 | } 14 | 15 | > canvas { 16 | position: relative; 17 | display: block; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto* eol=lf 2 | *.png binary 3 | *.jpg binary 4 | *.gif binary 5 | *.jar binary 6 | *.exe binary 7 | *.eot binary 8 | *.ttf binary 9 | *.otf binary 10 | *.pdf binary 11 | *.woff binary 12 | *.bz2 binary 13 | *.gz binary 14 | 15 | # Flash Binaries 16 | *.swc binary 17 | *.fla binary 18 | *.swf binary 19 | *.as binary 20 | 21 | *.mp3 binary 22 | *.ogg binary 23 | *.wav binary 24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "particulate-medusae", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "handlebars": "~4.0.5", 7 | "native-promise-only": "~0.8.0-a", 8 | "noise": "git@github.com:josephg/noisejs.git#3861daee43d62f809126eb9783cf4091d4796ee8", 9 | "particulate": "~0.3.2", 10 | "qunit": "~1.18.0", 11 | "red-sass": "~0.2.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /static/glsl/shaders/tentacle-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform float opacity; 3 | uniform float area; 4 | varying float centerDist; 5 | 6 | void main() { 7 | float illumination = area * 2.0 / (centerDist * centerDist); 8 | gl_FragColor = vec4( 9 | mix(vec3(1.0), diffuse, clamp(illumination, 0.0, 1.25)), 10 | clamp(opacity * illumination * illumination, 0.0, opacity)); 11 | } 12 | -------------------------------------------------------------------------------- /static/tests/assert/equal-array.js: -------------------------------------------------------------------------------- 1 | Test.assert.equalArray = assert_equalArray; 2 | function assert_equalArray(actual, expected, message) { 3 | var isEqual = true; 4 | 5 | for (var i = 0, il = expected.length; i < il; i ++) { 6 | if (actual[i] !== expected[i]) { 7 | isEqual = false; 8 | break; 9 | } 10 | } 11 | 12 | QUnit.push(isEqual, actual, expected, message); 13 | } 14 | -------------------------------------------------------------------------------- /grunt/config/connect.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/gruntjs/grunt-contrib-connect 5 | 6 | // Start a connect web server. 7 | 8 | module.exports = function (config) { 9 | return { 10 | options: { 11 | port: 8000, 12 | hostname: '*', 13 | base: './', 14 | livereload: 38000, 15 | open: true 16 | }, 17 | develop: {} 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /static/glsl/shaders/alpha-vert.glsl: -------------------------------------------------------------------------------- 1 | {{{chunks.common}}} 2 | {{{chunks.uv_pars_vertex}}} 3 | {{{chunks.color_pars_vertex}}} 4 | {{{chunks.logdepthbuf_pars_vertex}}} 5 | 6 | attribute float alpha; 7 | varying float vAlpha; 8 | 9 | void main() { 10 | {{{chunks.uv_vertex}}} 11 | {{{chunks.color_vertex}}} 12 | 13 | {{{chunks.begin_vertex}}} 14 | {{{chunks.project_vertex}}} 15 | {{{chunks.logdepthbuf_vertex}}} 16 | 17 | {{{chunks.worldpos_vertex}}} 18 | 19 | vAlpha = alpha; 20 | } 21 | -------------------------------------------------------------------------------- /static/glsl/shaders/gel-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform float opacity; 3 | varying vec3 vNormal; 4 | 5 | void main() { 6 | vec3 eye = vec3(0.0, 0.0, 1.0); 7 | vec3 normal = normalize(mat3(viewMatrix) * vNormal); 8 | float rim = 1.0 - max(dot(eye, normal), 0.0); 9 | 10 | float rimLight = 0.25 + 11 | smoothstep(0.25, 1.0, rim) * 0.5 + 12 | smoothstep(0.90, 1.0, rim) * 0.8; 13 | 14 | gl_FragColor.rgb = diffuse * vec3(rimLight); 15 | gl_FragColor.a = opacity; 16 | } 17 | -------------------------------------------------------------------------------- /grunt/config/jshint.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/gruntjs/grunt-contrib-jshint 5 | 6 | // Validate javascript files with JSHint. 7 | // The jshint options should be set in the .jshintrc file. 8 | 9 | module.exports = function (config) { 10 | return { 11 | options: { 12 | jshintrc: '.jshintrc' 13 | }, 14 | all: [ 15 | 'Gruntfile.js', 16 | 'grunt/{,**/}*.js', 17 | config.source + 'js/{,**/}*.js' 18 | ] 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /static/glsl/shaders/lerp-vert.glsl: -------------------------------------------------------------------------------- 1 | {{{chunks.common}}} 2 | {{{chunks.uv_pars_vertex}}} 3 | {{{chunks.uv2_pars_vertex}}} 4 | {{{chunks.color_pars_vertex}}} 5 | {{{chunks.lerp_pos_pars_vertex}}} 6 | {{{chunks.logdepthbuf_pars_vertex}}} 7 | 8 | void main() { 9 | {{{chunks.uv_vertex}}} 10 | {{{chunks.uv2_vertex}}} 11 | {{{chunks.color_vertex}}} 12 | 13 | {{{chunks.begin_vertex}}} 14 | {{{chunks.lerp_pos_vertex}}} 15 | {{{chunks.logdepthbuf_vertex}}} 16 | {{{chunks.worldpos_vertex}}} 17 | } 18 | -------------------------------------------------------------------------------- /static/glsl/shaders/lerp-point-vert.glsl: -------------------------------------------------------------------------------- 1 | uniform float size; 2 | uniform float scale; 3 | 4 | {{{chunks.common}}} 5 | {{{chunks.color_pars_vertex}}} 6 | {{{chunks.lerp_pos_pars_vertex}}} 7 | {{{chunks.logdepthbuf_pars_vertex}}} 8 | 9 | void main() { 10 | {{{chunks.color_vertex}}} 11 | {{{chunks.lerp_pos_vertex}}} 12 | {{{chunks.logdepthbuf_vertex}}} 13 | 14 | #ifdef USE_SIZEATTENUATION 15 | gl_PointSize = size * (scale / length(mvPosition.xyz)); 16 | #else 17 | gl_PointSize = size; 18 | #endif 19 | } 20 | -------------------------------------------------------------------------------- /static/js/application/App.js: -------------------------------------------------------------------------------- 1 | window.App = Object.create({ 2 | ctor : Particulate.ctor, 3 | log : (window.console && window.console.log.bind && 4 | window.console.log.bind(window.console)) || function () {}, 5 | 6 | shaders : window.App && window.App.shaders, 7 | 8 | _register : {}, 9 | register : function (name, fn) { 10 | this._register[name] = fn; 11 | }, 12 | 13 | run : function (name) { 14 | if (!this._register[name]) { return; } 15 | this._register[name].call(this); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /static/js/utils/Geometry.js: -------------------------------------------------------------------------------- 1 | var Geometry = App.Geometry = {}; 2 | 3 | Geometry.point = function (x, y, z, buffer) { 4 | buffer.push(x, y, z); 5 | return buffer; 6 | }; 7 | 8 | Geometry.circle = function (segments, radius, y, buffer) { 9 | var step = Math.PI * 2 / segments; 10 | var angle = 0; 11 | var x, z; 12 | 13 | for (var i = 0; i < segments; i ++) { 14 | x = Math.cos(angle) * radius; 15 | z = Math.sin(angle) * radius; 16 | 17 | buffer.push(x, y, z); 18 | angle += step; 19 | } 20 | return buffer; 21 | }; 22 | -------------------------------------------------------------------------------- /grunt/config/uglify.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/gruntjs/grunt-contrib-uglify 5 | 6 | // Minify files with UglifyJS. 7 | 8 | module.exports = function (config) { 9 | return { 10 | app: { 11 | src: [ 12 | config.static + 'js/libs.develop.js', 13 | config.static + 'js/shader-chunks.develop.js', 14 | config.static + 'js/shaders.develop.js', 15 | config.static + 'js/app.develop.js' 16 | ], 17 | dest: config.static + 'js/app.min.js' 18 | } 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/BasicShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://www.mrdoob.com 3 | * 4 | * Simple test shader 5 | */ 6 | 7 | THREE.BasicShader = { 8 | 9 | uniforms: {}, 10 | 11 | vertexShader: [ 12 | 13 | "void main() {", 14 | 15 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 16 | 17 | "}" 18 | 19 | ].join("\n"), 20 | 21 | fragmentShader: [ 22 | 23 | "void main() {", 24 | 25 | "gl_FragColor = vec4( 1.0, 0.0, 0.0, 0.5 );", 26 | 27 | "}" 28 | 29 | ].join("\n") 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /static/scss/components/_color.scss: -------------------------------------------------------------------------------- 1 | .color { 2 | @include controls-button; 3 | 4 | > .preview { @include controls-toggle; } 5 | > .label { @include controls-label; } 6 | 7 | > input { 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | 12 | display: block; 13 | width: 100%; 14 | height: 100%; 15 | padding: 0; 16 | margin: 0; 17 | border: none; 18 | 19 | opacity: 0; 20 | cursor: pointer; 21 | } 22 | 23 | &:hover > .label, 24 | &.focus > .label { 25 | visibility: visible; 26 | opacity: 1; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /static/glsl/shaders/normal-vert.glsl: -------------------------------------------------------------------------------- 1 | {{{chunks.common}}} 2 | {{{chunks.uv_pars_vertex}}} 3 | {{{chunks.uv2_pars_vertex}}} 4 | {{{chunks.color_pars_vertex}}} 5 | {{{chunks.lerp_pos_pars_vertex}}} 6 | {{{chunks.logdepthbuf_pars_vertex}}} 7 | 8 | varying vec3 vNormal; 9 | 10 | void main() { 11 | {{{chunks.uv_vertex}}} 12 | {{{chunks.uv2_vertex}}} 13 | {{{chunks.color_vertex}}} 14 | 15 | {{{chunks.begin_vertex}}} 16 | {{{chunks.lerp_pos_vertex}}} 17 | {{{chunks.logdepthbuf_vertex}}} 18 | {{{chunks.worldpos_vertex}}} 19 | 20 | vNormal = normalize(position); 21 | } 22 | -------------------------------------------------------------------------------- /static/js/utils/Format.js: -------------------------------------------------------------------------------- 1 | var Format = App.Format = {}; 2 | 3 | Format.number = function (val) { 4 | var chars = ('' + val).split(''); 5 | var str = ''; 6 | var index; 7 | 8 | for (var i = 0, il = chars.length; i < il; i ++) { 9 | index = il - i - 1; 10 | str += chars[i]; 11 | if (index % 3 === 0 && index > 0) { 12 | str += ','; 13 | } 14 | } 15 | 16 | return str; 17 | }; 18 | 19 | Format.absoluteLength = function (val, length) { 20 | val = '' + val; 21 | while (val.length < length) { 22 | val = '0' + val; 23 | } 24 | return val; 25 | }; 26 | -------------------------------------------------------------------------------- /static/js/app.js: -------------------------------------------------------------------------------- 1 | require('js/application/App'); 2 | require('js/utils/*'); 3 | require('js/components/*'); 4 | require('js/constraints/*'); 5 | require('js/forces/*'); 6 | require('js/materials/*'); 7 | require('js/post-processing/*'); 8 | require('js/items/*'); 9 | require('js/controllers/*'); 10 | require('js/scenes/*'); 11 | 12 | setTimeout(function setup() { 13 | var DEBUG = true; 14 | if (DEBUG && location.search.indexOf('test=true') > -1) { 15 | App.run('tests'); 16 | } else { 17 | App.run('index'); 18 | App.log('Particulate.js ' + Particulate.VERSION); 19 | } 20 | }, 0); 21 | -------------------------------------------------------------------------------- /static/js/materials/AlphaMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.AlphaMaterial = AlphaMaterial; 6 | function AlphaMaterial(parameters) { 7 | parameters = parameters || {}; 8 | ShaderMaterial.call(this, parameters); 9 | } 10 | 11 | AlphaMaterial.prototype = Object.create(ShaderMaterial.prototype); 12 | 13 | AlphaMaterial.prototype.shader = { 14 | vertexShader : 'alpha-vert', 15 | fragmentShader : 'alpha-frag', 16 | 17 | uniforms : THREE.UniformsUtils.merge([ 18 | uniforms.common 19 | ]) 20 | }; 21 | -------------------------------------------------------------------------------- /static/glsl/shaders/dust-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 psColor; 2 | uniform float opacity; 3 | uniform float area; 4 | varying float centerDist; 5 | 6 | {{{chunks.common}}} 7 | {{{chunks.color_pars_fragment}}} 8 | {{{chunks.map_particle_pars_fragment}}} 9 | 10 | void main() { 11 | vec4 diffuseColor = vec4(psColor, opacity); 12 | float radius = area * 0.5; 13 | float illumination = max(0.0, (radius - centerDist) / radius); 14 | 15 | {{{chunks.map_particle_fragment}}} 16 | {{{chunks.color_fragment}}} 17 | 18 | gl_FragColor = vec4(diffuseColor.rgb, 19 | illumination * illumination * diffuseColor.a); 20 | } 21 | -------------------------------------------------------------------------------- /static/js/materials/UVMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.UVMaterial = UVMaterial; 6 | function UVMaterial(parameters) { 7 | parameters = parameters || {}; 8 | parameters.map = true; 9 | ShaderMaterial.call(this, parameters); 10 | } 11 | 12 | UVMaterial.prototype = Object.create(ShaderMaterial.prototype); 13 | 14 | UVMaterial.prototype.shader = { 15 | vertexShader : 'basic-vert', 16 | fragmentShader : 'uvs-frag', 17 | 18 | uniforms : THREE.UniformsUtils.merge([ 19 | uniforms.common 20 | ]) 21 | }; 22 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "require" : true, 4 | "THREE" : true, 5 | "Particulate" : true, 6 | "App" : true, 7 | "noise" : true 8 | }, 9 | 10 | "browser" : true, 11 | "curly" : true, 12 | "eqeqeq" : true, 13 | "eqnull" : true, 14 | "forin" : true, 15 | "indent" : 2, 16 | "latedef" : "nofunc", 17 | "maxcomplexity" : 10, 18 | "maxdepth" : 3, 19 | "newcap" : true, 20 | "noarg" : true, 21 | "quotmark" : "single", 22 | "undef" : true, 23 | "unused" : "vars" 24 | } 25 | -------------------------------------------------------------------------------- /static/js/utils/KeyDelegator.js: -------------------------------------------------------------------------------- 1 | App.KeyDelegator = KeyDelegator; 2 | function KeyDelegator() { 3 | this._bindings = {}; 4 | document.addEventListener('keyup', this.onDocumentKey.bind(this), false); 5 | } 6 | 7 | KeyDelegator.create = App.ctor(KeyDelegator); 8 | 9 | KeyDelegator.prototype.addBinding = function (key, context, fn) { 10 | this._bindings[key] = { 11 | context : context, 12 | fn : fn 13 | }; 14 | }; 15 | 16 | KeyDelegator.prototype.onDocumentKey = function (event) { 17 | var binding = this._bindings[event.which]; 18 | if (!binding) { return; } 19 | 20 | binding.context[binding.fn].call(binding.context, event); 21 | }; 22 | -------------------------------------------------------------------------------- /grunt/config/handlebars.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/gruntjs/grunt-contrib-handlebars 5 | 6 | // Precompile Handlebars templates to JST file. 7 | 8 | module.exports = function (config) { 9 | return { 10 | options : { 11 | processName : function (path) { 12 | var name = path.split('/').pop(); 13 | return name.split('.')[0]; 14 | } 15 | }, 16 | 17 | shaders : { 18 | options : { 19 | namespace : 'App.shaders' 20 | }, 21 | src : config.source + 'glsl/shaders/{,*/}*', 22 | dest : config.static + 'js/shaders.develop.js' 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /grunt/config/haychtml.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/timrwood/haychtml 5 | 6 | // Compiles html templates. 7 | 8 | module.exports = function (config) { 9 | return { 10 | develop : { 11 | engine: 'swig', 12 | src: config.pages, 13 | dest: './', 14 | data : { 15 | TEMPLATE_DEBUG : true, 16 | STATIC_URL : config.static 17 | } 18 | }, 19 | build : { 20 | engine: 'swig', 21 | src: config.pages, 22 | dest: './', 23 | data : { 24 | TEMPLATE_DEBUG : false, 25 | STATIC_URL : config.static 26 | } 27 | } 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /static/js/materials/GelMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.GelMaterial = GelMaterial; 6 | function GelMaterial(parameters) { 7 | parameters = parameters || {}; 8 | ShaderMaterial.call(this, parameters); 9 | } 10 | 11 | GelMaterial.prototype = Object.create(ShaderMaterial.prototype); 12 | 13 | GelMaterial.prototype.shader = { 14 | vertexShader : 'gel-vert', 15 | fragmentShader : 'gel-frag', 16 | 17 | uniforms : THREE.UniformsUtils.merge([ 18 | uniforms.common, 19 | { 20 | stepProgress : { type : 'f', value : 0 } 21 | } 22 | ]) 23 | }; 24 | -------------------------------------------------------------------------------- /static/js/materials/LerpMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.LerpMaterial = LerpMaterial; 6 | function LerpMaterial(parameters) { 7 | parameters = parameters || {}; 8 | ShaderMaterial.call(this, parameters); 9 | } 10 | 11 | LerpMaterial.prototype = Object.create(ShaderMaterial.prototype); 12 | 13 | LerpMaterial.prototype.shader = { 14 | vertexShader : 'lerp-vert', 15 | fragmentShader : 'basic-frag', 16 | 17 | uniforms : THREE.UniformsUtils.merge([ 18 | uniforms.common, 19 | { 20 | stepProgress : { type : 'f', value : 0 } 21 | } 22 | ]) 23 | }; 24 | -------------------------------------------------------------------------------- /grunt/config/autoprefixer.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/nDmitry/grunt-autoprefixer 5 | 6 | // Parses CSS and add vendor-prefixed properties using the Can I Use database. 7 | 8 | module.exports = function (config) { 9 | return { 10 | options: { 11 | browsers: ['> 1%', 'last 2 versions', 'ie 9'] 12 | }, 13 | build: { 14 | files : [{ 15 | expand: true, 16 | cwd: '.temp/css/', 17 | src: '*.css', 18 | dest: config.static + 'css/' 19 | }] 20 | }, 21 | develop: { 22 | files : '<%= autoprefixer.build.files %>', 23 | options: { 24 | map: true 25 | } 26 | } 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /static/glsl/shaders/dust-vert.glsl: -------------------------------------------------------------------------------- 1 | uniform float size; 2 | uniform float scale; 3 | uniform float time; 4 | uniform float area; 5 | varying float centerDist; 6 | 7 | void main() { 8 | float offsetY = mod(position.y - 1.0 * time, area) - area * 0.5; 9 | vec3 offsetPosition = vec3( 10 | position.x + sin(cos(offsetY * 0.1) + sin(offsetY * 0.1 + position.x * 0.1) * 2.0), 11 | offsetY, 12 | position.z + sin(cos(offsetY * 0.1) + sin(offsetY * 0.1 + position.z * 0.1) * 2.0)); 13 | 14 | centerDist = length(offsetPosition); 15 | 16 | vec4 mvPosition = modelViewMatrix * vec4(offsetPosition, 1.0); 17 | 18 | gl_PointSize = size * (scale / length(mvPosition.xyz)); 19 | gl_Position = projectionMatrix * mvPosition; 20 | } 21 | -------------------------------------------------------------------------------- /static/js/materials/DustMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.DustMaterial = DustMaterial; 6 | function DustMaterial(parameters) { 7 | parameters = parameters || {}; 8 | ShaderMaterial.call(this, parameters); 9 | } 10 | 11 | DustMaterial.prototype = Object.create(ShaderMaterial.prototype); 12 | 13 | DustMaterial.prototype.shader = { 14 | vertexShader : 'dust-vert', 15 | fragmentShader : 'dust-frag', 16 | 17 | uniforms : THREE.UniformsUtils.merge([ 18 | uniforms.common, 19 | uniforms.points, 20 | { 21 | time : { type : 'f', value : 0 }, 22 | area : { type : 'f', value : 1 } 23 | } 24 | ]) 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Numerous always-ignore extensions 2 | *.diff 3 | *.err 4 | *.orig 5 | *.rej 6 | *.swo 7 | *.swp 8 | *.vi 9 | *~ 10 | *.psd 11 | 12 | # OS or Editor folders 13 | .DS_Store 14 | Thumbs.db 15 | .cache 16 | .project 17 | .pydevproject 18 | .settings 19 | .tmproj 20 | *.esproj 21 | nbproject 22 | *.sublime-project 23 | *.sublime-workspace 24 | 25 | # Translations 26 | *.mo 27 | 28 | # Django 29 | *.log 30 | *.pot 31 | *.pyc 32 | local_settings.py 33 | 34 | # Installer logs 35 | pip-log.txt 36 | 37 | # Unit test / coverage reports 38 | .coverage 39 | .tox 40 | 41 | # Compiled static files 42 | build 43 | index.html 44 | 45 | # npm 46 | node_modules 47 | 48 | # Bower 49 | static/lib 50 | 51 | # Sass 52 | .sass-cache 53 | .temp 54 | -------------------------------------------------------------------------------- /static/js/materials/TentacleMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.TentacleMaterial = TentacleMaterial; 6 | function TentacleMaterial(parameters) { 7 | parameters = parameters || {}; 8 | ShaderMaterial.call(this, parameters); 9 | } 10 | 11 | TentacleMaterial.prototype = Object.create(ShaderMaterial.prototype); 12 | 13 | TentacleMaterial.prototype.shader = { 14 | vertexShader : 'tentacle-vert', 15 | fragmentShader : 'tentacle-frag', 16 | 17 | uniforms : THREE.UniformsUtils.merge([ 18 | uniforms.common, 19 | { 20 | stepProgress : { type : 'f', value : 0 }, 21 | area : { type : 'f', value : 1 } 22 | } 23 | ]) 24 | }; 25 | -------------------------------------------------------------------------------- /grunt/config/neuter.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/trek/grunt-neuter 5 | 6 | // Concatenate files in the order you require. 7 | 8 | module.exports = function (config) { 9 | return { 10 | options : { 11 | basePath : config.source 12 | }, 13 | app: { 14 | src: config.source + 'js/app.js', 15 | dest: config.static + 'js/app.develop.js' 16 | }, 17 | tests: { 18 | src: config.source + 'tests/tests.js', 19 | dest: config.static + 'js/tests.js' 20 | }, 21 | libs: { 22 | options: { 23 | template : '{%= src %}' 24 | }, 25 | src: config.source + 'js/libs.js', 26 | dest: config.static + 'js/libs.develop.js' 27 | } 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /static/js/materials/LerpPointMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.LerpPointMaterial = LerpPointMaterial; 6 | function LerpPointMaterial(parameters) { 7 | parameters = parameters || {}; 8 | parameters.sizeAttenuation = true; 9 | ShaderMaterial.call(this, parameters); 10 | } 11 | 12 | LerpPointMaterial.prototype = Object.create(ShaderMaterial.prototype); 13 | 14 | LerpPointMaterial.prototype.shader = { 15 | vertexShader : 'lerp-point-vert', 16 | fragmentShader : 'basic-point-frag', 17 | 18 | uniforms : THREE.UniformsUtils.merge([ 19 | uniforms.points, 20 | { 21 | stepProgress : { type : 'f', value : 0 } 22 | } 23 | ]) 24 | }; 25 | -------------------------------------------------------------------------------- /static/glsl/shaders/basic-point-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 psColor; 2 | uniform float opacity; 3 | 4 | {{{chunks.common}}} 5 | {{{chunks.color_pars_fragment}}} 6 | {{{chunks.map_particle_pars_fragment}}} 7 | {{{chunks.fog_pars_fragment}}} 8 | {{{chunks.shadowmap_pars_fragment}}} 9 | {{{chunks.logdepthbuf_pars_fragment}}} 10 | 11 | void main() { 12 | vec3 outgoingLight = vec3(0.0); 13 | vec4 diffuseColor = vec4(psColor, opacity); 14 | 15 | {{{chunks.logdepthbuf_fragment}}} 16 | {{{chunks.map_particle_fragment}}} 17 | {{{chunks.color_fragment}}} 18 | {{{chunks.alphatest_fragment}}} 19 | 20 | outgoingLight = diffuseColor.rgb; 21 | 22 | {{{chunks.shadowmap_fragment}}} 23 | {{{chunks.fog_fragment}}} 24 | 25 | gl_FragColor = vec4(outgoingLight, diffuseColor.a); 26 | } 27 | -------------------------------------------------------------------------------- /grunt/config/sass.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/sindresorhus/grunt-sass 5 | 6 | // Compile Sass to CSS using node-sass. 7 | 8 | module.exports = function (config) { 9 | return { 10 | options: { 11 | includePaths: [ 12 | config.source + 'lib' 13 | ] 14 | }, 15 | build: { 16 | files : [{ 17 | expand: true, 18 | cwd : config.source + 'scss/', 19 | src: '*.scss', 20 | dest: '.temp/css/', 21 | ext: '.css' 22 | }], 23 | options : { 24 | outputStyle : 'compressed' 25 | } 26 | }, 27 | develop: { 28 | files : '<%= sass.build.files %>', 29 | options : { 30 | sourceComments : 'map' 31 | } 32 | } 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /static/js/materials/BulbMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.BulbMaterial = BulbMaterial; 6 | function BulbMaterial(parameters) { 7 | parameters = parameters || {}; 8 | parameters.map = true; 9 | ShaderMaterial.call(this, parameters); 10 | } 11 | 12 | BulbMaterial.prototype = Object.create(ShaderMaterial.prototype); 13 | 14 | BulbMaterial.prototype.shader = { 15 | vertexShader : 'normal-vert', 16 | fragmentShader : 'bulb-frag', 17 | 18 | uniforms : THREE.UniformsUtils.merge([ 19 | uniforms.common, 20 | { 21 | diffuseB : { type : 'c', value : null }, 22 | stepProgress : { type : 'f', value : 0 }, 23 | time : { type : 'f', value : 0 } 24 | } 25 | ]) 26 | }; 27 | -------------------------------------------------------------------------------- /static/js/materials/TailMaterial.js: -------------------------------------------------------------------------------- 1 | require('./ShaderMaterial'); 2 | var ShaderMaterial = App.ShaderMaterial; 3 | var uniforms = THREE.UniformsLib; 4 | 5 | App.TailMaterial = TailMaterial; 6 | function TailMaterial(parameters) { 7 | parameters = parameters || {}; 8 | parameters.map = true; 9 | ShaderMaterial.call(this, parameters); 10 | } 11 | 12 | TailMaterial.prototype = Object.create(ShaderMaterial.prototype); 13 | 14 | TailMaterial.prototype.shader = { 15 | vertexShader : 'normal-vert', 16 | fragmentShader : 'tail-frag', 17 | 18 | uniforms : THREE.UniformsUtils.merge([ 19 | uniforms.common, 20 | { 21 | diffuseB : { type : 'c', value : null }, 22 | scale : { type : 'f', value : 1 }, 23 | stepProgress : { type : 'f', value : 0 } 24 | } 25 | ]) 26 | }; 27 | -------------------------------------------------------------------------------- /grunt/config/copy.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/gruntjs/grunt-contrib-copy 5 | 6 | // Copy files and folders. 7 | 8 | module.exports = function (config) { 9 | return { 10 | build: { 11 | expand: true, 12 | src: [ 13 | config.source + '.htaccess', 14 | config.source + 'img/{,*/}*.{jpg,jpeg,png,webp,gif,ico}', 15 | config.source + 'audio/{,*/}*.{mp3,ogg,wav}', 16 | config.source + 'fonts/*', 17 | config.source + 'lib/modernizr/modernizr.js' 18 | ], 19 | dest: config.deploy 20 | }, 21 | develop: { 22 | expand: true, 23 | src: [ 24 | config.source + 'scss/**/*.scss', 25 | config.source + 'lib/**/*.js' 26 | ], 27 | dest: config.deploy 28 | } 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /static/glsl/shaders/alpha-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform float opacity; 3 | varying float vAlpha; 4 | 5 | {{{chunks.common}}} 6 | {{{chunks.color_pars_fragment}}} 7 | {{{chunks.uv_pars_fragment}}} 8 | {{{chunks.map_pars_fragment}}} 9 | {{{chunks.logdepthbuf_pars_fragment}}} 10 | 11 | void main() { 12 | vec3 outgoingLight = vec3(0.0); 13 | vec4 diffuseColor = vec4(diffuse, opacity * vAlpha); 14 | vec3 totalAmbientLight = vec3(1.0); // hardwired 15 | vec3 shadowMask = vec3(1.0); 16 | 17 | {{{chunks.logdepthbuf_fragment}}} 18 | {{{chunks.map_fragment}}} 19 | {{{chunks.color_fragment}}} 20 | {{{chunks.alphatest_fragment}}} 21 | 22 | outgoingLight = diffuseColor.rgb * totalAmbientLight; 23 | 24 | {{{chunks.linear_to_gamma_fragment}}} 25 | 26 | gl_FragColor = vec4(outgoingLight, diffuseColor.a); 27 | } 28 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/CopyShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Full-screen textured quad shader 5 | */ 6 | 7 | THREE.CopyShader = { 8 | 9 | uniforms: { 10 | 11 | "tDiffuse": { type: "t", value: null }, 12 | "opacity": { type: "f", value: 1.0 } 13 | 14 | }, 15 | 16 | vertexShader: [ 17 | 18 | "varying vec2 vUv;", 19 | 20 | "void main() {", 21 | 22 | "vUv = uv;", 23 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 24 | 25 | "}" 26 | 27 | ].join("\n"), 28 | 29 | fragmentShader: [ 30 | 31 | "uniform float opacity;", 32 | 33 | "uniform sampler2D tDiffuse;", 34 | 35 | "varying vec2 vUv;", 36 | 37 | "void main() {", 38 | 39 | "vec4 texel = texture2D( tDiffuse, vUv );", 40 | "gl_FragColor = opacity * texel;", 41 | 42 | "}" 43 | 44 | ].join("\n") 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /static/tests/assert/close-enough.js: -------------------------------------------------------------------------------- 1 | Test.assert.close = assert_close; 2 | function assert_close(actual, expected, maxDifference, message) { 3 | var passes = (actual === expected) || Math.abs(actual - expected) <= maxDifference; 4 | QUnit.push(passes, actual, expected, message); 5 | } 6 | 7 | Test.assert.closeArray = assert_closeMany; 8 | function assert_closeMany(actual, expected, maxDifference, message) { 9 | var passes; 10 | for (var i = 0, il = actual.length; i < il; i ++) { 11 | passes = (actual[i] === expected[i]) || Math.abs(actual[i] - expected[i]) <= maxDifference; 12 | if (!passes) { break; } 13 | } 14 | QUnit.push(passes, actual, expected, message); 15 | } 16 | 17 | Test.assert.notClose = assert_notClose; 18 | function assert_notClose(actual, expected, minDifference, message) { 19 | QUnit.push(Math.abs(actual - expected) > minDifference, actual, expected, message); 20 | } 21 | -------------------------------------------------------------------------------- /static/scss/apps/_application.scss: -------------------------------------------------------------------------------- 1 | body { 2 | @include absolute(0); 3 | width: 100%; 4 | height: 100%; 5 | background: #100A17; 6 | } 7 | 8 | .modal { 9 | $width: 300px; 10 | $height: 220px; 11 | 12 | position: absolute; 13 | z-index: 2; 14 | top: 50%; 15 | left: 50%; 16 | 17 | width: $width; 18 | padding: 20px; 19 | margin-left: -$width / 2; 20 | margin-top: -$height / 2; 21 | 22 | border-radius: 4px; 23 | background: rgba(#100A17, 0.9); 24 | opacity: 0; 25 | visibility: hidden; 26 | transform: scale(0.95); 27 | transition: all 200ms ease-in; 28 | 29 | &.active, 30 | &.active + .modal-cover { 31 | transform: scale(1); 32 | opacity: 1; 33 | visibility: visible; 34 | } 35 | } 36 | 37 | .modal-cover { 38 | @include absolute(0); 39 | z-index: 1; 40 | background: rgba(#100A17, 0.5); 41 | opacity: 0; 42 | visibility: hidden; 43 | transition: all 200ms ease-in; 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "particulate-medusae", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "three": "^0.72.0" 7 | }, 8 | "devDependencies": { 9 | "grunt": "~0.4.5", 10 | "grunt-autoprefixer": "~3.0.3", 11 | "grunt-contrib-clean": "~0.7.0", 12 | "grunt-contrib-connect": "~0.11.2", 13 | "grunt-contrib-copy": "~0.8.2", 14 | "grunt-contrib-handlebars": "^0.11.0", 15 | "grunt-contrib-jshint": "~0.11.3", 16 | "grunt-contrib-uglify": "~0.11.0", 17 | "grunt-contrib-watch": "~0.6.1", 18 | "grunt-haychtml": "~0.1.1", 19 | "grunt-neuter": "~0.6.0", 20 | "grunt-newer": "~1.1.1", 21 | "grunt-notify": "~0.4.3", 22 | "grunt-sass": "~1.1.0", 23 | "jit-grunt": "~0.9.1", 24 | "swig": "~1.4.2", 25 | "time-grunt": "~1.2.2" 26 | }, 27 | "engines": { 28 | "node": ">=0.8.0" 29 | }, 30 | "scripts": { 31 | "postinstall": "sh postinstall.sh" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/LuminosityShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Luminosity 5 | * http://en.wikipedia.org/wiki/Luminosity 6 | */ 7 | 8 | THREE.LuminosityShader = { 9 | 10 | uniforms: { 11 | 12 | "tDiffuse": { type: "t", value: null } 13 | 14 | }, 15 | 16 | vertexShader: [ 17 | 18 | "varying vec2 vUv;", 19 | 20 | "void main() {", 21 | 22 | "vUv = uv;", 23 | 24 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 25 | 26 | "}" 27 | 28 | ].join("\n"), 29 | 30 | fragmentShader: [ 31 | 32 | "uniform sampler2D tDiffuse;", 33 | 34 | "varying vec2 vUv;", 35 | 36 | "void main() {", 37 | 38 | "vec4 texel = texture2D( tDiffuse, vUv );", 39 | 40 | "vec3 luma = vec3( 0.299, 0.587, 0.114 );", 41 | 42 | "float v = dot( texel.xyz, luma );", 43 | 44 | "gl_FragColor = vec4( v, v, v, texel.w );", 45 | 46 | "}" 47 | 48 | ].join("\n") 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /static/glsl/shaders/basic-vert.glsl: -------------------------------------------------------------------------------- 1 | {{{chunks.common}}} 2 | {{{chunks.uv_pars_vertex}}} 3 | {{{chunks.uv2_pars_vertex}}} 4 | {{{chunks.envmap_pars_vertex}}} 5 | {{{chunks.color_pars_vertex}}} 6 | {{{chunks.morphtarget_pars_vertex}}} 7 | {{{chunks.skinning_pars_vertex}}} 8 | {{{chunks.shadowmap_pars_vertex}}} 9 | {{{chunks.logdepthbuf_pars_vertex}}} 10 | 11 | void main() { 12 | {{{chunks.uv_vertex}}} 13 | {{{chunks.uv2_vertex}}} 14 | {{{chunks.color_vertex}}} 15 | {{{chunks.skinbase_vertex}}} 16 | 17 | #ifdef USE_ENVMAP 18 | {{{chunks.beginnormal_vertex}}} 19 | {{{chunks.morphnormal_vertex}}} 20 | {{{chunks.skinnormal_vertex}}} 21 | {{{chunks.defaultnormal_vertex}}} 22 | #endif 23 | 24 | {{{chunks.begin_vertex}}} 25 | {{{chunks.morphtarget_vertex}}} 26 | {{{chunks.skinning_vertex}}} 27 | {{{chunks.project_vertex}}} 28 | {{{chunks.logdepthbuf_vertex}}} 29 | 30 | {{{chunks.worldpos_vertex}}} 31 | {{{chunks.envmap_vertex}}} 32 | {{{chunks.shadowmap_vertex}}} 33 | } 34 | -------------------------------------------------------------------------------- /static/js/libs.js: -------------------------------------------------------------------------------- 1 | require('lib/native-promise-only/lib/npo.src'); 2 | require('lib/handlebars/handlebars.runtime'); 3 | require('lib/particulate/dist/particulate'); 4 | require('lib/noise/perlin'); 5 | 6 | require('lib-extras/three/controls/TrackballControls'); 7 | require('lib-extras/three/geometries/PlaneBufferGeometry'); 8 | require('lib-extras/three/geometries/SphereBufferGeometry'); 9 | 10 | require('lib-extras/three/shaders/BasicShader'); 11 | require('lib-extras/three/shaders/CopyShader'); 12 | require('lib-extras/three/shaders/ConvolutionShader'); 13 | require('lib-extras/three/shaders/VignetteShader'); 14 | 15 | require('lib-extras/three/postprocessing/EffectComposer'); 16 | require('lib-extras/three/postprocessing/RenderPass'); 17 | require('lib-extras/three/postprocessing/SavePass'); 18 | require('lib-extras/three/postprocessing/ShaderPass'); 19 | require('lib-extras/three/postprocessing/TexturePass'); 20 | require('lib-extras/three/postprocessing/MaskPass'); 21 | require('lib-extras/three/postprocessing/BloomPass'); 22 | -------------------------------------------------------------------------------- /static/tests/post-processing/LensDirtPass.js: -------------------------------------------------------------------------------- 1 | module('Pass.LensDirt'); 2 | 3 | var _slice = Array.prototype.slice; 4 | var equalArray = Test.assert.equalArray; 5 | var pass = App.LensDirtPass; 6 | 7 | test('Set quad uvs', function () { 8 | var count = 5; 9 | var cells = 2; 10 | var uvAttr = pass.prototype._quadGeomUv(count, cells); 11 | var uvArray = uvAttr.array; 12 | 13 | var c0 = 0; 14 | var c1 = 1 / cells; 15 | var c2 = c1 * 2; 16 | 17 | equalArray(_slice.call(uvArray, 0, 8), 18 | [c0, c0, c1, c0, c1, c1, c0, c1], 19 | 'first quad uvs'); 20 | equalArray(_slice.call(uvArray, 8, 16), 21 | [c1, c0, c2, c0, c2, c1, c1, c1], 22 | 'second quad uvs'); 23 | equalArray(_slice.call(uvArray, 16, 24), 24 | [c0, c1, c1, c1, c1, c2, c0, c2], 25 | 'third quad uvs'); 26 | equalArray(_slice.call(uvArray, 24, 32), 27 | [c1, c1, c2, c1, c2, c2, c1, c2], 28 | 'fourth quad uvs'); 29 | equalArray(_slice.call(uvArray, 32, 40), 30 | [c0, c0, c1, c0, c1, c1, c0, c1], 31 | 'fifth quad uvs'); 32 | }); 33 | -------------------------------------------------------------------------------- /static/js/components/ModalComponent.js: -------------------------------------------------------------------------------- 1 | App.ModalComponent = ModalComponent; 2 | function ModalComponent(config) { 3 | var name = config.name; 4 | var cover = document.getElementById('cover-' + name); 5 | var toggle = this.toggle = document.getElementById('toggle-' + name); 6 | var modal = this.modal = document.getElementById(name); 7 | 8 | this.isActive = false; 9 | this._toggleClassName = toggle.className; 10 | this._modalClassName = modal.className; 11 | 12 | toggle.addEventListener('click', this.toggleState.bind(this), false); 13 | cover.addEventListener('click', this.toggleState.bind(this), false); 14 | } 15 | 16 | ModalComponent.create = App.ctor(ModalComponent); 17 | 18 | ModalComponent.prototype.toggleState = function (event) { 19 | if (this.isActive) { 20 | this.modal.className = this._modalClassName; 21 | this.toggle.className = this._toggleClassName; 22 | this.isActive = false; 23 | } else { 24 | this.modal.className += ' active'; 25 | this.toggle.className += ' active'; 26 | this.isActive = true; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /static/js/utils/Dispatcher.js: -------------------------------------------------------------------------------- 1 | var Dispatcher = App.Dispatcher = {}; 2 | 3 | Dispatcher.extend = function (proto) { 4 | proto.addListener = addListener; 5 | proto.triggerListeners = triggerListeners; 6 | }; 7 | 8 | function addListener(type, context, fn) { 9 | var listeners = this._listeners; 10 | if (!listeners) { listeners = this._listeners = {}; } 11 | if (!listeners[type]) { listeners[type] = []; } 12 | 13 | listeners[type].push({ 14 | context : context, 15 | fn : fn 16 | }); 17 | } 18 | 19 | function triggerListeners(type, event) { 20 | var listeners = this._listeners && this._listeners[type]; 21 | if (!listeners) { return; } 22 | var listener, context, fn; 23 | 24 | for (var i = 0, il = listeners.length; i < il; i ++) { 25 | listener = listeners[i]; 26 | context = listener.context; 27 | fn = listener.fn; 28 | 29 | if (typeof context === 'function') { 30 | fn = context; 31 | context = null; 32 | } else if (typeof fn === 'string') { 33 | fn = context[fn]; 34 | } 35 | 36 | fn.call(context, event); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /static/js/utils/Links.js: -------------------------------------------------------------------------------- 1 | var Links = App.Links = {}; 2 | 3 | Links.line = function (index, howMany, buffer) { 4 | var a, b; 5 | 6 | for (var i = 0; i < howMany - 1; i ++) { 7 | a = index + i; 8 | b = index + i + 1; 9 | 10 | buffer.push(a, b); 11 | } 12 | 13 | return buffer; 14 | }; 15 | 16 | Links.loop = function (index, howMany, buffer) { 17 | var a, b; 18 | 19 | for (var i = 0; i < howMany - 1; i ++) { 20 | a = index + i; 21 | b = index + i + 1; 22 | 23 | buffer.push(a, b); 24 | } 25 | 26 | a = index; 27 | b = index + howMany - 1; 28 | 29 | buffer.push(a, b); 30 | 31 | return buffer; 32 | }; 33 | 34 | Links.rings = function (index0, index1, howMany, buffer) { 35 | var a, b; 36 | 37 | for (var i = 0; i < howMany; i ++) { 38 | a = index0 + i; 39 | b = index1 + i; 40 | 41 | buffer.push(a, b); 42 | } 43 | 44 | return buffer; 45 | }; 46 | 47 | Links.radial = function (indexCenter, index, howMany, buffer) { 48 | var b; 49 | 50 | for (var i = 0; i < howMany; i ++) { 51 | b = index + i; 52 | 53 | buffer.push(indexCenter, b); 54 | } 55 | 56 | return buffer; 57 | }; 58 | -------------------------------------------------------------------------------- /grunt/tasks/shaderChunks.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | 6 | module.exports = function (grunt) { 7 | grunt.config('shaderChunks', { 8 | develop : { 9 | src : 'static/glsl/shader-chunks/*', 10 | dest : 'build/static/js/shader-chunks.develop.js' 11 | } 12 | }); 13 | 14 | grunt.registerMultiTask('shaderChunks', 'Concatenate shader chunks', function () { 15 | function handleFile(file) { 16 | var out = ''; 17 | var src = path.resolve(file); 18 | var name = file.split('/').pop().split('.')[0]; 19 | var source = grunt.file.read(src); 20 | var sourceLines = source.split('\n'); 21 | 22 | out += 'THREE.ShaderChunk[\'' + name + '\'] = [\n'; 23 | 24 | sourceLines.forEach(function (line) { 25 | line = line.trim(); 26 | if (line.length && line.indexOf('//') !== 0) { 27 | out += '\'' + line.trim() + '\',\n'; 28 | } 29 | }); 30 | 31 | out += '].join(\'\\n\');'; 32 | return out; 33 | } 34 | 35 | this.files.forEach(function (files) { 36 | var contents = files.src.map(handleFile).join('\n'); 37 | grunt.file.write(path.resolve(files.dest), contents, { encoding : 'utf8' }); 38 | }); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /static/tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "App" : true, 4 | "require" : true, 5 | "Particulate" : true, 6 | "Test" : true, 7 | 8 | "QUnit" : false, 9 | "asyncTest" : false, 10 | "deepEqual" : false, 11 | "equal" : false, 12 | "expect" : false, 13 | "module" : false, 14 | "notDeepEqual" : false, 15 | "notEqual" : false, 16 | "notStrictEqual" : false, 17 | "ok" : false, 18 | "raises" : false, 19 | "start" : false, 20 | "stop" : false, 21 | "strictEqual" : false, 22 | "test" : false, 23 | "throws" : false 24 | }, 25 | 26 | "browser" : true, 27 | "curly" : true, 28 | "eqeqeq" : true, 29 | "eqnull" : true, 30 | "forin" : true, 31 | "indent" : 2, 32 | "latedef" : "nofunc", 33 | "maxcomplexity" : 10, 34 | "maxdepth" : 3, 35 | "newcap" : true, 36 | "noarg" : true, 37 | "quotmark" : "single", 38 | "undef" : true, 39 | "unused" : "vars" 40 | } 41 | -------------------------------------------------------------------------------- /static/glsl/shaders/basic-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform float opacity; 3 | 4 | {{{chunks.common}}} 5 | {{{chunks.color_pars_fragment}}} 6 | {{{chunks.uv_pars_fragment}}} 7 | {{{chunks.uv2_pars_fragment}}} 8 | {{{chunks.map_pars_fragment}}} 9 | {{{chunks.alphamap_pars_fragment}}} 10 | {{{chunks.aomap_pars_fragment}}} 11 | {{{chunks.envmap_pars_fragment}}} 12 | {{{chunks.fog_pars_fragment}}} 13 | {{{chunks.shadowmap_pars_fragment}}} 14 | {{{chunks.specularmap_pars_fragment}}} 15 | {{{chunks.logdepthbuf_pars_fragment}}} 16 | 17 | void main() { 18 | vec3 outgoingLight = vec3(0.0); 19 | vec4 diffuseColor = vec4(diffuse, opacity); 20 | vec3 totalAmbientLight = vec3(1.0); 21 | 22 | {{{chunks.logdepthbuf_fragment}}} 23 | {{{chunks.map_fragment}}} 24 | {{{chunks.color_fragment}}} 25 | {{{chunks.alphamap_fragment}}} 26 | {{{chunks.alphatest_fragment}}} 27 | {{{chunks.specularmap_fragment}}} 28 | {{{chunks.aomap_fragment}}} 29 | 30 | outgoingLight = diffuseColor.rgb * totalAmbientLight; 31 | 32 | {{{chunks.envmap_fragment}}} 33 | {{{chunks.shadowmap_fragment}}} 34 | {{{chunks.linear_to_gamma_fragment}}} 35 | {{{chunks.fog_fragment}}} 36 | 37 | gl_FragColor = vec4(outgoingLight, diffuseColor.a); 38 | } 39 | -------------------------------------------------------------------------------- /static/glsl/shaders/tail-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform vec3 diffuseB; 3 | uniform float opacity; 4 | uniform float scale; 5 | varying vec2 vUv; 6 | varying vec3 vNormal; 7 | 8 | const vec3 eye = vec3(0.0, 0.0, 1.0); 9 | 10 | float accumulate(vec2 uv, float saturation, float scale) { 11 | saturation -= sin(uv.y * 12.0 * scale) * 0.8 + uv.y * 1.5 + sin(uv.x * 20.0 * scale) * 0.1 + 0.85; 12 | 13 | saturation -= sin(uv.y * sin(uv.x * 5.0) * 5.0 * scale) * 0.05; 14 | saturation -= sin(uv.y * sin((1.0 - uv.x) * 5.0) * 5.0 * scale) * 0.05; 15 | 16 | saturation -= sin(uv.y * sin(uv.y + cos(uv.x) * 2.0) * 10.0 * scale) * 0.15; 17 | saturation -= sin(uv.y * sin(uv.y + cos(1.0 - uv.x) * 2.0) * 10.0 * scale) * 0.15; 18 | 19 | return saturation; 20 | } 21 | 22 | void main() { 23 | vec2 uv = vUv; 24 | vec3 normal = normalize(mat3(viewMatrix) * vNormal); 25 | float rim = 1.0 - max(dot(eye, normal), 0.0); 26 | float saturation = 0.0; 27 | 28 | saturation += accumulate(uv, 2.0, scale); 29 | saturation += max(accumulate(vec2(rim), 0.75, scale * 0.25), -0.25); 30 | 31 | gl_FragColor = vec4( 32 | mix(diffuseB, diffuse, saturation) * opacity, 33 | clamp(saturation, 0.2, 1.0) * opacity); 34 | } 35 | -------------------------------------------------------------------------------- /static/tests/tests.js: -------------------------------------------------------------------------------- 1 | window.Test = { assert : {} }; 2 | 3 | require('lib/qunit/qunit/qunit.js'); 4 | 5 | require('./assert/*'); 6 | require('./constraints/*'); 7 | require('./forces/*'); 8 | require('./post-processing/*'); 9 | 10 | function setFavicon(uri) { 11 | var link = document.getElementById('favicon'); 12 | if (link) { 13 | document.head.removeChild(link); 14 | } else { 15 | link = document.createElement('link'); 16 | } 17 | 18 | link.setAttribute('id', 'favicon'); 19 | link.setAttribute('type', 'image/x-icon'); 20 | link.setAttribute('rel', 'icon'); 21 | link.setAttribute('href', uri); 22 | document.head.appendChild(link); 23 | } 24 | 25 | QUnit.done(function (results) { 26 | if (results.failed) { 27 | setFavicon('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAANElEQVQ4T2NkoBAwUqifgboG/GdgACLCAGgr3GIUF4waAA7B0UCkNAwIp0FMFdTNC+S4AAAt7hQR+uwkyQAAAABJRU5ErkJggg=='); 28 | document.title = results.failed + ' of ' + results.total + ' failed.'; 29 | } else { 30 | setFavicon('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAM0lEQVQ4T2NkoBAwUqifgcoG/Gf4T5SLGBEWo7pg1AAGYBCOBiLFYUBUMkRVROW8QIYLACPuFBFvqDn4AAAAAElFTkSuQmCC'); 31 | document.title = 'All ' + results.total + ' passed.'; 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /static/js/utils/Faces.js: -------------------------------------------------------------------------------- 1 | var Faces = App.Faces = {}; 2 | 3 | Faces.quad = function (a, b, c, d, buffer) { 4 | buffer.push( 5 | a, b, c, 6 | c, d, a); 7 | 8 | return buffer; 9 | }; 10 | 11 | Faces.quadDoubleSide = function (a, b, c, d, buffer) { 12 | buffer.push( 13 | a, b, c, 14 | c, d, a, 15 | d, c, b, 16 | b, a, d); 17 | 18 | return buffer; 19 | }; 20 | 21 | Faces.radial = function (indexCenter, index, howMany, buffer) { 22 | var b, c; 23 | 24 | for (var i = 0, il = howMany - 1; i < il; i ++) { 25 | b = index + i + 1; 26 | c = index + i; 27 | 28 | buffer.push(indexCenter, b, c); 29 | } 30 | 31 | b = index; 32 | c = index + howMany - 1; 33 | 34 | buffer.push(indexCenter, b, c); 35 | 36 | return buffer; 37 | }; 38 | 39 | Faces.rings = function (index0, index1, howMany, buffer) { 40 | var a, b, c, d; 41 | 42 | for (var i = 0, il = howMany - 1; i < il; i ++) { 43 | a = index0 + i; 44 | b = index0 + i + 1; 45 | c = index1 + i + 1; 46 | d = index1 + i; 47 | 48 | buffer.push( 49 | a, b, c, 50 | c, d, a); 51 | } 52 | 53 | a = index0 + howMany - 1; 54 | b = index0; 55 | c = index1; 56 | d = index1 + howMany - 1; 57 | 58 | buffer.push( 59 | a, b, c, 60 | c, d, a); 61 | 62 | return buffer; 63 | }; 64 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/TexturePass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.TexturePass = function ( texture, opacity ) { 6 | 7 | if ( THREE.CopyShader === undefined ) 8 | console.error( "THREE.TexturePass relies on THREE.CopyShader" ); 9 | 10 | var shader = THREE.CopyShader; 11 | 12 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 13 | 14 | this.uniforms[ "opacity" ].value = ( opacity !== undefined ) ? opacity : 1.0; 15 | this.uniforms[ "tDiffuse" ].value = texture; 16 | 17 | this.material = new THREE.ShaderMaterial( { 18 | 19 | uniforms: this.uniforms, 20 | vertexShader: shader.vertexShader, 21 | fragmentShader: shader.fragmentShader 22 | 23 | } ); 24 | 25 | this.enabled = true; 26 | this.needsSwap = false; 27 | 28 | 29 | this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); 30 | this.scene = new THREE.Scene(); 31 | 32 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 33 | this.scene.add( this.quad ); 34 | 35 | }; 36 | 37 | THREE.TexturePass.prototype = { 38 | 39 | render: function ( renderer, writeBuffer, readBuffer, delta ) { 40 | 41 | this.quad.material = this.material; 42 | 43 | renderer.render( this.scene, this.camera, readBuffer ); 44 | 45 | } 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /static/js/forces/PointRepulsorForce.js: -------------------------------------------------------------------------------- 1 | var PMath = Particulate.Math; 2 | App.PointRepulsorForce = PointRepulsorForce; 3 | 4 | function PointRepulsorForce(position, opts) { 5 | opts = opts || {}; 6 | Particulate.Force.apply(this, arguments); 7 | 8 | this.position = this.vector; 9 | this.intensity = opts.intensity != null ? opts.intensity : 0.05; 10 | this.setRadius(opts.radius || 0); 11 | } 12 | 13 | PointRepulsorForce.create = Particulate.ctor(PointRepulsorForce); 14 | PointRepulsorForce.prototype = Object.create(Particulate.Force.prototype); 15 | PointRepulsorForce.prototype.constructor = PointRepulsorForce; 16 | 17 | PointRepulsorForce.prototype.setRadius = function (r) { 18 | this._radius2 = r * r; 19 | }; 20 | 21 | PointRepulsorForce.prototype.applyForce = function (ix, f0, p0, p1) { 22 | var v0 = this.vector; 23 | var iy = ix + 1; 24 | var iz = ix + 2; 25 | 26 | var dx = p0[ix] - v0[0]; 27 | var dy = p0[iy] - v0[1]; 28 | var dz = p0[iz] - v0[2]; 29 | 30 | var dist = dx * dx + dy * dy + dz * dz; 31 | var diff = PMath.clamp(0.001, 100, 32 | dist - this._radius2 * this.intensity); 33 | var diffInv = 1 / diff; 34 | var scale = PMath.clamp(0, 10, 35 | diffInv * diffInv * diffInv); 36 | 37 | f0[ix] += dx * scale; 38 | f0[iy] += dy * scale; 39 | f0[iz] += dz * scale; 40 | }; 41 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/RenderPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) { 6 | 7 | this.scene = scene; 8 | this.camera = camera; 9 | 10 | this.overrideMaterial = overrideMaterial; 11 | 12 | this.clearColor = clearColor; 13 | this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 1; 14 | 15 | this.oldClearColor = new THREE.Color(); 16 | this.oldClearAlpha = 1; 17 | 18 | this.enabled = true; 19 | this.clear = true; 20 | this.needsSwap = false; 21 | 22 | }; 23 | 24 | THREE.RenderPass.prototype = { 25 | 26 | render: function ( renderer, writeBuffer, readBuffer, delta ) { 27 | 28 | this.scene.overrideMaterial = this.overrideMaterial; 29 | 30 | if ( this.clearColor ) { 31 | 32 | this.oldClearColor.copy( renderer.getClearColor() ); 33 | this.oldClearAlpha = renderer.getClearAlpha(); 34 | 35 | renderer.setClearColor( this.clearColor, this.clearAlpha ); 36 | 37 | } 38 | 39 | renderer.render( this.scene, this.camera, readBuffer, this.clear ); 40 | 41 | if ( this.clearColor ) { 42 | 43 | renderer.setClearColor( this.oldClearColor, this.oldClearAlpha ); 44 | 45 | } 46 | 47 | this.scene.overrideMaterial = null; 48 | 49 | } 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/NormalMapShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Normal map shader 5 | * - compute normals from heightmap 6 | */ 7 | 8 | THREE.NormalMapShader = { 9 | 10 | uniforms: { 11 | 12 | "heightMap": { type: "t", value: null }, 13 | "resolution": { type: "v2", value: new THREE.Vector2( 512, 512 ) }, 14 | "scale": { type: "v2", value: new THREE.Vector2( 1, 1 ) }, 15 | "height": { type: "f", value: 0.05 } 16 | 17 | }, 18 | 19 | vertexShader: [ 20 | 21 | "varying vec2 vUv;", 22 | 23 | "void main() {", 24 | 25 | "vUv = uv;", 26 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 27 | 28 | "}" 29 | 30 | ].join("\n"), 31 | 32 | fragmentShader: [ 33 | 34 | "uniform float height;", 35 | "uniform vec2 resolution;", 36 | "uniform sampler2D heightMap;", 37 | 38 | "varying vec2 vUv;", 39 | 40 | "void main() {", 41 | 42 | "float val = texture2D( heightMap, vUv ).x;", 43 | 44 | "float valU = texture2D( heightMap, vUv + vec2( 1.0 / resolution.x, 0.0 ) ).x;", 45 | "float valV = texture2D( heightMap, vUv + vec2( 0.0, 1.0 / resolution.y ) ).x;", 46 | 47 | "gl_FragColor = vec4( ( 0.5 * normalize( vec3( val - valU, val - valV, height ) ) + 0.5 ), 1.0 );", 48 | 49 | "}" 50 | 51 | ].join("\n") 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /static/js/constraints/LocalPlaneConstraint.js: -------------------------------------------------------------------------------- 1 | App.LocalPlaneConstraint = LocalPlaneConstraint; 2 | function LocalPlaneConstraint(planeA, planeB, planeC, a) { 3 | Particulate.PlaneConstraint.apply(this, arguments); 4 | this.distance = 0; 5 | } 6 | 7 | LocalPlaneConstraint.create = Particulate.ctor(LocalPlaneConstraint); 8 | LocalPlaneConstraint.prototype = Object.create(Particulate.PlaneConstraint.prototype); 9 | LocalPlaneConstraint.prototype.constructor = LocalPlaneConstraint; 10 | 11 | LocalPlaneConstraint.prototype.applyConstraint = function (index, p0, p1) { 12 | var b0 = this.bufferVec3; 13 | var ii = this.indices; 14 | var bi = ii[1], pi = ii[index + 3]; 15 | 16 | var bix = bi * 3, biy = bix + 1, biz = bix + 2; 17 | var pix = pi * 3, piy = pix + 1, piz = pix + 2; 18 | 19 | if (index === 0) { 20 | this._calculateNormal(index, p0); 21 | } 22 | 23 | if (!this._hasNormal) { return; } 24 | 25 | // N (plane normal vector) 26 | var nX = b0[0]; 27 | var nY = b0[1]; 28 | var nZ = b0[2]; 29 | 30 | // BP (B -> P) 31 | var opX = p0[pix] - p0[bix]; 32 | var opY = p0[piy] - p0[biy]; 33 | var opZ = p0[piz] - p0[biz]; 34 | 35 | // Project BP onto normal vector N 36 | var pt = opX * nX + opY * nY + opZ * nZ; 37 | if (pt > this.distance) { return; } 38 | 39 | p0[pix] -= nX * pt; 40 | p0[piy] -= nY * pt; 41 | p0[piz] -= nZ * pt; 42 | }; 43 | -------------------------------------------------------------------------------- /static/js/utils/Tweens.js: -------------------------------------------------------------------------------- 1 | var Tweens = App.Tweens = {}; 2 | 3 | Tweens.mapRange = function (a0, a1, b0, b1) { 4 | if (arguments.length === 2) { 5 | b1 = a1[1]; 6 | b0 = a1[0]; 7 | a1 = a0[1]; 8 | a0 = a0[0]; 9 | } 10 | 11 | var rangeAInv = 1 / (a1 - a0); 12 | var rangeB = b1 - b0; 13 | 14 | return function (x) { 15 | var t = (x - a0) * rangeAInv; 16 | return b0 + t * rangeB; 17 | }; 18 | }; 19 | 20 | // Tween to target by difference factor 21 | Tweens.factorTween = function (context, defaultFactor) { 22 | return function (name, target, instanceFactor) { 23 | var state = context[name]; 24 | if (state == null) { state = context[name] = target; } 25 | var factor = instanceFactor || defaultFactor; 26 | 27 | return context[name] += (target - state) * factor; 28 | }; 29 | }; 30 | 31 | // Tween to target by fixed step 32 | Tweens.stepTween = function (context, defaultStep) { 33 | return function (name, target, instanceStep) { 34 | var state = context[name]; 35 | if (state == null) { state = context[name] = target; } 36 | if (state === target) { return state; } 37 | var step = instanceStep || defaultStep; 38 | var dir = state < target ? 1 : -1; 39 | 40 | if ((target - state) * dir < step) { 41 | context[name] = target; 42 | return state; 43 | } 44 | 45 | return context[name] += step * dir; 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/ShaderPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.ShaderPass = function ( shader, textureID ) { 6 | 7 | this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; 8 | 9 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 10 | 11 | this.material = new THREE.ShaderMaterial( { 12 | 13 | uniforms: this.uniforms, 14 | vertexShader: shader.vertexShader, 15 | fragmentShader: shader.fragmentShader 16 | 17 | } ); 18 | 19 | this.renderToScreen = false; 20 | 21 | this.enabled = true; 22 | this.needsSwap = true; 23 | this.clear = false; 24 | 25 | 26 | this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); 27 | this.scene = new THREE.Scene(); 28 | 29 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 30 | this.scene.add( this.quad ); 31 | 32 | }; 33 | 34 | THREE.ShaderPass.prototype = { 35 | 36 | render: function ( renderer, writeBuffer, readBuffer, delta ) { 37 | 38 | if ( this.uniforms[ this.textureID ] ) { 39 | 40 | this.uniforms[ this.textureID ].value = readBuffer; 41 | 42 | } 43 | 44 | this.quad.material = this.material; 45 | 46 | if ( this.renderToScreen ) { 47 | 48 | renderer.render( this.scene, this.camera ); 49 | 50 | } else { 51 | 52 | renderer.render( this.scene, this.camera, writeBuffer, this.clear ); 53 | 54 | } 55 | 56 | } 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /grunt/config/watch.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | // https://github.com/gruntjs/grunt-contrib-watch 5 | 6 | // Run tasks whenever files are changed, added, or deleted. 7 | 8 | module.exports = function (config) { 9 | return { 10 | compass: { 11 | files: [config.source + 'scss/{,**/}{,*.scss,*.sass}'], 12 | tasks: ['sass:develop', 'autoprefixer:develop', 'newer:copy:develop'] 13 | }, 14 | haychtml: { 15 | files: [config.pages + '{,**/}*.html'], 16 | tasks: ['haychtml:develop'] 17 | }, 18 | neuter: { 19 | files: [config.source + 'js/{,**/}{,*.js}'], 20 | tasks: ['neuter:app'] 21 | }, 22 | neuterLibs: { 23 | files: [config.source + 'js/libs.js'], 24 | tasks: ['neuter:libs'] 25 | }, 26 | neuterTests: { 27 | files: [config.source + 'tests/{,**/}{,*.js}'], 28 | tasks: ['neuter:tests'] 29 | }, 30 | shaderChunks: { 31 | files: config.source + 'glsl/shader-chunks/*', 32 | tasks: ['shaderChunks'] 33 | }, 34 | handlebarsShaders: { 35 | files: config.source + 'glsl/shaders/*', 36 | tasks: ['handlebars:shaders'] 37 | }, 38 | copy : { 39 | files: '<%= copy.build.src %>', 40 | tasks: ['newer:copy:build'] 41 | }, 42 | livereload: { 43 | options: { 44 | debounceDelay: 250, 45 | livereload: 38000 46 | }, 47 | files: config.deploy + '**/*.{html,css,js,png,jpg,jpeg,gif,webp,svg}' 48 | } 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /static/js/utils/Looper.js: -------------------------------------------------------------------------------- 1 | /*global requestAnimationFrame*/ 2 | App.Looper = Looper; 3 | function Looper(context, update, render, delta) { 4 | var _this = this; 5 | var _update = context[update]; 6 | var _render = context[render]; 7 | 8 | var stepTime = 0; 9 | var targetDelta = delta || (1 / 30 * 1000); 10 | var maxDelta = targetDelta; 11 | 12 | var isLooping = false; 13 | var lastTime; 14 | 15 | function animateStep(delta) { 16 | stepTime += delta; 17 | var steps = Math.floor(stepTime / targetDelta); 18 | 19 | if (steps > 0) { 20 | stepTime -= steps * targetDelta; 21 | _this.didUpdate = true; 22 | } 23 | 24 | while (steps > 0) { 25 | _update.call(context, targetDelta); 26 | steps --; 27 | } 28 | 29 | var stepProgress = stepTime / targetDelta; 30 | _render.call(context, targetDelta, stepProgress); 31 | } 32 | 33 | function animate() { 34 | if (!isLooping) { return; } 35 | var time = Date.now(); 36 | var delta = Math.min(maxDelta, time - lastTime); 37 | 38 | _this.didUpdate = false; 39 | animateStep(delta); 40 | requestAnimationFrame(animate); 41 | lastTime = time; 42 | } 43 | 44 | this.stop = function () { 45 | isLooping = false; 46 | }; 47 | 48 | this.start = function () { 49 | lastTime = Date.now(); 50 | isLooping = true; 51 | animate(); 52 | }; 53 | 54 | this.toggle = function () { 55 | if (isLooping) { this.stop(); } 56 | else { this.start(); } 57 | }; 58 | } 59 | 60 | Looper.create = App.ctor(Looper); 61 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/BleachBypassShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Bleach bypass shader [http://en.wikipedia.org/wiki/Bleach_bypass] 5 | * - based on Nvidia example 6 | * http://developer.download.nvidia.com/shaderlibrary/webpages/shader_library.html#post_bleach_bypass 7 | */ 8 | 9 | THREE.BleachBypassShader = { 10 | 11 | uniforms: { 12 | 13 | "tDiffuse": { type: "t", value: null }, 14 | "opacity": { type: "f", value: 1.0 } 15 | 16 | }, 17 | 18 | vertexShader: [ 19 | 20 | "varying vec2 vUv;", 21 | 22 | "void main() {", 23 | 24 | "vUv = uv;", 25 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 26 | 27 | "}" 28 | 29 | ].join("\n"), 30 | 31 | fragmentShader: [ 32 | 33 | "uniform float opacity;", 34 | 35 | "uniform sampler2D tDiffuse;", 36 | 37 | "varying vec2 vUv;", 38 | 39 | "void main() {", 40 | 41 | "vec4 base = texture2D( tDiffuse, vUv );", 42 | 43 | "vec3 lumCoeff = vec3( 0.25, 0.65, 0.1 );", 44 | "float lum = dot( lumCoeff, base.rgb );", 45 | "vec3 blend = vec3( lum );", 46 | 47 | "float L = min( 1.0, max( 0.0, 10.0 * ( lum - 0.45 ) ) );", 48 | 49 | "vec3 result1 = 2.0 * base.rgb * blend;", 50 | "vec3 result2 = 1.0 - 2.0 * ( 1.0 - blend ) * ( 1.0 - base.rgb );", 51 | 52 | "vec3 newColor = mix( result1, result2, L );", 53 | 54 | "float A2 = opacity * base.a;", 55 | "vec3 mixRGB = A2 * newColor.rgb;", 56 | "mixRGB += ( ( 1.0 - A2 ) * base.rgb );", 57 | 58 | "gl_FragColor = vec4( mixRGB, base.a );", 59 | 60 | "}" 61 | 62 | ].join("\n") 63 | 64 | }; 65 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/VignetteShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Vignette shader 5 | * based on PaintEffect postprocess from ro.me 6 | * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js 7 | */ 8 | 9 | THREE.VignetteShader = { 10 | 11 | uniforms: { 12 | 13 | "tDiffuse": { type: "t", value: null }, 14 | "offset": { type: "f", value: 1.0 }, 15 | "darkness": { type: "f", value: 1.0 }, 16 | "color": { type: "c", value: null } 17 | 18 | }, 19 | 20 | vertexShader: [ 21 | 22 | "varying vec2 vUv;", 23 | 24 | "void main() {", 25 | 26 | "vUv = uv;", 27 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 28 | 29 | "}" 30 | 31 | ].join("\n"), 32 | 33 | fragmentShader: [ 34 | 35 | "uniform float offset;", 36 | "uniform float darkness;", 37 | "uniform vec3 color;", 38 | 39 | "uniform sampler2D tDiffuse;", 40 | 41 | "varying vec2 vUv;", 42 | 43 | "void main() {", 44 | 45 | // Eskil's vignette 46 | 47 | "vec4 texel = texture2D( tDiffuse, vUv );", 48 | "vec2 uv = ( vUv - vec2( 0.5 ) ) * vec2( offset );", 49 | "gl_FragColor = vec4( mix( texel.rgb, vec3( 1.0 - darkness ) * color, dot( uv, uv ) ), texel.a );", 50 | 51 | /* 52 | // alternative version from glfx.js 53 | // this one makes more "dusty" look (as opposed to "burned") 54 | 55 | "vec4 color = texture2D( tDiffuse, vUv );", 56 | "float dist = distance( vUv, vec2( 0.5 ) );", 57 | "color.rgb *= smoothstep( 0.8, offset * 0.799, dist *( darkness + offset ) );", 58 | "gl_FragColor = color;", 59 | */ 60 | 61 | "}" 62 | 63 | ].join("\n") 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/SavePass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.SavePass = function ( renderTarget ) { 6 | 7 | if ( THREE.CopyShader === undefined ) 8 | console.error( "THREE.SavePass relies on THREE.CopyShader" ); 9 | 10 | var shader = THREE.CopyShader; 11 | 12 | this.textureID = "tDiffuse"; 13 | 14 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 15 | 16 | this.material = new THREE.ShaderMaterial( { 17 | 18 | uniforms: this.uniforms, 19 | vertexShader: shader.vertexShader, 20 | fragmentShader: shader.fragmentShader 21 | 22 | } ); 23 | 24 | this.renderTarget = renderTarget; 25 | 26 | if ( this.renderTarget === undefined ) { 27 | 28 | this.renderTargetParameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBuffer: false }; 29 | this.renderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, this.renderTargetParameters ); 30 | 31 | } 32 | 33 | this.enabled = true; 34 | this.needsSwap = false; 35 | this.clear = false; 36 | 37 | 38 | this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); 39 | this.scene = new THREE.Scene(); 40 | 41 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 42 | this.scene.add( this.quad ); 43 | 44 | }; 45 | 46 | THREE.SavePass.prototype = { 47 | 48 | render: function ( renderer, writeBuffer, readBuffer, delta ) { 49 | 50 | if ( this.uniforms[ this.textureID ] ) { 51 | 52 | this.uniforms[ this.textureID ].value = readBuffer; 53 | 54 | } 55 | 56 | this.quad.material = this.material; 57 | 58 | renderer.render( this.scene, this.camera, this.renderTarget, this.clear ); 59 | 60 | } 61 | 62 | }; 63 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/FilmPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.FilmPass = function ( noiseIntensity, scanlinesIntensity, scanlinesCount, grayscale ) { 6 | 7 | if ( THREE.FilmShader === undefined ) 8 | console.error( "THREE.FilmPass relies on THREE.FilmShader" ); 9 | 10 | var shader = THREE.FilmShader; 11 | 12 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 13 | 14 | this.material = new THREE.ShaderMaterial( { 15 | 16 | uniforms: this.uniforms, 17 | vertexShader: shader.vertexShader, 18 | fragmentShader: shader.fragmentShader 19 | 20 | } ); 21 | 22 | if ( grayscale !== undefined ) this.uniforms.grayscale.value = grayscale; 23 | if ( noiseIntensity !== undefined ) this.uniforms.nIntensity.value = noiseIntensity; 24 | if ( scanlinesIntensity !== undefined ) this.uniforms.sIntensity.value = scanlinesIntensity; 25 | if ( scanlinesCount !== undefined ) this.uniforms.sCount.value = scanlinesCount; 26 | 27 | this.enabled = true; 28 | this.renderToScreen = false; 29 | this.needsSwap = true; 30 | 31 | 32 | this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); 33 | this.scene = new THREE.Scene(); 34 | 35 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 36 | this.scene.add( this.quad ); 37 | 38 | }; 39 | 40 | THREE.FilmPass.prototype = { 41 | 42 | render: function ( renderer, writeBuffer, readBuffer, delta ) { 43 | 44 | this.uniforms[ "tDiffuse" ].value = readBuffer; 45 | this.uniforms[ "time" ].value += delta; 46 | 47 | this.quad.material = this.material; 48 | 49 | if ( this.renderToScreen ) { 50 | 51 | renderer.render( this.scene, this.camera ); 52 | 53 | } else { 54 | 55 | renderer.render( this.scene, this.camera, writeBuffer, false ); 56 | 57 | } 58 | 59 | } 60 | 61 | }; 62 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/HueSaturationShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author tapio / http://tapio.github.com/ 3 | * 4 | * Hue and saturation adjustment 5 | * https://github.com/evanw/glfx.js 6 | * hue: -1 to 1 (-1 is 180 degrees in the negative direction, 0 is no change, etc. 7 | * saturation: -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast) 8 | */ 9 | 10 | THREE.HueSaturationShader = { 11 | 12 | uniforms: { 13 | 14 | "tDiffuse": { type: "t", value: null }, 15 | "hue": { type: "f", value: 0 }, 16 | "saturation": { type: "f", value: 0 } 17 | 18 | }, 19 | 20 | vertexShader: [ 21 | 22 | "varying vec2 vUv;", 23 | 24 | "void main() {", 25 | 26 | "vUv = uv;", 27 | 28 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 29 | 30 | "}" 31 | 32 | ].join("\n"), 33 | 34 | fragmentShader: [ 35 | 36 | "uniform sampler2D tDiffuse;", 37 | "uniform float hue;", 38 | "uniform float saturation;", 39 | 40 | "varying vec2 vUv;", 41 | 42 | "void main() {", 43 | 44 | "gl_FragColor = texture2D( tDiffuse, vUv );", 45 | 46 | // hue 47 | "float angle = hue * 3.14159265;", 48 | "float s = sin(angle), c = cos(angle);", 49 | "vec3 weights = (vec3(2.0 * c, -sqrt(3.0) * s - c, sqrt(3.0) * s - c) + 1.0) / 3.0;", 50 | "float len = length(gl_FragColor.rgb);", 51 | "gl_FragColor.rgb = vec3(", 52 | "dot(gl_FragColor.rgb, weights.xyz),", 53 | "dot(gl_FragColor.rgb, weights.zxy),", 54 | "dot(gl_FragColor.rgb, weights.yzx)", 55 | ");", 56 | 57 | // saturation 58 | "float average = (gl_FragColor.r + gl_FragColor.g + gl_FragColor.b) / 3.0;", 59 | "if (saturation > 0.0) {", 60 | "gl_FragColor.rgb += (average - gl_FragColor.rgb) * (1.0 - 1.0 / (1.001 - saturation));", 61 | "} else {", 62 | "gl_FragColor.rgb += (average - gl_FragColor.rgb) * (-saturation);", 63 | "}", 64 | 65 | "}" 66 | 67 | ].join("\n") 68 | 69 | }; 70 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/VerticalBlurShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author zz85 / http://www.lab4games.net/zz85/blog 3 | * 4 | * Two pass Gaussian blur filter (horizontal and vertical blur shaders) 5 | * - described in http://www.gamerendering.com/2008/10/11/gaussian-blur-filter-shader/ 6 | * and used in http://www.cake23.de/traveling-wavefronts-lit-up.html 7 | * 8 | * - 9 samples per pass 9 | * - standard deviation 2.7 10 | * - "h" and "v" parameters should be set to "1 / width" and "1 / height" 11 | */ 12 | 13 | THREE.VerticalBlurShader = { 14 | 15 | uniforms: { 16 | 17 | "tDiffuse": { type: "t", value: null }, 18 | "v": { type: "f", value: 1.0 / 512.0 } 19 | 20 | }, 21 | 22 | vertexShader: [ 23 | 24 | "varying vec2 vUv;", 25 | 26 | "void main() {", 27 | 28 | "vUv = uv;", 29 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 30 | 31 | "}" 32 | 33 | ].join("\n"), 34 | 35 | fragmentShader: [ 36 | 37 | "uniform sampler2D tDiffuse;", 38 | "uniform float v;", 39 | 40 | "varying vec2 vUv;", 41 | 42 | "void main() {", 43 | 44 | "vec4 sum = vec4( 0.0 );", 45 | 46 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 4.0 * v ) ) * 0.051;", 47 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 3.0 * v ) ) * 0.0918;", 48 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 2.0 * v ) ) * 0.12245;", 49 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 1.0 * v ) ) * 0.1531;", 50 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633;", 51 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 1.0 * v ) ) * 0.1531;", 52 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 2.0 * v ) ) * 0.12245;", 53 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 3.0 * v ) ) * 0.0918;", 54 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 4.0 * v ) ) * 0.051;", 55 | 56 | "gl_FragColor = sum;", 57 | 58 | "}" 59 | 60 | ].join("\n") 61 | 62 | }; 63 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/HorizontalBlurShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author zz85 / http://www.lab4games.net/zz85/blog 3 | * 4 | * Two pass Gaussian blur filter (horizontal and vertical blur shaders) 5 | * - described in http://www.gamerendering.com/2008/10/11/gaussian-blur-filter-shader/ 6 | * and used in http://www.cake23.de/traveling-wavefronts-lit-up.html 7 | * 8 | * - 9 samples per pass 9 | * - standard deviation 2.7 10 | * - "h" and "v" parameters should be set to "1 / width" and "1 / height" 11 | */ 12 | 13 | THREE.HorizontalBlurShader = { 14 | 15 | uniforms: { 16 | 17 | "tDiffuse": { type: "t", value: null }, 18 | "h": { type: "f", value: 1.0 / 512.0 } 19 | 20 | }, 21 | 22 | vertexShader: [ 23 | 24 | "varying vec2 vUv;", 25 | 26 | "void main() {", 27 | 28 | "vUv = uv;", 29 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 30 | 31 | "}" 32 | 33 | ].join("\n"), 34 | 35 | fragmentShader: [ 36 | 37 | "uniform sampler2D tDiffuse;", 38 | "uniform float h;", 39 | 40 | "varying vec2 vUv;", 41 | 42 | "void main() {", 43 | 44 | "vec4 sum = vec4( 0.0 );", 45 | 46 | "sum += texture2D( tDiffuse, vec2( vUv.x - 4.0 * h, vUv.y ) ) * 0.051;", 47 | "sum += texture2D( tDiffuse, vec2( vUv.x - 3.0 * h, vUv.y ) ) * 0.0918;", 48 | "sum += texture2D( tDiffuse, vec2( vUv.x - 2.0 * h, vUv.y ) ) * 0.12245;", 49 | "sum += texture2D( tDiffuse, vec2( vUv.x - 1.0 * h, vUv.y ) ) * 0.1531;", 50 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633;", 51 | "sum += texture2D( tDiffuse, vec2( vUv.x + 1.0 * h, vUv.y ) ) * 0.1531;", 52 | "sum += texture2D( tDiffuse, vec2( vUv.x + 2.0 * h, vUv.y ) ) * 0.12245;", 53 | "sum += texture2D( tDiffuse, vec2( vUv.x + 3.0 * h, vUv.y ) ) * 0.0918;", 54 | "sum += texture2D( tDiffuse, vec2( vUv.x + 4.0 * h, vUv.y ) ) * 0.051;", 55 | 56 | "gl_FragColor = sum;", 57 | 58 | "}" 59 | 60 | ].join("\n") 61 | 62 | }; 63 | -------------------------------------------------------------------------------- /static/js/components/ColorComponent.js: -------------------------------------------------------------------------------- 1 | App.ColorComponent = ColorComponent; 2 | function ColorComponent(opts) { 3 | opts = opts || {}; 4 | 5 | var element = this.element = document.createElement('div'); 6 | var input = this._input = document.createElement('input'); 7 | var preview = this._previewEl = document.createElement('div'); 8 | var label = this._labelEl = document.createElement('div'); 9 | 10 | this.color = opts.color || new THREE.Color(); 11 | this.setLabel(opts.label || ''); 12 | this.setValue(); 13 | this.syncState(); 14 | 15 | element.className = this._className = 'color'; 16 | preview.className = 'preview'; 17 | label.className = 'label'; 18 | 19 | input.setAttribute('type', 'color'); 20 | element.appendChild(preview); 21 | element.appendChild(label); 22 | element.appendChild(input); 23 | 24 | input.addEventListener('change', this.syncState.bind(this), false); 25 | input.addEventListener('focus', this.focus.bind(this), false); 26 | input.addEventListener('blur', this.blur.bind(this), false); 27 | } 28 | 29 | ColorComponent.create = App.ctor(ColorComponent); 30 | App.Dispatcher.extend(ColorComponent.prototype); 31 | 32 | ColorComponent.prototype.setLabel = function (label) { 33 | this._labelEl.textContent = label; 34 | }; 35 | 36 | ColorComponent.prototype.setValue = function (value) { 37 | value = value || ('#' + this.color.getHexString()); 38 | this._input.setAttribute('value', value); 39 | }; 40 | 41 | // FIXME: Losing focus when switching to color picker window 42 | ColorComponent.prototype.focus = function (event) { 43 | this.element.className = this._className + ' focus'; 44 | }; 45 | 46 | ColorComponent.prototype.blur = function (event) { 47 | this.element.className = this._className; 48 | }; 49 | 50 | ColorComponent.prototype.syncState = function (event) { 51 | var value = this._input.value; 52 | this._previewEl.style.background = value; 53 | this.color.setStyle(value); 54 | this.triggerListeners('change', value); 55 | }; 56 | -------------------------------------------------------------------------------- /static/js/materials/ShaderMaterial.js: -------------------------------------------------------------------------------- 1 | function compileShader(templateName) { 2 | var template = App.shaders[templateName]; 3 | return template({ 4 | chunks : THREE.ShaderChunk 5 | }); 6 | } 7 | 8 | App.ShaderMaterial = ShaderMaterial; 9 | function ShaderMaterial(parameters) { 10 | if (!this.shader) { return; } 11 | 12 | this.uniforms = THREE.UniformsUtils.clone(this.shader.uniforms); 13 | this.setUniformParameters(parameters); 14 | 15 | THREE.ShaderMaterial.call(this, { 16 | uniforms : this.uniforms, 17 | fragmentShader : compileShader(this.shader.fragmentShader), 18 | vertexShader : compileShader(this.shader.vertexShader) 19 | }); 20 | 21 | this.transparent = parameters.transparent || false; 22 | this.blending = parameters.blending || THREE.NormalBlending; 23 | this.side = parameters.side || THREE.FrontSide; 24 | this.linewidth = parameters.linewidth || 1; 25 | this.depthTest = parameters.depthTest != null ? parameters.depthTest : true; 26 | this.depthWrite = parameters.depthWrite != null ? parameters.depthWrite : true; 27 | 28 | this.size = parameters.size || 1; 29 | this.sizeAttenuation = parameters.sizeAttenuation; 30 | 31 | this.fog = !!parameters.fog; 32 | this.map = !!parameters.map; 33 | this.bumpMap = !!parameters.bumpMap; 34 | this.normalMap = !!parameters.normalMap; 35 | this.specularMap = !!parameters.specularMap; 36 | } 37 | 38 | ShaderMaterial.prototype = Object.create(THREE.ShaderMaterial.prototype); 39 | 40 | ShaderMaterial.prototype.setUniformParameters = function (parameters) { 41 | var uniforms = this.uniforms; 42 | Object.keys(parameters).forEach(function (key) { 43 | var uniform = uniforms[key]; 44 | if (!uniform) { return; } 45 | switch (uniform.type) { 46 | case 'c': 47 | this[key] = uniforms[key].value = new THREE.Color(parameters[key]); 48 | break; 49 | default: 50 | this[key] = uniforms[key].value = parameters[key]; 51 | break; 52 | } 53 | }.bind(this)); 54 | }; 55 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/HorizontalTiltShiftShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Simple fake tilt-shift effect, modulating two pass Gaussian blur (see above) by vertical position 5 | * 6 | * - 9 samples per pass 7 | * - standard deviation 2.7 8 | * - "h" and "v" parameters should be set to "1 / width" and "1 / height" 9 | * - "r" parameter control where "focused" horizontal line lies 10 | */ 11 | 12 | THREE.HorizontalTiltShiftShader = { 13 | 14 | uniforms: { 15 | 16 | "tDiffuse": { type: "t", value: null }, 17 | "h": { type: "f", value: 1.0 / 512.0 }, 18 | "r": { type: "f", value: 0.35 } 19 | 20 | }, 21 | 22 | vertexShader: [ 23 | 24 | "varying vec2 vUv;", 25 | 26 | "void main() {", 27 | 28 | "vUv = uv;", 29 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 30 | 31 | "}" 32 | 33 | ].join("\n"), 34 | 35 | fragmentShader: [ 36 | 37 | "uniform sampler2D tDiffuse;", 38 | "uniform float h;", 39 | "uniform float r;", 40 | 41 | "varying vec2 vUv;", 42 | 43 | "void main() {", 44 | 45 | "vec4 sum = vec4( 0.0 );", 46 | 47 | "float hh = h * abs( r - vUv.y );", 48 | 49 | "sum += texture2D( tDiffuse, vec2( vUv.x - 4.0 * hh, vUv.y ) ) * 0.051;", 50 | "sum += texture2D( tDiffuse, vec2( vUv.x - 3.0 * hh, vUv.y ) ) * 0.0918;", 51 | "sum += texture2D( tDiffuse, vec2( vUv.x - 2.0 * hh, vUv.y ) ) * 0.12245;", 52 | "sum += texture2D( tDiffuse, vec2( vUv.x - 1.0 * hh, vUv.y ) ) * 0.1531;", 53 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633;", 54 | "sum += texture2D( tDiffuse, vec2( vUv.x + 1.0 * hh, vUv.y ) ) * 0.1531;", 55 | "sum += texture2D( tDiffuse, vec2( vUv.x + 2.0 * hh, vUv.y ) ) * 0.12245;", 56 | "sum += texture2D( tDiffuse, vec2( vUv.x + 3.0 * hh, vUv.y ) ) * 0.0918;", 57 | "sum += texture2D( tDiffuse, vec2( vUv.x + 4.0 * hh, vUv.y ) ) * 0.051;", 58 | 59 | "gl_FragColor = sum;", 60 | 61 | "}" 62 | 63 | ].join("\n") 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/VerticalTiltShiftShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Simple fake tilt-shift effect, modulating two pass Gaussian blur (see above) by vertical position 5 | * 6 | * - 9 samples per pass 7 | * - standard deviation 2.7 8 | * - "h" and "v" parameters should be set to "1 / width" and "1 / height" 9 | * - "r" parameter control where "focused" horizontal line lies 10 | */ 11 | 12 | THREE.VerticalTiltShiftShader = { 13 | 14 | uniforms: { 15 | 16 | "tDiffuse": { type: "t", value: null }, 17 | "v": { type: "f", value: 1.0 / 512.0 }, 18 | "r": { type: "f", value: 0.35 } 19 | 20 | }, 21 | 22 | vertexShader: [ 23 | 24 | "varying vec2 vUv;", 25 | 26 | "void main() {", 27 | 28 | "vUv = uv;", 29 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 30 | 31 | "}" 32 | 33 | ].join("\n"), 34 | 35 | fragmentShader: [ 36 | 37 | "uniform sampler2D tDiffuse;", 38 | "uniform float v;", 39 | "uniform float r;", 40 | 41 | "varying vec2 vUv;", 42 | 43 | "void main() {", 44 | 45 | "vec4 sum = vec4( 0.0 );", 46 | 47 | "float vv = v * abs( r - vUv.y );", 48 | 49 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 4.0 * vv ) ) * 0.051;", 50 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 3.0 * vv ) ) * 0.0918;", 51 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 2.0 * vv ) ) * 0.12245;", 52 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 1.0 * vv ) ) * 0.1531;", 53 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633;", 54 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 1.0 * vv ) ) * 0.1531;", 55 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 2.0 * vv ) ) * 0.12245;", 56 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 3.0 * vv ) ) * 0.0918;", 57 | "sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 4.0 * vv ) ) * 0.051;", 58 | 59 | "gl_FragColor = sum;", 60 | 61 | "}" 62 | 63 | ].join("\n") 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/MaskPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.MaskPass = function ( scene, camera ) { 6 | 7 | this.scene = scene; 8 | this.camera = camera; 9 | 10 | this.enabled = true; 11 | this.clear = true; 12 | this.needsSwap = false; 13 | 14 | this.inverse = false; 15 | 16 | }; 17 | 18 | THREE.MaskPass.prototype = { 19 | 20 | render: function ( renderer, writeBuffer, readBuffer, delta ) { 21 | 22 | var context = renderer.context; 23 | 24 | // don't update color or depth 25 | 26 | context.colorMask( false, false, false, false ); 27 | context.depthMask( false ); 28 | 29 | // set up stencil 30 | 31 | var writeValue, clearValue; 32 | 33 | if ( this.inverse ) { 34 | 35 | writeValue = 0; 36 | clearValue = 1; 37 | 38 | } else { 39 | 40 | writeValue = 1; 41 | clearValue = 0; 42 | 43 | } 44 | 45 | context.enable( context.STENCIL_TEST ); 46 | context.stencilOp( context.REPLACE, context.REPLACE, context.REPLACE ); 47 | context.stencilFunc( context.ALWAYS, writeValue, 0xffffffff ); 48 | context.clearStencil( clearValue ); 49 | 50 | // draw into the stencil buffer 51 | 52 | renderer.render( this.scene, this.camera, readBuffer, this.clear ); 53 | renderer.render( this.scene, this.camera, writeBuffer, this.clear ); 54 | 55 | // re-enable update of color and depth 56 | 57 | context.colorMask( true, true, true, true ); 58 | context.depthMask( true ); 59 | 60 | // only render where stencil is set to 1 61 | 62 | context.stencilFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1 63 | context.stencilOp( context.KEEP, context.KEEP, context.KEEP ); 64 | 65 | } 66 | 67 | }; 68 | 69 | 70 | THREE.ClearMaskPass = function () { 71 | 72 | this.enabled = true; 73 | 74 | }; 75 | 76 | THREE.ClearMaskPass.prototype = { 77 | 78 | render: function ( renderer, writeBuffer, readBuffer, delta ) { 79 | 80 | var context = renderer.context; 81 | 82 | context.disable( context.STENCIL_TEST ); 83 | 84 | } 85 | 86 | }; 87 | -------------------------------------------------------------------------------- /static/glsl/shaders/bulb-frag.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform vec3 diffuseB; 3 | uniform float opacity; 4 | uniform float time; 5 | varying vec2 vUv; 6 | varying vec3 vNormal; 7 | 8 | const vec3 eye = vec3(0.0, 0.0, 1.0); 9 | 10 | float oscillate(float vMin, float vMax, float t) { 11 | float halfRange = (vMax - vMin) / vMax * 0.5; 12 | return (sin(t) * halfRange + (1.0 - halfRange)) * vMax; 13 | } 14 | 15 | float accumulate(vec2 uv, float saturation, float scale) { 16 | saturation -= sin(uv.x * 60.0) * 0.25 + sin(uv.x * 50.0 * scale) * 0.25 + 0.75; 17 | 18 | saturation -= sin(uv.y * sin(uv.x * 5.0) * 5.0 * scale) * 0.05; 19 | saturation -= sin(uv.y * sin((1.0 - uv.x) * 5.0) * 5.0 * scale) * 0.05; 20 | 21 | saturation -= sin(uv.y * sin(uv.y + cos(uv.x) * 2.0) * 3.0 * scale) * 0.15; 22 | saturation -= sin(uv.y * sin(uv.y + cos(1.0 - uv.x) * 2.0) * 3.0 * scale) * 0.15; 23 | 24 | saturation -= sin((uv.y - 1.5) * sin(uv.y + cos(uv.x - 1.0) * 2.0) * 4.0 * scale) * 0.15; 25 | saturation -= sin((uv.y - 1.5) * sin(uv.y + cos(uv.x) * 2.0) * 3.0 * scale) * 0.15; 26 | 27 | saturation -= sin(uv.y * 5.0) * 0.15 + sin(uv.y * 2.5) * 1.25; 28 | 29 | return saturation; 30 | } 31 | 32 | void main() { 33 | vec3 normal = normalize(mat3(viewMatrix) * vNormal); 34 | float rim = 1.0 - max(dot(eye, normal), 0.0); 35 | float saturation = 0.0; 36 | 37 | vec2 uv0 = vUv; 38 | vec2 uv1 = uv0 + rim; 39 | vec2 uv2 = vec2(-rim * 0.25); 40 | vec2 uv3 = vec2(rim, uv0.y); 41 | 42 | float scale0 = oscillate( 8.0, 15.0, time * 0.25 + 0.5); 43 | float scale1 = oscillate(12.0, 20.0, time * 0.125); 44 | float scale2 = 1.0; 45 | float scale3 = 1.0; 46 | 47 | saturation += max(accumulate(uv0, 2.0, scale0), -0.5); 48 | saturation += max(accumulate(uv1, 2.0, scale1), 0.25); 49 | saturation += max(accumulate(uv2, 1.0, scale2), -0.25); 50 | saturation += max(accumulate(uv3, 1.0, scale3), -0.25); 51 | 52 | gl_FragColor = vec4( 53 | mix(diffuse, diffuseB, smoothstep(-0.5, 0.5, saturation)), 54 | (1.0 - smoothstep(-0.5, 2.5, saturation)) * opacity); 55 | } 56 | -------------------------------------------------------------------------------- /static/tests/constraints/LocalPlaneConstraint.js: -------------------------------------------------------------------------------- 1 | module('Constraint.LocalPlane'); 2 | 3 | var ParticleSystem = Particulate.ParticleSystem; 4 | var LocalPlaneConstraint = App.LocalPlaneConstraint; 5 | var Vec3 = Particulate.Vec3; 6 | 7 | test('Creation', function () { 8 | var pa = 0, pb = 1, pc = 2; 9 | var a = 3; 10 | var indices = [3, 4, 5]; 11 | var fromArgs = LocalPlaneConstraint.create(pa, pb, pc, a); 12 | var fromArray = LocalPlaneConstraint.create(pa, pb, pc, indices); 13 | 14 | Test.assert.equalArray(fromArgs.indices, [pa, pb, pc, a], 15 | 'Should create indices from int arguments.'); 16 | Test.assert.equalArray(fromArray.indices, [pa, pb, pc].concat(indices), 17 | 'Should create indices from int array.'); 18 | }); 19 | 20 | function testPlane(expectedZ, v0, v1, v2) { 21 | var system = ParticleSystem.create(10, 10); 22 | var singleIndex = 3; 23 | var single = LocalPlaneConstraint.create(0, 1, 2, singleIndex); 24 | var manyIndices = [4, 5, 6, 7, 8, 9]; 25 | var many = LocalPlaneConstraint.create(0, 1, 2, manyIndices); 26 | var pos = Vec3.create(); 27 | 28 | function getZ(index) { 29 | return system.getPosition(index, pos)[2]; 30 | } 31 | 32 | function returnExpected() { 33 | return expectedZ; 34 | } 35 | 36 | system.setPosition(0, v0); 37 | system.setPosition(1, v1); 38 | system.setPosition(2, v2); 39 | 40 | system.addConstraint(single); 41 | system.addConstraint(many); 42 | system.tick(20); 43 | 44 | Test.assert.closeArray(many.bufferVec3, [0, 0, 1], 0.1, 45 | 'Should cache plane normal vector.'); 46 | Test.assert.close(getZ(singleIndex), expectedZ, 0.1, 47 | 'Should constrain single set of particles to plane.'); 48 | Test.assert.closeArray(manyIndices.map(getZ), manyIndices.map(returnExpected), 0.1, 49 | 'Should constrain multiple sets of particles to plane.'); 50 | } 51 | 52 | test('Application', function () { 53 | testPlane(10, 54 | [25, 15, 10], 55 | [10, 10, 10], 56 | [50, 30, 10]); 57 | }); 58 | 59 | test('Application with inline segments', function () { 60 | testPlane(10, 61 | [ 5, 5, 10], 62 | [10, 10, 10], 63 | [15, 15, 10]); 64 | }); 65 | 66 | // Plane behind particles, should have no effect 67 | test('Non-application when behind particles', function () { 68 | testPlane(0, 69 | [10, 10, -2], 70 | [ 0, 0, -2], 71 | [20, 20, -2]); 72 | }); 73 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var CONFIG = { 5 | pages: 'pages/', 6 | source: 'static/', 7 | static: './build/static/', 8 | deploy: './build/' 9 | }; 10 | 11 | module.exports = function (grunt) { 12 | require('time-grunt')(grunt); 13 | require('jit-grunt')(grunt)({ 14 | loadTasks : 'grunt/tasks' 15 | }); 16 | 17 | [ 18 | 'autoprefixer', 19 | 'clean', 20 | 'connect', 21 | 'copy', 22 | 'handlebars', 23 | 'haychtml', 24 | 'jshint', 25 | 'neuter', 26 | 'notify', 27 | 'sass', 28 | 'uglify', 29 | 'watch' 30 | ].forEach(function (key) { 31 | grunt.config(key, require('./grunt/config/' + key)(CONFIG)); 32 | }); 33 | 34 | grunt.registerTask('server', function (port) { 35 | var livereloadPort = Math.round(port) + 30000; 36 | if (port) { 37 | grunt.config('watch.livereload.options.livereload', livereloadPort); 38 | grunt.config('connect.options.livereload', livereloadPort); 39 | grunt.config('connect.options.port', port); 40 | } 41 | 42 | grunt.task.run([ 43 | // Run tasks once before starting watchers 44 | 'develop', 45 | 46 | // Start server 47 | 'connect', 48 | 49 | // Watch files for changes 50 | 'watch' 51 | ]); 52 | }); 53 | 54 | // Build unminified files during development 55 | grunt.registerTask('develop', [ 56 | 'clean', 57 | 58 | // JS 59 | 'handlebars', 60 | 'neuter', 61 | 'shaderChunks', 62 | 63 | // CSS 64 | 'sass:develop', 65 | 'autoprefixer:develop', 66 | 67 | // HTML 68 | 'haychtml:develop', 69 | 70 | // OTHER FILES 71 | 'copy:develop', 72 | 'copy:build' 73 | ]); 74 | 75 | // Build minified files for deployment 76 | grunt.registerTask('build', [ 77 | 'clean', 78 | 79 | // JS 80 | 'jshint', 81 | 'handlebars', 82 | 'neuter', 83 | 'shaderChunks', 84 | 'uglify', 85 | 86 | // CSS 87 | 'sass:build', 88 | 'autoprefixer:build', 89 | 90 | // HTML 91 | 'haychtml:build', 92 | 93 | // OTHER FILES 94 | 'copy:build', 95 | 96 | // TEMP FOLDER 97 | 'clean:temp', 98 | 99 | // NOTIFICATION 100 | 'notify:build' 101 | ]); 102 | 103 | grunt.registerTask('default', ['build']); 104 | }; 105 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/ConvolutionShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Convolution shader 5 | * ported from o3d sample to WebGL / GLSL 6 | * http://o3d.googlecode.com/svn/trunk/samples/convolution.html 7 | */ 8 | 9 | THREE.ConvolutionShader = { 10 | 11 | defines: { 12 | 13 | "KERNEL_SIZE_FLOAT": "25.0", 14 | "KERNEL_SIZE_INT": "25", 15 | 16 | }, 17 | 18 | uniforms: { 19 | 20 | "tDiffuse": { type: "t", value: null }, 21 | "uImageIncrement": { type: "v2", value: new THREE.Vector2( 0.001953125, 0.0 ) }, 22 | "cKernel": { type: "fv1", value: [] } 23 | 24 | }, 25 | 26 | vertexShader: [ 27 | 28 | "uniform vec2 uImageIncrement;", 29 | 30 | "varying vec2 vUv;", 31 | 32 | "void main() {", 33 | 34 | "vUv = uv - ( ( KERNEL_SIZE_FLOAT - 1.0 ) / 2.0 ) * uImageIncrement;", 35 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 36 | 37 | "}" 38 | 39 | ].join("\n"), 40 | 41 | fragmentShader: [ 42 | 43 | "uniform float cKernel[ KERNEL_SIZE_INT ];", 44 | 45 | "uniform sampler2D tDiffuse;", 46 | "uniform vec2 uImageIncrement;", 47 | 48 | "varying vec2 vUv;", 49 | 50 | "void main() {", 51 | 52 | "vec2 imageCoord = vUv;", 53 | "vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 );", 54 | 55 | "for( int i = 0; i < KERNEL_SIZE_INT; i ++ ) {", 56 | 57 | "sum += texture2D( tDiffuse, imageCoord ) * cKernel[ i ];", 58 | "imageCoord += uImageIncrement;", 59 | 60 | "}", 61 | 62 | "gl_FragColor = sum;", 63 | 64 | "}" 65 | 66 | 67 | ].join("\n"), 68 | 69 | buildKernel: function ( sigma ) { 70 | 71 | // We lop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway. 72 | 73 | function gauss( x, sigma ) { 74 | 75 | return Math.exp( - ( x * x ) / ( 2.0 * sigma * sigma ) ); 76 | 77 | } 78 | 79 | var i, values, sum, halfWidth, kMaxKernelSize = 25, kernelSize = 2 * Math.ceil( sigma * 3.0 ) + 1; 80 | 81 | if ( kernelSize > kMaxKernelSize ) kernelSize = kMaxKernelSize; 82 | halfWidth = ( kernelSize - 1 ) * 0.5; 83 | 84 | values = new Array( kernelSize ); 85 | sum = 0.0; 86 | for ( i = 0; i < kernelSize; ++i ) { 87 | 88 | values[ i ] = gauss( i - halfWidth, sigma ); 89 | sum += values[ i ]; 90 | 91 | } 92 | 93 | // normalize the kernel 94 | 95 | for ( i = 0; i < kernelSize; ++i ) values[ i ] /= sum; 96 | 97 | return values; 98 | 99 | } 100 | 101 | }; 102 | -------------------------------------------------------------------------------- /static/scss/apps/_index.scss: -------------------------------------------------------------------------------- 1 | #container { 2 | @include absolute(0); 3 | width: 100%; 4 | height: 100%; 5 | overflow: hidden; 6 | 7 | > canvas { 8 | opacity: 0; 9 | transition: opacity 500ms 200ms; 10 | 11 | &.active { 12 | opacity: 1; 13 | } 14 | } 15 | } 16 | 17 | #container-controls { 18 | position: absolute; 19 | z-index: 2; 20 | left: 0; 21 | bottom: 10px; 22 | opacity: 0; 23 | transition: opacity 500ms 300ms; 24 | 25 | &.active { 26 | opacity: 1; 27 | } 28 | } 29 | 30 | #container-graphs { 31 | position: absolute; 32 | right: 0; 33 | bottom: 30px; 34 | } 35 | 36 | #container-stats { 37 | position: absolute; 38 | z-index: 1; 39 | right: 20px; 40 | bottom: 20px; 41 | } 42 | 43 | .info-modal { 44 | h1 { 45 | margin: 4px 0 8px; 46 | 47 | &:after { 48 | display: block; 49 | content: "-------"; 50 | } 51 | } 52 | 53 | dl { 54 | display: block; 55 | margin: 4px 0; 56 | } 57 | 58 | dt { 59 | color: #e9e9e9; 60 | display: inline-block; 61 | opacity: 0.5; 62 | 63 | &:after { 64 | display: inline-block; 65 | padding: 0 4px; 66 | color: #e30060; 67 | content: "/"; 68 | } 69 | } 70 | 71 | dd { 72 | display: inline-block; 73 | color: #fff; 74 | } 75 | 76 | a { 77 | position: relative; 78 | display: inline-block; 79 | color: #fff; 80 | text-decoration: none; 81 | 82 | &:after { 83 | position: absolute; 84 | bottom: -1px; 85 | left: 0; 86 | right: 0; 87 | border-bottom: 1px solid #fff; 88 | opacity: 0.25; 89 | content: ""; 90 | } 91 | 92 | &:hover:after { 93 | opacity: 1; 94 | } 95 | } 96 | } 97 | 98 | .info-section { 99 | margin: 12px 0 0; 100 | } 101 | 102 | .stats-panel { 103 | color: #fff; 104 | opacity: 0; 105 | visibility: hidden; 106 | transition: all 200ms; 107 | 108 | dl { 109 | display: inline-block; 110 | 111 | &:not(:last-child):after { 112 | display: inline-block; 113 | padding: 0 4px; 114 | color: #e30060; 115 | content: "/"; 116 | } 117 | } 118 | 119 | dt { 120 | display: inline-block; 121 | 122 | &:after { 123 | display: inline-block; 124 | padding: 0 0 0 2px; 125 | content: ":"; 126 | } 127 | } 128 | 129 | dd { 130 | display: inline-block; 131 | } 132 | 133 | .show-info & { 134 | opacity: 1; 135 | visibility: visible; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /static/js/components/ToggleComponent.js: -------------------------------------------------------------------------------- 1 | App.ToggleComponent = ToggleComponent; 2 | function ToggleComponent(config) { 3 | var name = config.name; 4 | var toggle = this.toggle = document.getElementById('toggle-' + name); 5 | 6 | this.setupKey(config.key); 7 | this.setupMenu(config.menu); 8 | 9 | this.isActive = config.isActive != null ? config.isActive : false; 10 | this._toggleClassName = toggle.className; 11 | this.syncState(); 12 | 13 | toggle.addEventListener('click', this.toggleState.bind(this), false); 14 | } 15 | 16 | ToggleComponent.create = App.ctor(ToggleComponent); 17 | App.Dispatcher.extend(ToggleComponent.prototype); 18 | 19 | ToggleComponent.prototype.setupKey = function (key) { 20 | if (!key) { return; } 21 | this.keyDelegator.addBinding(key, this, 'toggleState'); 22 | }; 23 | 24 | ToggleComponent.prototype.setupMenu = function (name) { 25 | if (!name) { return; } 26 | 27 | var menu = this.menu = document.getElementById('menu-' + name); 28 | var inner = this.menuInner = document.createElement('div'); 29 | 30 | inner.className = 'inner'; 31 | menu.appendChild(inner); 32 | 33 | this._menuClassName = menu.className; 34 | this.toggle.className += ' has-menu'; 35 | }; 36 | 37 | ToggleComponent.prototype.toggleState = function (event) { 38 | this.isActive = !this.isActive; 39 | this.syncState(); 40 | this.triggerListeners('toggle', this.isActive); 41 | }; 42 | 43 | ToggleComponent.prototype.syncState = function () { 44 | this.updateElClass(this.toggle, this._toggleClassName); 45 | this.updateElClass(this.menu, this._menuClassName); 46 | this.updateElHeight(this.menu, this.menuInner); 47 | }; 48 | 49 | ToggleComponent.prototype.updateElClass = function (element, className) { 50 | if (!element) { return; } 51 | if (this.isActive) { 52 | element.className += ' active'; 53 | } else { 54 | element.className = className; 55 | } 56 | }; 57 | 58 | ToggleComponent.prototype.updateElHeight = function (element, inner) { 59 | if (!element) { return; } 60 | if (this.isActive) { 61 | element.style.height = inner.offsetHeight + 'px'; 62 | this._willBecomeVisible = setTimeout( 63 | this.becomeVisible.bind(null, element), 200); 64 | } else { 65 | element.style.height = ''; 66 | } 67 | }; 68 | 69 | ToggleComponent.prototype.becomeVisible = function (element) { 70 | element.className += ' visible'; 71 | }; 72 | 73 | ToggleComponent.prototype.hide = function () { 74 | this.toggle.className += ' hidden'; 75 | this.menu.className += ' hidden'; 76 | }; 77 | 78 | ToggleComponent.prototype.keyDelegator = App.KeyDelegator.create(); 79 | -------------------------------------------------------------------------------- /static/scss/components/_controls.scss: -------------------------------------------------------------------------------- 1 | $c-active: #E30060; 2 | 3 | @mixin controls-button { 4 | position: relative; 5 | display: block; 6 | width: 50px; 7 | height: 30px; 8 | cursor: pointer; 9 | } 10 | 11 | @mixin controls-toggle { 12 | position: absolute; 13 | top: 10px; 14 | left: 20px; 15 | width: 8px; 16 | height: 8px; 17 | box-sizing: content-box; 18 | border: 1px solid #fff; 19 | border-radius: 2px; 20 | 21 | background: rgba(#fff, 0.15); 22 | transition-property: border-color, background; 23 | transition-duration: 200ms; 24 | content: ""; 25 | } 26 | 27 | @mixin controls-label { 28 | position: absolute; 29 | top: 50%; 30 | left: 100%; 31 | color: #fff; 32 | white-space: nowrap; 33 | opacity: 0; 34 | visibility: hidden; 35 | 36 | transform: translateY(-50%); 37 | transition: all 150ms; 38 | 39 | &:before { 40 | position: absolute; 41 | top: 50%; 42 | right: 100%; 43 | width: 8px; 44 | border-top: 1px solid #fff; 45 | margin-right: 6px; 46 | content: ""; 47 | } 48 | } 49 | 50 | .controls-button { 51 | @include controls-button; 52 | 53 | &:before { @include controls-toggle; } 54 | > .label { @include controls-label; } 55 | 56 | &:hover > .label { 57 | visibility: visible; 58 | opacity: 1; 59 | } 60 | 61 | &.active:before { 62 | background: $c-active; 63 | border-color: $c-active; 64 | } 65 | 66 | &:hover:before { border-color: $c-active; } 67 | &.active:hover:before { border-color: #fff; } 68 | 69 | &.hidden { display: none; } 70 | } 71 | 72 | .controls-menu { 73 | $duration: 200ms; 74 | $delay: 200ms; 75 | 76 | position: relative; 77 | overflow: hidden; 78 | height: 0; 79 | 80 | transition: height $duration; 81 | transition-delay: $delay; 82 | 83 | > .inner { 84 | opacity: 0; 85 | visibility: hidden; 86 | transform: translateX(-8px); 87 | transition: all $duration; 88 | transition-delay: 0ms; 89 | } 90 | 91 | &:after { 92 | position: absolute; 93 | top: 0; 94 | left: 0; 95 | height: 100%; 96 | border-left: 1px solid #fff; 97 | content: ""; 98 | } 99 | 100 | &.active { 101 | opacity: 1; 102 | visibility: visible; 103 | transition-delay: 0ms; 104 | 105 | > .inner { 106 | opacity: 1; 107 | visibility: visible; 108 | transform: translateX(0); 109 | transition-delay: $delay; 110 | } 111 | } 112 | 113 | &.visible { 114 | overflow: visible; 115 | } 116 | 117 | &.hidden { display: none; } 118 | } 119 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/BokehPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Depth-of-field post-process with bokeh shader 3 | */ 4 | 5 | 6 | THREE.BokehPass = function ( scene, camera, params ) { 7 | 8 | this.scene = scene; 9 | this.camera = camera; 10 | 11 | var focus = ( params.focus !== undefined ) ? params.focus : 1.0; 12 | var aspect = ( params.aspect !== undefined ) ? params.aspect : camera.aspect; 13 | var aperture = ( params.aperture !== undefined ) ? params.aperture : 0.025; 14 | var maxblur = ( params.maxblur !== undefined ) ? params.maxblur : 1.0; 15 | 16 | // render targets 17 | 18 | var width = params.width || window.innerWidth || 1; 19 | var height = params.height || window.innerHeight || 1; 20 | 21 | this.renderTargetColor = new THREE.WebGLRenderTarget( width, height, { 22 | minFilter: THREE.LinearFilter, 23 | magFilter: THREE.LinearFilter, 24 | format: THREE.RGBFormat 25 | } ); 26 | 27 | this.renderTargetDepth = this.renderTargetColor.clone(); 28 | 29 | // depth material 30 | 31 | this.materialDepth = new THREE.MeshDepthMaterial(); 32 | 33 | // bokeh material 34 | 35 | if ( THREE.BokehShader === undefined ) { 36 | console.error( "THREE.BokehPass relies on THREE.BokehShader" ); 37 | } 38 | 39 | var bokehShader = THREE.BokehShader; 40 | var bokehUniforms = THREE.UniformsUtils.clone( bokehShader.uniforms ); 41 | 42 | bokehUniforms[ "tDepth" ].value = this.renderTargetDepth; 43 | 44 | bokehUniforms[ "focus" ].value = focus; 45 | bokehUniforms[ "aspect" ].value = aspect; 46 | bokehUniforms[ "aperture" ].value = aperture; 47 | bokehUniforms[ "maxblur" ].value = maxblur; 48 | 49 | this.materialBokeh = new THREE.ShaderMaterial({ 50 | uniforms: bokehUniforms, 51 | vertexShader: bokehShader.vertexShader, 52 | fragmentShader: bokehShader.fragmentShader 53 | }); 54 | 55 | this.uniforms = bokehUniforms; 56 | this.enabled = true; 57 | this.needsSwap = false; 58 | this.renderToScreen = false; 59 | this.clear = false; 60 | 61 | this.camera2 = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); 62 | this.scene2 = new THREE.Scene(); 63 | 64 | this.quad2 = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 65 | this.scene2.add( this.quad2 ); 66 | 67 | }; 68 | 69 | THREE.BokehPass.prototype = { 70 | 71 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 72 | 73 | this.quad2.material = this.materialBokeh; 74 | 75 | // Render depth into texture 76 | 77 | this.scene.overrideMaterial = this.materialDepth; 78 | 79 | renderer.render( this.scene, this.camera, this.renderTargetDepth, true ); 80 | 81 | // Render bokeh composite 82 | 83 | this.uniforms[ "tColor" ].value = readBuffer; 84 | 85 | if ( this.renderToScreen ) { 86 | 87 | renderer.render( this.scene2, this.camera2 ); 88 | 89 | } else { 90 | 91 | renderer.render( this.scene2, this.camera2, writeBuffer, this.clear ); 92 | 93 | } 94 | 95 | this.scene.overrideMaterial = null; 96 | 97 | } 98 | 99 | }; 100 | 101 | -------------------------------------------------------------------------------- /static/lib-extras/three/geometries/PlaneBufferGeometry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as 4 | */ 5 | 6 | THREE.PlaneBufferGeometry = function ( width, height, widthSegments, heightSegments ) { 7 | 8 | THREE.BufferGeometry.call( this ); 9 | 10 | this.type = 'PlaneBufferGeometry'; 11 | 12 | this.parameters = { 13 | width: width, 14 | height: height, 15 | widthSegments: widthSegments, 16 | heightSegments: heightSegments 17 | }; 18 | 19 | var width_half = width / 2; 20 | var height_half = height / 2; 21 | 22 | var gridX = Math.floor( widthSegments ) || 1; 23 | var gridY = Math.floor( heightSegments ) || 1; 24 | 25 | var gridX1 = gridX + 1; 26 | var gridY1 = gridY + 1; 27 | 28 | var segment_width = width / gridX; 29 | var segment_height = height / gridY; 30 | 31 | var vertices = new Float32Array( gridX1 * gridY1 * 3 ); 32 | var normals = new Float32Array( gridX1 * gridY1 * 3 ); 33 | var uvs = new Float32Array( gridX1 * gridY1 * 2 ); 34 | 35 | var offset = 0; 36 | var offset2 = 0; 37 | 38 | for ( var iy = 0; iy < gridY1; iy ++ ) { 39 | 40 | var y = iy * segment_height - height_half; 41 | 42 | for ( var ix = 0; ix < gridX1; ix ++ ) { 43 | 44 | var x = ix * segment_width - width_half; 45 | 46 | vertices[ offset ] = x; 47 | vertices[ offset + 1 ] = - y; 48 | 49 | normals[ offset + 2 ] = 1; 50 | 51 | uvs[ offset2 ] = ix / gridX; 52 | uvs[ offset2 + 1 ] = 1 - ( iy / gridY ); 53 | 54 | offset += 3; 55 | offset2 += 2; 56 | 57 | } 58 | 59 | } 60 | 61 | offset = 0; 62 | 63 | var indices = new ( ( vertices.length / 3 ) > 65535 ? Uint32Array : Uint16Array )( gridX * gridY * 6 ); 64 | 65 | for ( var iy = 0; iy < gridY; iy ++ ) { 66 | 67 | for ( var ix = 0; ix < gridX; ix ++ ) { 68 | 69 | var a = ix + gridX1 * iy; 70 | var b = ix + gridX1 * ( iy + 1 ); 71 | var c = ( ix + 1 ) + gridX1 * ( iy + 1 ); 72 | var d = ( ix + 1 ) + gridX1 * iy; 73 | 74 | indices[ offset ] = a; 75 | indices[ offset + 1 ] = b; 76 | indices[ offset + 2 ] = d; 77 | 78 | indices[ offset + 3 ] = b; 79 | indices[ offset + 4 ] = c; 80 | indices[ offset + 5 ] = d; 81 | 82 | offset += 6; 83 | 84 | } 85 | 86 | } 87 | 88 | this.setIndex( new THREE.BufferAttribute( indices, 1 ) ); 89 | this.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); 90 | this.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); 91 | this.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) ); 92 | 93 | }; 94 | 95 | THREE.PlaneBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); 96 | THREE.PlaneBufferGeometry.prototype.constructor = THREE.PlaneBufferGeometry; 97 | 98 | THREE.PlaneBufferGeometry.prototype.clone = function () { 99 | 100 | var geometry = new THREE.PlaneBufferGeometry( 101 | this.parameters.width, 102 | this.parameters.height, 103 | this.parameters.widthSegments, 104 | this.parameters.heightSegments 105 | ); 106 | 107 | geometry.copy( this ); 108 | 109 | return geometry; 110 | 111 | }; 112 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/FXAAShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author davidedc / http://www.sketchpatch.net/ 4 | * 5 | * NVIDIA FXAA by Timothy Lottes 6 | * http://timothylottes.blogspot.com/2011/06/fxaa3-source-released.html 7 | * - WebGL port by @supereggbert 8 | * http://www.glge.org/demos/fxaa/ 9 | */ 10 | 11 | THREE.FXAAShader = { 12 | 13 | uniforms: { 14 | 15 | "tDiffuse": { type: "t", value: null }, 16 | "resolution": { type: "v2", value: new THREE.Vector2( 1 / 1024, 1 / 512 ) } 17 | 18 | }, 19 | 20 | vertexShader: [ 21 | 22 | "void main() {", 23 | 24 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 25 | 26 | "}" 27 | 28 | ].join("\n"), 29 | 30 | fragmentShader: [ 31 | 32 | "uniform sampler2D tDiffuse;", 33 | "uniform vec2 resolution;", 34 | 35 | "#define FXAA_REDUCE_MIN (1.0/128.0)", 36 | "#define FXAA_REDUCE_MUL (1.0/8.0)", 37 | "#define FXAA_SPAN_MAX 8.0", 38 | 39 | "void main() {", 40 | 41 | "vec3 rgbNW = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( -1.0, -1.0 ) ) * resolution ).xyz;", 42 | "vec3 rgbNE = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( 1.0, -1.0 ) ) * resolution ).xyz;", 43 | "vec3 rgbSW = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( -1.0, 1.0 ) ) * resolution ).xyz;", 44 | "vec3 rgbSE = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( 1.0, 1.0 ) ) * resolution ).xyz;", 45 | "vec4 rgbaM = texture2D( tDiffuse, gl_FragCoord.xy * resolution );", 46 | "vec3 rgbM = rgbaM.xyz;", 47 | "vec3 luma = vec3( 0.299, 0.587, 0.114 );", 48 | 49 | "float lumaNW = dot( rgbNW, luma );", 50 | "float lumaNE = dot( rgbNE, luma );", 51 | "float lumaSW = dot( rgbSW, luma );", 52 | "float lumaSE = dot( rgbSE, luma );", 53 | "float lumaM = dot( rgbM, luma );", 54 | "float lumaMin = min( lumaM, min( min( lumaNW, lumaNE ), min( lumaSW, lumaSE ) ) );", 55 | "float lumaMax = max( lumaM, max( max( lumaNW, lumaNE) , max( lumaSW, lumaSE ) ) );", 56 | 57 | "vec2 dir;", 58 | "dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));", 59 | "dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));", 60 | 61 | "float dirReduce = max( ( lumaNW + lumaNE + lumaSW + lumaSE ) * ( 0.25 * FXAA_REDUCE_MUL ), FXAA_REDUCE_MIN );", 62 | 63 | "float rcpDirMin = 1.0 / ( min( abs( dir.x ), abs( dir.y ) ) + dirReduce );", 64 | "dir = min( vec2( FXAA_SPAN_MAX, FXAA_SPAN_MAX),", 65 | "max( vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),", 66 | "dir * rcpDirMin)) * resolution;", 67 | "vec4 rgbA = (1.0/2.0) * (", 68 | "texture2D(tDiffuse, gl_FragCoord.xy * resolution + dir * (1.0/3.0 - 0.5)) +", 69 | "texture2D(tDiffuse, gl_FragCoord.xy * resolution + dir * (2.0/3.0 - 0.5)));", 70 | "vec4 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * (", 71 | "texture2D(tDiffuse, gl_FragCoord.xy * resolution + dir * (0.0/3.0 - 0.5)) +", 72 | "texture2D(tDiffuse, gl_FragCoord.xy * resolution + dir * (3.0/3.0 - 0.5)));", 73 | "float lumaB = dot(rgbB, vec4(luma, 0.0));", 74 | 75 | "if ( ( lumaB < lumaMin ) || ( lumaB > lumaMax ) ) {", 76 | 77 | "gl_FragColor = rgbA;", 78 | 79 | "} else {", 80 | "gl_FragColor = rgbB;", 81 | 82 | "}", 83 | 84 | "}" 85 | 86 | ].join("\n") 87 | 88 | }; 89 | -------------------------------------------------------------------------------- /static/js/items/Dust.js: -------------------------------------------------------------------------------- 1 | var DEBUG_TEXTURE = false; 2 | var mapLinear = THREE.Math.mapLinear; 3 | 4 | App.Dust = Dust; 5 | function Dust(opts) { 6 | this.pxRatio = opts.pxRatio || 1; 7 | this.particleSize = 32 * this.pxRatio; 8 | this.particleCount = 8000; 9 | this.area = 300; 10 | this.createParticles(); 11 | this.createMaterials(); 12 | this.createItem(); 13 | } 14 | 15 | Dust.create = App.ctor(Dust); 16 | 17 | Dust.prototype.createParticles = function () { 18 | var count = this.particleCount; 19 | var geom = this.geometry = new THREE.BufferGeometry(); 20 | var verts = new Float32Array(count * 3); 21 | 22 | var area = this.area; 23 | var areaHalf = area * 0.5; 24 | var ix; 25 | 26 | for (var i = 0, il = verts.length / 3; i < il; i ++) { 27 | ix = i * 3; 28 | verts[ix] = Math.random() * area - areaHalf; 29 | verts[ix + 1] = Math.random() * area - areaHalf; 30 | verts[ix + 2] = Math.random() * area - areaHalf; 31 | } 32 | 33 | geom.addAttribute('position', 34 | new THREE.BufferAttribute(verts, 3)); 35 | }; 36 | 37 | Dust.prototype.createTexture = function () { 38 | var canvas = document.createElement('canvas'); 39 | var texture = new THREE.Texture(canvas); 40 | var ctx = canvas.getContext('2d'); 41 | 42 | var size = Math.pow(2, 6); 43 | var sizeHalf = size * 0.5; 44 | var rings = 2; 45 | var t, radius, alpha; 46 | 47 | canvas.width = canvas.height = size; 48 | ctx.fillStyle = '#fff'; 49 | 50 | for (var i = 0; i < rings; i ++) { 51 | t = i / (rings - 1); 52 | radius = mapLinear(t * t, 0, 1, 4, sizeHalf); 53 | alpha = mapLinear(t, 0, 1, 1, 0.05); 54 | 55 | ctx.beginPath(); 56 | ctx.arc(sizeHalf, sizeHalf, radius, 0, Math.PI * 2); 57 | ctx.globalAlpha = alpha; 58 | ctx.fill(); 59 | } 60 | 61 | texture.needsUpdate = true; 62 | 63 | if (DEBUG_TEXTURE) { 64 | document.body.appendChild(canvas); 65 | canvas.style.position = 'absolute'; 66 | } 67 | 68 | return texture; 69 | }; 70 | 71 | Dust.prototype.createMaterials = function () { 72 | var params = { 73 | psColor : 0xffffff, 74 | opacity : 0.95, 75 | size : this.particleSize, 76 | map : this.createTexture(), 77 | scale : 150, 78 | area : this.area, 79 | blending: THREE.AdditiveBlending, 80 | transparent : true, 81 | depthTest : false, 82 | depthWrite : false 83 | }; 84 | 85 | this.materialFore = new App.DustMaterial(params); 86 | 87 | // params.depthTest = false; 88 | // params.opacity = 0.25; 89 | // this.materialFaint = new App.DustMaterial(params); 90 | 91 | // this.timeAttrFaint = this.materialFaint.uniforms.time; 92 | this.timeAttrFore = this.materialFore.uniforms.time; 93 | }; 94 | 95 | Dust.prototype.createItem = function () { 96 | // this.itemFaint = new THREE.PointCloud(this.geometry, this.materialFaint); 97 | this.itemFore = new THREE.Points(this.geometry, this.materialFore); 98 | }; 99 | 100 | Dust.prototype.addTo = function (scene) { 101 | // scene.add(this.itemFaint); 102 | scene.add(this.itemFore); 103 | }; 104 | 105 | Dust.prototype.updateGraphics = function (delta) { 106 | // this.timeAttrFaint.value += delta * 0.005; 107 | this.timeAttrFore.value += delta * 0.005; 108 | }; 109 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/EffectComposer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.EffectComposer = function ( renderer, renderTarget ) { 6 | 7 | this.renderer = renderer; 8 | 9 | if ( renderTarget === undefined ) { 10 | 11 | var width = window.innerWidth || 1; 12 | var height = window.innerHeight || 1; 13 | var parameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBuffer: false }; 14 | 15 | renderTarget = new THREE.WebGLRenderTarget( width, height, parameters ); 16 | 17 | } 18 | 19 | this.renderTarget1 = renderTarget; 20 | this.renderTarget2 = renderTarget.clone(); 21 | 22 | this.writeBuffer = this.renderTarget1; 23 | this.readBuffer = this.renderTarget2; 24 | 25 | this.passes = []; 26 | 27 | if ( THREE.CopyShader === undefined ) 28 | console.error( "THREE.EffectComposer relies on THREE.CopyShader" ); 29 | 30 | this.copyPass = new THREE.ShaderPass( THREE.CopyShader ); 31 | 32 | }; 33 | 34 | THREE.EffectComposer.prototype = { 35 | 36 | swapBuffers: function() { 37 | 38 | var tmp = this.readBuffer; 39 | this.readBuffer = this.writeBuffer; 40 | this.writeBuffer = tmp; 41 | 42 | }, 43 | 44 | addPass: function ( pass ) { 45 | 46 | this.passes.push( pass ); 47 | 48 | }, 49 | 50 | insertPass: function ( pass, index ) { 51 | 52 | this.passes.splice( index, 0, pass ); 53 | 54 | }, 55 | 56 | render: function ( delta ) { 57 | 58 | this.writeBuffer = this.renderTarget1; 59 | this.readBuffer = this.renderTarget2; 60 | 61 | var maskActive = false; 62 | 63 | var pass, i, il = this.passes.length; 64 | 65 | for ( i = 0; i < il; i ++ ) { 66 | 67 | pass = this.passes[ i ]; 68 | 69 | if ( !pass.enabled ) continue; 70 | 71 | pass.render( this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive ); 72 | 73 | if ( pass.needsSwap ) { 74 | 75 | if ( maskActive ) { 76 | 77 | var context = this.renderer.context; 78 | 79 | context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); 80 | 81 | this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, delta ); 82 | 83 | context.stencilFunc( context.EQUAL, 1, 0xffffffff ); 84 | 85 | } 86 | 87 | this.swapBuffers(); 88 | 89 | } 90 | 91 | if ( pass instanceof THREE.MaskPass ) { 92 | 93 | maskActive = true; 94 | 95 | } else if ( pass instanceof THREE.ClearMaskPass ) { 96 | 97 | maskActive = false; 98 | 99 | } 100 | 101 | } 102 | 103 | }, 104 | 105 | reset: function ( renderTarget ) { 106 | 107 | if ( renderTarget === undefined ) { 108 | 109 | renderTarget = this.renderTarget1.clone(); 110 | 111 | renderTarget.width = window.innerWidth; 112 | renderTarget.height = window.innerHeight; 113 | 114 | } 115 | 116 | this.renderTarget1 = renderTarget; 117 | this.renderTarget2 = renderTarget.clone(); 118 | 119 | this.writeBuffer = this.renderTarget1; 120 | this.readBuffer = this.renderTarget2; 121 | 122 | }, 123 | 124 | setSize: function ( width, height ) { 125 | 126 | var renderTarget = this.renderTarget1.clone(); 127 | 128 | renderTarget.width = width; 129 | renderTarget.height = height; 130 | 131 | this.reset( renderTarget ); 132 | 133 | } 134 | 135 | }; 136 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Particulate Medusae 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 52 | 53 | 54 |
55 |
56 |
About
57 |
58 |
59 |
Particles
60 |
61 | 62 |
63 |
Colors
64 |
65 |
66 |
Audio
67 |
68 |
69 |
Post FX
70 |
71 |
72 |
Run Sim
73 |
74 |
75 | 76 |
77 |
78 |
79 |
Particles
80 |
81 |
82 |
83 |
Constraints
84 |
85 |
86 |
87 |
Forces
88 |
89 |
90 |
91 | 92 |
93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /static/js/controllers/IndexController.js: -------------------------------------------------------------------------------- 1 | var ToggleComponent = App.ToggleComponent; 2 | var ModalComponent = App.ModalComponent; 3 | var ColorComponent = App.ColorComponent; 4 | var Features = App.Features; 5 | var keysTop = [85, 73, 79, 80]; 6 | 7 | App.register('index', function index() { 8 | var scene = App.MainScene.create(); 9 | var controls = document.getElementById('container-controls'); 10 | 11 | var dotsToggle = ToggleComponent.create({ 12 | name : 'dots', 13 | key : keysTop[0] 14 | }); 15 | 16 | var postFxToggle = ToggleComponent.create({ 17 | name : 'postfx', 18 | key : keysTop[3], 19 | isActive : scene.usePostFx 20 | }); 21 | 22 | var simToggle = ToggleComponent.create({ 23 | name : 'sim', 24 | key : 32, 25 | isActive : scene.shouldAnimate 26 | }); 27 | 28 | ModalComponent.create({ 29 | name : 'info' 30 | }); 31 | 32 | scene.initItems(); 33 | scene.initForces(); 34 | scene.appendRenderer(); 35 | 36 | postFxToggle.addListener('toggle', scene, 'togglePostFx'); 37 | dotsToggle.addListener('toggle', scene, 'toggleDots'); 38 | dotsToggle.addListener('toggle', scene, 'toggleStats'); 39 | simToggle.addListener('toggle', scene, 'toggleAnimate'); 40 | 41 | setupAudio(scene); 42 | setupColors(scene); 43 | setupSystemUI(scene); 44 | 45 | setTimeout(function () { 46 | scene.loop.start(); 47 | controls.className = 'active'; 48 | }, 0); 49 | }); 50 | 51 | function setupAudio(scene) { 52 | /*global Promise*/ 53 | var tests = [ 54 | Features.detectWebAudio(), 55 | Features.detectAudioCodecs(['audio/ogg; codecs=vorbis', 'audio/mpeg;']), 56 | Features.detectAudioAutoplay() 57 | ]; 58 | 59 | var audioToggle = ToggleComponent.create({ 60 | name : 'audio', 61 | key : keysTop[2] 62 | }); 63 | 64 | Promise.all(tests).then(function () { 65 | scene.initAudio(); 66 | scene.addListener('load:audio', function () { 67 | audioToggle.addListener('toggle', scene, 'toggleAudio'); 68 | audioToggle.toggleState(); 69 | }); 70 | }, function (err) { 71 | audioToggle.hide(); 72 | App.log('Audio features not supported'); 73 | }); 74 | } 75 | 76 | function setupColors(scene) { 77 | var colorsToggle = ToggleComponent.create({ 78 | name : 'colors', 79 | menu : 'colors', 80 | key : keysTop[1] 81 | }); 82 | 83 | Features.detectInputType('color').then(function () { 84 | scene.medusae.colors.forEach(function (color) { 85 | var controller = ColorComponent.create({ 86 | label : color.label, 87 | color : color.uniform.value 88 | }); 89 | 90 | controller.addListener('change', scene, 'makeDirty'); 91 | colorsToggle.menuInner.appendChild(controller.element); 92 | }); 93 | }, function (err) { 94 | colorsToggle.hide(); 95 | App.log('Color input not supported'); 96 | }); 97 | } 98 | 99 | function setupSystemUI(scene) { 100 | var Format = App.Format; 101 | var system = scene.medusae.system; 102 | var particleEl = document.getElementById('particle-count'); 103 | var constraintEl = document.getElementById('constraint-count'); 104 | var forceEl = document.getElementById('force-count'); 105 | 106 | var constraintCount = system._localConstraints.reduce(function (prev, current) { 107 | return prev + current._count; 108 | }, 0); 109 | 110 | particleEl.textContent = Format.number(system._count); 111 | constraintEl.textContent = Format.number(constraintCount); 112 | forceEl.textContent = Format.number(system._forces.length); 113 | } 114 | -------------------------------------------------------------------------------- /static/lib-extras/three/geometries/SphereBufferGeometry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author benaadams / https://twitter.com/ben_a_adams 3 | * based on THREE.SphereGeometry 4 | */ 5 | 6 | THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { 7 | 8 | THREE.BufferGeometry.call( this ); 9 | 10 | this.type = 'SphereBufferGeometry'; 11 | 12 | this.parameters = { 13 | radius: radius, 14 | widthSegments: widthSegments, 15 | heightSegments: heightSegments, 16 | phiStart: phiStart, 17 | phiLength: phiLength, 18 | thetaStart: thetaStart, 19 | thetaLength: thetaLength 20 | }; 21 | 22 | radius = radius || 50; 23 | 24 | widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 ); 25 | heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 ); 26 | 27 | phiStart = phiStart !== undefined ? phiStart : 0; 28 | phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; 29 | 30 | thetaStart = thetaStart !== undefined ? thetaStart : 0; 31 | thetaLength = thetaLength !== undefined ? thetaLength : Math.PI; 32 | 33 | var thetaEnd = thetaStart + thetaLength; 34 | 35 | var vertexCount = ( ( widthSegments + 1 ) * ( heightSegments + 1 ) ); 36 | 37 | var positions = new THREE.BufferAttribute( new Float32Array( vertexCount * 3 ), 3 ); 38 | var normals = new THREE.BufferAttribute( new Float32Array( vertexCount * 3 ), 3 ); 39 | var uvs = new THREE.BufferAttribute( new Float32Array( vertexCount * 2 ), 2 ); 40 | 41 | var index = 0, vertices = [], normal = new THREE.Vector3(); 42 | 43 | for ( var y = 0; y <= heightSegments; y ++ ) { 44 | 45 | var verticesRow = []; 46 | 47 | var v = y / heightSegments; 48 | 49 | for ( var x = 0; x <= widthSegments; x ++ ) { 50 | 51 | var u = x / widthSegments; 52 | 53 | var px = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); 54 | var py = radius * Math.cos( thetaStart + v * thetaLength ); 55 | var pz = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); 56 | 57 | normal.set( px, py, pz ).normalize(); 58 | 59 | positions.setXYZ( index, px, py, pz ); 60 | normals.setXYZ( index, normal.x, normal.y, normal.z ); 61 | uvs.setXY( index, u, 1 - v ); 62 | 63 | verticesRow.push( index ); 64 | 65 | index ++; 66 | 67 | } 68 | 69 | vertices.push( verticesRow ); 70 | 71 | } 72 | 73 | var indices = []; 74 | 75 | for ( var y = 0; y < heightSegments; y ++ ) { 76 | 77 | for ( var x = 0; x < widthSegments; x ++ ) { 78 | 79 | var v1 = vertices[ y ][ x + 1 ]; 80 | var v2 = vertices[ y ][ x ]; 81 | var v3 = vertices[ y + 1 ][ x ]; 82 | var v4 = vertices[ y + 1 ][ x + 1 ]; 83 | 84 | if ( y !== 0 || thetaStart > 0 ) indices.push( v1, v2, v4 ); 85 | if ( y !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( v2, v3, v4 ); 86 | 87 | } 88 | 89 | } 90 | 91 | this.setIndex( new THREE.BufferAttribute( new Uint16Array( indices ), 1 ) ); 92 | this.addAttribute( 'position', positions ); 93 | this.addAttribute( 'normal', normals ); 94 | this.addAttribute( 'uv', uvs ); 95 | 96 | this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); 97 | 98 | }; 99 | 100 | THREE.SphereBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); 101 | THREE.SphereBufferGeometry.prototype.constructor = THREE.SphereBufferGeometry; 102 | 103 | THREE.SphereBufferGeometry.prototype.clone = function () { 104 | 105 | var geometry = new THREE.SphereBufferGeometry( 106 | this.parameters.radius, 107 | this.parameters.widthSegments, 108 | this.parameters.heightSegments, 109 | this.parameters.phiStart, 110 | this.parameters.phiLength, 111 | this.parameters.thetaStart, 112 | this.parameters.thetaLength 113 | ); 114 | 115 | geometry.copy( this ); 116 | 117 | return geometry; 118 | 119 | }; 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Medusae][medusae-url] 2 | 3 | [![Medusae][medusae-image-url]][medusae-url] 4 | 5 | > Soft body jellyfish simulation. 6 | 7 | ### Source 8 | 9 | - Jellyfish physics and graphics: [Medusae.js][medusae-source-url] 10 | - Jellyfish hood shader: [bulb-frag.glsl][hood-glsl-source-url] 11 | - Point repulsor: [PointRepulsorForce.js][point-force-source-url] 12 | - Ambient dust animation and graphics: [Dust.js][dust-source-url] and [dust-vert.glsl][dust-glsl-source-url] 13 | - Interpolated physics rendering: [Looper.js][looper-source-url] and [lerp_pos_vertex.glsl][lerp-vert-source-url] 14 | - WebAudio player: [AudioController.js][audio-source-url] 15 | - Canvas graph: [GraphComponent.js][graph-source-url] 16 | - Lens dirt post effect: [LensDirtPass.js][lens-dirt-source-url] 17 | 18 | ### Process 19 | 20 | - Screenshot and video [album][flickr-album-url] 21 | - Progress release [code and builds][source-releases-url] 22 | - Procedural GLSL hood texture [sketch][hood-glsl-url] 23 | - Procedural Canvas2D water drop texture [sketch][water-canvas-url] 24 | - Canvas graph [sketch][canvas-graph-url] 25 | 26 | ### Resources 27 | 28 | - Photography of [Alexander Semenov][semenov-url] 29 | - Paper on [advanced character physics][character-physics-url] by Thomas Jakobsen 30 | - Article on [interpolated physics rendering][interpolated-physics-url] by Andrew Petersen 31 | 32 | ### Credits 33 | 34 | - Concept, Design & Code: [Ash Weeks][portfolio-url] 35 | - Audio Design: JP Arsenault 36 | - Physics: [Particulate][particulate-url] 37 | - Graphics: [Three][three-url] 38 | 39 | ### License 40 | 41 | The Artistic License 2.0, see [LICENSE][license-source-url] for details. 42 | 43 | ### Development 44 | 45 | [Grunt][grunt-url] is used for building and developing the project. 46 | 47 | ```sh 48 | npm install 49 | grunt server 50 | ``` 51 | 52 | [medusae-url]: https://milcktoast.com/medusae/ 53 | [medusae-image-url]: https://farm2.staticflickr.com/1628/23884999242_457d932c7a_h.jpg 54 | [medusae-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/js/items/Medusae.js 55 | [hood-glsl-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/glsl/shaders/bulb-frag.glsl 56 | [point-force-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/js/forces/PointRepulsorForce.js 57 | [dust-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/js/items/Dust.js 58 | [dust-glsl-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/glsl/shaders/dust-vert.glsl 59 | [looper-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/js/utils/Looper.js 60 | [lerp-vert-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/glsl/shader-chunks/lerp_pos_vertex.glsl 61 | [audio-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/js/controllers/AudioController.js 62 | [graph-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/js/components/GraphComponent.js 63 | [lens-dirt-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/static/js/post-processing/LensDirtPass.js 64 | [flickr-album-url]: https://www.flickr.com/photos/jpweeks/sets/72157646887502644/ 65 | [source-releases-url]: https://github.com/jpweeks/particulate-medusae/releases 66 | [hood-glsl-url]: http://glslsandbox.com/e#20575.0 67 | [water-canvas-url]: https://jsbin.com/guqodi/11/edit?js,output 68 | [canvas-graph-url]: https://jsbin.com/yoteko/10/edit?js,output 69 | [semenov-url]: https://www.flickr.com/photos/a_semenov/7570746886/ 70 | [character-physics-url]: http://web.archive.org/web/20080410171619/http://www.teknikus.dk/tj/gdc2001.htm 71 | [interpolated-physics-url]: http://kirbysayshi.com/2013/09/24/interpolated-physics-rendering.html 72 | [portfolio-url]: https://milcktoast.com 73 | [source-url]: https://github.com/jpweeks/particulate-medusae 74 | [three-url]: http://threejs.org 75 | [particulate-url]: http://particulatejs.org 76 | [license-source-url]: https://github.com/jpweeks/particulate-medusae/blob/master/LICENSE 77 | [grunt-url]: http://gruntjs.com 78 | -------------------------------------------------------------------------------- /static/js/post-processing/LensDirtTexture.js: -------------------------------------------------------------------------------- 1 | App.LensDirtTexture = LensDirtTexture; 2 | function LensDirtTexture(size, cells, opts) { 3 | this.canvas = document.createElement('canvas'); 4 | this.ctx = this.canvas.getContext('2d'); 5 | this.texture = new THREE.Texture(this.canvas); 6 | this.drawTexture(size, cells, opts); 7 | } 8 | 9 | LensDirtTexture.prototype.grayscaleColor = function (start, range, alpha) { 10 | var c = Math.floor(Math.random() * range) + start; 11 | return 'rgba(' + [c, c, c, alpha].join(',') + ')'; 12 | }; 13 | 14 | LensDirtTexture.prototype.createGradients = function (ctx, count, radius) { 15 | var step = Math.PI * 2 / (count + 1); 16 | var angle = 0; 17 | 18 | var gradients = []; 19 | var colorA, colorB, alphaA, alphaB; 20 | var gradient, gx0, gy0, gx1, gy1; 21 | 22 | for (var i = 0; i < count; i ++) { 23 | gx0 = Math.cos(angle) * radius; 24 | gy0 = Math.sin(angle) * radius; 25 | gx1 = Math.cos(angle + Math.PI) * radius; 26 | gy1 = Math.sin(angle + Math.PI) * radius; 27 | 28 | alphaA = Math.random() * 0.1; 29 | alphaB = Math.random() * 0.5; 30 | colorA = this.grayscaleColor(100, 100, alphaA); 31 | colorB = this.grayscaleColor(100, 100, alphaB); 32 | 33 | gradient = ctx.createLinearGradient(gx0, gy0, gx1, gy1); 34 | gradient.addColorStop(0.2, colorA); 35 | gradient.addColorStop(0.8, colorB); 36 | gradients.push(gradient); 37 | gradient._alpha = alphaB; 38 | 39 | angle += step; 40 | } 41 | 42 | return gradients; 43 | }; 44 | 45 | LensDirtTexture.prototype.drawBlob = function (ctx, rx, ry, segments) { 46 | var step = Math.PI * 2 / segments; 47 | var angle = 0; 48 | var sx = Math.random() * 100; 49 | var sy = Math.random() * 100; 50 | var x, y, nx, ny; 51 | 52 | ctx.beginPath(); 53 | 54 | for (var i = 0, il = segments - 1; i < il; i ++) { 55 | x = Math.cos(angle) * rx; 56 | y = Math.sin(angle) * ry; 57 | nx = (sx + x) * 0.01; 58 | ny = (sy + y) * 0.01; 59 | x += noise.simplex2(nx, ny) * 5; 60 | y += noise.simplex2(nx, ny) * 5; 61 | 62 | angle += step; 63 | 64 | if (i === 0) { 65 | ctx.moveTo(x, y); 66 | } else { 67 | ctx.lineTo(x, y); 68 | } 69 | } 70 | 71 | ctx.closePath(); 72 | ctx.fill(); 73 | }; 74 | 75 | LensDirtTexture.prototype.drawShadow = function (ctx, iterations) { 76 | ctx.save(); 77 | ctx.shadowBlur = 10; 78 | ctx.shadowOffsetX = 0; 79 | ctx.shadowOffsetY = 0; 80 | ctx.shadowColor = this.grayscaleColor(200, 10, 1); 81 | 82 | for (var i = 0; i < iterations; i ++) { 83 | ctx.stroke(); 84 | } 85 | 86 | ctx.restore(); 87 | }; 88 | 89 | LensDirtTexture.prototype.drawTexture = function (size, cells, opts) { 90 | opts = opts || {}; 91 | 92 | var canvas = this.canvas; 93 | var ctx = this.ctx; 94 | 95 | var detail = opts.detail || 10; 96 | var cellPad = opts.cellPad || 10; 97 | 98 | var cellSize = size / cells; 99 | var cellSizeHalf = cellSize * 0.5; 100 | var blobRad = (cellSize - cellPad) * 0.5; 101 | var blobRadHalf = blobRad * 0.5; 102 | 103 | var gradients = this.createGradients(ctx, cells, cellSize); 104 | var gradient, gi, rx, ry; 105 | 106 | canvas.width = canvas.height = size; 107 | ctx.lineWidth = 1; 108 | 109 | for (var i = 0; i < cells; i ++) { 110 | ctx.setTransform(1, 0, 0, 1, 0, 0); 111 | ctx.translate(0, cellSize * i + cellSizeHalf); 112 | 113 | for (var j = 0; j < cells; j ++) { 114 | ctx.translate(j === 0 ? cellSizeHalf : cellSize, 0); 115 | rx = Math.random() * blobRadHalf + blobRadHalf; 116 | ry = Math.random() * blobRadHalf + blobRadHalf; 117 | gi = Math.floor(Math.random() * gradients.length); 118 | gradient = gradients[gi]; 119 | 120 | ctx.fillStyle = gradient; 121 | ctx.strokeStyle = this.grayscaleColor( 122 | 60, 30, gradient._alpha * 0.5); 123 | 124 | this.drawBlob(ctx, rx, ry, detail); 125 | this.drawShadow(ctx, 2); 126 | } 127 | } 128 | 129 | this.texture.needsUpdate = true; 130 | }; 131 | -------------------------------------------------------------------------------- /static/lib-extras/three/postprocessing/BloomPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.BloomPass = function ( strength, kernelSize, sigma, resolution ) { 6 | 7 | strength = ( strength !== undefined ) ? strength : 1; 8 | kernelSize = ( kernelSize !== undefined ) ? kernelSize : 25; 9 | sigma = ( sigma !== undefined ) ? sigma : 4.0; 10 | resolution = ( resolution !== undefined ) ? resolution : 256; 11 | 12 | // render targets 13 | 14 | var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat }; 15 | 16 | this.renderTargetX = new THREE.WebGLRenderTarget( resolution, resolution, pars ); 17 | this.renderTargetY = new THREE.WebGLRenderTarget( resolution, resolution, pars ); 18 | 19 | // copy material 20 | 21 | if ( THREE.CopyShader === undefined ) 22 | console.error( "THREE.BloomPass relies on THREE.CopyShader" ); 23 | 24 | var copyShader = THREE.CopyShader; 25 | 26 | this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms ); 27 | 28 | this.copyUniforms[ "opacity" ].value = strength; 29 | 30 | this.materialCopy = new THREE.ShaderMaterial( { 31 | 32 | uniforms: this.copyUniforms, 33 | vertexShader: copyShader.vertexShader, 34 | fragmentShader: copyShader.fragmentShader, 35 | blending: THREE.AdditiveBlending, 36 | transparent: true 37 | 38 | } ); 39 | 40 | // convolution material 41 | 42 | if ( THREE.ConvolutionShader === undefined ) 43 | console.error( "THREE.BloomPass relies on THREE.ConvolutionShader" ); 44 | 45 | var convolutionShader = THREE.ConvolutionShader; 46 | 47 | this.convolutionUniforms = THREE.UniformsUtils.clone( convolutionShader.uniforms ); 48 | 49 | this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurx; 50 | this.convolutionUniforms[ "cKernel" ].value = THREE.ConvolutionShader.buildKernel( sigma ); 51 | 52 | this.materialConvolution = new THREE.ShaderMaterial( { 53 | 54 | uniforms: this.convolutionUniforms, 55 | vertexShader: convolutionShader.vertexShader, 56 | fragmentShader: convolutionShader.fragmentShader, 57 | defines: { 58 | "KERNEL_SIZE_FLOAT": kernelSize.toFixed( 1 ), 59 | "KERNEL_SIZE_INT": kernelSize.toFixed( 0 ) 60 | } 61 | 62 | } ); 63 | 64 | this.enabled = true; 65 | this.needsSwap = false; 66 | this.clear = false; 67 | 68 | 69 | this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); 70 | this.scene = new THREE.Scene(); 71 | 72 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 73 | this.scene.add( this.quad ); 74 | 75 | }; 76 | 77 | THREE.BloomPass.prototype = { 78 | 79 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 80 | 81 | if ( maskActive ) renderer.context.disable( renderer.context.STENCIL_TEST ); 82 | 83 | // Render quad with blured scene into texture (convolution pass 1) 84 | 85 | this.quad.material = this.materialConvolution; 86 | 87 | this.convolutionUniforms[ "tDiffuse" ].value = readBuffer; 88 | this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurX; 89 | 90 | renderer.render( this.scene, this.camera, this.renderTargetX, true ); 91 | 92 | 93 | // Render quad with blured scene into texture (convolution pass 2) 94 | 95 | this.convolutionUniforms[ "tDiffuse" ].value = this.renderTargetX; 96 | this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurY; 97 | 98 | renderer.render( this.scene, this.camera, this.renderTargetY, true ); 99 | 100 | // Render original scene with superimposed blur to texture 101 | 102 | this.quad.material = this.materialCopy; 103 | 104 | this.copyUniforms[ "tDiffuse" ].value = this.renderTargetY; 105 | 106 | if ( maskActive ) renderer.context.enable( renderer.context.STENCIL_TEST ); 107 | 108 | renderer.render( this.scene, this.camera, readBuffer, this.clear ); 109 | 110 | } 111 | 112 | }; 113 | 114 | THREE.BloomPass.blurX = new THREE.Vector2( 0.001953125, 0.0 ); 115 | THREE.BloomPass.blurY = new THREE.Vector2( 0.0, 0.001953125 ); 116 | -------------------------------------------------------------------------------- /pages/_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Particulate Medusae 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% if TEMPLATE_DEBUG %} 17 | 18 | {% endif %} 19 | 20 | 21 | 54 | 55 | 56 |
57 |
58 |
About
59 |
60 |
61 |
Particles
62 |
63 | 64 |
65 |
Colors
66 |
67 |
68 |
Audio
69 |
70 |
71 |
Post FX
72 |
73 |
74 |
Run Sim
75 |
76 |
77 | 78 |
79 |
80 |
81 |
Particles
82 |
83 |
84 |
85 |
Constraints
86 |
87 |
88 |
89 |
Forces
90 |
91 |
92 |
93 | 94 |
95 | 96 | {% if TEMPLATE_DEBUG %} 97 | 98 | 99 | 100 | 101 | 102 | {# Run tests by appending ?test=true to the url #} 103 | 109 | {% else %} 110 | 111 | 112 | {% endif %} 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /static/js/components/GraphComponent.js: -------------------------------------------------------------------------------- 1 | App.GraphComponent = GraphComponent; 2 | function GraphComponent(opts) { 3 | opts = opts || {}; 4 | 5 | this.max = 0; 6 | this.current = 0; 7 | 8 | this.updateFactor = opts.updateFactor || 0.1; 9 | this.drawOffset = opts.drawOffset || 2; 10 | this.pxRatio = opts.pixelRatio || window.devicePixelRatio; 11 | 12 | this.createBuffers(); 13 | this.setSize(opts.width || 380, opts.height || 24); 14 | this.createLineBuffer(); 15 | this.createElement(); 16 | this.setLabel(opts.label || ''); 17 | } 18 | 19 | GraphComponent.create = App.ctor(GraphComponent); 20 | 21 | GraphComponent.prototype.setSize = function (width, height) { 22 | var pxRatio = this.pxRatio; 23 | 24 | this.width = width; 25 | this.height = height; 26 | this.fullWidth = width * pxRatio; 27 | this.fullHeight = height * pxRatio; 28 | 29 | this.canvas.width = this.buffer.width = this.fullWidth; 30 | this.canvas.height = this.buffer.height = this.fullHeight; 31 | 32 | this.canvas.style.width = width + 'px'; 33 | this.canvas.style.height = height + 'px'; 34 | }; 35 | 36 | GraphComponent.prototype.appendTo = function (parent) { 37 | parent.appendChild(this.element); 38 | }; 39 | 40 | GraphComponent.prototype.createElement = function () { 41 | var el = this.element = document.createElement('div'); 42 | var labelContainer = document.createElement('div'); 43 | var label = this._labelEl = document.createTextNode(''); 44 | var value = this._valueEl = document.createTextNode(''); 45 | var canvas = this.canvas; 46 | 47 | el.className = 'graph'; 48 | labelContainer.className = 'label'; 49 | 50 | el.appendChild(canvas); 51 | el.appendChild(labelContainer); 52 | 53 | labelContainer.appendChild(label); 54 | labelContainer.appendChild(value); 55 | }; 56 | 57 | GraphComponent.prototype.setLabel = function (label) { 58 | this._labelEl.textContent = label + ' '; 59 | }; 60 | 61 | GraphComponent.prototype.createBuffers = function () { 62 | this.canvas = document.createElement('canvas'); 63 | this.ctx = this.canvas.getContext('2d'); 64 | this.buffer = document.createElement('canvas'); 65 | this.btx = this.buffer.getContext('2d'); 66 | }; 67 | 68 | GraphComponent.prototype.grayscale = function (v, a) { 69 | return 'rgba(' + [v, v, v, a].join(',') + ')'; 70 | }; 71 | 72 | GraphComponent.prototype.createLineBuffer = function () { 73 | var height = this.fullHeight; 74 | var pxRatio = this.pxRatio; 75 | 76 | var line = document.createElement('canvas'); 77 | var ltx = line.getContext('2d'); 78 | 79 | line.width = pxRatio; 80 | line.height = height * 2 + 10; 81 | 82 | ltx.fillStyle = this.grayscale(255, 0.7); 83 | ltx.fillRect(0, height, pxRatio, pxRatio); 84 | ltx.fillStyle = this.grayscale(255, 0.1); 85 | ltx.fillRect(0, height + 2 * pxRatio, pxRatio, height + 10); 86 | 87 | this.line = line; 88 | }; 89 | 90 | GraphComponent.prototype.start = function () { 91 | this._startTime = Date.now(); 92 | }; 93 | 94 | GraphComponent.prototype.end = function () { 95 | this._endTime = Date.now(); 96 | }; 97 | 98 | GraphComponent.prototype.reset = function () { 99 | this._startTime = this._endTime = 0; 100 | }; 101 | 102 | GraphComponent.prototype._startTime = 0; 103 | GraphComponent.prototype._endTime = 0; 104 | GraphComponent.prototype._textTick = 0; 105 | 106 | GraphComponent.prototype.update = function (value, skipLabel) { 107 | var width = this.fullWidth; 108 | var height = this.fullHeight; 109 | var pxRatio = this.pxRatio; 110 | var drawOffset = this.drawOffset; 111 | 112 | var buffer = this.buffer; 113 | var canvas = this.canvas; 114 | var btx = this.btx; 115 | var ctx = this.ctx; 116 | 117 | var current, max; 118 | 119 | if (value == null) { 120 | value = this._endTime - this._startTime; 121 | current = this.current += (value - this.current) * this.updateFactor; 122 | max = this.max *= 0.99; 123 | } else { 124 | current = max = value; 125 | } 126 | 127 | if (current > max) { 128 | max = this.max = current; 129 | } 130 | 131 | btx.clearRect(0, 0, width, height); 132 | btx.drawImage(canvas, -drawOffset * pxRatio, 0, width, height); 133 | 134 | ctx.clearRect(0, 0, width, height); 135 | ctx.drawImage(buffer, 0, 0, width, height); 136 | ctx.drawImage(this.line, 137 | 0, current * height / (max || 1), pxRatio, height, 138 | width - pxRatio, 0, pxRatio, height); 139 | 140 | if (!skipLabel && ++ this._textTick > 6) { 141 | this._textTick = 0; 142 | this._valueEl.textContent = current.toFixed(3); 143 | } 144 | 145 | this._startTime = this._endTime = 0; 146 | }; 147 | -------------------------------------------------------------------------------- /static/js/utils/Features.js: -------------------------------------------------------------------------------- 1 | /*global Promise*/ 2 | var Features = App.Features = {}; 3 | 4 | Features.detectWebAudio = function () { 5 | return new Promise(function (resolve, reject) { 6 | var prefixed = 'webkitAudioContext' in window; 7 | var unprefixed = 'AudioContext' in window; 8 | 9 | if (prefixed || unprefixed) { 10 | resolve(); 11 | } else { 12 | reject(); 13 | } 14 | }); 15 | }; 16 | 17 | Features.detectAudioCodecs = function (codecs) { 18 | return new Promise(function (resolve, reject) { 19 | var audio = new Audio(); 20 | var canPlay = codecs.find(function (codec) { 21 | return !!audio.canPlayType(codec).replace(/^no$/, ''); 22 | }); 23 | 24 | if (canPlay) { 25 | resolve(); 26 | } else { 27 | reject(); 28 | } 29 | }); 30 | }; 31 | 32 | Features.detectAudioAutoplay = function () { 33 | return new Promise(function (resolve, reject) { 34 | var mp3 = 'data:audio/mpeg;base64,/+MYxAAAAANIAUAAAASEEB/jwOFM/0MM/90b/+RhST//w4NFwOjf///PZu////9lns5GFDv//l9GlUIEEIAAAgIg8Ir/JGq3/+MYxDsLIj5QMYcoAP0dv9HIjUcH//yYSg+CIbkGP//8w0bLVjUP///3Z0x5QCAv/yLjwtGKTEFNRTMuOTeqqqqqqqqqqqqq/+MYxEkNmdJkUYc4AKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; 35 | var ogg = 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAADqnjMlAAAAAOyyzPIBHgF2b3JiaXMAAAAAAUAfAABAHwAAQB8AAEAfAACZAU9nZ1MAAAAAAAAAAAAA6p4zJQEAAAANJGeqCj3//////////5ADdm9yYmlzLQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTAxMTAxIChTY2hhdWZlbnVnZ2V0KQAAAAABBXZvcmJpcw9CQ1YBAAABAAxSFCElGVNKYwiVUlIpBR1jUFtHHWPUOUYhZBBTiEkZpXtPKpVYSsgRUlgpRR1TTFNJlVKWKUUdYxRTSCFT1jFloXMUS4ZJCSVsTa50FkvomWOWMUYdY85aSp1j1jFFHWNSUkmhcxg6ZiVkFDpGxehifDA6laJCKL7H3lLpLYWKW4q91xpT6y2EGEtpwQhhc+211dxKasUYY4wxxsXiUyiC0JBVAAABAABABAFCQ1YBAAoAAMJQDEVRgNCQVQBABgCAABRFcRTHcRxHkiTLAkJDVgEAQAAAAgAAKI7hKJIjSZJkWZZlWZameZaouaov+64u667t6roOhIasBACAAAAYRqF1TCqDEEPKQ4QUY9AzoxBDDEzGHGNONKQMMogzxZAyiFssLqgQBKEhKwKAKAAAwBjEGGIMOeekZFIi55iUTkoDnaPUUcoolRRLjBmlEluJMYLOUeooZZRCjKXFjFKJscRUAABAgAMAQICFUGjIigAgCgCAMAYphZRCjCnmFHOIMeUcgwwxxiBkzinoGJNOSuWck85JiRhjzjEHlXNOSuekctBJyaQTAAAQ4AAAEGAhFBqyIgCIEwAwSJKmWZomipamiaJniqrqiaKqWp5nmp5pqqpnmqpqqqrrmqrqypbnmaZnmqrqmaaqiqbquqaquq6nqrZsuqoum65q267s+rZru77uqapsm6or66bqyrrqyrbuurbtS56nqqKquq5nqq6ruq5uq65r25pqyq6purJtuq4tu7Js664s67pmqq5suqotm64s667s2rYqy7ovuq5uq7Ks+6os+75s67ru2rrwi65r66os674qy74x27bwy7ouHJMnqqqnqq7rmarrqq5r26rr2rqmmq5suq4tm6or26os67Yry7aumaosm64r26bryrIqy77vyrJui67r66Ys67oqy8Lu6roxzLat+6Lr6roqy7qvyrKuu7ru+7JuC7umqrpuyrKvm7Ks+7auC8us27oxuq7vq7It/KosC7+u+8Iy6z5jdF1fV21ZGFbZ9n3d95Vj1nVhWW1b+V1bZ7y+bgy7bvzKrQvLstq2scy6rSyvrxvDLux8W/iVmqratum6um7Ksq/Lui60dd1XRtf1fdW2fV+VZd+3hV9pG8OwjK6r+6os68Jry8ov67qw7MIvLKttK7+r68ow27qw3L6wLL/uC8uq277v6rrStXVluX2fsSu38QsAABhwAAAIMKEMFBqyIgCIEwBAEHIOKQahYgpCCKGkEEIqFWNSMuakZM5JKaWUFEpJrWJMSuaclMwxKaGUlkopqYRSWiqlxBRKaS2l1mJKqcVQSmulpNZKSa2llGJMrcUYMSYlc05K5pyUklJrJZXWMucoZQ5K6iCklEoqraTUYuacpA46Kx2E1EoqMZWUYgupxFZKaq2kFGMrMdXUWo4hpRhLSrGVlFptMdXWWqs1YkxK5pyUzDkqJaXWSiqtZc5J6iC01DkoqaTUYiopxco5SR2ElDLIqJSUWiupxBJSia20FGMpqcXUYq4pxRZDSS2WlFosqcTWYoy1tVRTJ6XFklKMJZUYW6y5ttZqDKXEVkqLsaSUW2sx1xZjjqGkFksrsZWUWmy15dhayzW1VGNKrdYWY40x5ZRrrT2n1mJNMdXaWqy51ZZbzLXnTkprpZQWS0oxttZijTHmHEppraQUWykpxtZara3FXEMpsZXSWiypxNhirLXFVmNqrcYWW62ltVprrb3GVlsurdXcYqw9tZRrrLXmWFNtBQAADDgAAASYUAYKDVkJAEQBAADGMMYYhEYpx5yT0ijlnHNSKucghJBS5hyEEFLKnINQSkuZcxBKSSmUklJqrYVSUmqttQIAAAocAAACbNCUWByg0JCVAEAqAIDBcTRNFFXVdX1fsSxRVFXXlW3jVyxNFFVVdm1b+DVRVFXXtW3bFn5NFFVVdmXZtoWiqrqybduybgvDqKqua9uybeuorqvbuq3bui9UXVmWbVu3dR3XtnXd9nVd+Bmzbeu2buu+8CMMR9/4IeTj+3RCCAAAT3AAACqwYXWEk6KxwEJDVgIAGQAAgDFKGYUYM0gxphhjTDHGmAAAgAEHAIAAE8pAoSErAoAoAADAOeecc84555xzzjnnnHPOOeecc44xxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY0wAwE6EA8BOhIVQaMhKACAcAABACCEpKaWUUkoRU85BSSmllFKqFIOMSkoppZRSpBR1lFJKKaWUIqWgpJJSSimllElJKaWUUkoppYw6SimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaVUSimllFJKKaWUUkoppRQAYPLgAACVYOMMK0lnhaPBhYasBAByAwAAhRiDEEJpraRUUkolVc5BKCWUlEpKKZWUUqqYgxBKKqmlklJKKbXSQSihlFBKKSWUUkooJYQQSgmhlFRCK6mEUkoHoYQSQimhhFRKKSWUzkEoIYUOQkmllNRCSB10VFIpIZVSSiklpZQ6CKGUklJLLZVSWkqpdBJSKamV1FJqqbWSUgmhpFZKSSWl0lpJJbUSSkklpZRSSymFVFJJJYSSUioltZZaSqm11lJIqZWUUkqppdRSSiWlkEpKqZSSUmollZRSaiGVlEpJKaTUSimlpFRCSamlUlpKLbWUSkmptFRSSaWUlEpJKaVSSksppRJKSqmllFpJKYWSUkoplZJSSyW1VEoKJaWUUkmptJRSSymVklIBAEAHDgAAAUZUWoidZlx5BI4oZJiAAgAAQABAgAkgMEBQMApBgDACAQAAAADAAAAfAABHARAR0ZzBAUKCwgJDg8MDAAAAAAAAAAAAAACAT2dnUwAEAAAAAAAAAADqnjMlAgAAADzQPmcBAQA='; 36 | var audio = new Audio(); 37 | var src = audio.canPlayType('audio/ogg') ? ogg : mp3; 38 | 39 | audio.autoplay = true; 40 | audio.volume = 0; 41 | 42 | var _reject = setTimeout(reject, 20); 43 | audio.addEventListener('play', function() { 44 | clearTimeout(_reject); 45 | resolve(); 46 | }, false); 47 | 48 | audio.src = src; 49 | audio.play(); 50 | }); 51 | }; 52 | 53 | Features.detectInputType = function (type) { 54 | return new Promise(function (resolve, reject) { 55 | var el = document.createElement('input'); 56 | el.setAttribute('type', type); 57 | 58 | if (el.type === type) { 59 | resolve(); 60 | } else { 61 | reject(); 62 | } 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /static/scss/tests.scss: -------------------------------------------------------------------------------- 1 | $green : #5c4; 2 | $red : #d33; 3 | 4 | .testing { 5 | .controls, 6 | #container { 7 | display: none; 8 | } 9 | } 10 | 11 | #qunit { 12 | background: #222; 13 | color: #ccc; 14 | font: 14px/20px "Source Code Pro", "Menlo", monospace; 15 | padding: 30px 20px; 16 | border-bottom: 5px solid #111; 17 | position: relative; 18 | 19 | &.qunit-is-hidden { 20 | display: none; 21 | } 22 | 23 | * { 24 | margin: 0; 25 | padding: 0; 26 | border: 0; 27 | } 28 | 29 | #qunit-header { 30 | font-size: 24px; 31 | line-height: 30px; 32 | margin: 0 520px 10px 0; 33 | 34 | a { 35 | color: #ccc; 36 | text-decoration: none; 37 | 38 | &:hover { 39 | color: #ccc; 40 | text-decoration: underline; 41 | } 42 | } 43 | } 44 | 45 | #qunit-testresult { 46 | margin: 0 -20px 20px; 47 | padding: 10px 20px; 48 | background: #111; 49 | 50 | .module-name { 51 | font-weight: bold; 52 | } 53 | 54 | br { 55 | content: ''; 56 | } 57 | 58 | span:before { 59 | content: ' '; 60 | } 61 | 62 | span.failed { 63 | color: $red; 64 | } 65 | 66 | span.passed { 67 | color: $green; 68 | } 69 | } 70 | 71 | #qunit-testrunner-toolbar { 72 | position: absolute; 73 | right: 20px; 74 | top: 30px; 75 | 76 | input { 77 | font: inherit; 78 | margin: 0 6px 0 24px; 79 | } 80 | 81 | label { 82 | display: inline-block; 83 | } 84 | 85 | .qunit-filter { 86 | display: none; 87 | } 88 | } 89 | 90 | #qunit-modulefilter-container { 91 | position: absolute; 92 | right: 0; 93 | top: 30px; 94 | 95 | label { 96 | margin-right: 10px; 97 | } 98 | } 99 | 100 | #qunit-banner { 101 | position: absolute; 102 | top: 0; 103 | left: 0; 104 | right: 0; 105 | height: 10px; 106 | 107 | &.qunit-fail { 108 | background-color: $red; 109 | } 110 | 111 | &.qunit-pass { 112 | background-color: $green; 113 | } 114 | } 115 | 116 | #qunit-tests { 117 | color: #666; 118 | list-style: outside decimal; 119 | padding-left: 30px; 120 | line-height: 30px; 121 | 122 | b.counts { 123 | font-weight: normal; 124 | b { 125 | font-weight: normal; 126 | } 127 | } 128 | 129 | &.hidepass { 130 | li.pass, 131 | li.running { 132 | display: none; 133 | } 134 | } 135 | 136 | li { 137 | a { 138 | color: #666; 139 | margin: 0 6px; 140 | text-decoration: underline; 141 | 142 | &:hover { 143 | color: #ccc; 144 | } 145 | } 146 | 147 | b.counts { 148 | color: #666; 149 | } 150 | 151 | &.fail { 152 | span.test-message { 153 | color: $red; 154 | } 155 | 156 | strong { 157 | color: $red; 158 | 159 | b.counts b.failed { 160 | color: $red; 161 | } 162 | } 163 | } 164 | 165 | &.pass { 166 | b.passed { 167 | color: $green; 168 | } 169 | 170 | span.test-message { 171 | color: $green; 172 | } 173 | 174 | strong { 175 | color: #ccc; 176 | } 177 | } 178 | 179 | strong { 180 | font-weight: normal; 181 | cursor: pointer; 182 | } 183 | } 184 | 185 | ol { 186 | list-style: inside lower-alpha; 187 | 188 | li table { 189 | border-collapse: collapse; 190 | 191 | td pre { 192 | background-color: #181818; 193 | border-radius: 4px; 194 | color: #ccc; 195 | font: inherit; 196 | margin: 0 0 5px 10px; 197 | padding: 5px 10px; 198 | white-space: pre-wrap; 199 | word-wrap: break-word; 200 | line-height: 20px; 201 | float: left; 202 | } 203 | 204 | th { 205 | font-weight: normal; 206 | line-height: 30px; 207 | text-align: right; 208 | } 209 | 210 | th, 211 | td { 212 | padding: 0; 213 | vertical-align: top; 214 | } 215 | 216 | tr.test-actual { 217 | color: $red; 218 | } 219 | 220 | tr.test-diff { 221 | del { 222 | color: $green; 223 | } 224 | 225 | ins { 226 | color: $red; 227 | } 228 | 229 | del, 230 | ins { 231 | text-decoration: none; 232 | } 233 | } 234 | 235 | tr.test-expected { 236 | color: $green; 237 | } 238 | } 239 | } 240 | 241 | .qunit-assert-list.qunit-collapsed { 242 | display: none; 243 | } 244 | } 245 | 246 | #qunit-userAgent { 247 | color: #666; 248 | font-size: 10px; 249 | margin-bottom: 10px; 250 | } 251 | } 252 | 253 | #qunit-fixture { 254 | position: absolute; 255 | height: 768px; 256 | left: -1024px; 257 | top: -768px; 258 | width: 1024px; 259 | } 260 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/BokehShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Depth-of-field shader with bokeh 5 | * ported from GLSL shader by Martins Upitis 6 | * http://artmartinsh.blogspot.com/2010/02/glsl-lens-blur-filter-with-bokeh.html 7 | */ 8 | 9 | THREE.BokehShader = { 10 | 11 | uniforms: { 12 | 13 | "tColor": { type: "t", value: null }, 14 | "tDepth": { type: "t", value: null }, 15 | "focus": { type: "f", value: 1.0 }, 16 | "aspect": { type: "f", value: 1.0 }, 17 | "aperture": { type: "f", value: 0.025 }, 18 | "maxblur": { type: "f", value: 1.0 } 19 | 20 | }, 21 | 22 | vertexShader: [ 23 | 24 | "varying vec2 vUv;", 25 | 26 | "void main() {", 27 | 28 | "vUv = uv;", 29 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 30 | 31 | "}" 32 | 33 | ].join("\n"), 34 | 35 | fragmentShader: [ 36 | 37 | "varying vec2 vUv;", 38 | 39 | "uniform sampler2D tColor;", 40 | "uniform sampler2D tDepth;", 41 | 42 | "uniform float maxblur;", // max blur amount 43 | "uniform float aperture;", // aperture - bigger values for shallower depth of field 44 | 45 | "uniform float focus;", 46 | "uniform float aspect;", 47 | 48 | "void main() {", 49 | 50 | "vec2 aspectcorrect = vec2( 1.0, aspect );", 51 | 52 | "vec4 depth1 = texture2D( tDepth, vUv );", 53 | 54 | "float factor = depth1.x - focus;", 55 | 56 | "vec2 dofblur = vec2 ( clamp( factor * aperture, -maxblur, maxblur ) );", 57 | 58 | "vec2 dofblur9 = dofblur * 0.9;", 59 | "vec2 dofblur7 = dofblur * 0.7;", 60 | "vec2 dofblur4 = dofblur * 0.4;", 61 | 62 | "vec4 col = vec4( 0.0 );", 63 | 64 | "col += texture2D( tColor, vUv.xy );", 65 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.0, 0.4 ) * aspectcorrect ) * dofblur );", 66 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.15, 0.37 ) * aspectcorrect ) * dofblur );", 67 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.29, 0.29 ) * aspectcorrect ) * dofblur );", 68 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.37, 0.15 ) * aspectcorrect ) * dofblur );", 69 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.40, 0.0 ) * aspectcorrect ) * dofblur );", 70 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.37, -0.15 ) * aspectcorrect ) * dofblur );", 71 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.29, -0.29 ) * aspectcorrect ) * dofblur );", 72 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.15, -0.37 ) * aspectcorrect ) * dofblur );", 73 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.0, -0.4 ) * aspectcorrect ) * dofblur );", 74 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.15, 0.37 ) * aspectcorrect ) * dofblur );", 75 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.29, 0.29 ) * aspectcorrect ) * dofblur );", 76 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.37, 0.15 ) * aspectcorrect ) * dofblur );", 77 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.4, 0.0 ) * aspectcorrect ) * dofblur );", 78 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.37, -0.15 ) * aspectcorrect ) * dofblur );", 79 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur );", 80 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.15, -0.37 ) * aspectcorrect ) * dofblur );", 81 | 82 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.15, 0.37 ) * aspectcorrect ) * dofblur9 );", 83 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.37, 0.15 ) * aspectcorrect ) * dofblur9 );", 84 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.37, -0.15 ) * aspectcorrect ) * dofblur9 );", 85 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.15, -0.37 ) * aspectcorrect ) * dofblur9 );", 86 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.15, 0.37 ) * aspectcorrect ) * dofblur9 );", 87 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.37, 0.15 ) * aspectcorrect ) * dofblur9 );", 88 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.37, -0.15 ) * aspectcorrect ) * dofblur9 );", 89 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.15, -0.37 ) * aspectcorrect ) * dofblur9 );", 90 | 91 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.29, 0.29 ) * aspectcorrect ) * dofblur7 );", 92 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.40, 0.0 ) * aspectcorrect ) * dofblur7 );", 93 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.29, -0.29 ) * aspectcorrect ) * dofblur7 );", 94 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.0, -0.4 ) * aspectcorrect ) * dofblur7 );", 95 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.29, 0.29 ) * aspectcorrect ) * dofblur7 );", 96 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.4, 0.0 ) * aspectcorrect ) * dofblur7 );", 97 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur7 );", 98 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.0, 0.4 ) * aspectcorrect ) * dofblur7 );", 99 | 100 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.29, 0.29 ) * aspectcorrect ) * dofblur4 );", 101 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.4, 0.0 ) * aspectcorrect ) * dofblur4 );", 102 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.29, -0.29 ) * aspectcorrect ) * dofblur4 );", 103 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.0, -0.4 ) * aspectcorrect ) * dofblur4 );", 104 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.29, 0.29 ) * aspectcorrect ) * dofblur4 );", 105 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.4, 0.0 ) * aspectcorrect ) * dofblur4 );", 106 | "col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur4 );", 107 | "col += texture2D( tColor, vUv.xy + ( vec2( 0.0, 0.4 ) * aspectcorrect ) * dofblur4 );", 108 | 109 | "gl_FragColor = col / 41.0;", 110 | "gl_FragColor.a = 1.0;", 111 | 112 | "}" 113 | 114 | ].join("\n") 115 | 116 | }; 117 | -------------------------------------------------------------------------------- /static/lib-extras/three/shaders/SSAOShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Screen-space ambient occlusion shader 5 | * - ported from 6 | * SSAO GLSL shader v1.2 7 | * assembled by Martins Upitis (martinsh) (http://devlog-martinsh.blogspot.com) 8 | * original technique is made by ArKano22 (http://www.gamedev.net/topic/550699-ssao-no-halo-artifacts/) 9 | * - modifications 10 | * - modified to use RGBA packed depth texture (use clear color 1,1,1,1 for depth pass) 11 | * - refactoring and optimizations 12 | */ 13 | 14 | THREE.SSAOShader = { 15 | 16 | uniforms: { 17 | 18 | "tDiffuse": { type: "t", value: null }, 19 | "tDepth": { type: "t", value: null }, 20 | "size": { type: "v2", value: new THREE.Vector2( 512, 512 ) }, 21 | "cameraNear": { type: "f", value: 1 }, 22 | "cameraFar": { type: "f", value: 100 }, 23 | "onlyAO": { type: "i", value: 0 }, 24 | "aoClamp": { type: "f", value: 0.5 }, 25 | "lumInfluence": { type: "f", value: 0.5 } 26 | 27 | }, 28 | 29 | vertexShader: [ 30 | 31 | "varying vec2 vUv;", 32 | 33 | "void main() {", 34 | 35 | "vUv = uv;", 36 | 37 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 38 | 39 | "}" 40 | 41 | ].join("\n"), 42 | 43 | fragmentShader: [ 44 | 45 | "uniform float cameraNear;", 46 | "uniform float cameraFar;", 47 | 48 | "uniform bool onlyAO;", // use only ambient occlusion pass? 49 | 50 | "uniform vec2 size;", // texture width, height 51 | "uniform float aoClamp;", // depth clamp - reduces haloing at screen edges 52 | 53 | "uniform float lumInfluence;", // how much luminance affects occlusion 54 | 55 | "uniform sampler2D tDiffuse;", 56 | "uniform sampler2D tDepth;", 57 | 58 | "varying vec2 vUv;", 59 | 60 | // "#define PI 3.14159265", 61 | "#define DL 2.399963229728653", // PI * ( 3.0 - sqrt( 5.0 ) ) 62 | "#define EULER 2.718281828459045", 63 | 64 | // helpers 65 | 66 | "float width = size.x;", // texture width 67 | "float height = size.y;", // texture height 68 | 69 | "float cameraFarPlusNear = cameraFar + cameraNear;", 70 | "float cameraFarMinusNear = cameraFar - cameraNear;", 71 | "float cameraCoef = 2.0 * cameraNear;", 72 | 73 | // user variables 74 | 75 | "const int samples = 8;", // ao sample count 76 | "const float radius = 5.0;", // ao radius 77 | 78 | "const bool useNoise = false;", // use noise instead of pattern for sample dithering 79 | "const float noiseAmount = 0.0003;", // dithering amount 80 | 81 | "const float diffArea = 0.4;", // self-shadowing reduction 82 | "const float gDisplace = 0.4;", // gauss bell center 83 | 84 | 85 | // RGBA depth 86 | 87 | "float unpackDepth( const in vec4 rgba_depth ) {", 88 | 89 | "const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );", 90 | "float depth = dot( rgba_depth, bit_shift );", 91 | "return depth;", 92 | 93 | "}", 94 | 95 | // generating noise / pattern texture for dithering 96 | 97 | "vec2 rand( const vec2 coord ) {", 98 | 99 | "vec2 noise;", 100 | 101 | "if ( useNoise ) {", 102 | 103 | "float nx = dot ( coord, vec2( 12.9898, 78.233 ) );", 104 | "float ny = dot ( coord, vec2( 12.9898, 78.233 ) * 2.0 );", 105 | 106 | "noise = clamp( fract ( 43758.5453 * sin( vec2( nx, ny ) ) ), 0.0, 1.0 );", 107 | 108 | "} else {", 109 | 110 | "float ff = fract( 1.0 - coord.s * ( width / 2.0 ) );", 111 | "float gg = fract( coord.t * ( height / 2.0 ) );", 112 | 113 | "noise = vec2( 0.25, 0.75 ) * vec2( ff ) + vec2( 0.75, 0.25 ) * gg;", 114 | 115 | "}", 116 | 117 | "return ( noise * 2.0 - 1.0 ) * noiseAmount;", 118 | 119 | "}", 120 | 121 | "float readDepth( const in vec2 coord ) {", 122 | 123 | // "return ( 2.0 * cameraNear ) / ( cameraFar + cameraNear - unpackDepth( texture2D( tDepth, coord ) ) * ( cameraFar - cameraNear ) );", 124 | "return cameraCoef / ( cameraFarPlusNear - unpackDepth( texture2D( tDepth, coord ) ) * cameraFarMinusNear );", 125 | 126 | 127 | "}", 128 | 129 | "float compareDepths( const in float depth1, const in float depth2, inout int far ) {", 130 | 131 | "float garea = 2.0;", // gauss bell width 132 | "float diff = ( depth1 - depth2 ) * 100.0;", // depth difference (0-100) 133 | 134 | // reduce left bell width to avoid self-shadowing 135 | 136 | "if ( diff < gDisplace ) {", 137 | 138 | "garea = diffArea;", 139 | 140 | "} else {", 141 | 142 | "far = 1;", 143 | 144 | "}", 145 | 146 | "float dd = diff - gDisplace;", 147 | "float gauss = pow( EULER, -2.0 * dd * dd / ( garea * garea ) );", 148 | "return gauss;", 149 | 150 | "}", 151 | 152 | "float calcAO( float depth, float dw, float dh ) {", 153 | 154 | "float dd = radius - depth * radius;", 155 | "vec2 vv = vec2( dw, dh );", 156 | 157 | "vec2 coord1 = vUv + dd * vv;", 158 | "vec2 coord2 = vUv - dd * vv;", 159 | 160 | "float temp1 = 0.0;", 161 | "float temp2 = 0.0;", 162 | 163 | "int far = 0;", 164 | "temp1 = compareDepths( depth, readDepth( coord1 ), far );", 165 | 166 | // DEPTH EXTRAPOLATION 167 | 168 | "if ( far > 0 ) {", 169 | 170 | "temp2 = compareDepths( readDepth( coord2 ), depth, far );", 171 | "temp1 += ( 1.0 - temp1 ) * temp2;", 172 | 173 | "}", 174 | 175 | "return temp1;", 176 | 177 | "}", 178 | 179 | "void main() {", 180 | 181 | "vec2 noise = rand( vUv );", 182 | "float depth = readDepth( vUv );", 183 | 184 | "float tt = clamp( depth, aoClamp, 1.0 );", 185 | 186 | "float w = ( 1.0 / width ) / tt + ( noise.x * ( 1.0 - noise.x ) );", 187 | "float h = ( 1.0 / height ) / tt + ( noise.y * ( 1.0 - noise.y ) );", 188 | 189 | "float ao = 0.0;", 190 | 191 | "float dz = 1.0 / float( samples );", 192 | "float z = 1.0 - dz / 2.0;", 193 | "float l = 0.0;", 194 | 195 | "for ( int i = 0; i <= samples; i ++ ) {", 196 | 197 | "float r = sqrt( 1.0 - z );", 198 | 199 | "float pw = cos( l ) * r;", 200 | "float ph = sin( l ) * r;", 201 | "ao += calcAO( depth, pw * w, ph * h );", 202 | "z = z - dz;", 203 | "l = l + DL;", 204 | 205 | "}", 206 | 207 | "ao /= float( samples );", 208 | "ao = 1.0 - ao;", 209 | 210 | "vec3 color = texture2D( tDiffuse, vUv ).rgb;", 211 | 212 | "vec3 lumcoeff = vec3( 0.299, 0.587, 0.114 );", 213 | "float lum = dot( color.rgb, lumcoeff );", 214 | "vec3 luminance = vec3( lum );", 215 | 216 | "vec3 final = vec3( color * mix( vec3( ao ), vec3( 1.0 ), luminance * lumInfluence ) );", // mix( color * ao, white, luminance ) 217 | 218 | "if ( onlyAO ) {", 219 | 220 | "final = vec3( mix( vec3( ao ), vec3( 1.0 ), luminance * lumInfluence ) );", // ambient occlusion only 221 | 222 | "}", 223 | 224 | "gl_FragColor = vec4( final, 1.0 );", 225 | 226 | "}" 227 | 228 | ].join("\n") 229 | 230 | }; 231 | -------------------------------------------------------------------------------- /static/js/post-processing/LensDirtPass.js: -------------------------------------------------------------------------------- 1 | require('./LensDirtTexture'); 2 | 3 | App.LensDirtPass = LensDirtPass; 4 | function LensDirtPass(opts) { 5 | opts = opts || {}; 6 | 7 | var quads = opts.quads || 100; 8 | var textureSize = opts.textureSize || 1024; 9 | var textureCells = opts.textureCells || 10; 10 | var textureCellPad = opts.textureCellPad || 20; 11 | var textureDetail = opts.textureDetail || 50; 12 | 13 | this.renderToScreen = false; 14 | this.enabled = true; 15 | this.needsSwap = false; 16 | this.clear = false; 17 | 18 | this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); 19 | this.scene = new THREE.Scene(); 20 | this.scale = 1; 21 | 22 | this.textureMap = new App.LensDirtTexture(textureSize, textureCells, { 23 | detail : textureDetail, 24 | cellPad : textureCellPad 25 | }); 26 | 27 | this.geom = this.createQuadGeom(quads, textureCells); 28 | this.mesh = new THREE.Mesh(this.geom, new App.AlphaMaterial({ 29 | color : 0xffffff, 30 | opacity : 0.5, 31 | map : this.textureMap.texture, 32 | blending : THREE.AdditiveBlending, 33 | transparent : true 34 | })); 35 | 36 | this.scene.add(this.mesh); 37 | 38 | this._quadIndex = 0; 39 | this._quadCount = quads; 40 | } 41 | 42 | LensDirtPass.prototype.setSize = function (width, height) { 43 | var camera = this.camera; 44 | var w, h, s; 45 | 46 | if (width > height) { 47 | w = 1; 48 | h = s = height / width; 49 | } else { 50 | w = s = width / height; 51 | h = 1; 52 | } 53 | 54 | camera.left = -w; 55 | camera.right = w; 56 | camera.top = h; 57 | camera.bottom = -h; 58 | this.scale = s; 59 | 60 | camera.updateProjectionMatrix(); 61 | }; 62 | 63 | // .................................................. 64 | // Quad geometry 65 | // 66 | 67 | LensDirtPass.prototype._quadGeomPosition = function (count) { 68 | var verts = new Float32Array(count * 4 * 3); 69 | var positionAttr = new THREE.BufferAttribute(verts, 3); 70 | 71 | return positionAttr; 72 | }; 73 | 74 | LensDirtPass.prototype._quadGeomIndex = function (count) { 75 | var indices = new Uint16Array(count * 6); 76 | var indexAttr = new THREE.BufferAttribute(indices, 1); 77 | var qi = 0, qj = 0; 78 | var a, b, c, d; 79 | 80 | for (var i = 0; i < count; i ++) { 81 | a = qi; 82 | b = qi + 1; 83 | c = qi + 2; 84 | d = qi + 3; 85 | 86 | indices[qj] = a; 87 | indices[qj + 1] = b; 88 | indices[qj + 2] = c; 89 | indices[qj + 3] = c; 90 | indices[qj + 4] = d; 91 | indices[qj + 5] = a; 92 | 93 | qi += 4; 94 | qj += 6; 95 | } 96 | 97 | return indexAttr; 98 | }; 99 | 100 | LensDirtPass.prototype._quadGeomUv = function (count, cells) { 101 | var uvs = new Float32Array(count * 4 * 2); 102 | var uvAttr = new THREE.BufferAttribute(uvs, 2); 103 | var step = 1 / cells; 104 | var qi = 0, row = 0, col = 0; 105 | 106 | for (var i = 0; i < count; i ++) { 107 | uvs[qi] = uvs[qi + 6] = step * col; // au, du 108 | uvs[qi + 1] = uvs[qi + 3] = step * row; // av, bv 109 | uvs[qi + 2] = uvs[qi + 4] = step * (col + 1); // bu, cu 110 | uvs[qi + 5] = uvs[qi + 7] = step * (row + 1); // cv, dv 111 | 112 | qi += 8; 113 | 114 | if (++ col === cells) { 115 | col = 0; 116 | if (++ row === cells) { 117 | row = 0; 118 | } 119 | } 120 | } 121 | 122 | return uvAttr; 123 | }; 124 | 125 | LensDirtPass.prototype._quadGeomAlpha = function (count) { 126 | var alpha = new Float32Array(count * 4); 127 | var alphaAttr = new THREE.BufferAttribute(alpha, 1); 128 | 129 | return alphaAttr; 130 | }; 131 | 132 | LensDirtPass.prototype.createQuadGeom = function (count, cells) { 133 | var geom = new THREE.BufferGeometry(); 134 | 135 | geom.addAttribute('position', this._quadGeomPosition(count)); 136 | geom.addAttribute('uv', this._quadGeomUv(count, cells)); 137 | geom.addAttribute('alpha', this._quadGeomAlpha(count)); 138 | geom.setIndex(this._quadGeomIndex(count)); 139 | 140 | return geom; 141 | }; 142 | 143 | // .................................................. 144 | // Geometry updates 145 | // 146 | 147 | LensDirtPass.prototype._quadIndex = 0; 148 | 149 | LensDirtPass.prototype.setQuadPosition = (function () { 150 | var pos = new THREE.Matrix4(); 151 | var rot = new THREE.Matrix4(); 152 | var scale = new THREE.Matrix4(); 153 | var transform = new THREE.Matrix4(); 154 | 155 | var a = new THREE.Vector3(); 156 | var b = new THREE.Vector3(); 157 | var c = new THREE.Vector3(); 158 | var d = new THREE.Vector3(); 159 | 160 | return function (index, x, y, r, s) { 161 | var position = this.geom.attributes.position; 162 | var ai = index * 4, bi = ai + 1, ci = ai + 2, di = ai + 3; 163 | 164 | scale.makeScale(s * this.scale, s * this.scale, 1); 165 | rot.makeRotationZ(r); 166 | pos.makeTranslation(x, y, 0); 167 | 168 | transform.identity(); 169 | transform.multiply(pos); 170 | transform.multiply(rot); 171 | transform.multiply(scale); 172 | 173 | a.set(-1, -1, 0); 174 | b.set( 1, -1, 0); 175 | c.set( 1, 1, 0); 176 | d.set(-1, 1, 0); 177 | 178 | a.applyMatrix4(transform); 179 | b.applyMatrix4(transform); 180 | c.applyMatrix4(transform); 181 | d.applyMatrix4(transform); 182 | 183 | position.setXY(ai, a.x, a.y); 184 | position.setXY(bi, b.x, b.y); 185 | position.setXY(ci, c.x, c.y); 186 | position.setXY(di, d.x, d.y); 187 | 188 | position.needsUpdate = true; 189 | }; 190 | }()); 191 | 192 | LensDirtPass.prototype.setQuadAlpha = function (index, alpha) { 193 | var attr = this.geom.attributes.alpha; 194 | var array = attr.array; 195 | var ai = index * 4; 196 | 197 | array[ai] = alpha; 198 | array[ai + 1] = alpha; 199 | array[ai + 2] = alpha; 200 | array[ai + 3] = alpha; 201 | 202 | attr.needsUpdate = true; 203 | }; 204 | 205 | LensDirtPass.prototype.setGroup = function (count, x, y, spread) { 206 | var total = this._quadCount; 207 | var index = this._quadIndex; 208 | var qi = index; 209 | var xi, yi, rot, scale; 210 | 211 | for (var i = 0; i < count; i ++) { 212 | xi = x + (Math.random() - 0.5) * spread; 213 | yi = y + (Math.random() - 0.5) * spread; 214 | rot = Math.random() * Math.PI * 2; 215 | scale = Math.random() * 0.15; 216 | 217 | this.setQuadPosition(qi, xi, yi, rot, scale); 218 | this.setQuadAlpha(qi, 1); 219 | 220 | qi = index + i; 221 | if (qi >= total) { 222 | index = this._quadIndex = 0; 223 | } 224 | } 225 | 226 | this._quadIndex = qi; 227 | }; 228 | 229 | LensDirtPass.prototype.update = function (delta) { 230 | var alphaAttr = this.geom.attributes.alpha; 231 | var alphaArray = alphaAttr.array; 232 | 233 | for (var i = 0, il = alphaArray.length; i < il; i ++) { 234 | alphaArray[i] *= 0.995; 235 | } 236 | 237 | alphaAttr.needsUpdate = true; 238 | }; 239 | 240 | LensDirtPass.prototype.render = function (renderer, writeBuffer, readBuffer, delta) { 241 | if (this.renderToScreen) { 242 | renderer.render(this.scene, this.camera); 243 | } else { 244 | renderer.render(this.scene, this.camera, readBuffer, this.clear); 245 | } 246 | }; 247 | -------------------------------------------------------------------------------- /static/js/controllers/AudioController.js: -------------------------------------------------------------------------------- 1 | /*global Promise*/ 2 | var Tweens = App.Tweens; 3 | 4 | App.AudioController = AudioController; 5 | function AudioController(params) { 6 | params = params || {}; 7 | 8 | this.ctx = this.createAudioContext(); 9 | this.baseUrl = params.baseUrl; 10 | this.volume = 0; 11 | this.distance = 0; 12 | this.tween = Tweens.factorTween({ volume : 0 }, 0.1); 13 | 14 | this._bufferCache = {}; 15 | this._activeRequests = {}; 16 | this._activeSounds = []; 17 | } 18 | 19 | AudioController.create = App.ctor(AudioController); 20 | App.Dispatcher.extend(AudioController.prototype); 21 | AudioController.prototype.VOLUME_ZERO = 0.001; 22 | 23 | AudioController.prototype.AUDIO_TYPES = [ 24 | { 25 | ext : 'ogg', 26 | type : 'audio/ogg; codecs=vorbis' 27 | }, { 28 | ext : 'mp3', 29 | type : 'audio/mpeg;' 30 | } 31 | ]; 32 | 33 | AudioController.prototype.createAudioContext = function () { 34 | var AudioContext = window.AudioContext || window.webkitAudioContext; 35 | return new AudioContext(); 36 | }; 37 | 38 | AudioController.prototype.canCopyBuffers = window.AudioBuffer && 39 | window.AudioBuffer.prototype.copyFromChannel; 40 | 41 | AudioController.prototype.getAudioType = function () { 42 | if (this._audioType) { return this._audioType; } 43 | 44 | var audio = new Audio(); 45 | var type = this.AUDIO_TYPES.find(function (codec) { 46 | return !!audio.canPlayType(codec.type).replace(/^no$/, ''); 47 | }); 48 | 49 | this._audioType = type; 50 | return type; 51 | }; 52 | 53 | AudioController.prototype._findOrLoadBuffer = function (path) { 54 | var cached = this._bufferCache[path]; 55 | if (!cached) { 56 | return this._loadBuffer(path); 57 | } 58 | 59 | return new Promise(function (resolve) { 60 | resolve(cached); 61 | }); 62 | }; 63 | 64 | AudioController.prototype._loadBuffer = function (path) { 65 | var activeRequests = this._activeRequests; 66 | var request = activeRequests[path]; 67 | if (request) { return request; } 68 | 69 | var cache = this._bufferCache; 70 | var ctx = this.ctx; 71 | var audioType = this.getAudioType(); 72 | var fullUrl = this.baseUrl + path + '.' + audioType.ext; 73 | var xhr = new XMLHttpRequest(); 74 | 75 | xhr.open('GET', fullUrl, true); 76 | xhr.responseType = 'arraybuffer'; 77 | 78 | request = activeRequests[path] = new Promise(function (resolve, reject) { 79 | xhr.addEventListener('load', function() { 80 | ctx.decodeAudioData(xhr.response, resolve); 81 | }); 82 | 83 | xhr.send(); 84 | }).then(function (buffer) { 85 | cache[path] = buffer; 86 | delete activeRequests[path]; 87 | return buffer; 88 | }); 89 | 90 | return request; 91 | }; 92 | 93 | AudioController.prototype._addActiveSound = function (sound, sounds) { 94 | sounds = sounds || this._activeSounds; 95 | sound.sourceNode.onended = this._removeActiveSound.bind(this, sound, sounds); 96 | return sounds.push(sound); 97 | }; 98 | 99 | AudioController.prototype._removeActiveSound = function (sound, sounds) { 100 | sounds = sounds || this._activeSounds; 101 | var index = sounds.indexOf(sound); 102 | 103 | if (index !== -1) { 104 | sounds.splice(index, 1); 105 | } 106 | 107 | return sounds.length; 108 | }; 109 | 110 | AudioController.prototype.loadBuffer = function (params) { 111 | var path = params.path; 112 | return this._findOrLoadBuffer(path); 113 | }; 114 | 115 | AudioController.prototype.sliceBuffer = function (buffer, begin, end) { 116 | var ctx = this.ctx; 117 | var channels = buffer.numberOfChannels; 118 | var rate = buffer.sampleRate; 119 | 120 | var startOffset = rate * begin; 121 | var endOffset = rate * end; 122 | var frameCount = endOffset - startOffset; 123 | 124 | var slicedBuffer = ctx.createBuffer(channels, frameCount * 2, rate); 125 | var copyBuffer = new Float32Array(frameCount); 126 | var channel; 127 | 128 | for (channel = 0; channel < channels; channel ++) { 129 | buffer.copyFromChannel(copyBuffer, channel, startOffset); 130 | slicedBuffer.copyToChannel(copyBuffer, channel, 0); 131 | copyBuffer.reverse(); 132 | slicedBuffer.copyToChannel(copyBuffer, channel, frameCount); 133 | } 134 | 135 | return slicedBuffer; 136 | }; 137 | 138 | AudioController.prototype.createSound = function (buffer, params) { 139 | var ctx = this.ctx; 140 | var sourceNode = ctx.createBufferSource(); 141 | var gainNode = ctx.createGain(); 142 | var filterNode = ctx.createBiquadFilter(); 143 | 144 | var globalVolume = this.volume; 145 | var volume = params.volume != null ? params.volume : 1; 146 | var offsetTime = params.offsetTime; 147 | 148 | var sound = { 149 | volume : volume, 150 | buffer : buffer, 151 | startTime : ctx.currentTime, 152 | offsetTime : offsetTime, 153 | sourceNode : sourceNode, 154 | gainNode : gainNode, 155 | filterNode : filterNode 156 | }; 157 | 158 | filterNode.type = 'lowpass'; 159 | filterNode.frequency.value = 320; 160 | sourceNode.buffer = buffer; 161 | sourceNode.loop = !!params.loop; 162 | gainNode.gain.value = globalVolume * volume; 163 | 164 | sourceNode.connect(gainNode); 165 | gainNode.connect(filterNode); 166 | filterNode.connect(ctx.destination); 167 | 168 | if (offsetTime != null) { 169 | sourceNode.start(0, offsetTime); 170 | } 171 | 172 | return sound; 173 | }; 174 | 175 | AudioController.prototype.createSoundSlice = function (duration, sound) { 176 | var ctx = this.ctx; 177 | var buffer = sound.buffer; 178 | var soundStart = sound.startTime; 179 | var soundOffset = sound.offsetTime || 0; 180 | var offsetTime = (ctx.currentTime - soundStart + soundOffset) % buffer.duration; 181 | var bufferSlice = this.sliceBuffer(buffer, offsetTime, offsetTime + duration); 182 | 183 | sound.offsetTime = offsetTime; 184 | 185 | return this.createSound(bufferSlice, { 186 | volume : sound.volume * 0.8, 187 | offsetTime : 0, 188 | loop : true 189 | }); 190 | }; 191 | 192 | AudioController.prototype.playSound = function (params) { 193 | var path = params.path; 194 | 195 | return this._findOrLoadBuffer(path).then(function (buffer) { 196 | var sound = this.createSound(buffer, { 197 | volume : params.volume, 198 | loop : params.loop, 199 | offsetTime : 0 200 | }); 201 | 202 | this._addActiveSound(sound); 203 | return sound; 204 | }.bind(this)); 205 | }; 206 | 207 | AudioController.prototype.updateVolume = function (volume) { 208 | this._activeSounds.forEach(function (sound) { 209 | sound.gainNode.gain.value = volume * sound.volume; 210 | }); 211 | }; 212 | 213 | AudioController.prototype.pause = function () { 214 | if (!this.canCopyBuffers) { return; } 215 | 216 | var sounds = this._activeSounds.slice(); 217 | var soundSlices = sounds.map( 218 | this.createSoundSlice.bind(this, 0.5)); 219 | 220 | sounds.forEach(function (sound) { 221 | sound.sourceNode.stop(); 222 | }); 223 | 224 | this._pausedSounds = sounds; 225 | this._activeSounds = soundSlices; 226 | this.updateVolume(this.volume); 227 | }; 228 | 229 | AudioController.prototype.resume = function () { 230 | if (!this.canCopyBuffers) { return; } 231 | 232 | var prevSounds = this._activeSounds; 233 | var pausedSounds = this._pausedSounds; 234 | if (!pausedSounds) { return; } 235 | 236 | var activeSounds = []; 237 | 238 | pausedSounds.forEach(function (sound) { 239 | var newSound = this.createSound(sound.buffer, { 240 | offsetTime : sound.offsetTime, 241 | volume : sound.volume, 242 | loop : sound.sourceNode.loop 243 | }); 244 | 245 | this._addActiveSound(newSound, activeSounds); 246 | }.bind(this)); 247 | 248 | prevSounds.forEach(function (sound) { 249 | sound.sourceNode.stop(); 250 | }); 251 | 252 | this._pausedSounds = null; 253 | this._activeSounds = activeSounds; 254 | this.updateVolume(this.volume); 255 | }; 256 | 257 | AudioController.prototype.update = function () { 258 | var volume = this.tween('volume', this.volume) * (1 - this.distance); 259 | 260 | if (volume !== this.volume) { 261 | this.updateVolume(volume); 262 | } 263 | 264 | if (this.isMuted && volume > this.VOLUME_ZERO) { 265 | this.triggerListeners('unmute'); 266 | this.isMuted = false; 267 | } 268 | 269 | if (!this.isMuted && volume <= this.VOLUME_ZERO) { 270 | this.triggerListeners('mute'); 271 | this.isMuted = true; 272 | } 273 | }; 274 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2014-2016 Ash Weeks 4 | 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | -------------------------------------------------------------------------------- /static/lib-extras/three/controls/TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | * @author Mark Lundin / http://mark-lundin.com 4 | */ 5 | 6 | THREE.TrackballControls = function ( object, domElement ) { 7 | 8 | var _this = this; 9 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 }; 10 | 11 | this.object = object; 12 | this.domElement = ( domElement !== undefined ) ? domElement : document; 13 | 14 | // API 15 | 16 | this.enabled = true; 17 | 18 | this.screen = { left: 0, top: 0, width: 0, height: 0 }; 19 | 20 | this.rotateSpeed = 1.0; 21 | this.zoomSpeed = 1.2; 22 | this.panSpeed = 0.3; 23 | 24 | this.noRotate = false; 25 | this.noZoom = false; 26 | this.noPan = false; 27 | this.noRoll = false; 28 | 29 | this.staticMoving = false; 30 | this.dynamicDampingFactor = 0.2; 31 | 32 | this.minDistance = 0; 33 | this.maxDistance = Infinity; 34 | 35 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 36 | 37 | // internals 38 | 39 | this.target = new THREE.Vector3(); 40 | 41 | var lastPosition = new THREE.Vector3(); 42 | 43 | var _state = STATE.NONE, 44 | _prevState = STATE.NONE, 45 | 46 | _eye = new THREE.Vector3(), 47 | 48 | _rotateStart = new THREE.Vector3(), 49 | _rotateEnd = new THREE.Vector3(), 50 | 51 | _zoomStart = new THREE.Vector2(), 52 | _zoomEnd = new THREE.Vector2(), 53 | 54 | _touchZoomDistanceStart = 0, 55 | _touchZoomDistanceEnd = 0, 56 | 57 | _panStart = new THREE.Vector2(), 58 | _panEnd = new THREE.Vector2(); 59 | 60 | // for reset 61 | 62 | this.target0 = this.target.clone(); 63 | this.position0 = this.object.position.clone(); 64 | this.up0 = this.object.up.clone(); 65 | 66 | // events 67 | 68 | var changeEvent = { type: 'change' }; 69 | var startEvent = { type: 'start'}; 70 | var endEvent = { type: 'end'}; 71 | 72 | 73 | // methods 74 | 75 | this.handleResize = function () { 76 | 77 | if ( this.domElement === document ) { 78 | 79 | this.screen.left = 0; 80 | this.screen.top = 0; 81 | this.screen.width = window.innerWidth; 82 | this.screen.height = window.innerHeight; 83 | 84 | } else { 85 | 86 | this.screen = this.domElement.getBoundingClientRect(); 87 | // adjustments come from similar code in the jquery offset() function 88 | var d = this.domElement.ownerDocument.documentElement 89 | this.screen.left += window.pageXOffset - d.clientLeft 90 | this.screen.top += window.pageYOffset - d.clientTop 91 | 92 | } 93 | 94 | }; 95 | 96 | this.handleEvent = function ( event ) { 97 | 98 | if ( typeof this[ event.type ] == 'function' ) { 99 | 100 | this[ event.type ]( event ); 101 | 102 | } 103 | 104 | }; 105 | 106 | this.getMouseOnScreen = function ( pageX, pageY, vector ) { 107 | 108 | return vector.set( 109 | ( pageX - _this.screen.left ) / _this.screen.width, 110 | ( pageY - _this.screen.top ) / _this.screen.height 111 | ); 112 | 113 | }; 114 | 115 | this.getMouseProjectionOnBall = (function(){ 116 | 117 | var objectUp = new THREE.Vector3(), 118 | mouseOnBall = new THREE.Vector3(); 119 | 120 | 121 | return function ( pageX, pageY, projection ) { 122 | 123 | mouseOnBall.set( 124 | ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5), 125 | ( _this.screen.height * 0.5 + _this.screen.top - pageY ) / (_this.screen.height*.5), 126 | 0.0 127 | ); 128 | 129 | var length = mouseOnBall.length(); 130 | 131 | if ( _this.noRoll ) { 132 | 133 | if ( length < Math.SQRT1_2 ) { 134 | 135 | mouseOnBall.z = Math.sqrt( 1.0 - length*length ); 136 | 137 | } else { 138 | 139 | mouseOnBall.z = .5 / length; 140 | 141 | } 142 | 143 | } else if ( length > 1.0 ) { 144 | 145 | mouseOnBall.normalize(); 146 | 147 | } else { 148 | 149 | mouseOnBall.z = Math.sqrt( 1.0 - length * length ); 150 | 151 | } 152 | 153 | _eye.copy( _this.object.position ).sub( _this.target ); 154 | 155 | projection.copy( _this.object.up ).setLength( mouseOnBall.y ) 156 | projection.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) ); 157 | projection.add( _eye.setLength( mouseOnBall.z ) ); 158 | 159 | return projection; 160 | } 161 | 162 | }()); 163 | 164 | this.rotateCamera = (function(){ 165 | 166 | var axis = new THREE.Vector3(), 167 | quaternion = new THREE.Quaternion(); 168 | 169 | 170 | return function () { 171 | 172 | var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() ); 173 | 174 | if ( angle ) { 175 | 176 | axis.crossVectors( _rotateStart, _rotateEnd ).normalize(); 177 | 178 | angle *= _this.rotateSpeed; 179 | 180 | quaternion.setFromAxisAngle( axis, -angle ); 181 | 182 | _eye.applyQuaternion( quaternion ); 183 | _this.object.up.applyQuaternion( quaternion ); 184 | 185 | _rotateEnd.applyQuaternion( quaternion ); 186 | 187 | if ( _this.staticMoving ) { 188 | 189 | _rotateStart.copy( _rotateEnd ); 190 | 191 | } else { 192 | 193 | quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) ); 194 | _rotateStart.applyQuaternion( quaternion ); 195 | 196 | } 197 | 198 | } 199 | } 200 | 201 | }()); 202 | 203 | this.zoomCamera = function () { 204 | 205 | if ( _state === STATE.TOUCH_ZOOM ) { 206 | 207 | var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 208 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 209 | _eye.multiplyScalar( factor ); 210 | 211 | } else { 212 | 213 | var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 214 | 215 | if ( factor !== 1.0 && factor > 0.0 ) { 216 | 217 | _eye.multiplyScalar( factor ); 218 | 219 | if ( _this.staticMoving ) { 220 | 221 | _zoomStart.copy( _zoomEnd ); 222 | 223 | } else { 224 | 225 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 226 | 227 | } 228 | 229 | } 230 | 231 | } 232 | 233 | }; 234 | 235 | this.panCamera = (function(){ 236 | 237 | var mouseChange = new THREE.Vector2(), 238 | objectUp = new THREE.Vector3(), 239 | pan = new THREE.Vector3(); 240 | 241 | return function () { 242 | 243 | mouseChange.copy( _panEnd ).sub( _panStart ); 244 | 245 | if ( mouseChange.lengthSq() ) { 246 | 247 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 248 | 249 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); 250 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); 251 | 252 | _this.object.position.add( pan ); 253 | _this.target.add( pan ); 254 | 255 | if ( _this.staticMoving ) { 256 | 257 | _panStart.copy( _panEnd ); 258 | 259 | } else { 260 | 261 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 262 | 263 | } 264 | 265 | } 266 | } 267 | 268 | }()); 269 | 270 | this.checkDistances = function () { 271 | 272 | if ( !_this.noZoom || !_this.noPan ) { 273 | 274 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) { 275 | 276 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) ); 277 | 278 | } 279 | 280 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 281 | 282 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 283 | 284 | } 285 | 286 | } 287 | 288 | }; 289 | 290 | this.update = function () { 291 | 292 | _eye.subVectors( _this.object.position, _this.target ); 293 | 294 | if ( !_this.noRotate ) { 295 | 296 | _this.rotateCamera(); 297 | 298 | } 299 | 300 | if ( !_this.noZoom ) { 301 | 302 | _this.zoomCamera(); 303 | 304 | } 305 | 306 | if ( !_this.noPan ) { 307 | 308 | _this.panCamera(); 309 | 310 | } 311 | 312 | _this.object.position.addVectors( _this.target, _eye ); 313 | 314 | _this.checkDistances(); 315 | 316 | _this.object.lookAt( _this.target ); 317 | 318 | if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) { 319 | 320 | _this.dispatchEvent( changeEvent ); 321 | 322 | lastPosition.copy( _this.object.position ); 323 | 324 | } 325 | 326 | }; 327 | 328 | this.reset = function () { 329 | 330 | _state = STATE.NONE; 331 | _prevState = STATE.NONE; 332 | 333 | _this.target.copy( _this.target0 ); 334 | _this.object.position.copy( _this.position0 ); 335 | _this.object.up.copy( _this.up0 ); 336 | 337 | _eye.subVectors( _this.object.position, _this.target ); 338 | 339 | _this.object.lookAt( _this.target ); 340 | 341 | _this.dispatchEvent( changeEvent ); 342 | 343 | lastPosition.copy( _this.object.position ); 344 | 345 | }; 346 | 347 | // listeners 348 | 349 | function keydown( event ) { 350 | 351 | if ( _this.enabled === false ) return; 352 | 353 | window.removeEventListener( 'keydown', keydown ); 354 | 355 | _prevState = _state; 356 | 357 | if ( _state !== STATE.NONE ) { 358 | 359 | return; 360 | 361 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) { 362 | 363 | _state = STATE.ROTATE; 364 | 365 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) { 366 | 367 | _state = STATE.ZOOM; 368 | 369 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) { 370 | 371 | _state = STATE.PAN; 372 | 373 | } 374 | 375 | } 376 | 377 | function keyup( event ) { 378 | 379 | if ( _this.enabled === false ) return; 380 | 381 | _state = _prevState; 382 | 383 | window.addEventListener( 'keydown', keydown, false ); 384 | 385 | } 386 | 387 | function mousedown( event ) { 388 | 389 | if ( _this.enabled === false ) return; 390 | 391 | // event.preventDefault(); 392 | // event.stopPropagation(); 393 | 394 | if ( _state === STATE.NONE ) { 395 | 396 | _state = event.button; 397 | 398 | } 399 | 400 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 401 | 402 | _this.getMouseProjectionOnBall( event.pageX, event.pageY, _rotateStart ); 403 | _rotateEnd.copy(_rotateStart) 404 | 405 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 406 | 407 | _this.getMouseOnScreen( event.pageX, event.pageY, _zoomStart ); 408 | _zoomEnd.copy(_zoomStart); 409 | 410 | } else if ( _state === STATE.PAN && !_this.noPan ) { 411 | 412 | _this.getMouseOnScreen( event.pageX, event.pageY, _panStart ); 413 | _panEnd.copy(_panStart) 414 | 415 | } 416 | 417 | document.addEventListener( 'mousemove', mousemove, false ); 418 | document.addEventListener( 'mouseup', mouseup, false ); 419 | _this.dispatchEvent( startEvent ); 420 | 421 | 422 | } 423 | 424 | function mousemove( event ) { 425 | 426 | if ( _this.enabled === false ) return; 427 | 428 | event.preventDefault(); 429 | event.stopPropagation(); 430 | 431 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 432 | 433 | _this.getMouseProjectionOnBall( event.pageX, event.pageY, _rotateEnd ); 434 | 435 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 436 | 437 | _this.getMouseOnScreen( event.pageX, event.pageY, _zoomEnd ); 438 | 439 | } else if ( _state === STATE.PAN && !_this.noPan ) { 440 | 441 | _this.getMouseOnScreen( event.pageX, event.pageY, _panEnd ); 442 | 443 | } 444 | 445 | } 446 | 447 | function mouseup( event ) { 448 | 449 | if ( _this.enabled === false ) return; 450 | 451 | event.preventDefault(); 452 | event.stopPropagation(); 453 | 454 | _state = STATE.NONE; 455 | 456 | document.removeEventListener( 'mousemove', mousemove ); 457 | document.removeEventListener( 'mouseup', mouseup ); 458 | _this.dispatchEvent( endEvent ); 459 | 460 | } 461 | 462 | function mousewheel( event ) { 463 | 464 | if ( _this.enabled === false ) return; 465 | 466 | event.preventDefault(); 467 | event.stopPropagation(); 468 | 469 | var delta = 0; 470 | 471 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 472 | 473 | delta = event.wheelDelta / 40; 474 | 475 | } else if ( event.detail ) { // Firefox 476 | 477 | delta = - event.detail / 3; 478 | 479 | } 480 | 481 | _zoomStart.y += delta * 0.01; 482 | _this.dispatchEvent( startEvent ); 483 | _this.dispatchEvent( endEvent ); 484 | 485 | } 486 | 487 | function touchstart( event ) { 488 | 489 | if ( _this.enabled === false ) return; 490 | 491 | switch ( event.touches.length ) { 492 | 493 | case 1: 494 | _state = STATE.TOUCH_ROTATE; 495 | _rotateEnd.copy( _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateStart )); 496 | break; 497 | 498 | case 2: 499 | _state = STATE.TOUCH_ZOOM; 500 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 501 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 502 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 503 | break; 504 | 505 | case 3: 506 | _state = STATE.TOUCH_PAN; 507 | _panEnd.copy( _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panStart )); 508 | break; 509 | 510 | default: 511 | _state = STATE.NONE; 512 | 513 | } 514 | _this.dispatchEvent( startEvent ); 515 | 516 | 517 | } 518 | 519 | function touchmove( event ) { 520 | 521 | if ( _this.enabled === false ) return; 522 | 523 | event.preventDefault(); 524 | event.stopPropagation(); 525 | 526 | switch ( event.touches.length ) { 527 | 528 | case 1: 529 | _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateEnd ); 530 | break; 531 | 532 | case 2: 533 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 534 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 535 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ) 536 | break; 537 | 538 | case 3: 539 | _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panEnd ); 540 | break; 541 | 542 | default: 543 | _state = STATE.NONE; 544 | 545 | } 546 | 547 | } 548 | 549 | function touchend( event ) { 550 | 551 | if ( _this.enabled === false ) return; 552 | 553 | switch ( event.touches.length ) { 554 | 555 | case 1: 556 | _rotateStart.copy( _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateEnd )); 557 | break; 558 | 559 | case 2: 560 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; 561 | break; 562 | 563 | case 3: 564 | _panStart.copy( _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panEnd )); 565 | break; 566 | 567 | } 568 | 569 | _state = STATE.NONE; 570 | _this.dispatchEvent( endEvent ); 571 | 572 | } 573 | 574 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 575 | 576 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 577 | 578 | this.domElement.addEventListener( 'mousewheel', mousewheel, false ); 579 | this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox 580 | 581 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 582 | this.domElement.addEventListener( 'touchend', touchend, false ); 583 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 584 | 585 | window.addEventListener( 'keydown', keydown, false ); 586 | window.addEventListener( 'keyup', keyup, false ); 587 | 588 | this.handleResize(); 589 | 590 | }; 591 | 592 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 593 | --------------------------------------------------------------------------------