├── .gitignore
├── LICENSE
├── README.md
├── app
├── css
│ ├── index.css
│ └── reset.css
├── images
│ ├── hand-pull.png
│ ├── hand-push.png
│ ├── mouse-pull.png
│ └── mouse-push.png
├── index.html
├── js
│ ├── lib
│ │ ├── Clock.js
│ │ ├── CopyShader.js
│ │ ├── OBJLoader.js
│ │ ├── OrbitControls.js
│ │ ├── Stats.js
│ │ ├── TrackballControls.js
│ │ ├── dat.gui.js
│ │ ├── dat.gui.min.js
│ │ ├── leap-0.6.4.js
│ │ ├── leap-plugins-0.1.10.js
│ │ ├── mousetrap.js
│ │ ├── mousetrap.min.js
│ │ ├── stats.min.js
│ │ ├── three.min.js
│ │ ├── three69.js
│ │ └── three70.js
│ └── src
│ │ ├── App.js
│ │ ├── App2.js
│ │ ├── LeapManager.js
│ │ ├── Mouse.js
│ │ ├── ParticleEngine.js
│ │ ├── ParticleSimulation.js
│ │ ├── RenderContext.js
│ │ ├── ShaderPass.js
│ │ ├── SimulationRenderer.js
│ │ ├── UVMapAnimator.js
│ │ ├── UVMapper.js
│ │ ├── UpdateLoop.js
│ │ ├── Utils.js
│ │ ├── index.js
│ │ └── shaders
│ │ ├── ParticleShader.js
│ │ ├── SimDebugShader.js
│ │ ├── SimInitShader.js
│ │ ├── SimShader.js
│ │ └── UVMapShader.js
├── leaptest.html
├── models
│ ├── animals.json
│ ├── bear.json
│ ├── bison.json
│ ├── chowchow.json
│ ├── cow.json
│ ├── deer.json
│ ├── eagle.json
│ ├── elk.json
│ ├── flamingo.json
│ ├── fox.json
│ ├── horse.json
│ ├── moose.json
│ ├── owl.json
│ ├── panther.json
│ ├── parrot.json
│ ├── rabbit.json
│ ├── raccoon.json
│ ├── raven.json
│ ├── retriever.json
│ ├── stork.json
│ └── wolf.json
├── shaders
│ ├── Basic.vs.glsl
│ ├── BasicParticleShader.fs.glsl
│ ├── BasicParticleShader.vs.glsl
│ ├── BasicSimShader.fs.glsl
│ ├── ParticleShader.fs.glsl
│ ├── ParticleShader.vs.glsl
│ ├── SimInitShader.fs.glsl
│ ├── SimShader.fs.glsl
│ ├── UVMapShader.fs.glsl
│ ├── UVMapShader.vs.glsl
│ └── chunks
│ │ ├── Constants.glsl
│ │ ├── NoiseFuncs.glsl
│ │ ├── Rand.glsl
│ │ ├── SimBasicShapes.glsl
│ │ ├── SimGalaxy.glsl
│ │ ├── SimInputPos.glsl
│ │ ├── SimRoseGalaxy.glsl
│ │ └── SimTextureTarget.glsl
└── uvtest.html
└── run.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sublime-project
2 | *.sublime-workspace
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The BSD 3-Clause License
2 |
3 | Copyright (c) 2015, Nop Jiarathanakul
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Particle Dream
2 |
3 | An interactive particle simulation for your mind
4 |
5 | See it [live here](http://www.iamnop.com/particles/) or as [video here](https://www.youtube.com/watch?v=i7DR_Cedbmc)
6 |
7 | Inspired by ["Particle Dreams (1988)"](http://www.karlsims.com/particle-dreams.html) by Karl Sims
8 |
9 | 
10 |
11 | ## Credits
12 |
13 | Nop Jiarathanakul [iamnop.com](http://www.iamnop.com/)
14 |
15 | Animals by [Mirada](http://mirada.com/) from [ro.me](http://www.ro.me/)
16 |
17 | Music by [Kai Engel](https://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/Kai_Engel_-_Irsens_Tale_-_04_Moonlight_Reprise)
18 |
19 | ## License
20 |
21 | The BSD 3-Clause License
22 |
23 | Copyright © 2015 [Nop Jiarathanakul](http://iamnop.com/)
24 |
--------------------------------------------------------------------------------
/app/css/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family:'Lucida Grande',Verdana,Helvetica,sans-serif;
3 | font-size:12px;
4 | line-height:150%;
5 |
6 | overflow: hidden;
7 |
8 | -webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
9 | -webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
10 | }
11 |
12 | #webgl-container {
13 | overflow: hidden;
14 | position: absolute;
15 | width: 100%;
16 | top: 0;
17 | bottom: 0;
18 | }
19 | #webgl-container>canvas {
20 | float: left;
21 | width: 100%;
22 | height: 100%;
23 | }
24 |
25 | #debug-box {
26 | position:absolute;
27 | bottom: 0;
28 | color: #ffffff;
29 | font-size: 32px;
30 | line-height:100%;
31 | }
32 |
33 | #help-box {
34 | width: 350px;
35 | height: 350px;
36 | position: absolute;
37 | top: 0;
38 | bottom: 0;
39 | left: 0;
40 | right: 0;
41 | margin: auto;
42 | z-index: 1;
43 |
44 | padding: 50px;
45 | color: white;
46 | background: rgba(0,0,0,0.5);
47 | text-align: center;
48 | }
49 | #help-box img {
50 | width: 120px;
51 | margin-top: 10px;
52 | margin-bottom: 10px;
53 | }
54 | #help-box a {
55 | color: cyan;
56 | }
57 | #help-box {
58 | transition: opacity 0.5s, visibility 0.5s;
59 | visibility: visible;
60 | opacity: 1.0;
61 | }
62 | #help-box.hidden {
63 | visibility: hidden;
64 | opacity: 0.0;
65 | }
66 |
67 | .gradient-bg-cyan-black {
68 | /* IE10 Consumer Preview */
69 | background-image: -ms-linear-gradient(top, #005C7A 0%, #000000 100%);
70 |
71 | /* Mozilla Firefox */
72 | background-image: -moz-linear-gradient(top, #005C7A 0%, #000000 100%);
73 |
74 | /* Opera */
75 | background-image: -o-linear-gradient(top, #005C7A 0%, #000000 100%);
76 |
77 | /* Webkit (Safari/Chrome 10) */
78 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #005C7A), color-stop(1, #000000));
79 |
80 | /* Webkit (Chrome 11+) */
81 | background-image: -webkit-linear-gradient(top, #005C7A 0%, #000000 100%);
82 |
83 | /* W3C Markup, IE10 Release Preview */
84 | background-image: linear-gradient(to bottom, #005C7A 0%, #000000 100%);
85 | }
86 |
87 | .gradient-bg-gray-black {
88 | /* IE10 Consumer Preview */
89 | background-image: -ms-linear-gradient(top, #666666 0%, #000000 100%);
90 |
91 | /* Mozilla Firefox */
92 | background-image: -moz-linear-gradient(top, #666666 0%, #000000 100%);
93 |
94 | /* Opera */
95 | background-image: -o-linear-gradient(top, #666666 0%, #000000 100%);
96 |
97 | /* Webkit (Safari/Chrome 10) */
98 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #666666), color-stop(1, #000000));
99 |
100 | /* Webkit (Chrome 11+) */
101 | background-image: -webkit-linear-gradient(top, #666666 0%, #000000 100%);
102 |
103 | /* W3C Markup, IE10 Release Preview */
104 | background-image: linear-gradient(to bottom, #666666 0%, #000000 100%);
105 | }
106 |
107 | .gradient-bg-darkgray-black {
108 | /* IE10 Consumer Preview */
109 | background-image: -ms-linear-gradient(top, #333333 0%, #000000 100%);
110 |
111 | /* Mozilla Firefox */
112 | background-image: -moz-linear-gradient(top, #333333 0%, #000000 100%);
113 |
114 | /* Opera */
115 | background-image: -o-linear-gradient(top, #333333 0%, #000000 100%);
116 |
117 | /* Webkit (Safari/Chrome 10) */
118 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #333333), color-stop(1, #000000));
119 |
120 | /* Webkit (Chrome 11+) */
121 | background-image: -webkit-linear-gradient(top, #333333 0%, #000000 100%);
122 |
123 | /* W3C Markup, IE10 Release Preview */
124 | background-image: linear-gradient(to bottom, #333333 0%, #000000 100%);
125 | }
--------------------------------------------------------------------------------
/app/css/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
--------------------------------------------------------------------------------
/app/images/hand-pull.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nopjia/particles/2e2f10641759c750e2ec9238e0e8534711fd1bdf/app/images/hand-pull.png
--------------------------------------------------------------------------------
/app/images/hand-push.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nopjia/particles/2e2f10641759c750e2ec9238e0e8534711fd1bdf/app/images/hand-push.png
--------------------------------------------------------------------------------
/app/images/mouse-pull.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nopjia/particles/2e2f10641759c750e2ec9238e0e8534711fd1bdf/app/images/mouse-pull.png
--------------------------------------------------------------------------------
/app/images/mouse-push.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nopjia/particles/2e2f10641759c750e2ec9238e0e8534711fd1bdf/app/images/mouse-push.png
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | A Particle Dream - v1.1
7 |
8 |
9 |
10 |
11 |
12 |
13 | push and pull particles
14 |
15 |
16 |
17 | try different shapes, or take the tour
18 | sit back, relax, and let your imagination free
19 |
20 | bonus, Leap Motion supported
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/js/lib/Clock.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author alteredq / http://alteredqualia.com/
3 | */
4 |
5 | var Clock = function ( autoStart ) {
6 |
7 | this.autoStart = ( autoStart !== undefined ) ? autoStart : true;
8 |
9 | this.startTime = 0;
10 | this.oldTime = 0;
11 | this.elapsedTime = 0;
12 |
13 | this.running = false;
14 |
15 | };
16 |
17 | Clock.prototype.start = function () {
18 |
19 | this.startTime = Date.now();
20 | this.oldTime = this.startTime;
21 |
22 | this.running = true;
23 |
24 | };
25 |
26 | Clock.prototype.stop = function () {
27 |
28 | this.getElapsedTime();
29 |
30 | this.running = false;
31 |
32 | };
33 |
34 | Clock.prototype.getElapsedTime = function () {
35 |
36 | this.elapsedTime += this.getDelta();
37 |
38 | return this.elapsedTime;
39 |
40 | };
41 |
42 |
43 | Clock.prototype.getDelta = function () {
44 |
45 | var diff = 0;
46 |
47 | if ( this.autoStart && ! this.running ) {
48 |
49 | this.start();
50 |
51 | }
52 |
53 | if ( this.running ) {
54 |
55 | var newTime = Date.now();
56 | diff = 0.001 * ( newTime - this.oldTime );
57 | this.oldTime = newTime;
58 |
59 | this.elapsedTime += diff;
60 |
61 | }
62 |
63 | return diff;
64 |
65 | };
--------------------------------------------------------------------------------
/app/js/lib/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 |
--------------------------------------------------------------------------------
/app/js/lib/OBJLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 |
5 | THREE.OBJLoader = function ( manager ) {
6 |
7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
8 |
9 | };
10 |
11 | THREE.OBJLoader.prototype = {
12 |
13 | constructor: THREE.OBJLoader,
14 |
15 | load: function ( url, onLoad, onProgress, onError ) {
16 |
17 | var scope = this;
18 |
19 | var loader = new THREE.XHRLoader( scope.manager );
20 | loader.setCrossOrigin( this.crossOrigin );
21 | loader.load( url, function ( text ) {
22 |
23 | onLoad( scope.parse( text ) );
24 |
25 | }, onProgress, onError );
26 |
27 | },
28 |
29 | parse: function ( text ) {
30 |
31 | console.time( 'OBJLoader' );
32 |
33 | var object, objects = [];
34 | var geometry, material;
35 |
36 | function parseVertexIndex( value ) {
37 |
38 | var index = parseInt( value );
39 |
40 | return ( index >= 0 ? index - 1 : index + vertices.length / 3 ) * 3;
41 |
42 | }
43 |
44 | function parseNormalIndex( value ) {
45 |
46 | var index = parseInt( value );
47 |
48 | return ( index >= 0 ? index - 1 : index + normals.length / 3 ) * 3;
49 |
50 | }
51 |
52 | function parseUVIndex( value ) {
53 |
54 | var index = parseInt( value );
55 |
56 | return ( index >= 0 ? index - 1 : index + uvs.length / 2 ) * 2;
57 |
58 | }
59 |
60 | function addVertex( a, b, c ) {
61 |
62 | geometry.vertices.push(
63 | vertices[ a ], vertices[ a + 1 ], vertices[ a + 2 ],
64 | vertices[ b ], vertices[ b + 1 ], vertices[ b + 2 ],
65 | vertices[ c ], vertices[ c + 1 ], vertices[ c + 2 ]
66 | );
67 |
68 | }
69 |
70 | function addNormal( a, b, c ) {
71 |
72 | geometry.normals.push(
73 | normals[ a ], normals[ a + 1 ], normals[ a + 2 ],
74 | normals[ b ], normals[ b + 1 ], normals[ b + 2 ],
75 | normals[ c ], normals[ c + 1 ], normals[ c + 2 ]
76 | );
77 |
78 | }
79 |
80 | function addUV( a, b, c ) {
81 |
82 | geometry.uvs.push(
83 | uvs[ a ], uvs[ a + 1 ],
84 | uvs[ b ], uvs[ b + 1 ],
85 | uvs[ c ], uvs[ c + 1 ]
86 | );
87 |
88 | }
89 |
90 | function addFace( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) {
91 |
92 | var ia = parseVertexIndex( a );
93 | var ib = parseVertexIndex( b );
94 | var ic = parseVertexIndex( c );
95 |
96 | if ( d === undefined ) {
97 |
98 | addVertex( ia, ib, ic );
99 |
100 | } else {
101 |
102 | var id = parseVertexIndex( d );
103 |
104 | addVertex( ia, ib, id );
105 | addVertex( ib, ic, id );
106 |
107 | }
108 |
109 | if ( ua !== undefined ) {
110 |
111 | var ia = parseUVIndex( ua );
112 | var ib = parseUVIndex( ub );
113 | var ic = parseUVIndex( uc );
114 |
115 | if ( d === undefined ) {
116 |
117 | addUV( ia, ib, ic );
118 |
119 | } else {
120 |
121 | var id = parseUVIndex( ud );
122 |
123 | addUV( ia, ib, id );
124 | addUV( ib, ic, id );
125 |
126 | }
127 |
128 | }
129 |
130 | if ( na !== undefined ) {
131 |
132 | var ia = parseNormalIndex( na );
133 | var ib = parseNormalIndex( nb );
134 | var ic = parseNormalIndex( nc );
135 |
136 | if ( d === undefined ) {
137 |
138 | addNormal( ia, ib, ic );
139 |
140 | } else {
141 |
142 | var id = parseNormalIndex( nd );
143 |
144 | addNormal( ia, ib, id );
145 | addNormal( ib, ic, id );
146 |
147 | }
148 |
149 | }
150 |
151 | }
152 |
153 | // create mesh if no objects in text
154 |
155 | if ( /^o /gm.test( text ) === false ) {
156 |
157 | geometry = {
158 | vertices: [],
159 | normals: [],
160 | uvs: []
161 | };
162 |
163 | material = {
164 | name: ''
165 | };
166 |
167 | object = {
168 | name: '',
169 | geometry: geometry,
170 | material: material
171 | };
172 |
173 | objects.push( object );
174 |
175 | }
176 |
177 | var vertices = [];
178 | var normals = [];
179 | var uvs = [];
180 |
181 | // v float float float
182 |
183 | var vertex_pattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
184 |
185 | // vn float float float
186 |
187 | var normal_pattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
188 |
189 | // vt float float
190 |
191 | var uv_pattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
192 |
193 | // f vertex vertex vertex ...
194 |
195 | var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/;
196 |
197 | // f vertex/uv vertex/uv vertex/uv ...
198 |
199 | var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/;
200 |
201 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
202 |
203 | var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/;
204 |
205 | // f vertex//normal vertex//normal vertex//normal ...
206 |
207 | var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/
208 |
209 | //
210 |
211 | var lines = text.split( '\n' );
212 |
213 | for ( var i = 0; i < lines.length; i ++ ) {
214 |
215 | var line = lines[ i ];
216 | line = line.trim();
217 |
218 | var result;
219 |
220 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
221 |
222 | continue;
223 |
224 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) {
225 |
226 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
227 |
228 | vertices.push(
229 | parseFloat( result[ 1 ] ),
230 | parseFloat( result[ 2 ] ),
231 | parseFloat( result[ 3 ] )
232 | );
233 |
234 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) {
235 |
236 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
237 |
238 | normals.push(
239 | parseFloat( result[ 1 ] ),
240 | parseFloat( result[ 2 ] ),
241 | parseFloat( result[ 3 ] )
242 | );
243 |
244 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) {
245 |
246 | // ["vt 0.1 0.2", "0.1", "0.2"]
247 |
248 | uvs.push(
249 | parseFloat( result[ 1 ] ),
250 | parseFloat( result[ 2 ] )
251 | );
252 |
253 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) {
254 |
255 | // ["f 1 2 3", "1", "2", "3", undefined]
256 |
257 | addFace(
258 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ]
259 | );
260 |
261 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) {
262 |
263 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined]
264 |
265 | addFace(
266 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ],
267 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ]
268 | );
269 |
270 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) {
271 |
272 | // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined]
273 |
274 | addFace(
275 | result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ],
276 | result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ],
277 | result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ]
278 | );
279 |
280 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) {
281 |
282 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined]
283 |
284 | addFace(
285 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ],
286 | undefined, undefined, undefined, undefined,
287 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ]
288 | );
289 |
290 | } else if ( /^o /.test( line ) ) {
291 |
292 | geometry = {
293 | vertices: [],
294 | normals: [],
295 | uvs: []
296 | };
297 |
298 | material = {
299 | name: ''
300 | };
301 |
302 | object = {
303 | name: line.substring( 2 ).trim(),
304 | geometry: geometry,
305 | material: material
306 | };
307 |
308 | objects.push( object )
309 |
310 | } else if ( /^g /.test( line ) ) {
311 |
312 | // group
313 |
314 | } else if ( /^usemtl /.test( line ) ) {
315 |
316 | // material
317 |
318 | material.name = line.substring( 7 ).trim();
319 |
320 | } else if ( /^mtllib /.test( line ) ) {
321 |
322 | // mtl file
323 |
324 | } else if ( /^s /.test( line ) ) {
325 |
326 | // smooth shading
327 |
328 | } else {
329 |
330 | // console.log( "THREE.OBJLoader: Unhandled line " + line );
331 |
332 | }
333 |
334 | }
335 |
336 | var container = new THREE.Object3D();
337 |
338 | for ( var i = 0, l = objects.length; i < l; i ++ ) {
339 |
340 | var object = objects[ i ];
341 | var geometry = object.geometry;
342 |
343 | var buffergeometry = new THREE.BufferGeometry();
344 |
345 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) );
346 |
347 | if ( geometry.normals.length > 0 ) {
348 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) );
349 | }
350 |
351 | if ( geometry.uvs.length > 0 ) {
352 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) );
353 | }
354 |
355 | var material = new THREE.MeshLambertMaterial();
356 | material.name = object.material.name;
357 |
358 | var mesh = new THREE.Mesh( buffergeometry, material );
359 | mesh.name = object.name;
360 |
361 | container.add( mesh );
362 |
363 | }
364 |
365 | console.timeEnd( 'OBJLoader' );
366 |
367 | return container;
368 |
369 | }
370 |
371 | };
--------------------------------------------------------------------------------
/app/js/lib/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | */
8 | /*global THREE, console */
9 |
10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains
11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
12 | // supported.
13 | //
14 | // Orbit - left mouse / touch: one finger move
15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
16 | // Pan - right mouse, or arrow keys / touch: three finter swipe
17 | //
18 | // This is a drop-in replacement for (most) TrackballControls used in examples.
19 | // That is, include this js file and wherever you see:
20 | // controls = new THREE.TrackballControls( camera );
21 | // controls.target.z = 150;
22 | // Simple substitute "OrbitControls" and the control should work as-is.
23 |
24 | THREE.OrbitControls = function ( object, domElement ) {
25 |
26 | this.object = object;
27 | this.domElement = ( domElement !== undefined ) ? domElement : document;
28 |
29 | // API
30 |
31 | // Set to false to disable this control
32 | this.enabled = true;
33 |
34 | // "target" sets the location of focus, where the control orbits around
35 | // and where it pans with respect to.
36 | this.target = new THREE.Vector3();
37 |
38 | // center is old, deprecated; use "target" instead
39 | this.center = this.target;
40 |
41 | // This option actually enables dollying in and out; left as "zoom" for
42 | // backwards compatibility
43 | this.noZoom = false;
44 | this.zoomSpeed = 1.0;
45 |
46 | // Limits to how far you can dolly in and out
47 | this.minDistance = 0;
48 | this.maxDistance = Infinity;
49 |
50 | // Set to true to disable this control
51 | this.noRotate = false;
52 | this.rotateSpeed = 1.0;
53 |
54 | // Set to true to disable this control
55 | this.noPan = false;
56 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
57 |
58 | // Set to true to automatically rotate around the target
59 | this.autoRotate = false;
60 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
61 |
62 | // How far you can orbit vertically, upper and lower limits.
63 | // Range is 0 to Math.PI radians.
64 | this.minPolarAngle = 0; // radians
65 | this.maxPolarAngle = Math.PI; // radians
66 |
67 | // How far you can orbit horizontally, upper and lower limits.
68 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
69 | this.minAzimuthAngle = - Infinity; // radians
70 | this.maxAzimuthAngle = Infinity; // radians
71 |
72 | // Set to true to disable use of the keys
73 | this.noKeys = false;
74 |
75 | // The four arrow keys
76 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
77 |
78 | // Mouse buttons
79 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
80 |
81 | ////////////
82 | // internals
83 |
84 | var scope = this;
85 |
86 | var EPS = 0.000001;
87 |
88 | var rotateStart = new THREE.Vector2();
89 | var rotateEnd = new THREE.Vector2();
90 | var rotateDelta = new THREE.Vector2();
91 |
92 | var panStart = new THREE.Vector2();
93 | var panEnd = new THREE.Vector2();
94 | var panDelta = new THREE.Vector2();
95 | var panOffset = new THREE.Vector3();
96 |
97 | var offset = new THREE.Vector3();
98 |
99 | var dollyStart = new THREE.Vector2();
100 | var dollyEnd = new THREE.Vector2();
101 | var dollyDelta = new THREE.Vector2();
102 |
103 | var theta;
104 | var phi;
105 | var phiDelta = 0;
106 | var thetaDelta = 0;
107 | var scale = 1;
108 | var pan = new THREE.Vector3();
109 |
110 | var lastPosition = new THREE.Vector3();
111 | var lastQuaternion = new THREE.Quaternion();
112 |
113 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
114 |
115 | var state = STATE.NONE;
116 |
117 | // for reset
118 |
119 | this.target0 = this.target.clone();
120 | this.position0 = this.object.position.clone();
121 |
122 | // so camera.up is the orbit axis
123 |
124 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
125 | var quatInverse = quat.clone().inverse();
126 |
127 | // events
128 |
129 | var changeEvent = { type: 'change' };
130 | var startEvent = { type: 'start'};
131 | var endEvent = { type: 'end'};
132 |
133 | this.rotateLeft = function ( angle ) {
134 |
135 | if ( angle === undefined ) {
136 |
137 | angle = getAutoRotationAngle();
138 |
139 | }
140 |
141 | thetaDelta -= angle;
142 |
143 | };
144 |
145 | this.rotateUp = function ( angle ) {
146 |
147 | if ( angle === undefined ) {
148 |
149 | angle = getAutoRotationAngle();
150 |
151 | }
152 |
153 | phiDelta -= angle;
154 |
155 | };
156 |
157 | // pass in distance in world space to move left
158 | this.panLeft = function ( distance ) {
159 |
160 | var te = this.object.matrix.elements;
161 |
162 | // get X column of matrix
163 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] );
164 | panOffset.multiplyScalar( - distance );
165 |
166 | pan.add( panOffset );
167 |
168 | };
169 |
170 | // pass in distance in world space to move up
171 | this.panUp = function ( distance ) {
172 |
173 | var te = this.object.matrix.elements;
174 |
175 | // get Y column of matrix
176 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] );
177 | panOffset.multiplyScalar( distance );
178 |
179 | pan.add( panOffset );
180 |
181 | };
182 |
183 | // pass in x,y of change desired in pixel space,
184 | // right and down are positive
185 | this.pan = function ( deltaX, deltaY ) {
186 |
187 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
188 |
189 | if ( scope.object.fov !== undefined ) {
190 |
191 | // perspective
192 | var position = scope.object.position;
193 | var offset = position.clone().sub( scope.target );
194 | var targetDistance = offset.length();
195 |
196 | // half of the fov is center to top of screen
197 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
198 |
199 | // we actually don't use screenWidth, since perspective camera is fixed to screen height
200 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
201 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
202 |
203 | } else if ( scope.object.top !== undefined ) {
204 |
205 | // orthographic
206 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
207 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight );
208 |
209 | } else {
210 |
211 | // camera neither orthographic or perspective
212 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
213 |
214 | }
215 |
216 | };
217 |
218 | this.dollyIn = function ( dollyScale ) {
219 |
220 | if ( dollyScale === undefined ) {
221 |
222 | dollyScale = getZoomScale();
223 |
224 | }
225 |
226 | scale /= dollyScale;
227 |
228 | };
229 |
230 | this.dollyOut = function ( dollyScale ) {
231 |
232 | if ( dollyScale === undefined ) {
233 |
234 | dollyScale = getZoomScale();
235 |
236 | }
237 |
238 | scale *= dollyScale;
239 |
240 | };
241 |
242 | this.update = function () {
243 |
244 | var position = this.object.position;
245 |
246 | offset.copy( position ).sub( this.target );
247 |
248 | // rotate offset to "y-axis-is-up" space
249 | offset.applyQuaternion( quat );
250 |
251 | // angle from z-axis around y-axis
252 |
253 | theta = Math.atan2( offset.x, offset.z );
254 |
255 | // angle from y-axis
256 |
257 | phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
258 |
259 | if ( this.autoRotate && state === STATE.NONE ) {
260 |
261 | this.rotateLeft( getAutoRotationAngle() );
262 |
263 | }
264 |
265 | theta += thetaDelta;
266 | phi += phiDelta;
267 |
268 | // restrict theta to be between desired limits
269 | theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) );
270 |
271 | // restrict phi to be between desired limits
272 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
273 |
274 | // restrict phi to be betwee EPS and PI-EPS
275 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
276 |
277 | var radius = offset.length() * scale;
278 |
279 | // restrict radius to be between desired limits
280 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
281 |
282 | // move target to panned location
283 | this.target.add( pan );
284 |
285 | offset.x = radius * Math.sin( phi ) * Math.sin( theta );
286 | offset.y = radius * Math.cos( phi );
287 | offset.z = radius * Math.sin( phi ) * Math.cos( theta );
288 |
289 | // rotate offset back to "camera-up-vector-is-up" space
290 | offset.applyQuaternion( quatInverse );
291 |
292 | position.copy( this.target ).add( offset );
293 |
294 | this.object.lookAt( this.target );
295 |
296 | thetaDelta = 0;
297 | phiDelta = 0;
298 | scale = 1;
299 | pan.set( 0, 0, 0 );
300 |
301 | // update condition is:
302 | // min(camera displacement, camera rotation in radians)^2 > EPS
303 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
304 |
305 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS
306 | || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) {
307 |
308 | this.dispatchEvent( changeEvent );
309 |
310 | lastPosition.copy( this.object.position );
311 | lastQuaternion.copy (this.object.quaternion );
312 |
313 | }
314 |
315 | };
316 |
317 |
318 | this.reset = function () {
319 |
320 | state = STATE.NONE;
321 |
322 | this.target.copy( this.target0 );
323 | this.object.position.copy( this.position0 );
324 |
325 | this.update();
326 |
327 | };
328 |
329 | this.getPolarAngle = function () {
330 |
331 | return phi;
332 |
333 | };
334 |
335 | this.getAzimuthalAngle = function () {
336 |
337 | return theta
338 |
339 | };
340 |
341 | function getAutoRotationAngle() {
342 |
343 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
344 |
345 | }
346 |
347 | function getZoomScale() {
348 |
349 | return Math.pow( 0.95, scope.zoomSpeed );
350 |
351 | }
352 |
353 | function onMouseDown( event ) {
354 |
355 | if ( scope.enabled === false ) return;
356 | event.preventDefault();
357 |
358 | if ( event.button === scope.mouseButtons.ORBIT ) {
359 | if ( scope.noRotate === true ) return;
360 |
361 | state = STATE.ROTATE;
362 |
363 | rotateStart.set( event.clientX, event.clientY );
364 |
365 | } else if ( event.button === scope.mouseButtons.ZOOM ) {
366 | if ( scope.noZoom === true ) return;
367 |
368 | state = STATE.DOLLY;
369 |
370 | dollyStart.set( event.clientX, event.clientY );
371 |
372 | } else if ( event.button === scope.mouseButtons.PAN ) {
373 | if ( scope.noPan === true ) return;
374 |
375 | state = STATE.PAN;
376 |
377 | panStart.set( event.clientX, event.clientY );
378 |
379 | }
380 |
381 | if ( state !== STATE.NONE ) {
382 | document.addEventListener( 'mousemove', onMouseMove, false );
383 | document.addEventListener( 'mouseup', onMouseUp, false );
384 | scope.dispatchEvent( startEvent );
385 | }
386 |
387 | }
388 |
389 | function onMouseMove( event ) {
390 |
391 | if ( scope.enabled === false ) return;
392 |
393 | event.preventDefault();
394 |
395 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
396 |
397 | if ( state === STATE.ROTATE ) {
398 |
399 | if ( scope.noRotate === true ) return;
400 |
401 | rotateEnd.set( event.clientX, event.clientY );
402 | rotateDelta.subVectors( rotateEnd, rotateStart );
403 |
404 | // rotating across whole screen goes 360 degrees around
405 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
406 |
407 | // rotating up and down along whole screen attempts to go 360, but limited to 180
408 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
409 |
410 | rotateStart.copy( rotateEnd );
411 |
412 | } else if ( state === STATE.DOLLY ) {
413 |
414 | if ( scope.noZoom === true ) return;
415 |
416 | dollyEnd.set( event.clientX, event.clientY );
417 | dollyDelta.subVectors( dollyEnd, dollyStart );
418 |
419 | if ( dollyDelta.y > 0 ) {
420 |
421 | scope.dollyIn();
422 |
423 | } else {
424 |
425 | scope.dollyOut();
426 |
427 | }
428 |
429 | dollyStart.copy( dollyEnd );
430 |
431 | } else if ( state === STATE.PAN ) {
432 |
433 | if ( scope.noPan === true ) return;
434 |
435 | panEnd.set( event.clientX, event.clientY );
436 | panDelta.subVectors( panEnd, panStart );
437 |
438 | scope.pan( panDelta.x, panDelta.y );
439 |
440 | panStart.copy( panEnd );
441 |
442 | }
443 |
444 | if ( state !== STATE.NONE ) scope.update();
445 |
446 | }
447 |
448 | function onMouseUp( /* event */ ) {
449 |
450 | if ( scope.enabled === false ) return;
451 |
452 | document.removeEventListener( 'mousemove', onMouseMove, false );
453 | document.removeEventListener( 'mouseup', onMouseUp, false );
454 | scope.dispatchEvent( endEvent );
455 | state = STATE.NONE;
456 |
457 | }
458 |
459 | function onMouseWheel( event ) {
460 |
461 | if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return;
462 |
463 | event.preventDefault();
464 | event.stopPropagation();
465 |
466 | var delta = 0;
467 |
468 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
469 |
470 | delta = event.wheelDelta;
471 |
472 | } else if ( event.detail !== undefined ) { // Firefox
473 |
474 | delta = - event.detail;
475 |
476 | }
477 |
478 | if ( delta > 0 ) {
479 |
480 | scope.dollyOut();
481 |
482 | } else {
483 |
484 | scope.dollyIn();
485 |
486 | }
487 |
488 | scope.update();
489 | scope.dispatchEvent( startEvent );
490 | scope.dispatchEvent( endEvent );
491 |
492 | }
493 |
494 | function onKeyDown( event ) {
495 |
496 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return;
497 |
498 | switch ( event.keyCode ) {
499 |
500 | case scope.keys.UP:
501 | scope.pan( 0, scope.keyPanSpeed );
502 | scope.update();
503 | break;
504 |
505 | case scope.keys.BOTTOM:
506 | scope.pan( 0, - scope.keyPanSpeed );
507 | scope.update();
508 | break;
509 |
510 | case scope.keys.LEFT:
511 | scope.pan( scope.keyPanSpeed, 0 );
512 | scope.update();
513 | break;
514 |
515 | case scope.keys.RIGHT:
516 | scope.pan( - scope.keyPanSpeed, 0 );
517 | scope.update();
518 | break;
519 |
520 | }
521 |
522 | }
523 |
524 | function touchstart( event ) {
525 |
526 | if ( scope.enabled === false ) return;
527 |
528 | switch ( event.touches.length ) {
529 |
530 | case 1: // one-fingered touch: rotate
531 |
532 | if ( scope.noRotate === true ) return;
533 |
534 | state = STATE.TOUCH_ROTATE;
535 |
536 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
537 | break;
538 |
539 | case 2: // two-fingered touch: dolly
540 |
541 | if ( scope.noZoom === true ) return;
542 |
543 | state = STATE.TOUCH_DOLLY;
544 |
545 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
546 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
547 | var distance = Math.sqrt( dx * dx + dy * dy );
548 | dollyStart.set( 0, distance );
549 | break;
550 |
551 | case 3: // three-fingered touch: pan
552 |
553 | if ( scope.noPan === true ) return;
554 |
555 | state = STATE.TOUCH_PAN;
556 |
557 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
558 | break;
559 |
560 | default:
561 |
562 | state = STATE.NONE;
563 |
564 | }
565 |
566 | if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent );
567 |
568 | }
569 |
570 | function touchmove( event ) {
571 |
572 | if ( scope.enabled === false ) return;
573 |
574 | event.preventDefault();
575 | event.stopPropagation();
576 |
577 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
578 |
579 | switch ( event.touches.length ) {
580 |
581 | case 1: // one-fingered touch: rotate
582 |
583 | if ( scope.noRotate === true ) return;
584 | if ( state !== STATE.TOUCH_ROTATE ) return;
585 |
586 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
587 | rotateDelta.subVectors( rotateEnd, rotateStart );
588 |
589 | // rotating across whole screen goes 360 degrees around
590 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
591 | // rotating up and down along whole screen attempts to go 360, but limited to 180
592 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
593 |
594 | rotateStart.copy( rotateEnd );
595 |
596 | scope.update();
597 | break;
598 |
599 | case 2: // two-fingered touch: dolly
600 |
601 | if ( scope.noZoom === true ) return;
602 | if ( state !== STATE.TOUCH_DOLLY ) return;
603 |
604 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
605 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
606 | var distance = Math.sqrt( dx * dx + dy * dy );
607 |
608 | dollyEnd.set( 0, distance );
609 | dollyDelta.subVectors( dollyEnd, dollyStart );
610 |
611 | if ( dollyDelta.y > 0 ) {
612 |
613 | scope.dollyOut();
614 |
615 | } else {
616 |
617 | scope.dollyIn();
618 |
619 | }
620 |
621 | dollyStart.copy( dollyEnd );
622 |
623 | scope.update();
624 | break;
625 |
626 | case 3: // three-fingered touch: pan
627 |
628 | if ( scope.noPan === true ) return;
629 | if ( state !== STATE.TOUCH_PAN ) return;
630 |
631 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
632 | panDelta.subVectors( panEnd, panStart );
633 |
634 | scope.pan( panDelta.x, panDelta.y );
635 |
636 | panStart.copy( panEnd );
637 |
638 | scope.update();
639 | break;
640 |
641 | default:
642 |
643 | state = STATE.NONE;
644 |
645 | }
646 |
647 | }
648 |
649 | function touchend( /* event */ ) {
650 |
651 | if ( scope.enabled === false ) return;
652 |
653 | scope.dispatchEvent( endEvent );
654 | state = STATE.NONE;
655 |
656 | }
657 |
658 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
659 | this.domElement.addEventListener( 'mousedown', onMouseDown, false );
660 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
661 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
662 |
663 | this.domElement.addEventListener( 'touchstart', touchstart, false );
664 | this.domElement.addEventListener( 'touchend', touchend, false );
665 | this.domElement.addEventListener( 'touchmove', touchmove, false );
666 |
667 | window.addEventListener( 'keydown', onKeyDown, false );
668 |
669 | // force an update at start
670 | this.update();
671 |
672 | };
673 |
674 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
675 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
676 |
--------------------------------------------------------------------------------
/app/js/lib/Stats.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 |
5 | var Stats = function () {
6 |
7 | var startTime = Date.now(), prevTime = startTime;
8 | var ms = 0, msMin = Infinity, msMax = 0;
9 | var fps = 0, fpsMin = Infinity, fpsMax = 0;
10 | var frames = 0, mode = 0;
11 |
12 | var container = document.createElement( 'div' );
13 | container.id = 'stats';
14 | container.addEventListener( 'mousedown', function ( event ) { event.preventDefault(); setMode( ++ mode % 2 ) }, false );
15 | container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer';
16 |
17 | var fpsDiv = document.createElement( 'div' );
18 | fpsDiv.id = 'fps';
19 | fpsDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#002';
20 | container.appendChild( fpsDiv );
21 |
22 | var fpsText = document.createElement( 'div' );
23 | fpsText.id = 'fpsText';
24 | fpsText.style.cssText = 'color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
25 | fpsText.innerHTML = 'FPS';
26 | fpsDiv.appendChild( fpsText );
27 |
28 | var fpsGraph = document.createElement( 'div' );
29 | fpsGraph.id = 'fpsGraph';
30 | fpsGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0ff';
31 | fpsDiv.appendChild( fpsGraph );
32 |
33 | while ( fpsGraph.children.length < 74 ) {
34 |
35 | var bar = document.createElement( 'span' );
36 | bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#113';
37 | fpsGraph.appendChild( bar );
38 |
39 | }
40 |
41 | var msDiv = document.createElement( 'div' );
42 | msDiv.id = 'ms';
43 | msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;display:none';
44 | container.appendChild( msDiv );
45 |
46 | var msText = document.createElement( 'div' );
47 | msText.id = 'msText';
48 | msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
49 | msText.innerHTML = 'MS';
50 | msDiv.appendChild( msText );
51 |
52 | var msGraph = document.createElement( 'div' );
53 | msGraph.id = 'msGraph';
54 | msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0';
55 | msDiv.appendChild( msGraph );
56 |
57 | while ( msGraph.children.length < 74 ) {
58 |
59 | var bar = document.createElement( 'span' );
60 | bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131';
61 | msGraph.appendChild( bar );
62 |
63 | }
64 |
65 | var setMode = function ( value ) {
66 |
67 | mode = value;
68 |
69 | switch ( mode ) {
70 |
71 | case 0:
72 | fpsDiv.style.display = 'block';
73 | msDiv.style.display = 'none';
74 | break;
75 | case 1:
76 | fpsDiv.style.display = 'none';
77 | msDiv.style.display = 'block';
78 | break;
79 | }
80 |
81 | };
82 |
83 | var updateGraph = function ( dom, value ) {
84 |
85 | var child = dom.appendChild( dom.firstChild );
86 | child.style.height = value + 'px';
87 |
88 | };
89 |
90 | return {
91 |
92 | REVISION: 12,
93 |
94 | domElement: container,
95 |
96 | setMode: setMode,
97 |
98 | begin: function () {
99 |
100 | startTime = Date.now();
101 |
102 | },
103 |
104 | end: function () {
105 |
106 | var time = Date.now();
107 |
108 | ms = time - startTime;
109 | msMin = Math.min( msMin, ms );
110 | msMax = Math.max( msMax, ms );
111 |
112 | msText.textContent = ms + ' MS (' + msMin + '-' + msMax + ')';
113 | updateGraph( msGraph, Math.min( 30, 30 - ( ms / 200 ) * 30 ) );
114 |
115 | frames ++;
116 |
117 | if ( time > prevTime + 1000 ) {
118 |
119 | fps = Math.round( ( frames * 1000 ) / ( time - prevTime ) );
120 | fpsMin = Math.min( fpsMin, fps );
121 | fpsMax = Math.max( fpsMax, fps );
122 |
123 | fpsText.textContent = fps + ' FPS (' + fpsMin + '-' + fpsMax + ')';
124 | updateGraph( fpsGraph, Math.min( 30, 30 - ( fps / 100 ) * 30 ) );
125 |
126 | prevTime = time;
127 | frames = 0;
128 |
129 | }
130 |
131 | return time;
132 |
133 | },
134 |
135 | update: function () {
136 |
137 | startTime = this.end();
138 |
139 | }
140 |
141 | }
142 |
143 | };
144 |
145 | if ( typeof module === 'object' ) {
146 |
147 | module.exports = Stats;
148 |
149 | }
--------------------------------------------------------------------------------
/app/js/lib/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_PAN: 4 };
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 EPS = 0.000001;
42 |
43 | var lastPosition = new THREE.Vector3();
44 |
45 | var _state = STATE.NONE,
46 | _prevState = STATE.NONE,
47 |
48 | _eye = new THREE.Vector3(),
49 |
50 | _rotateStart = new THREE.Vector3(),
51 | _rotateEnd = new THREE.Vector3(),
52 |
53 | _zoomStart = new THREE.Vector2(),
54 | _zoomEnd = new THREE.Vector2(),
55 |
56 | _touchZoomDistanceStart = 0,
57 | _touchZoomDistanceEnd = 0,
58 |
59 | _panStart = new THREE.Vector2(),
60 | _panEnd = new THREE.Vector2();
61 |
62 | // for reset
63 |
64 | this.target0 = this.target.clone();
65 | this.position0 = this.object.position.clone();
66 | this.up0 = this.object.up.clone();
67 |
68 | // events
69 |
70 | var changeEvent = { type: 'change' };
71 | var startEvent = { type: 'start'};
72 | var endEvent = { type: 'end'};
73 |
74 |
75 | // methods
76 |
77 | this.handleResize = function () {
78 |
79 | if ( this.domElement === document ) {
80 |
81 | this.screen.left = 0;
82 | this.screen.top = 0;
83 | this.screen.width = window.innerWidth;
84 | this.screen.height = window.innerHeight;
85 |
86 | } else {
87 |
88 | var box = this.domElement.getBoundingClientRect();
89 | // adjustments come from similar code in the jquery offset() function
90 | var d = this.domElement.ownerDocument.documentElement;
91 | this.screen.left = box.left + window.pageXOffset - d.clientLeft;
92 | this.screen.top = box.top + window.pageYOffset - d.clientTop;
93 | this.screen.width = box.width;
94 | this.screen.height = box.height;
95 |
96 | }
97 |
98 | };
99 |
100 | this.handleEvent = function ( event ) {
101 |
102 | if ( typeof this[ event.type ] == 'function' ) {
103 |
104 | this[ event.type ]( event );
105 |
106 | }
107 |
108 | };
109 |
110 | var getMouseOnScreen = ( function () {
111 |
112 | var vector = new THREE.Vector2();
113 |
114 | return function ( pageX, pageY ) {
115 |
116 | vector.set(
117 | ( pageX - _this.screen.left ) / _this.screen.width,
118 | ( pageY - _this.screen.top ) / _this.screen.height
119 | );
120 |
121 | return vector;
122 |
123 | };
124 |
125 | }() );
126 |
127 | var getMouseProjectionOnBall = ( function () {
128 |
129 | var vector = new THREE.Vector3();
130 | var objectUp = new THREE.Vector3();
131 | var mouseOnBall = new THREE.Vector3();
132 |
133 | return function ( pageX, pageY ) {
134 |
135 | mouseOnBall.set(
136 | ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5),
137 | ( _this.screen.height * 0.5 + _this.screen.top - pageY ) / (_this.screen.height*.5),
138 | 0.0
139 | );
140 |
141 | var length = mouseOnBall.length();
142 |
143 | if ( _this.noRoll ) {
144 |
145 | if ( length < Math.SQRT1_2 ) {
146 |
147 | mouseOnBall.z = Math.sqrt( 1.0 - length*length );
148 |
149 | } else {
150 |
151 | mouseOnBall.z = .5 / length;
152 |
153 | }
154 |
155 | } else if ( length > 1.0 ) {
156 |
157 | mouseOnBall.normalize();
158 |
159 | } else {
160 |
161 | mouseOnBall.z = Math.sqrt( 1.0 - length * length );
162 |
163 | }
164 |
165 | _eye.copy( _this.object.position ).sub( _this.target );
166 |
167 | vector.copy( _this.object.up ).setLength( mouseOnBall.y )
168 | vector.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) );
169 | vector.add( _eye.setLength( mouseOnBall.z ) );
170 |
171 | return vector;
172 |
173 | };
174 |
175 | }() );
176 |
177 | this.rotateCamera = (function(){
178 |
179 | var axis = new THREE.Vector3(),
180 | quaternion = new THREE.Quaternion();
181 |
182 |
183 | return function () {
184 |
185 | var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
186 |
187 | if ( angle ) {
188 |
189 | axis.crossVectors( _rotateStart, _rotateEnd ).normalize();
190 |
191 | angle *= _this.rotateSpeed;
192 |
193 | quaternion.setFromAxisAngle( axis, -angle );
194 |
195 | _eye.applyQuaternion( quaternion );
196 | _this.object.up.applyQuaternion( quaternion );
197 |
198 | _rotateEnd.applyQuaternion( quaternion );
199 |
200 | if ( _this.staticMoving ) {
201 |
202 | _rotateStart.copy( _rotateEnd );
203 |
204 | } else {
205 |
206 | quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
207 | _rotateStart.applyQuaternion( quaternion );
208 |
209 | }
210 |
211 | }
212 | }
213 |
214 | }());
215 |
216 | this.zoomCamera = function () {
217 |
218 | if ( _state === STATE.TOUCH_ZOOM_PAN ) {
219 |
220 | var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
221 | _touchZoomDistanceStart = _touchZoomDistanceEnd;
222 | _eye.multiplyScalar( factor );
223 |
224 | } else {
225 |
226 | var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
227 |
228 | if ( factor !== 1.0 && factor > 0.0 ) {
229 |
230 | _eye.multiplyScalar( factor );
231 |
232 | if ( _this.staticMoving ) {
233 |
234 | _zoomStart.copy( _zoomEnd );
235 |
236 | } else {
237 |
238 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
239 |
240 | }
241 |
242 | }
243 |
244 | }
245 |
246 | };
247 |
248 | this.panCamera = (function(){
249 |
250 | var mouseChange = new THREE.Vector2(),
251 | objectUp = new THREE.Vector3(),
252 | pan = new THREE.Vector3();
253 |
254 | return function () {
255 |
256 | mouseChange.copy( _panEnd ).sub( _panStart );
257 |
258 | if ( mouseChange.lengthSq() ) {
259 |
260 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
261 |
262 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
263 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
264 |
265 | _this.object.position.add( pan );
266 | _this.target.add( pan );
267 |
268 | if ( _this.staticMoving ) {
269 |
270 | _panStart.copy( _panEnd );
271 |
272 | } else {
273 |
274 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
275 |
276 | }
277 |
278 | }
279 | }
280 |
281 | }());
282 |
283 | this.checkDistances = function () {
284 |
285 | if ( !_this.noZoom || !_this.noPan ) {
286 |
287 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
288 |
289 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
290 |
291 | }
292 |
293 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
294 |
295 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
296 |
297 | }
298 |
299 | }
300 |
301 | };
302 |
303 | this.update = function () {
304 |
305 | _eye.subVectors( _this.object.position, _this.target );
306 |
307 | if ( !_this.noRotate ) {
308 |
309 | _this.rotateCamera();
310 |
311 | }
312 |
313 | if ( !_this.noZoom ) {
314 |
315 | _this.zoomCamera();
316 |
317 | }
318 |
319 | if ( !_this.noPan ) {
320 |
321 | _this.panCamera();
322 |
323 | }
324 |
325 | _this.object.position.addVectors( _this.target, _eye );
326 |
327 | _this.checkDistances();
328 |
329 | _this.object.lookAt( _this.target );
330 |
331 | if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) {
332 |
333 | _this.dispatchEvent( changeEvent );
334 |
335 | lastPosition.copy( _this.object.position );
336 |
337 | }
338 |
339 | };
340 |
341 | this.reset = function () {
342 |
343 | _state = STATE.NONE;
344 | _prevState = STATE.NONE;
345 |
346 | _this.target.copy( _this.target0 );
347 | _this.object.position.copy( _this.position0 );
348 | _this.object.up.copy( _this.up0 );
349 |
350 | _eye.subVectors( _this.object.position, _this.target );
351 |
352 | _this.object.lookAt( _this.target );
353 |
354 | _this.dispatchEvent( changeEvent );
355 |
356 | lastPosition.copy( _this.object.position );
357 |
358 | };
359 |
360 | // listeners
361 |
362 | function keydown( event ) {
363 |
364 | if ( _this.enabled === false ) return;
365 |
366 | window.removeEventListener( 'keydown', keydown );
367 |
368 | _prevState = _state;
369 |
370 | if ( _state !== STATE.NONE ) {
371 |
372 | return;
373 |
374 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {
375 |
376 | _state = STATE.ROTATE;
377 |
378 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {
379 |
380 | _state = STATE.ZOOM;
381 |
382 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {
383 |
384 | _state = STATE.PAN;
385 |
386 | }
387 |
388 | }
389 |
390 | function keyup( event ) {
391 |
392 | if ( _this.enabled === false ) return;
393 |
394 | _state = _prevState;
395 |
396 | window.addEventListener( 'keydown', keydown, false );
397 |
398 | }
399 |
400 | function mousedown( event ) {
401 |
402 | if ( _this.enabled === false ) return;
403 |
404 | event.preventDefault();
405 | event.stopPropagation();
406 |
407 | if ( _state === STATE.NONE ) {
408 |
409 | _state = event.button;
410 |
411 | }
412 |
413 | if ( _state === STATE.ROTATE && !_this.noRotate ) {
414 |
415 | _rotateStart.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
416 | _rotateEnd.copy( _rotateStart );
417 |
418 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) {
419 |
420 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
421 | _zoomEnd.copy(_zoomStart);
422 |
423 | } else if ( _state === STATE.PAN && !_this.noPan ) {
424 |
425 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
426 | _panEnd.copy(_panStart)
427 |
428 | }
429 |
430 | document.addEventListener( 'mousemove', mousemove, false );
431 | document.addEventListener( 'mouseup', mouseup, false );
432 |
433 | _this.dispatchEvent( startEvent );
434 |
435 | }
436 |
437 | function mousemove( event ) {
438 |
439 | if ( _this.enabled === false ) return;
440 |
441 | event.preventDefault();
442 | event.stopPropagation();
443 |
444 | if ( _state === STATE.ROTATE && !_this.noRotate ) {
445 |
446 | _rotateEnd.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
447 |
448 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) {
449 |
450 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
451 |
452 | } else if ( _state === STATE.PAN && !_this.noPan ) {
453 |
454 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
455 |
456 | }
457 |
458 | }
459 |
460 | function mouseup( event ) {
461 |
462 | if ( _this.enabled === false ) return;
463 |
464 | event.preventDefault();
465 | event.stopPropagation();
466 |
467 | _state = STATE.NONE;
468 |
469 | document.removeEventListener( 'mousemove', mousemove );
470 | document.removeEventListener( 'mouseup', mouseup );
471 | _this.dispatchEvent( endEvent );
472 |
473 | }
474 |
475 | function mousewheel( event ) {
476 |
477 | if ( _this.enabled === false ) return;
478 |
479 | event.preventDefault();
480 | event.stopPropagation();
481 |
482 | var delta = 0;
483 |
484 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
485 |
486 | delta = event.wheelDelta / 40;
487 |
488 | } else if ( event.detail ) { // Firefox
489 |
490 | delta = - event.detail / 3;
491 |
492 | }
493 |
494 | _zoomStart.y += delta * 0.01;
495 | _this.dispatchEvent( startEvent );
496 | _this.dispatchEvent( endEvent );
497 |
498 | }
499 |
500 | function touchstart( event ) {
501 |
502 | if ( _this.enabled === false ) return;
503 |
504 | switch ( event.touches.length ) {
505 |
506 | case 1:
507 | _state = STATE.TOUCH_ROTATE;
508 | _rotateStart.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
509 | _rotateEnd.copy( _rotateStart );
510 | break;
511 |
512 | case 2:
513 | _state = STATE.TOUCH_ZOOM_PAN;
514 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
515 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
516 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
517 |
518 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
519 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
520 | _panStart.copy( getMouseOnScreen( x, y ) );
521 | _panEnd.copy( _panStart );
522 | break;
523 |
524 | default:
525 | _state = STATE.NONE;
526 |
527 | }
528 | _this.dispatchEvent( startEvent );
529 |
530 |
531 | }
532 |
533 | function touchmove( event ) {
534 |
535 | if ( _this.enabled === false ) return;
536 |
537 | event.preventDefault();
538 | event.stopPropagation();
539 |
540 | switch ( event.touches.length ) {
541 |
542 | case 1:
543 | _rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
544 | break;
545 |
546 | case 2:
547 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
548 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
549 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
550 |
551 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
552 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
553 | _panEnd.copy( getMouseOnScreen( x, y ) );
554 | break;
555 |
556 | default:
557 | _state = STATE.NONE;
558 |
559 | }
560 |
561 | }
562 |
563 | function touchend( event ) {
564 |
565 | if ( _this.enabled === false ) return;
566 |
567 | switch ( event.touches.length ) {
568 |
569 | case 1:
570 | _rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
571 | _rotateStart.copy( _rotateEnd );
572 | break;
573 |
574 | case 2:
575 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
576 |
577 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
578 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
579 | _panEnd.copy( getMouseOnScreen( x, y ) );
580 | _panStart.copy( _panEnd );
581 | break;
582 |
583 | }
584 |
585 | _state = STATE.NONE;
586 | _this.dispatchEvent( endEvent );
587 |
588 | }
589 |
590 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
591 |
592 | this.domElement.addEventListener( 'mousedown', mousedown, false );
593 |
594 | this.domElement.addEventListener( 'mousewheel', mousewheel, false );
595 | this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
596 |
597 | this.domElement.addEventListener( 'touchstart', touchstart, false );
598 | this.domElement.addEventListener( 'touchend', touchend, false );
599 | this.domElement.addEventListener( 'touchmove', touchmove, false );
600 |
601 | window.addEventListener( 'keydown', keydown, false );
602 | window.addEventListener( 'keyup', keyup, false );
603 |
604 | this.handleResize();
605 |
606 | // force an update at start
607 | this.update();
608 |
609 | };
610 |
611 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
612 | THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;
613 |
--------------------------------------------------------------------------------
/app/js/lib/mousetrap.js:
--------------------------------------------------------------------------------
1 | /*global define:false */
2 | /**
3 | * Copyright 2013 Craig Campbell
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * Mousetrap is a simple keyboard shortcut library for Javascript with
18 | * no external dependencies
19 | *
20 | * @version 1.4.6
21 | * @url craig.is/killing/mice
22 | */
23 | (function(window, document, undefined) {
24 |
25 | /**
26 | * mapping of special keycodes to their corresponding keys
27 | *
28 | * everything in this dictionary cannot use keypress events
29 | * so it has to be here to map to the correct keycodes for
30 | * keyup/keydown events
31 | *
32 | * @type {Object}
33 | */
34 | var _MAP = {
35 | 8: 'backspace',
36 | 9: 'tab',
37 | 13: 'enter',
38 | 16: 'shift',
39 | 17: 'ctrl',
40 | 18: 'alt',
41 | 20: 'capslock',
42 | 27: 'esc',
43 | 32: 'space',
44 | 33: 'pageup',
45 | 34: 'pagedown',
46 | 35: 'end',
47 | 36: 'home',
48 | 37: 'left',
49 | 38: 'up',
50 | 39: 'right',
51 | 40: 'down',
52 | 45: 'ins',
53 | 46: 'del',
54 | 91: 'meta',
55 | 93: 'meta',
56 | 224: 'meta'
57 | },
58 |
59 | /**
60 | * mapping for special characters so they can support
61 | *
62 | * this dictionary is only used incase you want to bind a
63 | * keyup or keydown event to one of these keys
64 | *
65 | * @type {Object}
66 | */
67 | _KEYCODE_MAP = {
68 | 106: '*',
69 | 107: '+',
70 | 109: '-',
71 | 110: '.',
72 | 111 : '/',
73 | 186: ';',
74 | 187: '=',
75 | 188: ',',
76 | 189: '-',
77 | 190: '.',
78 | 191: '/',
79 | 192: '`',
80 | 219: '[',
81 | 220: '\\',
82 | 221: ']',
83 | 222: '\''
84 | },
85 |
86 | /**
87 | * this is a mapping of keys that require shift on a US keypad
88 | * back to the non shift equivelents
89 | *
90 | * this is so you can use keyup events with these keys
91 | *
92 | * note that this will only work reliably on US keyboards
93 | *
94 | * @type {Object}
95 | */
96 | _SHIFT_MAP = {
97 | '~': '`',
98 | '!': '1',
99 | '@': '2',
100 | '#': '3',
101 | '$': '4',
102 | '%': '5',
103 | '^': '6',
104 | '&': '7',
105 | '*': '8',
106 | '(': '9',
107 | ')': '0',
108 | '_': '-',
109 | '+': '=',
110 | ':': ';',
111 | '\"': '\'',
112 | '<': ',',
113 | '>': '.',
114 | '?': '/',
115 | '|': '\\'
116 | },
117 |
118 | /**
119 | * this is a list of special strings you can use to map
120 | * to modifier keys when you specify your keyboard shortcuts
121 | *
122 | * @type {Object}
123 | */
124 | _SPECIAL_ALIASES = {
125 | 'option': 'alt',
126 | 'command': 'meta',
127 | 'return': 'enter',
128 | 'escape': 'esc',
129 | 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
130 | },
131 |
132 | /**
133 | * variable to store the flipped version of _MAP from above
134 | * needed to check if we should use keypress or not when no action
135 | * is specified
136 | *
137 | * @type {Object|undefined}
138 | */
139 | _REVERSE_MAP,
140 |
141 | /**
142 | * a list of all the callbacks setup via Mousetrap.bind()
143 | *
144 | * @type {Object}
145 | */
146 | _callbacks = {},
147 |
148 | /**
149 | * direct map of string combinations to callbacks used for trigger()
150 | *
151 | * @type {Object}
152 | */
153 | _directMap = {},
154 |
155 | /**
156 | * keeps track of what level each sequence is at since multiple
157 | * sequences can start out with the same sequence
158 | *
159 | * @type {Object}
160 | */
161 | _sequenceLevels = {},
162 |
163 | /**
164 | * variable to store the setTimeout call
165 | *
166 | * @type {null|number}
167 | */
168 | _resetTimer,
169 |
170 | /**
171 | * temporary state where we will ignore the next keyup
172 | *
173 | * @type {boolean|string}
174 | */
175 | _ignoreNextKeyup = false,
176 |
177 | /**
178 | * temporary state where we will ignore the next keypress
179 | *
180 | * @type {boolean}
181 | */
182 | _ignoreNextKeypress = false,
183 |
184 | /**
185 | * are we currently inside of a sequence?
186 | * type of action ("keyup" or "keydown" or "keypress") or false
187 | *
188 | * @type {boolean|string}
189 | */
190 | _nextExpectedAction = false;
191 |
192 | /**
193 | * loop through the f keys, f1 to f19 and add them to the map
194 | * programatically
195 | */
196 | for (var i = 1; i < 20; ++i) {
197 | _MAP[111 + i] = 'f' + i;
198 | }
199 |
200 | /**
201 | * loop through to map numbers on the numeric keypad
202 | */
203 | for (i = 0; i <= 9; ++i) {
204 | _MAP[i + 96] = i;
205 | }
206 |
207 | /**
208 | * cross browser add event method
209 | *
210 | * @param {Element|HTMLDocument} object
211 | * @param {string} type
212 | * @param {Function} callback
213 | * @returns void
214 | */
215 | function _addEvent(object, type, callback) {
216 | if (object.addEventListener) {
217 | object.addEventListener(type, callback, false);
218 | return;
219 | }
220 |
221 | object.attachEvent('on' + type, callback);
222 | }
223 |
224 | /**
225 | * takes the event and returns the key character
226 | *
227 | * @param {Event} e
228 | * @return {string}
229 | */
230 | function _characterFromEvent(e) {
231 |
232 | // for keypress events we should return the character as is
233 | if (e.type == 'keypress') {
234 | var character = String.fromCharCode(e.which);
235 |
236 | // if the shift key is not pressed then it is safe to assume
237 | // that we want the character to be lowercase. this means if
238 | // you accidentally have caps lock on then your key bindings
239 | // will continue to work
240 | //
241 | // the only side effect that might not be desired is if you
242 | // bind something like 'A' cause you want to trigger an
243 | // event when capital A is pressed caps lock will no longer
244 | // trigger the event. shift+a will though.
245 | if (!e.shiftKey) {
246 | character = character.toLowerCase();
247 | }
248 |
249 | return character;
250 | }
251 |
252 | // for non keypress events the special maps are needed
253 | if (_MAP[e.which]) {
254 | return _MAP[e.which];
255 | }
256 |
257 | if (_KEYCODE_MAP[e.which]) {
258 | return _KEYCODE_MAP[e.which];
259 | }
260 |
261 | // if it is not in the special map
262 |
263 | // with keydown and keyup events the character seems to always
264 | // come in as an uppercase character whether you are pressing shift
265 | // or not. we should make sure it is always lowercase for comparisons
266 | return String.fromCharCode(e.which).toLowerCase();
267 | }
268 |
269 | /**
270 | * checks if two arrays are equal
271 | *
272 | * @param {Array} modifiers1
273 | * @param {Array} modifiers2
274 | * @returns {boolean}
275 | */
276 | function _modifiersMatch(modifiers1, modifiers2) {
277 | return modifiers1.sort().join(',') === modifiers2.sort().join(',');
278 | }
279 |
280 | /**
281 | * resets all sequence counters except for the ones passed in
282 | *
283 | * @param {Object} doNotReset
284 | * @returns void
285 | */
286 | function _resetSequences(doNotReset) {
287 | doNotReset = doNotReset || {};
288 |
289 | var activeSequences = false,
290 | key;
291 |
292 | for (key in _sequenceLevels) {
293 | if (doNotReset[key]) {
294 | activeSequences = true;
295 | continue;
296 | }
297 | _sequenceLevels[key] = 0;
298 | }
299 |
300 | if (!activeSequences) {
301 | _nextExpectedAction = false;
302 | }
303 | }
304 |
305 | /**
306 | * finds all callbacks that match based on the keycode, modifiers,
307 | * and action
308 | *
309 | * @param {string} character
310 | * @param {Array} modifiers
311 | * @param {Event|Object} e
312 | * @param {string=} sequenceName - name of the sequence we are looking for
313 | * @param {string=} combination
314 | * @param {number=} level
315 | * @returns {Array}
316 | */
317 | function _getMatches(character, modifiers, e, sequenceName, combination, level) {
318 | var i,
319 | callback,
320 | matches = [],
321 | action = e.type;
322 |
323 | // if there are no events related to this keycode
324 | if (!_callbacks[character]) {
325 | return [];
326 | }
327 |
328 | // if a modifier key is coming up on its own we should allow it
329 | if (action == 'keyup' && _isModifier(character)) {
330 | modifiers = [character];
331 | }
332 |
333 | // loop through all callbacks for the key that was pressed
334 | // and see if any of them match
335 | for (i = 0; i < _callbacks[character].length; ++i) {
336 | callback = _callbacks[character][i];
337 |
338 | // if a sequence name is not specified, but this is a sequence at
339 | // the wrong level then move onto the next match
340 | if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
341 | continue;
342 | }
343 |
344 | // if the action we are looking for doesn't match the action we got
345 | // then we should keep going
346 | if (action != callback.action) {
347 | continue;
348 | }
349 |
350 | // if this is a keypress event and the meta key and control key
351 | // are not pressed that means that we need to only look at the
352 | // character, otherwise check the modifiers as well
353 | //
354 | // chrome will not fire a keypress if meta or control is down
355 | // safari will fire a keypress if meta or meta+shift is down
356 | // firefox will fire a keypress if meta or control is down
357 | if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
358 |
359 | // when you bind a combination or sequence a second time it
360 | // should overwrite the first one. if a sequenceName or
361 | // combination is specified in this call it does just that
362 | //
363 | // @todo make deleting its own method?
364 | var deleteCombo = !sequenceName && callback.combo == combination;
365 | var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
366 | if (deleteCombo || deleteSequence) {
367 | _callbacks[character].splice(i, 1);
368 | }
369 |
370 | matches.push(callback);
371 | }
372 | }
373 |
374 | return matches;
375 | }
376 |
377 | /**
378 | * takes a key event and figures out what the modifiers are
379 | *
380 | * @param {Event} e
381 | * @returns {Array}
382 | */
383 | function _eventModifiers(e) {
384 | var modifiers = [];
385 |
386 | if (e.shiftKey) {
387 | modifiers.push('shift');
388 | }
389 |
390 | if (e.altKey) {
391 | modifiers.push('alt');
392 | }
393 |
394 | if (e.ctrlKey) {
395 | modifiers.push('ctrl');
396 | }
397 |
398 | if (e.metaKey) {
399 | modifiers.push('meta');
400 | }
401 |
402 | return modifiers;
403 | }
404 |
405 | /**
406 | * prevents default for this event
407 | *
408 | * @param {Event} e
409 | * @returns void
410 | */
411 | function _preventDefault(e) {
412 | if (e.preventDefault) {
413 | e.preventDefault();
414 | return;
415 | }
416 |
417 | e.returnValue = false;
418 | }
419 |
420 | /**
421 | * stops propogation for this event
422 | *
423 | * @param {Event} e
424 | * @returns void
425 | */
426 | function _stopPropagation(e) {
427 | if (e.stopPropagation) {
428 | e.stopPropagation();
429 | return;
430 | }
431 |
432 | e.cancelBubble = true;
433 | }
434 |
435 | /**
436 | * actually calls the callback function
437 | *
438 | * if your callback function returns false this will use the jquery
439 | * convention - prevent default and stop propogation on the event
440 | *
441 | * @param {Function} callback
442 | * @param {Event} e
443 | * @returns void
444 | */
445 | function _fireCallback(callback, e, combo, sequence) {
446 |
447 | // if this event should not happen stop here
448 | if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
449 | return;
450 | }
451 |
452 | if (callback(e, combo) === false) {
453 | _preventDefault(e);
454 | _stopPropagation(e);
455 | }
456 | }
457 |
458 | /**
459 | * handles a character key event
460 | *
461 | * @param {string} character
462 | * @param {Array} modifiers
463 | * @param {Event} e
464 | * @returns void
465 | */
466 | function _handleKey(character, modifiers, e) {
467 | var callbacks = _getMatches(character, modifiers, e),
468 | i,
469 | doNotReset = {},
470 | maxLevel = 0,
471 | processedSequenceCallback = false;
472 |
473 | // Calculate the maxLevel for sequences so we can only execute the longest callback sequence
474 | for (i = 0; i < callbacks.length; ++i) {
475 | if (callbacks[i].seq) {
476 | maxLevel = Math.max(maxLevel, callbacks[i].level);
477 | }
478 | }
479 |
480 | // loop through matching callbacks for this key event
481 | for (i = 0; i < callbacks.length; ++i) {
482 |
483 | // fire for all sequence callbacks
484 | // this is because if for example you have multiple sequences
485 | // bound such as "g i" and "g t" they both need to fire the
486 | // callback for matching g cause otherwise you can only ever
487 | // match the first one
488 | if (callbacks[i].seq) {
489 |
490 | // only fire callbacks for the maxLevel to prevent
491 | // subsequences from also firing
492 | //
493 | // for example 'a option b' should not cause 'option b' to fire
494 | // even though 'option b' is part of the other sequence
495 | //
496 | // any sequences that do not match here will be discarded
497 | // below by the _resetSequences call
498 | if (callbacks[i].level != maxLevel) {
499 | continue;
500 | }
501 |
502 | processedSequenceCallback = true;
503 |
504 | // keep a list of which sequences were matches for later
505 | doNotReset[callbacks[i].seq] = 1;
506 | _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
507 | continue;
508 | }
509 |
510 | // if there were no sequence matches but we are still here
511 | // that means this is a regular match so we should fire that
512 | if (!processedSequenceCallback) {
513 | _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
514 | }
515 | }
516 |
517 | // if the key you pressed matches the type of sequence without
518 | // being a modifier (ie "keyup" or "keypress") then we should
519 | // reset all sequences that were not matched by this event
520 | //
521 | // this is so, for example, if you have the sequence "h a t" and you
522 | // type "h e a r t" it does not match. in this case the "e" will
523 | // cause the sequence to reset
524 | //
525 | // modifier keys are ignored because you can have a sequence
526 | // that contains modifiers such as "enter ctrl+space" and in most
527 | // cases the modifier key will be pressed before the next key
528 | //
529 | // also if you have a sequence such as "ctrl+b a" then pressing the
530 | // "b" key will trigger a "keypress" and a "keydown"
531 | //
532 | // the "keydown" is expected when there is a modifier, but the
533 | // "keypress" ends up matching the _nextExpectedAction since it occurs
534 | // after and that causes the sequence to reset
535 | //
536 | // we ignore keypresses in a sequence that directly follow a keydown
537 | // for the same character
538 | var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
539 | if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) {
540 | _resetSequences(doNotReset);
541 | }
542 |
543 | _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
544 | }
545 |
546 | /**
547 | * handles a keydown event
548 | *
549 | * @param {Event} e
550 | * @returns void
551 | */
552 | function _handleKeyEvent(e) {
553 |
554 | // normalize e.which for key events
555 | // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
556 | if (typeof e.which !== 'number') {
557 | e.which = e.keyCode;
558 | }
559 |
560 | var character = _characterFromEvent(e);
561 |
562 | // no character found then stop
563 | if (!character) {
564 | return;
565 | }
566 |
567 | // need to use === for the character check because the character can be 0
568 | if (e.type == 'keyup' && _ignoreNextKeyup === character) {
569 | _ignoreNextKeyup = false;
570 | return;
571 | }
572 |
573 | Mousetrap.handleKey(character, _eventModifiers(e), e);
574 | }
575 |
576 | /**
577 | * determines if the keycode specified is a modifier key or not
578 | *
579 | * @param {string} key
580 | * @returns {boolean}
581 | */
582 | function _isModifier(key) {
583 | return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
584 | }
585 |
586 | /**
587 | * called to set a 1 second timeout on the specified sequence
588 | *
589 | * this is so after each key press in the sequence you have 1 second
590 | * to press the next key before you have to start over
591 | *
592 | * @returns void
593 | */
594 | function _resetSequenceTimer() {
595 | clearTimeout(_resetTimer);
596 | _resetTimer = setTimeout(_resetSequences, 1000);
597 | }
598 |
599 | /**
600 | * reverses the map lookup so that we can look for specific keys
601 | * to see what can and can't use keypress
602 | *
603 | * @return {Object}
604 | */
605 | function _getReverseMap() {
606 | if (!_REVERSE_MAP) {
607 | _REVERSE_MAP = {};
608 | for (var key in _MAP) {
609 |
610 | // pull out the numeric keypad from here cause keypress should
611 | // be able to detect the keys from the character
612 | if (key > 95 && key < 112) {
613 | continue;
614 | }
615 |
616 | if (_MAP.hasOwnProperty(key)) {
617 | _REVERSE_MAP[_MAP[key]] = key;
618 | }
619 | }
620 | }
621 | return _REVERSE_MAP;
622 | }
623 |
624 | /**
625 | * picks the best action based on the key combination
626 | *
627 | * @param {string} key - character for key
628 | * @param {Array} modifiers
629 | * @param {string=} action passed in
630 | */
631 | function _pickBestAction(key, modifiers, action) {
632 |
633 | // if no action was picked in we should try to pick the one
634 | // that we think would work best for this key
635 | if (!action) {
636 | action = _getReverseMap()[key] ? 'keydown' : 'keypress';
637 | }
638 |
639 | // modifier keys don't work as expected with keypress,
640 | // switch to keydown
641 | if (action == 'keypress' && modifiers.length) {
642 | action = 'keydown';
643 | }
644 |
645 | return action;
646 | }
647 |
648 | /**
649 | * binds a key sequence to an event
650 | *
651 | * @param {string} combo - combo specified in bind call
652 | * @param {Array} keys
653 | * @param {Function} callback
654 | * @param {string=} action
655 | * @returns void
656 | */
657 | function _bindSequence(combo, keys, callback, action) {
658 |
659 | // start off by adding a sequence level record for this combination
660 | // and setting the level to 0
661 | _sequenceLevels[combo] = 0;
662 |
663 | /**
664 | * callback to increase the sequence level for this sequence and reset
665 | * all other sequences that were active
666 | *
667 | * @param {string} nextAction
668 | * @returns {Function}
669 | */
670 | function _increaseSequence(nextAction) {
671 | return function() {
672 | _nextExpectedAction = nextAction;
673 | ++_sequenceLevels[combo];
674 | _resetSequenceTimer();
675 | };
676 | }
677 |
678 | /**
679 | * wraps the specified callback inside of another function in order
680 | * to reset all sequence counters as soon as this sequence is done
681 | *
682 | * @param {Event} e
683 | * @returns void
684 | */
685 | function _callbackAndReset(e) {
686 | _fireCallback(callback, e, combo);
687 |
688 | // we should ignore the next key up if the action is key down
689 | // or keypress. this is so if you finish a sequence and
690 | // release the key the final key will not trigger a keyup
691 | if (action !== 'keyup') {
692 | _ignoreNextKeyup = _characterFromEvent(e);
693 | }
694 |
695 | // weird race condition if a sequence ends with the key
696 | // another sequence begins with
697 | setTimeout(_resetSequences, 10);
698 | }
699 |
700 | // loop through keys one at a time and bind the appropriate callback
701 | // function. for any key leading up to the final one it should
702 | // increase the sequence. after the final, it should reset all sequences
703 | //
704 | // if an action is specified in the original bind call then that will
705 | // be used throughout. otherwise we will pass the action that the
706 | // next key in the sequence should match. this allows a sequence
707 | // to mix and match keypress and keydown events depending on which
708 | // ones are better suited to the key provided
709 | for (var i = 0; i < keys.length; ++i) {
710 | var isFinal = i + 1 === keys.length;
711 | var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
712 | _bindSingle(keys[i], wrappedCallback, action, combo, i);
713 | }
714 | }
715 |
716 | /**
717 | * Converts from a string key combination to an array
718 | *
719 | * @param {string} combination like "command+shift+l"
720 | * @return {Array}
721 | */
722 | function _keysFromString(combination) {
723 | if (combination === '+') {
724 | return ['+'];
725 | }
726 |
727 | return combination.split('+');
728 | }
729 |
730 | /**
731 | * Gets info for a specific key combination
732 | *
733 | * @param {string} combination key combination ("command+s" or "a" or "*")
734 | * @param {string=} action
735 | * @returns {Object}
736 | */
737 | function _getKeyInfo(combination, action) {
738 | var keys,
739 | key,
740 | i,
741 | modifiers = [];
742 |
743 | // take the keys from this pattern and figure out what the actual
744 | // pattern is all about
745 | keys = _keysFromString(combination);
746 |
747 | for (i = 0; i < keys.length; ++i) {
748 | key = keys[i];
749 |
750 | // normalize key names
751 | if (_SPECIAL_ALIASES[key]) {
752 | key = _SPECIAL_ALIASES[key];
753 | }
754 |
755 | // if this is not a keypress event then we should
756 | // be smart about using shift keys
757 | // this will only work for US keyboards however
758 | if (action && action != 'keypress' && _SHIFT_MAP[key]) {
759 | key = _SHIFT_MAP[key];
760 | modifiers.push('shift');
761 | }
762 |
763 | // if this key is a modifier then add it to the list of modifiers
764 | if (_isModifier(key)) {
765 | modifiers.push(key);
766 | }
767 | }
768 |
769 | // depending on what the key combination is
770 | // we will try to pick the best event for it
771 | action = _pickBestAction(key, modifiers, action);
772 |
773 | return {
774 | key: key,
775 | modifiers: modifiers,
776 | action: action
777 | };
778 | }
779 |
780 | /**
781 | * binds a single keyboard combination
782 | *
783 | * @param {string} combination
784 | * @param {Function} callback
785 | * @param {string=} action
786 | * @param {string=} sequenceName - name of sequence if part of sequence
787 | * @param {number=} level - what part of the sequence the command is
788 | * @returns void
789 | */
790 | function _bindSingle(combination, callback, action, sequenceName, level) {
791 |
792 | // store a direct mapped reference for use with Mousetrap.trigger
793 | _directMap[combination + ':' + action] = callback;
794 |
795 | // make sure multiple spaces in a row become a single space
796 | combination = combination.replace(/\s+/g, ' ');
797 |
798 | var sequence = combination.split(' '),
799 | info;
800 |
801 | // if this pattern is a sequence of keys then run through this method
802 | // to reprocess each pattern one key at a time
803 | if (sequence.length > 1) {
804 | _bindSequence(combination, sequence, callback, action);
805 | return;
806 | }
807 |
808 | info = _getKeyInfo(combination, action);
809 |
810 | // make sure to initialize array if this is the first time
811 | // a callback is added for this key
812 | _callbacks[info.key] = _callbacks[info.key] || [];
813 |
814 | // remove an existing match if there is one
815 | _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
816 |
817 | // add this call back to the array
818 | // if it is a sequence put it at the beginning
819 | // if not put it at the end
820 | //
821 | // this is important because the way these are processed expects
822 | // the sequence ones to come first
823 | _callbacks[info.key][sequenceName ? 'unshift' : 'push']({
824 | callback: callback,
825 | modifiers: info.modifiers,
826 | action: info.action,
827 | seq: sequenceName,
828 | level: level,
829 | combo: combination
830 | });
831 | }
832 |
833 | /**
834 | * binds multiple combinations to the same callback
835 | *
836 | * @param {Array} combinations
837 | * @param {Function} callback
838 | * @param {string|undefined} action
839 | * @returns void
840 | */
841 | function _bindMultiple(combinations, callback, action) {
842 | for (var i = 0; i < combinations.length; ++i) {
843 | _bindSingle(combinations[i], callback, action);
844 | }
845 | }
846 |
847 | // start!
848 | _addEvent(document, 'keypress', _handleKeyEvent);
849 | _addEvent(document, 'keydown', _handleKeyEvent);
850 | _addEvent(document, 'keyup', _handleKeyEvent);
851 |
852 | var Mousetrap = {
853 |
854 | /**
855 | * binds an event to mousetrap
856 | *
857 | * can be a single key, a combination of keys separated with +,
858 | * an array of keys, or a sequence of keys separated by spaces
859 | *
860 | * be sure to list the modifier keys first to make sure that the
861 | * correct key ends up getting bound (the last key in the pattern)
862 | *
863 | * @param {string|Array} keys
864 | * @param {Function} callback
865 | * @param {string=} action - 'keypress', 'keydown', or 'keyup'
866 | * @returns void
867 | */
868 | bind: function(keys, callback, action) {
869 | keys = keys instanceof Array ? keys : [keys];
870 | _bindMultiple(keys, callback, action);
871 | return this;
872 | },
873 |
874 | /**
875 | * unbinds an event to mousetrap
876 | *
877 | * the unbinding sets the callback function of the specified key combo
878 | * to an empty function and deletes the corresponding key in the
879 | * _directMap dict.
880 | *
881 | * TODO: actually remove this from the _callbacks dictionary instead
882 | * of binding an empty function
883 | *
884 | * the keycombo+action has to be exactly the same as
885 | * it was defined in the bind method
886 | *
887 | * @param {string|Array} keys
888 | * @param {string} action
889 | * @returns void
890 | */
891 | unbind: function(keys, action) {
892 | return Mousetrap.bind(keys, function() {}, action);
893 | },
894 |
895 | /**
896 | * triggers an event that has already been bound
897 | *
898 | * @param {string} keys
899 | * @param {string=} action
900 | * @returns void
901 | */
902 | trigger: function(keys, action) {
903 | if (_directMap[keys + ':' + action]) {
904 | _directMap[keys + ':' + action]({}, keys);
905 | }
906 | return this;
907 | },
908 |
909 | /**
910 | * resets the library back to its initial state. this is useful
911 | * if you want to clear out the current keyboard shortcuts and bind
912 | * new ones - for example if you switch to another page
913 | *
914 | * @returns void
915 | */
916 | reset: function() {
917 | _callbacks = {};
918 | _directMap = {};
919 | return this;
920 | },
921 |
922 | /**
923 | * should we stop this event before firing off callbacks
924 | *
925 | * @param {Event} e
926 | * @param {Element} element
927 | * @return {boolean}
928 | */
929 | stopCallback: function(e, element) {
930 |
931 | // if the element has the class "mousetrap" then no need to stop
932 | if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
933 | return false;
934 | }
935 |
936 | // stop for input, select, and textarea
937 | return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
938 | },
939 |
940 | /**
941 | * exposes _handleKey publicly so it can be overwritten by extensions
942 | */
943 | handleKey: _handleKey
944 | };
945 |
946 | // expose mousetrap to the global object
947 | window.Mousetrap = Mousetrap;
948 |
949 | // expose mousetrap as an AMD module
950 | if (typeof define === 'function' && define.amd) {
951 | define(Mousetrap);
952 | }
953 | }) (window, document);
954 |
--------------------------------------------------------------------------------
/app/js/lib/mousetrap.min.js:
--------------------------------------------------------------------------------
1 | /* mousetrap v1.4.6 craig.is/killing/mice */
2 | (function(J,r,f){function s(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent("on"+b,d)}function A(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return h[a.which]?h[a.which]:B[a.which]?B[a.which]:String.fromCharCode(a.which).toLowerCase()}function t(a){a=a||{};var b=!1,d;for(d in n)a[d]?b=!0:n[d]=0;b||(u=!1)}function C(a,b,d,c,e,v){var g,k,f=[],h=d.type;if(!l[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(g=0;gg||h.hasOwnProperty(g)&&(p[h[g]]=g)}e=p[d]?"keydown":"keypress"}"keypress"==e&&f.length&&(e="keydown");return{key:c,modifiers:f,action:e}}function F(a,b,d,c,e){q[a+":"+d]=b;a=a.replace(/\s+/g," ");var f=a.split(" ");1":".","?":"/","|":"\\"},G={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p,l={},q={},n={},D,z=!1,I=!1,u=!1;for(f=1;20>f;++f)h[111+f]="f"+f;for(f=0;9>=f;++f)h[f+96]=f;s(r,"keypress",y);s(r,"keydown",y);s(r,"keyup",y);var m={bind:function(a,b,d){a=a instanceof Array?a:[a];for(var c=0;cc.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:12,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};"object"===typeof module&&(module.exports=Stats);
7 |
--------------------------------------------------------------------------------
/app/js/src/App.js:
--------------------------------------------------------------------------------
1 | var App = function() {
2 |
3 | var _params, _gui, _guiFields, _engine;
4 | var _customUpdate;
5 |
6 | var _currShape; // to display in GUI on init
7 |
8 |
9 | // ENGINE PRESETS
10 |
11 | var BasicPreset = function() {
12 | this.size = Utils.isMobile ? 128 : 512;
13 | var _drawMat = this.drawMat = createShaderMaterial(BasicParticleShader);
14 | var _color = new THREE.Color(1.0, 0.5, 0.3);
15 | var _alpha = 0.3;
16 | this.update = function(dt, t) {
17 | _color.offsetHSL(0.1*dt, 0.0, 0.0);
18 | _drawMat.uniforms.uColor.value.set(_color.r, _color.g, _color.b, _alpha);
19 | };
20 | };
21 |
22 | var Preset = function() {
23 | this.size = 512;
24 | this.simMat = createShaderMaterial(SimShader);
25 | var _drawMat = this.drawMat = createShaderMaterial(ParticleShader);
26 | this.update = function(dt, t) {
27 | _drawMat.uniforms.uTime.value = t; // for ParticleShader.vs
28 | if (_customUpdate) _customUpdate(dt, t);
29 | };
30 | };
31 |
32 |
33 | // SHAPE PRESETS
34 |
35 | var _DEFAULT_SHAPE = "galaxy";
36 |
37 | var _presetShapes = {
38 | "none": "SIM_NO_SHAPE",
39 | "plane": "SIM_PLANE",
40 | "cube": "SIM_CUBE",
41 | "disc": "SIM_DISC",
42 | "sphere": "SIM_SPHERE",
43 | "ball": "SIM_BALL",
44 | "petals": "SIM_ROSE_GALAXY",
45 | "galaxy": "SIM_GALAXY",
46 | "noise": "SIM_NOISE",
47 | "object": "SIM_TEXTURE",
48 | };
49 |
50 | var _switchShape = function(name) {
51 | if (!_presetShapes[name]) return;
52 |
53 | Object.keys(_presetShapes).forEach(function(k) {
54 | delete _params.simMat.defines[_presetShapes[k]];
55 | });
56 | _params.simMat.defines[_presetShapes[name]] = "";
57 | _params.simMat.needsUpdate = true;
58 |
59 | _currShape = name;
60 | };
61 |
62 |
63 | // FUNCTIONS
64 |
65 | var _takeScreenshot = function() {
66 | _engine.renderer.getImageData(function(dataUrl) {
67 | var url = Utils.dataUrlToBlobUrl(dataUrl);
68 | Utils.openUrlInNewWindow(url, window.innerWidth, window.innerHeight);
69 | });
70 | };
71 |
72 |
73 | // INIT FUNCTIONS
74 |
75 | var _init = function() {
76 | // choose preset
77 | var preset = Utils.isMobile ? BasicPreset : Preset;
78 | _params = new preset();
79 |
80 | // routing, configure params
81 | var routeName = window.location.hash.length > 0 ?
82 | window.location.hash.substr(1) : "";
83 | console.log("route: " + routeName);
84 |
85 | if (!_presetShapes[routeName]) {
86 | window.location.hash = ""; // fix address bar
87 | routeName = _DEFAULT_SHAPE; // default shape
88 | }
89 |
90 | if (!Utils.isMobile) _switchShape(routeName);
91 |
92 | // init engine, with params
93 | _engine = new ParticleEngine(_params);
94 | };
95 |
96 | var _initUI = function() {
97 | _gui = new dat.GUI();
98 | _guiFields = {
99 | "color1": [_params.drawMat.uniforms.uColor1.value.x*255, _params.drawMat.uniforms.uColor1.value.y*255, _params.drawMat.uniforms.uColor1.value.z*255],
100 | "color2": [_params.drawMat.uniforms.uColor2.value.x*255, _params.drawMat.uniforms.uColor2.value.y*255, _params.drawMat.uniforms.uColor2.value.z*255],
101 | "alpha": _params.drawMat.uniforms.uAlpha.value,
102 | "color speed": _params.drawMat.uniforms.uColorSpeed.value,
103 | "color freq": _params.drawMat.uniforms.uColorFreq.value,
104 | "point size": _params.drawMat.uniforms.uPointSize.value,
105 | "user gravity": _params.simMat.uniforms.uInputAccel.value,
106 | "shape gravity": _params.simMat.uniforms.uShapeAccel.value,
107 | "shape": _currShape,
108 | "paused": false,
109 | "camera rotate": true,
110 | "camera control": false,
111 | "screenshot": _takeScreenshot,
112 | };
113 |
114 | _gui.addColor(_guiFields, "color1").onChange(function(value) {
115 | if (value[0] === "#") value = Utils.hexToRgb(value);
116 | _params.drawMat.uniforms.uColor1.value.x = value[0] / 255.0;
117 | _params.drawMat.uniforms.uColor1.value.y = value[1] / 255.0;
118 | _params.drawMat.uniforms.uColor1.value.z = value[2] / 255.0;
119 | });
120 | _gui.addColor(_guiFields, "color2").onChange(function(value) {
121 | if (value[0] === "#") value = Utils.hexToRgb(value);
122 | _params.drawMat.uniforms.uColor2.value.x = value[0] / 255.0;
123 | _params.drawMat.uniforms.uColor2.value.y = value[1] / 255.0;
124 | _params.drawMat.uniforms.uColor2.value.z = value[2] / 255.0;
125 | });
126 | _gui.add(_guiFields, "alpha", 0, 1).onChange(function(value) {
127 | _params.drawMat.uniforms.uAlpha.value = value;
128 | });
129 | _gui.add(_guiFields, "color speed", -10, 10).onChange(function(value) {
130 | _params.drawMat.uniforms.uColorSpeed.value = value;
131 | });
132 | _gui.add(_guiFields, "color freq", 0, 5).onChange(function(value) {
133 | _params.drawMat.uniforms.uColorFreq.value = value;
134 | });
135 | _gui.add(_guiFields, "point size", 1, 10).onChange(function(value) {
136 | _params.drawMat.uniforms.uPointSize.value = value;
137 | });
138 | _gui.add(_guiFields, "user gravity", 0, 10).onChange(function(value) {
139 | _params.simMat.uniforms.uInputAccel.value = value;
140 | });
141 | _gui.add(_guiFields, "shape gravity", 0, 10).onChange(function(value) {
142 | _params.simMat.uniforms.uShapeAccel.value = value;
143 | });
144 | _gui.add(_guiFields, "shape", Object.keys(_presetShapes))
145 | .onFinishChange(_switchShape);
146 | _gui.add(_guiFields, "paused").onChange(function(value) {
147 | _engine.pauseSimulation(value);
148 | }).listen();
149 | _gui.add(_guiFields, "camera rotate").onChange(function(value) {
150 | _engine.enableCameraAutoRotate(value);
151 | });
152 | _gui.add(_guiFields, "camera control").onChange(function(value) {
153 | _engine.enableCameraControl(value);
154 | }).listen();
155 | _gui.add(_guiFields, "screenshot");
156 | };
157 |
158 | var _initKeyboard = function() {
159 |
160 | // pause simulation
161 | Mousetrap.bind("space", function() {
162 | _guiFields.paused = !_guiFields.paused;
163 | _engine.pauseSimulation(_guiFields.paused);
164 | return false;
165 | });
166 |
167 | // mouse camera control
168 | Mousetrap.bind(["alt", "option"], function() {
169 | _guiFields["camera control"] = true;
170 | _engine.enableCameraControl(true);
171 | return false;
172 | }, "keydown");
173 | Mousetrap.bind(["alt", "option"], function() {
174 | _guiFields["camera control"] = false;
175 | _engine.enableCameraControl(false);
176 | return false;
177 | }, "keyup");
178 |
179 | };
180 |
181 |
182 | // RUN PROGRAM
183 |
184 | _init();
185 |
186 | if (!Utils.isMobile) {
187 | _initUI();
188 | _initKeyboard();
189 |
190 | // TODO_NOP TEST
191 |
192 | var uvAnim = new UVMapAnimator(_engine.renderer.getRenderer(), _params.size);
193 | _params.simMat.uniforms.tTarget = { type: "t", value: uvAnim.target };
194 | _customUpdate = uvAnim.update;
195 |
196 | var url = "models/wolf.json";
197 | var loader = new THREE.JSONLoader(true);
198 | loader.load(url, function(geometry) {
199 | var mesh = new THREE.MorphAnimMesh(geometry);
200 | var scale = 0.04;
201 | mesh.scale.set(scale,scale,scale);
202 | mesh.position.y -= 1;
203 | mesh.duration = 1000 / 0.1;
204 | uvAnim.setMesh(mesh);
205 | });
206 | }
207 |
208 | _engine.start();
209 | };
--------------------------------------------------------------------------------
/app/js/src/App2.js:
--------------------------------------------------------------------------------
1 | var App = function() {
2 | var _gui, _guiFields;
3 | var _engine;
4 | var _currPreset = Utils.getParameterByName("shape") || "galaxy"; // initial preset
5 | var _currSimMode;
6 | var _uvAnim;
7 | var _tourMode = false;
8 | var _musicElem = document.getElementById("music");
9 |
10 | // DEFINES
11 |
12 | var _params = {
13 | size: 512,
14 | simMat: createShaderMaterial(SimShader),
15 | drawMat: createShaderMaterial(ParticleShader),
16 | update: undefined, // defined later in the file
17 | };
18 |
19 | var _simModes = [
20 | "SIM_PLANE",
21 | "SIM_CUBE",
22 | "SIM_DISC",
23 | "SIM_SPHERE",
24 | "SIM_BALL",
25 | "SIM_ROSE_GALAXY",
26 | "SIM_GALAXY",
27 | "SIM_NOISE",
28 | "SIM_TEXTURE"
29 | ];
30 |
31 | // must have same name as preset, for async loading to work properly
32 | var _meshes = {
33 | bear: { scale:0.023, yOffset:-2.30, speed:0.05, url:"models/bear.json" },
34 | bison: { scale:0.020, yOffset:-2.00, speed:0.10, url:"models/bison.json" },
35 | // deer: { scale:0.040, yOffset:-2.00, speed:0.10, url:"models/deer.json" },
36 | // dog: { scale:0.040, yOffset:-1.65, speed:0.10, url:"models/retriever.json" },
37 | // fox: { scale:0.070, yOffset:-1.50, speed:0.10, url:"models/fox.json" },
38 | horse: { scale:0.022, yOffset:-2.30, speed:0.08, url:"models/horse.json" },
39 | panther: { scale:0.030, yOffset:-1.70, speed:0.10, url:"models/panther.json" },
40 | // rabbit: { scale:0.040, yOffset:-1.00, speed:0.05, url:"models/rabbit.json" },
41 | wolf: { scale:0.040, yOffset:-1.70, speed:0.10, url:"models/wolf.json" },
42 | };
43 |
44 | var _presets = {
45 | "none": { "user gravity":3, "shape gravity":1, _shape:"" },
46 | // "noise": { "user gravity":3, "shape gravity":1, _shape:"SIM_NOISE" },
47 | "plane": { "user gravity":4, "shape gravity":3, _shape:"SIM_PLANE" },
48 | "sphere": { "user gravity":4, "shape gravity":3, _shape:"SIM_SPHERE" },
49 | "galaxy": { "user gravity":3, "shape gravity":1, _shape:"SIM_GALAXY" },
50 | "petals": { "user gravity":3, "shape gravity":2, _shape:"SIM_ROSE_GALAXY" },
51 | "bear": { "user gravity":3, "shape gravity":5, _shape:_meshes.bear },
52 | "bison": { "user gravity":3, "shape gravity":5, _shape:_meshes.bison },
53 | // "deer": { "user gravity":3, "shape gravity":5, _shape:_meshes.deer },
54 | // "dog": { "user gravity":3, "shape gravity":5, _shape:_meshes.dog },
55 | // "fox": { "user gravity":3, "shape gravity":5, _shape:_meshes.fox },
56 | "horse": { "user gravity":3, "shape gravity":5, _shape:_meshes.horse },
57 | "panther": { "user gravity":3, "shape gravity":5, _shape:_meshes.panther },
58 | // "rabbit": { "user gravity":3, "shape gravity":5, _shape:_meshes.rabbit },
59 | "wolf": { "user gravity":3, "shape gravity":5, _shape:_meshes.wolf },
60 | };
61 |
62 |
63 |
64 | // FUNCTIONS
65 |
66 | var _setSimMode = function(name) {
67 | if (name === _currSimMode)
68 | return;
69 | _currSimMode = name; // cache mode, prevent shader recompile
70 |
71 | _simModes.forEach(function(s) {
72 | delete _params.simMat.defines[s];
73 | });
74 | if (name)
75 | _params.simMat.defines[name] = "";
76 | _params.simMat.needsUpdate = true;
77 | };
78 |
79 | var _setPreset = function(name) {
80 | var preset = _presets[name] || _presets.none;
81 | _currPreset = name;
82 |
83 | // set shape
84 | if (preset._shape.length >= 0) {
85 | _setSimMode(preset._shape);
86 | _uvAnim.setMesh(); // set no mesh
87 | }
88 | else {
89 | _setSimMode("SIM_TEXTURE");
90 | _uvAnim.setMesh(preset._shape.mesh);
91 | }
92 |
93 | _guiFields["user gravity"] = _params.simMat.uniforms.uInputAccel.value = preset["user gravity"];
94 | _guiFields["shape gravity"] = _params.simMat.uniforms.uShapeAccel.value = preset["shape gravity"];
95 | };
96 |
97 | var _takeScreenshot = function() {
98 | _engine.renderer.getImageData(function(dataUrl) {
99 | var url = Utils.dataUrlToBlobUrl(dataUrl);
100 | Utils.openUrlInNewWindow(url, window.innerWidth, window.innerHeight);
101 | });
102 | };
103 |
104 | var _toggleMusic = function() {
105 | if (_musicElem.paused)
106 | _musicElem.play();
107 | else
108 | _musicElem.pause();
109 | };
110 |
111 |
112 |
113 | // UPDATE
114 |
115 | var _update = _params.update = function(dt,t) {
116 | _params.drawMat.uniforms.uTime.value = t; // for ParticleShader.vs
117 | _uvAnim.update(dt,t);
118 | if(_tourMode) _tourUpdate(dt,t);
119 | };
120 |
121 | var _tourUpdate = (function() {
122 | var SHAPE_DURATION = 25.0;
123 | var BETWEEN_DURATION = 15.0;
124 | var BETWEEN_PRESET = "galaxy";
125 | var sequence = ["wolf", "petals", "bear", "sphere", "horse", "panther", "plane", "bison"];
126 | var timer = 0.0;
127 | var seqIdx = 0;
128 | var seqName;
129 |
130 | return function(dt,t) {
131 | if (timer <= 0.0) {
132 | // check against sequence
133 | // if user navigate to different preset
134 | // next tour trigger will go into shape instead of the between
135 | if (_currPreset === seqName) {
136 | _setPreset(BETWEEN_PRESET);
137 | _guiFields.shape = BETWEEN_PRESET;
138 | _gui.__controllers[0].updateDisplay(); // HARDCODE: controller idx
139 | timer = BETWEEN_DURATION;
140 | }
141 | else {
142 | // sequence finished
143 | if (seqIdx >= sequence.length) {
144 | seqIdx = 0;
145 | Utils.shuffle(sequence);
146 | console.log("tour shuffled: " + sequence);
147 | }
148 | seqName = sequence[seqIdx++];
149 | _setPreset(seqName);
150 | _guiFields.shape = seqName;
151 | _gui.__controllers[0].updateDisplay();
152 | console.log("tour: "+seqName);
153 | timer = SHAPE_DURATION;
154 | }
155 | }
156 |
157 | timer -= dt;
158 | };
159 | })();
160 |
161 |
162 |
163 | // INIT
164 |
165 | var _init = function() {
166 | _engine = new ParticleEngine(_params);
167 |
168 | _uvAnim = new UVMapAnimator(_engine.renderer.getRenderer(), _params.size);
169 | _params.simMat.uniforms.tTarget = { type: "t", value: _uvAnim.target };
170 | };
171 |
172 | var _initUI = function() {
173 | _gui = new dat.GUI();
174 | _guiFields = {
175 | "color1": [_params.drawMat.uniforms.uColor1.value.x*255, _params.drawMat.uniforms.uColor1.value.y*255, _params.drawMat.uniforms.uColor1.value.z*255],
176 | "color2": [_params.drawMat.uniforms.uColor2.value.x*255, _params.drawMat.uniforms.uColor2.value.y*255, _params.drawMat.uniforms.uColor2.value.z*255],
177 | "alpha": _params.drawMat.uniforms.uAlpha.value,
178 | "color speed": _params.drawMat.uniforms.uColorSpeed.value,
179 | "color freq": _params.drawMat.uniforms.uColorFreq.value,
180 | "point size": _params.drawMat.uniforms.uPointSize.value,
181 | "user gravity": _params.simMat.uniforms.uInputAccel.value,
182 | "shape gravity": _params.simMat.uniforms.uShapeAccel.value,
183 | "shape": _currPreset,
184 | "paused": false,
185 | "camera rotate": true,
186 | "camera control": false,
187 | "screenshot": _takeScreenshot,
188 | "fullscreen": Utils.toggleFullscreen,
189 | "take tour!": _tourMode,
190 | "music": true,
191 | };
192 |
193 | _gui.add(_guiFields, "shape", Object.keys(_presets))
194 | .onFinishChange(_setPreset);
195 | _gui.add(_guiFields, "take tour!").onChange(function(value) {
196 | _tourMode = value;
197 | });
198 | _gui.add(_guiFields, "music").onChange(function(value) {
199 | _toggleMusic();
200 | });
201 |
202 | var fAppearance = _gui.addFolder("Appearance");
203 | fAppearance.addColor(_guiFields, "color1").onChange(function(value) {
204 | if (value[0] === "#") value = Utils.hexToRgb(value);
205 | _params.drawMat.uniforms.uColor1.value.x = value[0] / 255.0;
206 | _params.drawMat.uniforms.uColor1.value.y = value[1] / 255.0;
207 | _params.drawMat.uniforms.uColor1.value.z = value[2] / 255.0;
208 | });
209 | fAppearance.addColor(_guiFields, "color2").onChange(function(value) {
210 | if (value[0] === "#") value = Utils.hexToRgb(value);
211 | _params.drawMat.uniforms.uColor2.value.x = value[0] / 255.0;
212 | _params.drawMat.uniforms.uColor2.value.y = value[1] / 255.0;
213 | _params.drawMat.uniforms.uColor2.value.z = value[2] / 255.0;
214 | });
215 | fAppearance.add(_guiFields, "alpha", 0, 1).onChange(function(value) {
216 | _params.drawMat.uniforms.uAlpha.value = value;
217 | });
218 | fAppearance.add(_guiFields, "color speed", -10, 10).onChange(function(value) {
219 | _params.drawMat.uniforms.uColorSpeed.value = value;
220 | });
221 | fAppearance.add(_guiFields, "color freq", 0, 5).onChange(function(value) {
222 | _params.drawMat.uniforms.uColorFreq.value = value;
223 | });
224 | fAppearance.add(_guiFields, "point size", 1, 10).onChange(function(value) {
225 | _params.drawMat.uniforms.uPointSize.value = value;
226 | });
227 |
228 | var fPhysics = _gui.addFolder("Physics");
229 | fPhysics.add(_guiFields, "user gravity", 0, 10)
230 | .listen()
231 | .onChange(function(value) {
232 | _params.simMat.uniforms.uInputAccel.value = value;
233 | });
234 | fPhysics.add(_guiFields, "shape gravity", 0, 10)
235 | .listen()
236 | .onChange(function(value) {
237 | _params.simMat.uniforms.uShapeAccel.value = value;
238 | });
239 |
240 | var fControls = _gui.addFolder("Controls");
241 | fControls.add(_guiFields, "paused").onChange(function(value) {
242 | _engine.pauseSimulation(value);
243 | }).listen();
244 | fControls.add(_guiFields, "camera rotate").onChange(function(value) {
245 | _engine.enableCameraAutoRotate(value);
246 | });
247 | fControls.add(_guiFields, "camera control").onChange(function(value) {
248 | _engine.enableCameraControl(value);
249 | }).listen();
250 |
251 | _gui.add(_guiFields, "screenshot");
252 | _gui.add(_guiFields, "fullscreen");
253 | };
254 |
255 | var _initKeyboard = function() {
256 |
257 | // pause simulation
258 | Mousetrap.bind("space", function() {
259 | _guiFields.paused = !_guiFields.paused;
260 | _engine.pauseSimulation(_guiFields.paused);
261 | return false;
262 | });
263 |
264 | // mouse camera control
265 | Mousetrap.bind(["alt", "option"], function() {
266 | _guiFields["camera control"] = true;
267 | _engine.enableCameraControl(true);
268 | return false;
269 | }, "keydown");
270 | Mousetrap.bind(["alt", "option"], function() {
271 | _guiFields["camera control"] = false;
272 | _engine.enableCameraControl(false);
273 | return false;
274 | }, "keyup");
275 |
276 | };
277 |
278 | var _loadMeshes = function() {
279 | var loader = new THREE.JSONLoader(true);
280 | Object.keys(_meshes).forEach(function(k) {
281 | loader.load(_meshes[k].url, function(geometry) {
282 | var mesh = new THREE.MorphAnimMesh(geometry); // no material
283 | mesh.scale.set(_meshes[k].scale,_meshes[k].scale,_meshes[k].scale);
284 | mesh.position.y = _meshes[k].yOffset;
285 | mesh.duration = 1000 / _meshes[k].speed;
286 | mesh.name = k; // for debugging
287 | _meshes[k].mesh = mesh;
288 |
289 | // refresh mesh if same name as preset
290 | if (_currPreset === k)
291 | _uvAnim.setMesh(mesh);
292 | });
293 | });
294 | };
295 |
296 |
297 |
298 | // RUN PROGRAM
299 |
300 | _loadMeshes();
301 | _init();
302 | _initUI();
303 | _initKeyboard();
304 | _setPreset(_currPreset);
305 | _engine.start();
306 |
307 | };
--------------------------------------------------------------------------------
/app/js/src/LeapManager.js:
--------------------------------------------------------------------------------
1 | var LeapManager = function(renderer, camera, transform) {
2 | var _scene, _root, _controller;
3 |
4 | var MAX_HANDS = 4; // apparently leap can have more than 2 hands
5 | // more than MAX_HANDS will draw, from plugin
6 | // but will not be taken into account
7 |
8 | this.renderer = renderer;
9 | this.camera = camera;
10 | this.matrix = transform;
11 |
12 |
13 | // PUBLIC
14 |
15 | this.followCamera = true;
16 |
17 | // read-only
18 | this.frame = undefined;
19 | this.palmPositions = []; for (var i=0; i= _mouseStructs.length)
93 | _mouseStructs[idx] = new MouseStruct();
94 |
95 | return _mouseStructs[idx];
96 | };
97 |
98 |
99 | // BIND EVENTS
100 |
101 | dom.addEventListener("mousemove", _onMouseMove, false);
102 | dom.addEventListener("mousedown", _onMouseDown, false);
103 | dom.addEventListener("mouseup", _onMouseUp, false);
104 |
105 | dom.addEventListener("touchmove", _onTouchMove, false);
106 | dom.addEventListener("touchstart", _onTouchDown, false);
107 | dom.addEventListener("touchend", _onTouchUp, false);
108 | dom.addEventListener("touchleave", _onTouchUp, false);
109 | dom.addEventListener("touchcancel", _onTouchUp, false);
110 | };
--------------------------------------------------------------------------------
/app/js/src/ParticleEngine.js:
--------------------------------------------------------------------------------
1 | var ParticleEngine = function(params) {
2 |
3 | var _this = this;
4 |
5 | var _canvas, _stats;
6 | var _updateLoop;
7 | var _renderer, _camera, _scene;
8 | var _sim, _simMat, _initMat, _drawMat;
9 | var _mouse;
10 | var _controls, _raycaster;
11 | var _leapMan;
12 | var _customUpdate;
13 | var _pauseSim = false;
14 |
15 |
16 | // PARAMS
17 |
18 | params = params || {};
19 | _size = params.size || 512;
20 | _simMat = params.simMat || createShaderMaterial(BasicSimShader);
21 | _initMat = params.initMat || createShaderMaterial(SimInitShader);
22 | _drawMat = params.drawMat || createShaderMaterial(BasicParticleShader);
23 | _customUpdate = params.update;
24 |
25 |
26 | // EVENTS
27 |
28 | var _onWindowResize = function() {
29 | _renderer.setSize(window.innerWidth, window.innerHeight);
30 | };
31 |
32 | var _onFrameUpdate = function(dt, t) {
33 | _stats.begin();
34 |
35 | _leapUpdate();
36 |
37 | _inputUpdate();
38 |
39 | if (!_controls.enabled) _controls.update();
40 |
41 | if(_customUpdate) _customUpdate(dt, t);
42 |
43 | _renderer.update(dt);
44 | _leapMan.render();
45 |
46 | _stats.end();
47 | };
48 |
49 | var _onFixedUpdate = function(dt, t) {
50 | if (!_pauseSim) _sim.update(dt, t);
51 | };
52 |
53 |
54 | // PRIVATE FUNCTIONS
55 |
56 | var _init = function() {
57 | window.addEventListener("resize", _onWindowResize, false);
58 |
59 | _stats = new Stats();
60 | document.body.appendChild(_stats.domElement);
61 |
62 | _updateLoop = new UpdateLoop();
63 | _updateLoop.frameCallback = _onFrameUpdate;
64 | _updateLoop.fixedCallback = _onFixedUpdate;
65 |
66 | _canvas = document.querySelector("#webgl-canvas");
67 |
68 | _mouse = new Mouse(_canvas);
69 |
70 | _renderer = new RenderContext(_canvas);
71 | _renderer.init();
72 | _camera = _renderer.getCamera();
73 | _scene = _renderer.getScene();
74 | };
75 |
76 | var _sceneInit = function() {
77 | _sim = new ParticleSimulation(_renderer.getRenderer(), _size, {
78 | simMat: _simMat,
79 | initMat: _initMat,
80 | drawMat: _drawMat
81 | });
82 | _scene.add(_sim.getParticleObject());
83 |
84 | _camera.position.set(0,0,8);
85 | _controls = new THREE.OrbitControls(_camera, _canvas);
86 | _controls.rotateUp(Math.PI/6);
87 | _controls.autoRotate = true;
88 | _controls.autoRotateSpeed = 1.0;
89 | _controls.noPan = true;
90 | _controls.enabled = false; // disable user input
91 |
92 | _raycaster = new THREE.Raycaster();
93 |
94 | var tmat = (new THREE.Matrix4()).compose(
95 | new THREE.Vector3(0.0, -3.0, -_camera.position.z),
96 | new THREE.Quaternion(),
97 | new THREE.Vector3(0.015,0.015,0.015));
98 | _leapMan = new LeapManager(_renderer.getRenderer(), _camera, tmat);
99 | _simMat.defines.MULTIPLE_INPUT = ""; // TODO_NOP: at least even hardcode numbers for this in shader
100 | _simMat.needsUpdate = true;
101 |
102 | _debugBox = document.querySelector("#debug-box");
103 | };
104 |
105 | var _mouseUpdate = function() {
106 | var mIdMax = Utils.isMobile ? 4 : 1;
107 | for (var mId=0; mId"+_simMat.uniforms.uInputPosAccel.value.x.toFixed(2)
132 | // +" "+_simMat.uniforms.uInputPosAccel.value.y.toFixed(2)
133 | // +" "+_simMat.uniforms.uInputPosAccel.value.z.toFixed(2)
134 | // +" "+_simMat.uniforms.uInputPosAccel.value.w.toFixed(2);
135 | };
136 |
137 | var _leapUpdate = function() {
138 | var K_PULL = 1.0; // in grabStrength
139 | var K_PUSH = 100.0; // in sphereRadius
140 |
141 | _leapMan.update();
142 |
143 | for (var i=0; i<_leapMan.activeHandCount; i++) {
144 | var inputIdx = 3-i; // iterate backwards on input, so mouse can interact at same time
145 | if (_leapMan.frame.hands[i].grabStrength === K_PULL) {
146 | _simMat.uniforms.uInputPos.value[inputIdx].copy(_leapMan.palmPositions[i]);
147 | _simMat.uniforms.uInputPosAccel.value.setComponent(inputIdx, 1.0);
148 | }
149 | else if (_leapMan.frame.hands[i].sphereRadius >= K_PUSH) {
150 | _simMat.uniforms.uInputPos.value[inputIdx].copy(_leapMan.palmPositions[i]);
151 | _simMat.uniforms.uInputPosAccel.value.setComponent(inputIdx, -1.0);
152 | }
153 | }
154 |
155 | // _debugBox.innerHTML =
156 | // "hand1: " + (_leapMan.frame.hands[0] ? _leapMan.frame.hands[0].sphereRadius : "") + " " +
157 | // "hand2: " + (_leapMan.frame.hands[1] ? _leapMan.frame.hands[1].sphereRadius : "") +
158 | // "";
159 | };
160 |
161 | var _inputUpdate = function() {
162 | // reset input accels
163 | _simMat.uniforms.uInputPosAccel.value.set(0,0,0,0);
164 | if (!_controls.enabled) _mouseUpdate();
165 | _leapUpdate();
166 | };
167 |
168 |
169 | // PUBLIC FUNCTIONS
170 |
171 | this.start = function() {
172 | _updateLoop.start();
173 | };
174 |
175 | this.stop = function() {
176 | _updateLoop.stop();
177 | };
178 |
179 | this.pauseSimulation = function(value) {
180 | _pauseSim = value;
181 | };
182 |
183 | this.enableCameraAutoRotate = function(value) {
184 | _controls.autoRotate = value;
185 | };
186 |
187 | this.enableCameraControl = function(value) {
188 | _controls.enabled = value;
189 | };
190 |
191 |
192 | // INIT
193 |
194 | _init();
195 |
196 | _sceneInit();
197 |
198 | // expose variables
199 | this.renderer = _renderer;
200 | this.scene = _scene;
201 | this.camera = _camera;
202 | };
203 |
--------------------------------------------------------------------------------
/app/js/src/ParticleSimulation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * params = {
3 | * simMat: THREE.Material,
4 | * initMat: THREE.Material,
5 | * drawMat: THREE.Material
6 | * }
7 | */
8 | var ParticleSimulation = function(renderer, size, params) {
9 | var _this = this;
10 | var _size = size;
11 | var _sim, _simMat, _initMat, _drawMat, _particles;
12 |
13 | params = params || {};
14 |
15 | var _createParticleGeometry = function(size) {
16 | var ATTR_WIDTH = 3;
17 | var geo = new THREE.BufferGeometry();
18 | var pos = new Float32Array(size*size*ATTR_WIDTH);
19 | for (var x=0; x 3)
90 | _currUpdateTarget = 1;
91 | };
92 |
93 | this.registerUniform = function(uniform) {
94 | _registeredUniforms.push(uniform);
95 | uniform.value = _outTargetPtr;
96 | };
97 |
98 | this.reset = function() {
99 | _initPass.render(_renderer, _target1);
100 | _initPass.render(_renderer, _target2);
101 | _initPass.render(_renderer, _target3);
102 | };
103 |
104 | // INITIALIZATION
105 |
106 | _checkSupport();
107 |
108 | // init targets
109 |
110 | _target1 = _createTarget(_size);
111 | _target2 = _createTarget(_size);
112 | _target3 = _createTarget(_size);
113 |
114 | _target1.name = "SimulationRenderer._target1"; // debug tags
115 | _target2.name = "SimulationRenderer._target2";
116 | _target3.name = "SimulationRenderer._target3";
117 |
118 | _currUpdateTarget = 1;
119 | _outTargetPtr = _target1;
120 |
121 | // init shader pass
122 |
123 | _simPass = new ShaderPass(simMat);
124 | _initPass = new ShaderPass(initMat);
125 | // _debugPass = new ShaderPass(SimDebugShader);
126 | // _debugPass.material.uniforms.tTarget1.value = _target1;
127 | // _debugPass.material.uniforms.tTarget2.value = _target2;
128 | // _debugPass.material.uniforms.tTarget3.value = _target3;
129 |
130 | this.reset(); // reset targets
131 | };
132 |
133 | SimulationRenderer.prototype.constructor = SimulationRenderer;
--------------------------------------------------------------------------------
/app/js/src/UVMapAnimator.js:
--------------------------------------------------------------------------------
1 | var UVMapAnimator = function(renderer, size) {
2 | var _this = this;
3 | var _mesh;
4 | var _mapper = new UVMapper(renderer);
5 | var _speed = 1.0;
6 |
7 | this.target = _mapper.createTarget(size);
8 |
9 | this.update = function(dt, t) {
10 | if (!_mesh) return;
11 |
12 | _mesh.updateAnimation(dt * 1000);
13 | _mapper.render(_mesh, _this.target);
14 | };
15 |
16 | this.setMesh = function(mesh) {
17 | _mesh = mesh;
18 | };
19 | };
--------------------------------------------------------------------------------
/app/js/src/UVMapper.js:
--------------------------------------------------------------------------------
1 | var UVMapper = function(renderer) {
2 | var _renderer = renderer;
3 |
4 | var _camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); // not really used
5 | var _scene = new THREE.Scene();
6 |
7 | var _mat = createShaderMaterial(UVMapShader);
8 | _mat.side = THREE.DoubleSide;
9 | _mat.blending = THREE.NoBlending;
10 | _mat.depthTest = false;
11 | _mat.depthWrite = false;
12 | _mat.morphTargets = true;
13 | _scene.overrideMaterial = _mat;
14 |
15 | this.render = function(mesh, target) {
16 | // might need to make geo, so don't steal from its original parent
17 | _scene.add(mesh);
18 | _renderer.render(_scene, _camera, target, true);
19 | _scene.remove(mesh);
20 | };
21 |
22 | this.createTarget = function(size) {
23 | var target = new THREE.WebGLRenderTarget(size, size, {
24 | minFilter: THREE.LinearFilter,
25 | magFilter: THREE.LinearFilter,
26 | format: THREE.RGBAFormat,
27 | type: THREE.FloatType,
28 | depthBuffer: false,
29 | stencilBuffer: false
30 | });
31 | target.generateMipmaps = false;
32 |
33 | return target;
34 | };
35 |
36 | this.createMap = function(mesh, size) {
37 | var target = this.createTarget(size);
38 | this.render(mesh, target);
39 | return target;
40 | };
41 | };
--------------------------------------------------------------------------------
/app/js/src/UpdateLoop.js:
--------------------------------------------------------------------------------
1 | // TODO: timer sync issue between fixed and frame (?)
2 |
3 | var UpdateLoop = function() {
4 |
5 | var _this = this;
6 |
7 | var _timer = 0.0;
8 | var _timeScale = 1.0;
9 | var _fixedTimer = 0.0;
10 | var _fixedTimeRemainder = 0.0;
11 | var _FIXED_TIME_STEP = 0.02;
12 | var _FIXED_TIME_STEP_MAX = 0.2;
13 |
14 | var _clock = new Clock(false);
15 |
16 | var _requestId;
17 |
18 | // PRIVATE FUNCTIONS
19 |
20 | var _fixedUpdate = function() {
21 | var fixedDt = _FIXED_TIME_STEP * _timeScale;
22 |
23 | _fixedTimer += fixedDt;
24 |
25 | _this.fixedCallback(fixedDt, _fixedTimer);
26 | };
27 |
28 | var _frameUpdate = function() {
29 | var frameDt = _clock.getDelta();
30 |
31 | _timer += frameDt * _timeScale;
32 | _fixedTimeRemainder += frameDt;
33 |
34 | // cap remainder
35 | if (_fixedTimeRemainder > _FIXED_TIME_STEP_MAX) {
36 | _fixedTimeRemainder = _FIXED_TIME_STEP_MAX;
37 | }
38 |
39 | // loop remainder
40 | while (_fixedTimeRemainder > _FIXED_TIME_STEP) {
41 | _fixedUpdate();
42 | _fixedTimeRemainder -= _FIXED_TIME_STEP;
43 | }
44 |
45 | _this.frameCallback(frameDt, _timer);
46 | };
47 |
48 | var _loop = function() {
49 | _frameUpdate();
50 | _requestId = window.requestAnimationFrame(_loop);
51 | };
52 |
53 | // PUBLIC FUNCTIONS
54 |
55 | this.start = function() {
56 | if (!_requestId) {
57 | _clock.start();
58 | _loop();
59 | }
60 | };
61 |
62 | this.stop = function() {
63 | if (_requestId) {
64 | window.cancelAnimationFrame(_requestId);
65 | _requestId = undefined;
66 | _clock.stop();
67 | }
68 | };
69 |
70 | this.setTimeScale = function(scale) {
71 | _timeScale = Math.max(scale, 0);
72 | };
73 |
74 | // EMPTY FUNCTIONS TO SET
75 |
76 | this.fixedCallback = function(dt, t) {};
77 | this.frameCallback = function(dt, t) {};
78 |
79 | };
80 |
81 | UpdateLoop.prototype.constructor = UpdateLoop;
--------------------------------------------------------------------------------
/app/js/src/Utils.js:
--------------------------------------------------------------------------------
1 | var Utils = {};
2 |
3 | // pre-computed on load
4 | Utils.isMobile = (function(a) {
5 | return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4));
6 | })(navigator.userAgent||navigator.vendor||window.opera);
7 |
8 | Utils.toRadians = function(degrees) {
9 | return degrees * Math.PI / 180.0;
10 | };
11 |
12 | Utils.toDegrees = function(rads) {
13 | return rads * 180.0 / Math.PI;
14 | };
15 |
16 | Utils.hexToRgb = function(hex) {
17 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
18 | return result ? [
19 | parseInt(result[1], 16),
20 | parseInt(result[2], 16),
21 | parseInt(result[3], 16)
22 | ] : null;
23 | };
24 |
25 | Utils.rgbToHex = function() {
26 | return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
27 | };
28 |
29 | Utils.loadTextFile = function(url) {
30 | var result;
31 |
32 | var req = new XMLHttpRequest();
33 | req.onerror = function() {
34 | console.log("Error: request error on " + url);
35 | };
36 | req.onload = function() {
37 | result = this.responseText;
38 | };
39 | req.open("GET", url, false);
40 | req.send();
41 |
42 | return result;
43 | };
44 |
45 | Utils.loadTextFileInject = function(url) {
46 | var regex = /#inject .+/g;
47 | var fileStr = Utils.loadTextFile(url);
48 | var matches = fileStr.match(regex);
49 |
50 | if (!matches) return fileStr;
51 |
52 | for (var i=0; i
2 |
3 |
4 | Leap Test
5 |
6 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
141 |
142 |
--------------------------------------------------------------------------------
/app/shaders/Basic.vs.glsl:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 | void main() {
3 | vUv = uv;
4 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
5 | }
--------------------------------------------------------------------------------
/app/shaders/BasicParticleShader.fs.glsl:
--------------------------------------------------------------------------------
1 | uniform vec4 uColor;
2 |
3 | void main() {
4 | gl_FragColor = uColor;
5 | }
--------------------------------------------------------------------------------
/app/shaders/BasicParticleShader.vs.glsl:
--------------------------------------------------------------------------------
1 | uniform sampler2D tPos;
2 |
3 | void main() {
4 | gl_PointSize = POINT_SIZE;
5 | gl_Position = projectionMatrix * modelViewMatrix * vec4(texture2D(tPos, position.xy).rgb, 1.0);
6 | }
--------------------------------------------------------------------------------
/app/shaders/BasicSimShader.fs.glsl:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 |
3 | uniform sampler2D tPrev;
4 | uniform sampler2D tCurr;
5 | uniform float uDeltaT;
6 | uniform float uTime;
7 | uniform vec3 uInputPos[4];
8 | uniform vec4 uInputPosAccel;
9 |
10 | void main() {
11 |
12 | // read data
13 | vec3 prevPos = texture2D(tPrev, vUv).rgb;
14 | vec3 currPos = texture2D(tCurr, vUv).rgb;
15 | vec3 vel = (currPos - prevPos) / uDeltaT;
16 |
17 | // CALC ACCEL
18 |
19 | vec3 accel = vec3(0.0);
20 |
21 | // input pos
22 | #inject shaders/chunks/SimInputPos.glsl
23 |
24 | // state updates
25 | vel = K_VEL_DECAY * vel + accel * uDeltaT;
26 | currPos += vel * uDeltaT;
27 |
28 | // write out
29 | gl_FragColor = vec4(currPos, 1.0);
30 | }
--------------------------------------------------------------------------------
/app/shaders/ParticleShader.fs.glsl:
--------------------------------------------------------------------------------
1 | #inject shaders/chunks/Constants.glsl
2 |
3 |
4 | varying vec3 vColor;
5 |
6 | uniform float uTime;
7 | uniform float uAlpha;
8 |
9 | void main() {
10 |
11 | // calc alpha for shape
12 | vec2 tmpCoord = 0.5 * cos(M_2PI*gl_PointCoord+M_PI) + 0.5;
13 | float alpha = tmpCoord.x * tmpCoord.y;
14 |
15 | gl_FragColor = vec4(vColor, uAlpha*alpha);
16 | }
--------------------------------------------------------------------------------
/app/shaders/ParticleShader.vs.glsl:
--------------------------------------------------------------------------------
1 | #inject shaders/chunks/Constants.glsl
2 |
3 | #define PS_CAM_MAX_DIST 12.0
4 |
5 | varying vec3 vColor;
6 |
7 | uniform sampler2D tPos;
8 | uniform float uTime;
9 | uniform float uPointSize;
10 | uniform vec3 uColor1;
11 | uniform vec3 uColor2;
12 | uniform float uColorFreq;
13 | uniform float uColorSpeed;
14 |
15 | void main() {
16 | vColor = mix(uColor1, uColor2, sin(uColorSpeed*uTime + uColorFreq*position.z*M_2PI)/2.0+0.5);
17 |
18 | vec4 posSample = texture2D(tPos, position.xy);
19 | vec3 pos = posSample.rgb;
20 |
21 | vec3 camToPos = pos - cameraPosition;
22 | float camDist = length(camToPos);
23 |
24 | gl_PointSize = max(uPointSize * PS_CAM_MAX_DIST/camDist, 1.0);
25 |
26 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
27 | }
--------------------------------------------------------------------------------
/app/shaders/SimInitShader.fs.glsl:
--------------------------------------------------------------------------------
1 | #inject shaders/chunks/Constants.glsl
2 | #inject shaders/chunks/Rand.glsl
3 |
4 |
5 | varying vec2 vUv;
6 |
7 | uniform sampler2D tDiffuse;
8 | uniform vec4 uColor;
9 |
10 | void main() {
11 | // vec3 pos = vec3(vUv.x, vUv.y, rand(vUv));
12 |
13 | // square sheet
14 | // vec3 pos = vec3(vUv.x, vUv.y, 0.0);
15 | // vec3 center = vec3(0.0, 0.0, 0.0);
16 | // vec3 size = vec3(1.0, 1.0, 1.0);
17 | // pos = pos*size + center - size/2.0;
18 |
19 | // sphere, continuous along vUv.y
20 | // vec2 coords = vUv;
21 | // coords.x = coords.x * M_2PI - M_PI; // theta (lat)
22 | // coords.y = coords.y * M_PI; // phi (long)
23 | // vec3 sphereCoords = vec3(
24 | // sin(coords.y) * cos(coords.x),
25 | // cos(coords.y),
26 | // sin(coords.y) * sin(coords.x)
27 | // );
28 | // vec3 pos = sphereCoords * rand(vUv);
29 |
30 | // sphere coords, rand radius, offset y+0.5 for snoise vel
31 | vec2 coords = vUv;
32 | coords.x = coords.x * M_2PI - M_PI;
33 | coords.y = mod(coords.y+0.5, 1.0) * M_PI;
34 | vec3 sphereCoords = vec3(
35 | sin(coords.y) * cos(coords.x),
36 | cos(coords.y),
37 | sin(coords.y) * sin(coords.x)
38 | );
39 | vec3 pos = sphereCoords * rand(vUv);
40 | pos *= 5.0;
41 |
42 | gl_FragColor = vec4(pos, 1.0);
43 | }
--------------------------------------------------------------------------------
/app/shaders/SimShader.fs.glsl:
--------------------------------------------------------------------------------
1 | #inject shaders/chunks/Constants.glsl
2 | #inject shaders/chunks/Rand.glsl
3 | #inject shaders/chunks/NoiseFuncs.glsl
4 |
5 |
6 | varying vec2 vUv;
7 |
8 | uniform sampler2D tPrev;
9 | uniform sampler2D tCurr;
10 | uniform float uDeltaT;
11 | uniform float uTime;
12 | uniform vec3 uInputPos[4];
13 | uniform vec4 uInputPosAccel;
14 | uniform float uInputAccel;
15 | uniform float uShapeAccel;
16 |
17 | #ifdef SIM_TEXTURE
18 | uniform sampler2D tTarget;
19 | #endif
20 |
21 | void main() {
22 |
23 | // read data
24 | vec3 prevPos = texture2D(tPrev, vUv).rgb;
25 | vec3 currPos = texture2D(tCurr, vUv).rgb;
26 | vec3 vel = (currPos - prevPos) / uDeltaT;
27 |
28 | // CALC ACCEL
29 |
30 | vec3 accel = vec3(0.0);
31 |
32 | #inject shaders/chunks/SimBasicShapes.glsl
33 | #inject shaders/chunks/SimRoseGalaxy.glsl
34 | #inject shaders/chunks/SimGalaxy.glsl
35 | #inject shaders/chunks/SimTextureTarget.glsl
36 |
37 | #inject shaders/chunks/SimInputPos.glsl
38 |
39 | // state updates
40 | vel = K_VEL_DECAY * vel + accel * uDeltaT;
41 | currPos += vel * uDeltaT;
42 |
43 | // write out
44 | gl_FragColor = vec4(currPos, 1.0);
45 | }
--------------------------------------------------------------------------------
/app/shaders/UVMapShader.fs.glsl:
--------------------------------------------------------------------------------
1 | varying vec3 vPos;
2 |
3 | void main() {
4 | gl_FragColor = vec4(vPos, 1.0);
5 | }
--------------------------------------------------------------------------------
/app/shaders/UVMapShader.vs.glsl:
--------------------------------------------------------------------------------
1 | varying vec3 vPos;
2 |
3 | #ifdef USE_MORPHTARGETS
4 | uniform float morphTargetInfluences[ 4 ];
5 | #endif
6 |
7 | void main() {
8 |
9 | #ifdef USE_MORPHTARGETS
10 | vec3 morphed = vec3( 0.0 );
11 | morphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];
12 | morphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];
13 | morphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];
14 | morphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];
15 | morphed += position;
16 |
17 | vPos = (modelMatrix * vec4(morphed, 1.0)).xyz;
18 | #else
19 | vPos = (modelMatrix * vec4(position, 1.0)).xyz;
20 | #endif
21 |
22 | vec2 drawUV = uv * 2.0 - 1.0;
23 | gl_Position = vec4(drawUV.x, drawUV.y, 0.0, 1.0);
24 | }
--------------------------------------------------------------------------------
/app/shaders/chunks/Constants.glsl:
--------------------------------------------------------------------------------
1 | #define M_PI 3.14159265358979323846264338327950
2 | #define M_2PI 6.28318530717958647692528676655900
3 | #define M_PI2 1.57079632679489661923132169163975
4 |
5 | #define EPS 0.0001
6 |
7 | #define EQUALS(A,B) ( abs((A)-(B)) < EPS )
8 | #define EQUALSZERO(A) ( ((A)-EPS) )
9 |
--------------------------------------------------------------------------------
/app/shaders/chunks/NoiseFuncs.glsl:
--------------------------------------------------------------------------------
1 | /*
2 | * Description : Array and textureless GLSL 2D/3D/4D simplex
3 | * noise functions.
4 | * Author : Ian McEwan, Ashima Arts.
5 | * Maintainer : ijm
6 | * Lastmod : 20110822 (ijm)
7 | * License : Copyright (C) 2011 Ashima Arts. All rights reserved.
8 | * Distributed under the MIT License. See LICENSE file.
9 | * https://github.com/ashima/webgl-noise
10 | */
11 |
12 | vec4 _mod289(vec4 x)
13 | {
14 | return x - floor(x * (1.0 / 289.0)) * 289.0;
15 | }
16 |
17 | vec3 _mod289(vec3 x)
18 | {
19 | return x - floor(x * (1.0 / 289.0)) * 289.0;
20 | }
21 |
22 | vec2 _mod289(vec2 x)
23 | {
24 | return x - floor(x * (1.0 / 289.0)) * 289.0;
25 | }
26 |
27 | float _mod289(float x)
28 | {
29 | return x - floor(x * (1.0 / 289.0)) * 289.0;
30 | }
31 |
32 | vec4 _permute(vec4 x)
33 | {
34 | return _mod289(((x*34.0)+1.0)*x);
35 | }
36 |
37 | vec3 _permute(vec3 x)
38 | {
39 | return _mod289(((x*34.0)+1.0)*x);
40 | }
41 |
42 | float _permute(float x)
43 | {
44 | return _mod289(((x*34.0)+1.0)*x);
45 | }
46 |
47 | vec4 _taylorInvSqrt(vec4 r)
48 | {
49 | return 1.79284291400159 - 0.85373472095314 * r;
50 | }
51 |
52 | float _taylorInvSqrt(float r)
53 | {
54 | return 1.79284291400159 - 0.85373472095314 * r;
55 | }
56 |
57 | vec4 _grad4(float j, vec4 ip)
58 | {
59 | const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
60 | vec4 p,s;
61 |
62 | p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
63 | p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
64 | s = vec4(lessThan(p, vec4(0.0)));
65 | p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;
66 |
67 | return p;
68 | }
69 |
70 | /*
71 | * Implemented by Ian McEwan, Ashima Arts, and distributed under the MIT License. {@link https://github.com/ashima/webgl-noise}
72 | */
73 | float snoise(vec2 v)
74 | {
75 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
76 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
77 | -0.577350269189626, // -1.0 + 2.0 * C.x
78 | 0.024390243902439); // 1.0 / 41.0
79 | // First corner
80 | vec2 i = floor(v + dot(v, C.yy) );
81 | vec2 x0 = v - i + dot(i, C.xx);
82 |
83 | // Other corners
84 | vec2 i1;
85 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
86 | //i1.y = 1.0 - i1.x;
87 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
88 | // x0 = x0 - 0.0 + 0.0 * C.xx ;
89 | // x1 = x0 - i1 + 1.0 * C.xx ;
90 | // x2 = x0 - 1.0 + 2.0 * C.xx ;
91 | vec4 x12 = x0.xyxy + C.xxzz;
92 | x12.xy -= i1;
93 |
94 | // Permutations
95 | i = _mod289(i); // Avoid truncation effects in permutation
96 | vec3 p = _permute( _permute( i.y + vec3(0.0, i1.y, 1.0 )) + i.x + vec3(0.0, i1.x, 1.0 ));
97 |
98 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
99 | m = m*m ;
100 | m = m*m ;
101 |
102 | // Gradients: 41 points uniformly over a line, mapped onto a diamond.
103 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
104 | vec3 x = 2.0 * fract(p * C.www) - 1.0;
105 | vec3 h = abs(x) - 0.5;
106 | vec3 ox = floor(x + 0.5);
107 | vec3 a0 = x - ox;
108 |
109 | // Normalise gradients implicitly by scaling m
110 | // Approximation of: m *= inversesqrt( a0*a0 + h*h );
111 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
112 |
113 | // Compute final noise value at P
114 | vec3 g;
115 | g.x = a0.x * x0.x + h.x * x0.y;
116 | g.yz = a0.yz * x12.xz + h.yz * x12.yw;
117 | return 130.0 * dot(m, g);
118 | }
119 |
120 | float snoise(vec3 v)
121 | {
122 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
123 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
124 |
125 | // First corner
126 | vec3 i = floor(v + dot(v, C.yyy) );
127 | vec3 x0 = v - i + dot(i, C.xxx) ;
128 |
129 | // Other corners
130 | vec3 g = step(x0.yzx, x0.xyz);
131 | vec3 l = 1.0 - g;
132 | vec3 i1 = min( g.xyz, l.zxy );
133 | vec3 i2 = max( g.xyz, l.zxy );
134 |
135 | // x0 = x0 - 0.0 + 0.0 * C.xxx;
136 | // x1 = x0 - i1 + 1.0 * C.xxx;
137 | // x2 = x0 - i2 + 2.0 * C.xxx;
138 | // x3 = x0 - 1.0 + 3.0 * C.xxx;
139 | vec3 x1 = x0 - i1 + C.xxx;
140 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
141 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
142 |
143 | // Permutations
144 | i = _mod289(i);
145 | vec4 p = _permute( _permute( _permute(
146 | i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
147 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
148 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
149 |
150 | // Gradients: 7x7 points over a square, mapped onto an octahedron.
151 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
152 | float n_ = 0.142857142857; // 1.0/7.0
153 | vec3 ns = n_ * D.wyz - D.xzx;
154 |
155 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
156 |
157 | vec4 x_ = floor(j * ns.z);
158 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
159 |
160 | vec4 x = x_ *ns.x + ns.yyyy;
161 | vec4 y = y_ *ns.x + ns.yyyy;
162 | vec4 h = 1.0 - abs(x) - abs(y);
163 |
164 | vec4 b0 = vec4( x.xy, y.xy );
165 | vec4 b1 = vec4( x.zw, y.zw );
166 |
167 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
168 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
169 | vec4 s0 = floor(b0)*2.0 + 1.0;
170 | vec4 s1 = floor(b1)*2.0 + 1.0;
171 | vec4 sh = -step(h, vec4(0.0));
172 |
173 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
174 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
175 |
176 | vec3 p0 = vec3(a0.xy,h.x);
177 | vec3 p1 = vec3(a0.zw,h.y);
178 | vec3 p2 = vec3(a1.xy,h.z);
179 | vec3 p3 = vec3(a1.zw,h.w);
180 |
181 | //Normalise gradients
182 | vec4 norm = _taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
183 | p0 *= norm.x;
184 | p1 *= norm.y;
185 | p2 *= norm.z;
186 | p3 *= norm.w;
187 |
188 | // Mix final noise value
189 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
190 | m = m * m;
191 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
192 | dot(p2,x2), dot(p3,x3) ) );
193 | }
194 |
195 | // Using @eddietree implementation from
196 | // His brilliant demo 'Artifacts'
197 |
198 | vec3 snoiseVec3( vec3 x ){
199 |
200 | float s = snoise(vec3( x ));
201 | float s1 = snoise(vec3( x.y - 19.1 , x.z + 33.4 , x.x + 47.2 ));
202 | float s2 = snoise(vec3( x.z + 74.2 , x.x - 124.5 , x.y + 99.4 ));
203 | vec3 c = vec3( s , s1 , s2 );
204 | return c;
205 |
206 | }
207 |
208 |
209 | vec3 curlNoise( vec3 p ){
210 |
211 | const float e = 1e-1;
212 | vec3 dx = vec3( e , 0.0 , 0.0 );
213 | vec3 dy = vec3( 0.0 , e , 0.0 );
214 | vec3 dz = vec3( 0.0 , 0.0 , e );
215 |
216 | vec3 p_x0 = snoiseVec3( p - dx );
217 | vec3 p_x1 = snoiseVec3( p + dx );
218 | vec3 p_y0 = snoiseVec3( p - dy );
219 | vec3 p_y1 = snoiseVec3( p + dy );
220 | vec3 p_z0 = snoiseVec3( p - dz );
221 | vec3 p_z1 = snoiseVec3( p + dz );
222 |
223 | float x = p_y1.z - p_y0.z - p_z1.y + p_z0.y;
224 | float y = p_z1.x - p_z0.x - p_x1.z + p_x0.z;
225 | float z = p_x1.y - p_x0.y - p_y1.x + p_y0.x;
226 |
227 | const float divisor = 1.0 / ( 2.0 * e );
228 | return normalize( vec3( x , y , z ) * divisor );
229 | }
230 |
--------------------------------------------------------------------------------
/app/shaders/chunks/Rand.glsl:
--------------------------------------------------------------------------------
1 | // source: http://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
2 | float rand(vec2 seed) {
3 | return fract(sin(dot(seed.xy,vec2(12.9898,78.233))) * 43758.5453);
4 | }
5 |
--------------------------------------------------------------------------------
/app/shaders/chunks/SimBasicShapes.glsl:
--------------------------------------------------------------------------------
1 | {
2 | #ifdef SIM_PLANE
3 | vec2 coords = vUv * 2.0 - 1.0;
4 | vec3 targetPos = vec3(coords.x, 0.0, coords.y);
5 | targetPos *= 3.0;
6 | #endif
7 |
8 | #ifdef SIM_CUBE
9 | vec3 targetPos = vec3(vUv.x, vUv.y, rand(vUv)) * 2.0 - 1.0;
10 | targetPos *= 2.0;
11 | #endif
12 |
13 | #ifdef SIM_DISC
14 | // cylindrical coords
15 | float radius = vUv.y;
16 | float theta = vUv.x * M_2PI;
17 | vec3 targetPos = vec3(
18 | radius * sin(theta),
19 | 0.0,
20 | radius * cos(theta)
21 | );
22 | targetPos *= 3.0;
23 | #endif
24 |
25 | #ifdef SIM_SPHERE
26 | // sphere, continuous along vUv.y
27 | vec2 coords = vUv;
28 | coords.x = coords.x * M_2PI - M_PI; // theta (lat)
29 | coords.y = coords.y * M_PI; // phi (long)
30 | vec3 sphereCoords = vec3(
31 | sin(coords.y) * cos(coords.x),
32 | cos(coords.y),
33 | sin(coords.y) * sin(coords.x)
34 | );
35 |
36 | float r = 1.0;
37 | vec3 targetPos = r * sphereCoords;
38 | targetPos *= 2.0;
39 | #endif
40 |
41 | #ifdef SIM_BALL
42 | // sphere coords, rand radius, offset y+0.5 for snoise vel
43 | vec2 coords = vUv;
44 | coords.x = coords.x * M_2PI - M_PI;
45 | coords.y = coords.y * M_PI;
46 | vec3 sphereCoords = vec3(
47 | sin(coords.y) * cos(coords.x),
48 | cos(coords.y),
49 | sin(coords.y) * sin(coords.x)
50 | );
51 | vec3 targetPos = sphereCoords * rand(vUv);
52 | targetPos *= 2.0;
53 | #endif
54 |
55 | #if defined(SIM_PLANE) || defined(SIM_SPHERE) || defined(SIM_BALL) || defined(SIM_CUBE) || defined(SIM_DISC)
56 | vec3 toTarget = targetPos - currPos;
57 | float toTargetLength = length(toTarget);
58 | if (!EQUALSZERO(toTargetLength))
59 | accel += uShapeAccel * toTarget/toTargetLength;
60 | #endif
61 | }
62 |
63 | #ifdef SIM_NOISE
64 | accel += 0.2 * uShapeAccel * curlNoise(currPos);
65 | #endif
--------------------------------------------------------------------------------
/app/shaders/chunks/SimGalaxy.glsl:
--------------------------------------------------------------------------------
1 | {
2 | #ifdef SIM_GALAXY
3 |
4 | #define K_NUM_ARMS 7.0
5 | #define K_HEIGHT 0.5
6 | #define K_SPIN_SPEED 0.25
7 |
8 | #define K_NOISE_ACCEL 0.1
9 |
10 | // cylindrical coords
11 | float radius = vUv.y;
12 | float theta = vUv.x * M_2PI;
13 |
14 | float randVal = rand(vec2(theta, radius));
15 |
16 | // jitter coords
17 | radius += randVal * 0.5;
18 | theta += randVal * 0.5;
19 |
20 | float radialArms = sin(K_NUM_ARMS * theta);
21 |
22 | float taperComponent = cos(0.6*radius*M_PI/2.0);
23 | taperComponent *= taperComponent;
24 | float heightParam = K_HEIGHT // height constant
25 | * (rand(vec2(radius, theta))-0.5) // provide unit thickness with rand
26 | * taperComponent; // taper along radius using cosine curve
27 |
28 | float spinParam = theta // angle parameter
29 | + radius*radius // twist at rate r^2
30 | - K_SPIN_SPEED * uTime; // spin at constant speed
31 |
32 | vec3 targetPos = vec3(
33 | radius * sin(spinParam),
34 | heightParam,
35 | radius * cos(spinParam)
36 | );
37 | targetPos *= 3.0;
38 |
39 | vec3 toTarget = targetPos - currPos;
40 | float toTargetLength = length(toTarget);
41 | accel += uShapeAccel * toTarget/toTargetLength
42 | * (radialArms/2.0+0.5) // gravity stronger in arms
43 | * randVal; // randomize gravity prevents banding
44 |
45 |
46 | // noise
47 | float noiseTime = uTime;
48 | accel += K_NOISE_ACCEL * curlNoise(currPos);// + vec3(sin(noiseTime), cos(noiseTime), sin(noiseTime)*cos(noiseTime)));
49 |
50 | #endif
51 | }
--------------------------------------------------------------------------------
/app/shaders/chunks/SimInputPos.glsl:
--------------------------------------------------------------------------------
1 | {
2 | #define PROCESS_INPUT_POS(ACC, POS) if ((ACC) != 0.0) { vec3 toCenter = (POS)-currPos; float toCenterLength = length(toCenter); accel += (toCenter/toCenterLength) * (ACC)*uInputAccel/toCenterLength; }
3 |
4 | PROCESS_INPUT_POS(uInputPosAccel.x, uInputPos[0]);
5 | #ifdef MULTIPLE_INPUT
6 | PROCESS_INPUT_POS(uInputPosAccel.y, uInputPos[1]);
7 | PROCESS_INPUT_POS(uInputPosAccel.z, uInputPos[2]);
8 | PROCESS_INPUT_POS(uInputPosAccel.w, uInputPos[3]);
9 | #endif
10 | }
--------------------------------------------------------------------------------
/app/shaders/chunks/SimRoseGalaxy.glsl:
--------------------------------------------------------------------------------
1 | #ifdef SIM_ROSE_GALAXY
2 | {
3 | // cylindrical coords
4 | float radius = vUv.y;
5 | float theta = vUv.x * M_2PI;
6 |
7 | // outward spiral function
8 | radius *= M_PI;
9 | vec3 targetPos = vec3(
10 | radius * sin(theta),
11 | radius*radius * sin(4.0*theta + sin(3.0*M_PI*radius+uTime/2.0)) / 10.0,
12 | radius * cos(theta)
13 | );
14 |
15 | vec3 toTarget = targetPos - currPos;
16 | float toTargetLength = length(toTarget);
17 | accel += uShapeAccel * toTarget/toTargetLength;
18 | }
19 | #endif
--------------------------------------------------------------------------------
/app/shaders/chunks/SimTextureTarget.glsl:
--------------------------------------------------------------------------------
1 | {
2 | #ifdef SIM_TEXTURE
3 |
4 | #define K_NOISE_ACCEL 0.1
5 | #define K_UV_OFFSET 0.02
6 |
7 | // jitter uv
8 | vec2 uvOffset = vec2(
9 | rand(vec2(vUv.x, vUv.y+uTime)),
10 | rand(vec2(vUv.x+uTime, vUv.y))
11 | );
12 |
13 | vec4 targetCol = texture2D(tTarget, vUv + uvOffset*K_UV_OFFSET);
14 |
15 | if (targetCol.a > 0.0) {
16 | vec3 toTarget = targetCol.rgb - currPos;
17 | float toTargetLength = length(toTarget);
18 | if (!EQUALSZERO(toTargetLength))
19 | accel += uShapeAccel * toTarget/toTargetLength;
20 | }
21 | else {
22 | accel += K_NOISE_ACCEL * curlNoise(currPos);
23 | }
24 | #endif
25 | }
--------------------------------------------------------------------------------
/app/uvtest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
81 |
82 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | static app -a 0.0.0.0 -H "{\"Cache-Control\": \"no-cache, must-revalidate\"}"
--------------------------------------------------------------------------------