├── LICENSE
├── README.md
├── assets
└── title.png
├── image.jpg
├── index.html
└── js
├── EquirectangularToCubemap.js
├── Kick.js
├── Maf.js
├── OBJLoader.js
├── OrbitControls.js
├── THREE.FBOHelper.js
├── isMobile.min.js
├── three.js
└── three.min.js
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Jaume Sanchez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pumpkin Jam (halloween-2016)
2 |
3 | #### A silly Halloween-themed stravaganza
4 | Pumpkin-based music visualisation using WebGL and Web Audio
5 |
6 |
7 | 3D models and textures by BitGem Halloween Pumpkins
8 |
9 | > The repo is missing the assets from BitGem. I wasn't sure about the license, so I haven't uploaded them
10 |
11 | > I know, it sucks! The project won't work without them! But the code is there, and you can buy the assets
12 |
13 | Made with three.js , THREE.FBOHelper , isMobile
14 |
15 | Curl noise from glsl-curl-noise by @cabbibo
16 |
17 | Fog equation adapted from glsl-fog by @hughskennedy
18 |
19 | Kick detection adapted from dancer.js by @jsantell
20 |
21 | GLSL Perlin noise from webgl-noise
22 |
23 | # Credits
24 |
25 | Jaume Sanchez @thespite · www.clicktorelease.com
26 |
27 | # License
28 |
29 | MIT licensed
30 |
31 | Copyright (C) 2016 Jaume Sanchez Elias, http://www.clicktorelease.com
32 |
--------------------------------------------------------------------------------
/assets/title.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/halloween-2016/3bf81f92fd5a4e4a24f0b62bf728f677eb088d98/assets/title.png
--------------------------------------------------------------------------------
/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/halloween-2016/3bf81f92fd5a4e4a24f0b62bf728f677eb088d98/image.jpg
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Pumpkin Jam - Halloween 2016
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
174 |
175 |
176 |
177 |
178 |
179 |
Loading...
180 |
181 |
182 |
Welcome! · Controls
183 | · Credits
184 |
199 |
211 |
212 |
You can pause and resume the song with the control right next to the title
213 |
Press space to pause movement
214 |
Demo mode locks the camera
215 |
Demo mode
216 |
Demo mode disabled allows you to:
217 |
Click and drag to rotate camera
218 |
Zoom in and out with scroll
219 |
220 |
221 |
Close
222 |
223 |
224 |
225 |
232 | Mobile and tablet are unsupported at the moment Sorry, I coded this in two nights,
233 | I'd need more time to get mobile ready!
234 |
235 |
236 |
Click to start
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
270 |
491 |
579 |
620 |
638 |
655 |
670 |
699 |
733 |
816 |
1664 |
1665 |
1666 |
1667 |
1668 |
--------------------------------------------------------------------------------
/js/EquirectangularToCubemap.js:
--------------------------------------------------------------------------------
1 | ;(function() {
2 |
3 | "use strict";
4 |
5 | var root = this
6 |
7 | var has_require = typeof require !== 'undefined'
8 |
9 | var THREE = root.THREE || has_require && require('three')
10 | if( !THREE )
11 | throw new Error( 'EquirectangularToCubemap requires three.js' )
12 |
13 | function EquirectangularToCubemap( renderer ) {
14 |
15 | this.renderer = renderer;
16 | this.scene = new THREE.Scene();
17 |
18 | var gl = this.renderer.getContext();
19 | this.maxSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE )
20 |
21 | this.camera = new THREE.CubeCamera( 1, 100000, 1 );
22 |
23 | this.material = new THREE.MeshBasicMaterial( {
24 | map: null,
25 | side: THREE.BackSide
26 | } );
27 |
28 | this.mesh = new THREE.Mesh(
29 | new THREE.IcosahedronGeometry( 100, 4 ),
30 | this.material
31 | );
32 | this.scene.add( this.mesh );
33 |
34 | }
35 |
36 | EquirectangularToCubemap.prototype.convert = function( source, size ) {
37 |
38 | var mapSize = Math.min( size, this.maxSize );
39 | this.camera = new THREE.CubeCamera( 1, 100000, mapSize );
40 | this.material.map = source;
41 |
42 | this.camera.updateCubeMap( this.renderer, this.scene );
43 |
44 | return this.camera.renderTarget.texture;
45 |
46 | }
47 |
48 | if( typeof exports !== 'undefined' ) {
49 | if( typeof module !== 'undefined' && module.exports ) {
50 | exports = module.exports = EquirectangularToCubemap
51 | }
52 | exports.EquirectangularToCubemap = EquirectangularToCubemap
53 | }
54 | else {
55 | root.EquirectangularToCubemap = EquirectangularToCubemap
56 | }
57 |
58 | }).call(this);
59 |
--------------------------------------------------------------------------------
/js/Kick.js:
--------------------------------------------------------------------------------
1 | var Kick = function ( o ) {
2 | o = o || {};
3 | this.frequency = o.frequency !== undefined ? o.frequency : [ 0, 10 ];
4 | this.threshold = o.threshold !== undefined ? o.threshold : 0.3;
5 | this.decay = o.decay !== undefined ? o.decay : 0.02;
6 | this.onKick = o.onKick;
7 | this.offKick = o.offKick;
8 | this.isOn = false;
9 | this.currentThreshold = this.threshold;
10 | };
11 |
12 | Kick.prototype = {
13 | on : function () {
14 | this.isOn = true;
15 | return this;
16 | },
17 | off : function () {
18 | this.isOn = false;
19 | return this;
20 | },
21 |
22 | set : function ( o ) {
23 | o = o || {};
24 | this.frequency = o.frequency !== undefined ? o.frequency : this.frequency;
25 | this.threshold = o.threshold !== undefined ? o.threshold : this.threshold;
26 | this.decay = o.decay !== undefined ? o.decay : this.decay;
27 | this.onKick = o.onKick || this.onKick;
28 | this.offKick = o.offKick || this.offKick;
29 | },
30 |
31 | onUpdate : function () {
32 | if ( !this.isOn ) { return; }
33 | var magnitude = this.maxAmplitude( this.frequency );
34 | if ( magnitude >= this.currentThreshold &&
35 | magnitude >= this.threshold ) {
36 | this.currentThreshold = magnitude;
37 | this.onKick && this.onKick.call( this.dancer, magnitude );
38 | } else {
39 | this.offKick && this.offKick.call( this.dancer, magnitude );
40 | this.currentThreshold -= this.decay;
41 | }
42 | },
43 | maxAmplitude : function ( frequency ) {
44 | var
45 | max = 0,
46 | fft = frequencyData;
47 |
48 | // Sloppy array check
49 | if ( !frequency.length ) {
50 | return frequency < fft.length ?
51 | fft[ ~~frequency ] :
52 | null;
53 | }
54 |
55 | for ( var i = frequency[ 0 ], l = frequency[ 1 ]; i <= l; i++ ) {
56 | if ( fft[ i ] > max ) { max = fft[ i ]; }
57 | }
58 | return max;
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/js/Maf.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | // Module code from underscore.js
4 |
5 | // Establish the root object, `window` (`self`) in the browser, `global`
6 | // on the server, or `this` in some virtual machines. We use `self`
7 | // instead of `window` for `WebWorker` support.
8 | var root = typeof self == 'object' && self.self === self && self ||
9 | typeof global == 'object' && global.global === global && global ||
10 | this;
11 |
12 | var Maf = function(obj) {
13 | if (obj instanceof Maf ) return obj;
14 | if (!(this instanceof Maf )) return new Maf(obj);
15 | this._wrapped = obj;
16 | };
17 |
18 | // Export the Underscore object for **Node.js**, with
19 | // backwards-compatibility for their old module API. If we're in
20 | // the browser, add `Maf` as a global object.
21 | // (`nodeType` is checked to ensure that `module`
22 | // and `exports` are not HTML elements.)
23 | if (typeof exports != 'undefined' && !exports.nodeType) {
24 | if (typeof module != 'undefined' && !module.nodeType && module.exports) {
25 | exports = module.exports = Maf;
26 | }
27 | exports.Maf = Maf;
28 | } else {
29 | root.Maf = Maf;
30 | }
31 |
32 | // Current version.
33 | Maf.VERSION = '1.0.0';
34 |
35 | Maf.PI = Math.PI;
36 |
37 | // https://www.opengl.org/sdk/docs/man/html/clamp.xhtml
38 |
39 | Maf.clamp = function( v, minVal, maxVal ) {
40 | return Math.min( maxVal, Math.max( minVal, v ) );
41 | };
42 |
43 | // https://www.opengl.org/sdk/docs/man/html/step.xhtml
44 |
45 | Maf.step = function( edge, v ) {
46 | return ( v < edge ) ? 0 : 1;
47 | }
48 |
49 | // https://www.opengl.org/sdk/docs/man/html/smoothstep.xhtml
50 |
51 | Maf.smoothStep = function ( edge0, edge1, v ) {
52 | var t = Maf.clamp( ( v - edge0 ) / ( edge1 - edge0 ), 0.0, 1.0 );
53 | return t * t * ( 3.0 - 2.0 * t );
54 | };
55 |
56 | // http://docs.unity3d.com/ScriptReference/Mathf.html
57 | // http://www.shaderific.com/glsl-functions/
58 | // https://www.opengl.org/sdk/docs/man4/html/
59 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ff471376(v=vs.85).aspx
60 | // http://moutjs.com/docs/v0.11/math.html#map
61 | // https://code.google.com/p/kuda/source/browse/public/js/hemi/utils/mathUtils.js?r=8d581c02651077c4ac3f5fc4725323210b6b13cc
62 |
63 | // Converts from degrees to radians.
64 | Maf.deg2Rad = function( degrees ) {
65 | return degrees * Math.PI / 180;
66 | };
67 |
68 | Maf.toRadians = Maf.deg2Rad;
69 |
70 | // Converts from radians to degrees.
71 | Maf.rad2Deg = function(radians) {
72 | return radians * 180 / Math.PI;
73 | };
74 |
75 | Maf.toDegrees = Maf.rad2Deg;
76 |
77 | Maf.clamp01 = function( v ) {
78 | return Maf.clamp( v, 0, 1 );
79 | };
80 |
81 | // https://www.opengl.org/sdk/docs/man/html/mix.xhtml
82 |
83 | Maf.mix = function( x, y, a ) {
84 | if( a <= 0 ) return x;
85 | if( a >= 1 ) return y;
86 | return x + a * (y - x)
87 | };
88 |
89 | Maf.lerp = Maf.mix;
90 |
91 | Maf.inverseMix = function( a, b, v ) {
92 | return ( v - a ) / ( b - a );
93 | };
94 |
95 | Maf.inverseLerp = Maf.inverseMix;
96 |
97 | Maf.mixUnclamped = function( x, y, a ) {
98 | if( a <= 0 ) return x;
99 | if( a >= 1 ) return y;
100 | return x + a * (y - x)
101 | };
102 |
103 | Maf.lerpUnclamped = Maf.mixUnclamped;
104 |
105 | // https://www.opengl.org/sdk/docs/man/html/fract.xhtml
106 |
107 | Maf.fract = function( v ) {
108 | return v - Math.floor( v );
109 | };
110 |
111 | Maf.frac = Maf.fract;
112 |
113 | // http://stackoverflow.com/questions/4965301/finding-if-a-number-is-a-power-of-2
114 |
115 | Maf.isPowerOfTwo = function( v ) {
116 | return ( ( ( v - 1) & v ) == 0 );
117 | };
118 |
119 | // https://bocoup.com/weblog/find-the-closest-power-of-2-with-javascript
120 |
121 | Maf.closestPowerOfTwo = function( v ) {
122 | return Math.pow( 2, Math.round( Math.log( v ) / Math.log( 2 ) ) );
123 | };
124 |
125 | Maf.nextPowerOfTwo = function( v ) {
126 | return Math.pow( 2, Math.ceil( Math.log( v ) / Math.log( 2 ) ) );
127 | }
128 |
129 | // http://stackoverflow.com/questions/1878907/the-smallest-difference-between-2-angles
130 |
131 | //function mod(a, n) { return a - Math.floor(a/n) * n; }
132 | Maf.mod = function(a, n) { return (a % n + n) % n; }
133 |
134 | Maf.deltaAngle = function( a, b ) {
135 | var d = Maf.mod( b - a, 360 );
136 | if( d > 180 ) d = Math.abs( d - 360 );
137 | return d;
138 | };
139 |
140 | Maf.deltaAngleDeg = Maf.deltaAngle;
141 |
142 | Maf.deltaAngleRad = function( a, b ) {
143 | return Maf.toRadians( Maf.deltaAngle( Maf.toDegrees( a ), Maf.toDegrees( b ) ) );
144 | };
145 |
146 | Maf.lerpAngle = function( a, b, t ) {
147 | var angle = Maf.deltaAngle( a, b );
148 | return Maf.mod( a + Maf.lerp( 0, angle, t ), 360 );
149 | };
150 |
151 | Maf.lerpAngleDeg = Maf.lerpAngle;
152 |
153 | Maf.lerpAngleRad = function( a, b, t ) {
154 | return Maf.toRadians( Maf.lerpAngleDeg( Maf.toDegrees( a ), Maf.toDegrees( b ), t ) );
155 | };
156 |
157 | // http://gamedev.stackexchange.com/questions/74324/gamma-space-and-linear-space-with-shader
158 |
159 | Maf.gammaToLinearSpace = function( v ) {
160 | return Math.pow( v, 2.2 );
161 | };
162 |
163 | Maf.linearToGammaSpace = function( v ) {
164 | return Math.pow( v, 1 / 2.2 );
165 | };
166 |
167 | Maf.map = function( from1, to1, from2, to2, v ) {
168 | return from2 + ( v - from1 ) * ( to2 - from2 ) / ( to1 - from1 );
169 | }
170 |
171 | Maf.scale = Maf.map;
172 |
173 | // http://www.iquilezles.org/www/articles/functions/functions.htm
174 |
175 | Maf.almostIdentity = function( x, m, n ) {
176 |
177 | if( x > m ) return x;
178 |
179 | var a = 2 * n - m;
180 | var b = 2 * m - 3 * n;
181 | var t = x / m;
182 |
183 | return ( a * t + b) * t * t + n;
184 | }
185 |
186 | Maf.impulse = function( k, x ) {
187 | var h = k * x;
188 | return h * Math.exp( 1 - h );
189 | };
190 |
191 | Maf.cubicPulse = function( c, w, x ) {
192 | x = Math.abs( x - c );
193 | if( x > w ) return 0;
194 | x /= w;
195 | return 1 - x * x * ( 3 - 2 * x );
196 | }
197 |
198 | Maf.expStep = function( x, k, n ) {
199 | return Math.exp( -k * Math.pow( x, n ) );
200 | }
201 |
202 | Maf.parabola = function( x, k ) {
203 | return Math.pow( 4 * x * ( 1 - x ), k );
204 | }
205 |
206 | Maf.powerCurve = function( x, a, b ) {
207 | var k = Math.pow( a + b, a + b ) / ( Math.pow( a, a ) * Math.pow( b, b ) );
208 | return k * Math.pow( x, a ) * Math.pow( 1 - x, b );
209 | }
210 |
211 | // http://iquilezles.org/www/articles/smin/smin.htm ?
212 |
213 | Maf.latLonToCartesian = function( lat, lon ) {
214 |
215 | lon += 180;
216 | lat = Maf.clamp( lat, -85, 85 );
217 | var phi = Maf.toRadians( 90 - lat );
218 | var theta = Maf.toRadians( 180 - lon );
219 | var x = Math.sin( phi ) * Math.cos( theta );
220 | var y = Math.cos( phi );
221 | var z = Math.sin( phi ) * Math.sin( theta );
222 |
223 | return { x: x, y: y, z: z }
224 |
225 | }
226 |
227 | Maf.cartesianToLatLon = function( x, y, z ) {
228 | var n = Math.sqrt( x * x + y * y + z * z );
229 | return{ lat: Math.asin( z / n ), lon: Math.atan2( y, x ) };
230 | }
231 |
232 | Maf.randomInRange = function( min, max ) {
233 | return min + Math.random() * ( max - min );
234 | }
235 |
236 | Maf.norm = function( v, minVal, maxVal ) {
237 | return ( v - minVal ) / ( maxVal - minVal );
238 | }
239 |
240 | Maf.hash = function( n ) {
241 | return Maf.fract( (1.0 + Math.cos(n)) * 415.92653);
242 | }
243 |
244 | Maf.noise2d = function( x, y ) {
245 | var xhash = Maf.hash( x * 37.0 );
246 | var yhash = Maf.hash( y * 57.0 );
247 | return Maf.fract( xhash + yhash );
248 | }
249 |
250 | // http://iquilezles.org/www/articles/smin/smin.htm
251 |
252 | Maf.smoothMin = function( a, b, k ) {
253 | var res = Math.exp( -k*a ) + Math.exp( -k*b );
254 | return - Math.log( res )/k;
255 | }
256 |
257 | Maf.smoothMax = function( a, b, k ){
258 | return Math.log( Math.exp(a) + Math.exp(b) )/k;
259 | }
260 |
261 | Maf.almost = function( a, b ) {
262 | return ( Math.abs( a - b ) < .0001 );
263 | }
264 |
265 | }());
--------------------------------------------------------------------------------
/js/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 | this.materials = null;
10 |
11 | };
12 |
13 | THREE.OBJLoader.prototype = {
14 |
15 | constructor: THREE.OBJLoader,
16 |
17 | load: function ( url, onLoad, onProgress, onError ) {
18 |
19 | var scope = this;
20 |
21 | var loader = new THREE.XHRLoader( scope.manager );
22 | loader.setPath( this.path );
23 | loader.load( url, function ( text ) {
24 |
25 | onLoad( scope.parse( text ) );
26 |
27 | }, onProgress, onError );
28 |
29 | },
30 |
31 | setPath: function ( value ) {
32 |
33 | this.path = value;
34 |
35 | },
36 |
37 | setMaterials: function ( materials ) {
38 |
39 | this.materials = materials;
40 |
41 | },
42 |
43 | parse: function ( text ) {
44 |
45 | console.time( 'OBJLoader' );
46 |
47 | var objects = [];
48 | var object;
49 | var foundObjects = false;
50 | var vertices = [];
51 | var normals = [];
52 | var uvs = [];
53 |
54 | function addObject( name ) {
55 |
56 | var geometry = {
57 | vertices: [],
58 | normals: [],
59 | uvs: []
60 | };
61 |
62 | var material = {
63 | name: '',
64 | smooth: true
65 | };
66 |
67 | object = {
68 | name: name,
69 | geometry: geometry,
70 | material: material
71 | };
72 |
73 | objects.push( object );
74 |
75 | }
76 |
77 | function parseVertexIndex( value ) {
78 |
79 | var index = parseInt( value );
80 |
81 | return ( index >= 0 ? index - 1 : index + vertices.length / 3 ) * 3;
82 |
83 | }
84 |
85 | function parseNormalIndex( value ) {
86 |
87 | var index = parseInt( value );
88 |
89 | return ( index >= 0 ? index - 1 : index + normals.length / 3 ) * 3;
90 |
91 | }
92 |
93 | function parseUVIndex( value ) {
94 |
95 | var index = parseInt( value );
96 |
97 | return ( index >= 0 ? index - 1 : index + uvs.length / 2 ) * 2;
98 |
99 | }
100 |
101 | function addVertex( a, b, c ) {
102 |
103 | object.geometry.vertices.push(
104 | vertices[ a ], vertices[ a + 1 ], vertices[ a + 2 ],
105 | vertices[ b ], vertices[ b + 1 ], vertices[ b + 2 ],
106 | vertices[ c ], vertices[ c + 1 ], vertices[ c + 2 ]
107 | );
108 |
109 | }
110 |
111 | function addNormal( a, b, c ) {
112 |
113 | object.geometry.normals.push(
114 | normals[ a ], normals[ a + 1 ], normals[ a + 2 ],
115 | normals[ b ], normals[ b + 1 ], normals[ b + 2 ],
116 | normals[ c ], normals[ c + 1 ], normals[ c + 2 ]
117 | );
118 |
119 | }
120 |
121 | function addUV( a, b, c ) {
122 |
123 | object.geometry.uvs.push(
124 | uvs[ a ], uvs[ a + 1 ],
125 | uvs[ b ], uvs[ b + 1 ],
126 | uvs[ c ], uvs[ c + 1 ]
127 | );
128 |
129 | }
130 |
131 | function addFace( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) {
132 |
133 | var ia = parseVertexIndex( a );
134 | var ib = parseVertexIndex( b );
135 | var ic = parseVertexIndex( c );
136 | var id;
137 |
138 | if ( d === undefined ) {
139 |
140 | addVertex( ia, ib, ic );
141 |
142 | } else {
143 |
144 | id = parseVertexIndex( d );
145 |
146 | addVertex( ia, ib, id );
147 | addVertex( ib, ic, id );
148 |
149 | }
150 |
151 | if ( ua !== undefined ) {
152 |
153 | ia = parseUVIndex( ua );
154 | ib = parseUVIndex( ub );
155 | ic = parseUVIndex( uc );
156 |
157 | if ( d === undefined ) {
158 |
159 | addUV( ia, ib, ic );
160 |
161 | } else {
162 |
163 | id = parseUVIndex( ud );
164 |
165 | addUV( ia, ib, id );
166 | addUV( ib, ic, id );
167 |
168 | }
169 |
170 | }
171 |
172 | if ( na !== undefined ) {
173 |
174 | ia = parseNormalIndex( na );
175 | ib = parseNormalIndex( nb );
176 | ic = parseNormalIndex( nc );
177 |
178 | if ( d === undefined ) {
179 |
180 | addNormal( ia, ib, ic );
181 |
182 | } else {
183 |
184 | id = parseNormalIndex( nd );
185 |
186 | addNormal( ia, ib, id );
187 | addNormal( ib, ic, id );
188 |
189 | }
190 |
191 | }
192 |
193 | }
194 |
195 | addObject( '' );
196 |
197 | // v float float float
198 | var vertex_pattern = /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/;
199 |
200 | // vn float float float
201 | var normal_pattern = /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/;
202 |
203 | // vt float float
204 | var uv_pattern = /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/;
205 |
206 | // f vertex vertex vertex ...
207 | var face_pattern1 = /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/;
208 |
209 | // f vertex/uv vertex/uv vertex/uv ...
210 | var face_pattern2 = /^f\s+((-?\d+)\/(-?\d+))\s+((-?\d+)\/(-?\d+))\s+((-?\d+)\/(-?\d+))(?:\s+((-?\d+)\/(-?\d+)))?/;
211 |
212 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
213 | var face_pattern3 = /^f\s+((-?\d+)\/(-?\d+)\/(-?\d+))\s+((-?\d+)\/(-?\d+)\/(-?\d+))\s+((-?\d+)\/(-?\d+)\/(-?\d+))(?:\s+((-?\d+)\/(-?\d+)\/(-?\d+)))?/;
214 |
215 | // f vertex//normal vertex//normal vertex//normal ...
216 | var face_pattern4 = /^f\s+((-?\d+)\/\/(-?\d+))\s+((-?\d+)\/\/(-?\d+))\s+((-?\d+)\/\/(-?\d+))(?:\s+((-?\d+)\/\/(-?\d+)))?/;
217 |
218 | var object_pattern = /^[og]\s+(.+)/;
219 |
220 | var smoothing_pattern = /^s\s+(\d+|on|off)/;
221 |
222 | //
223 |
224 | var lines = text.split( '\n' );
225 |
226 | for ( var i = 0; i < lines.length; i ++ ) {
227 |
228 | var line = lines[ i ];
229 | line = line.trim();
230 |
231 | var result;
232 |
233 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
234 |
235 | continue;
236 |
237 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) {
238 |
239 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
240 |
241 | vertices.push(
242 | parseFloat( result[ 1 ] ),
243 | parseFloat( result[ 2 ] ),
244 | parseFloat( result[ 3 ] )
245 | );
246 |
247 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) {
248 |
249 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
250 |
251 | normals.push(
252 | parseFloat( result[ 1 ] ),
253 | parseFloat( result[ 2 ] ),
254 | parseFloat( result[ 3 ] )
255 | );
256 |
257 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) {
258 |
259 | // ["vt 0.1 0.2", "0.1", "0.2"]
260 |
261 | uvs.push(
262 | parseFloat( result[ 1 ] ),
263 | parseFloat( result[ 2 ] )
264 | );
265 |
266 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) {
267 |
268 | // ["f 1 2 3", "1", "2", "3", undefined]
269 |
270 | addFace(
271 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ]
272 | );
273 |
274 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) {
275 |
276 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined]
277 |
278 | addFace(
279 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ],
280 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ]
281 | );
282 |
283 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) {
284 |
285 | // ["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]
286 |
287 | addFace(
288 | result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ],
289 | result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ],
290 | result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ]
291 | );
292 |
293 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) {
294 |
295 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined]
296 |
297 | addFace(
298 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ],
299 | undefined, undefined, undefined, undefined,
300 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ]
301 | );
302 |
303 | } else if ( ( result = object_pattern.exec( line ) ) !== null ) {
304 |
305 | // o object_name
306 | // or
307 | // g group_name
308 |
309 | var name = result[ 1 ].trim();
310 |
311 | if ( foundObjects === false ) {
312 |
313 | foundObjects = true;
314 | object.name = name;
315 |
316 | } else {
317 |
318 | addObject( name );
319 |
320 | }
321 |
322 | } else if ( /^usemtl /.test( line ) ) {
323 |
324 | // material
325 |
326 | object.material.name = line.substring( 7 ).trim();
327 |
328 | } else if ( /^mtllib /.test( line ) ) {
329 |
330 | // mtl file
331 |
332 | } else if ( ( result = smoothing_pattern.exec( line ) ) !== null ) {
333 |
334 | // smooth shading
335 |
336 | object.material.smooth = result[ 1 ] === "1" || result[ 1 ] === "on";
337 |
338 | } else {
339 |
340 | throw new Error( "Unexpected line: " + line );
341 |
342 | }
343 |
344 | }
345 |
346 | var container = new THREE.Group();
347 |
348 | for ( var i = 0, l = objects.length; i < l; i ++ ) {
349 |
350 | object = objects[ i ];
351 | var geometry = object.geometry;
352 |
353 | var buffergeometry = new THREE.BufferGeometry();
354 |
355 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) );
356 |
357 | if ( geometry.normals.length > 0 ) {
358 |
359 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) );
360 |
361 | } else {
362 |
363 | buffergeometry.computeVertexNormals();
364 |
365 | }
366 |
367 | if ( geometry.uvs.length > 0 ) {
368 |
369 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) );
370 |
371 | }
372 |
373 | var material;
374 |
375 | if ( this.materials !== null ) {
376 |
377 | material = this.materials.create( object.material.name );
378 |
379 | }
380 |
381 | if ( !material ) {
382 |
383 | material = new THREE.MeshPhongMaterial();
384 | material.name = object.material.name;
385 |
386 | }
387 |
388 | material.shading = object.material.smooth ? THREE.SmoothShading : THREE.FlatShading;
389 |
390 | var mesh = new THREE.Mesh( buffergeometry, material );
391 | mesh.name = object.name;
392 |
393 | container.add( mesh );
394 |
395 | }
396 |
397 | console.timeEnd( 'OBJLoader' );
398 |
399 | return container;
400 |
401 | }
402 |
403 | };
--------------------------------------------------------------------------------
/js/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 | // Set to true to disable use of the keys
68 | this.noKeys = false;
69 |
70 | // The four arrow keys
71 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
72 |
73 | ////////////
74 | // internals
75 |
76 | var scope = this;
77 |
78 | var EPS = 0.000001;
79 |
80 | var rotateStart = new THREE.Vector2();
81 | var rotateEnd = new THREE.Vector2();
82 | var rotateDelta = new THREE.Vector2();
83 |
84 | var panStart = new THREE.Vector2();
85 | var panEnd = new THREE.Vector2();
86 | var panDelta = new THREE.Vector2();
87 | var panOffset = new THREE.Vector3();
88 |
89 | var offset = new THREE.Vector3();
90 |
91 | var dollyStart = new THREE.Vector2();
92 | var dollyEnd = new THREE.Vector2();
93 | var dollyDelta = new THREE.Vector2();
94 |
95 | var phiDelta = 0;
96 | var thetaDelta = 0;
97 | var scale = 1;
98 | var pan = new THREE.Vector3();
99 |
100 | var lastPosition = new THREE.Vector3();
101 |
102 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
103 |
104 | var state = STATE.NONE;
105 |
106 | // for reset
107 |
108 | this.target0 = this.target.clone();
109 | this.position0 = this.object.position.clone();
110 |
111 | // so camera.up is the orbit axis
112 |
113 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
114 | var quatInverse = quat.clone().inverse();
115 |
116 | // events
117 |
118 | var changeEvent = { type: 'change' };
119 | var startEvent = { type: 'start'};
120 | var endEvent = { type: 'end'};
121 |
122 | this.rotateLeft = function ( angle ) {
123 |
124 | if ( angle === undefined ) {
125 |
126 | angle = getAutoRotationAngle();
127 |
128 | }
129 |
130 | thetaDelta -= angle;
131 |
132 | };
133 |
134 | this.rotateUp = function ( angle ) {
135 |
136 | if ( angle === undefined ) {
137 |
138 | angle = getAutoRotationAngle();
139 |
140 | }
141 |
142 | phiDelta -= angle;
143 |
144 | };
145 |
146 | // pass in distance in world space to move left
147 | this.panLeft = function ( distance ) {
148 |
149 | var te = this.object.matrix.elements;
150 |
151 | // get X column of matrix
152 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] );
153 | panOffset.multiplyScalar( - distance );
154 |
155 | pan.add( panOffset );
156 |
157 | };
158 |
159 | // pass in distance in world space to move up
160 | this.panUp = function ( distance ) {
161 |
162 | var te = this.object.matrix.elements;
163 |
164 | // get Y column of matrix
165 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] );
166 | panOffset.multiplyScalar( distance );
167 |
168 | pan.add( panOffset );
169 |
170 | };
171 |
172 | // pass in x,y of change desired in pixel space,
173 | // right and down are positive
174 | this.pan = function ( deltaX, deltaY ) {
175 |
176 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
177 |
178 | if ( scope.object.fov !== undefined ) {
179 |
180 | // perspective
181 | var position = scope.object.position;
182 | var offset = position.clone().sub( scope.target );
183 | var targetDistance = offset.length();
184 |
185 | // half of the fov is center to top of screen
186 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
187 |
188 | // we actually don't use screenWidth, since perspective camera is fixed to screen height
189 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
190 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
191 |
192 | } else if ( scope.object.top !== undefined ) {
193 |
194 | // orthographic
195 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
196 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight );
197 |
198 | } else {
199 |
200 | // camera neither orthographic or perspective
201 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
202 |
203 | }
204 |
205 | };
206 |
207 | this.dollyIn = function ( dollyScale ) {
208 |
209 | if ( dollyScale === undefined ) {
210 |
211 | dollyScale = getZoomScale();
212 |
213 | }
214 |
215 | scale /= dollyScale;
216 |
217 | };
218 |
219 | this.dollyOut = function ( dollyScale ) {
220 |
221 | if ( dollyScale === undefined ) {
222 |
223 | dollyScale = getZoomScale();
224 |
225 | }
226 |
227 | scale *= dollyScale;
228 |
229 | };
230 |
231 | this.update = function () {
232 |
233 | var position = this.object.position;
234 |
235 | offset.copy( position ).sub( this.target );
236 |
237 | // rotate offset to "y-axis-is-up" space
238 | offset.applyQuaternion( quat );
239 |
240 | // angle from z-axis around y-axis
241 |
242 | var theta = Math.atan2( offset.x, offset.z );
243 |
244 | // angle from y-axis
245 |
246 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
247 |
248 | if ( this.autoRotate ) {
249 |
250 | this.rotateLeft( getAutoRotationAngle() );
251 |
252 | }
253 |
254 | theta += thetaDelta;
255 | phi += phiDelta;
256 |
257 | // restrict phi to be between desired limits
258 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
259 |
260 | // restrict phi to be betwee EPS and PI-EPS
261 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
262 |
263 | var radius = offset.length() * scale;
264 |
265 | // restrict radius to be between desired limits
266 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
267 |
268 | // move target to panned location
269 | this.target.add( pan );
270 |
271 | offset.x = radius * Math.sin( phi ) * Math.sin( theta );
272 | offset.y = radius * Math.cos( phi );
273 | offset.z = radius * Math.sin( phi ) * Math.cos( theta );
274 |
275 | // rotate offset back to "camera-up-vector-is-up" space
276 | offset.applyQuaternion( quatInverse );
277 |
278 | position.copy( this.target ).add( offset );
279 |
280 | this.object.lookAt( this.target );
281 |
282 | thetaDelta = 0;
283 | phiDelta = 0;
284 | scale = 1;
285 | pan.set( 0, 0, 0 );
286 |
287 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS ) {
288 |
289 | this.dispatchEvent( changeEvent );
290 |
291 | lastPosition.copy( this.object.position );
292 |
293 | }
294 |
295 | };
296 |
297 |
298 | this.reset = function () {
299 |
300 | state = STATE.NONE;
301 |
302 | this.target.copy( this.target0 );
303 | this.object.position.copy( this.position0 );
304 |
305 | this.update();
306 |
307 | };
308 |
309 | function getAutoRotationAngle() {
310 |
311 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
312 |
313 | }
314 |
315 | function getZoomScale() {
316 |
317 | return Math.pow( 0.95, scope.zoomSpeed );
318 |
319 | }
320 |
321 | function onMouseDown( event ) {
322 |
323 | if ( scope.enabled === false ) return;
324 | event.preventDefault();
325 |
326 | if ( event.button === 0 ) {
327 | if ( scope.noRotate === true ) return;
328 |
329 | state = STATE.ROTATE;
330 |
331 | rotateStart.set( event.clientX, event.clientY );
332 |
333 | } else if ( event.button === 1 ) {
334 | if ( scope.noZoom === true ) return;
335 |
336 | state = STATE.DOLLY;
337 |
338 | dollyStart.set( event.clientX, event.clientY );
339 |
340 | } else if ( event.button === 2 ) {
341 | if ( scope.noPan === true ) return;
342 |
343 | state = STATE.PAN;
344 |
345 | panStart.set( event.clientX, event.clientY );
346 |
347 | }
348 |
349 | scope.domElement.addEventListener( 'mousemove', onMouseMove, false );
350 | scope.domElement.addEventListener( 'mouseup', onMouseUp, false );
351 | scope.dispatchEvent( startEvent );
352 |
353 | }
354 |
355 | function onMouseMove( event ) {
356 |
357 | if ( scope.enabled === false ) return;
358 |
359 | event.preventDefault();
360 |
361 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
362 |
363 | if ( state === STATE.ROTATE ) {
364 |
365 | if ( scope.noRotate === true ) return;
366 |
367 | rotateEnd.set( event.clientX, event.clientY );
368 | rotateDelta.subVectors( rotateEnd, rotateStart );
369 |
370 | // rotating across whole screen goes 360 degrees around
371 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
372 |
373 | // rotating up and down along whole screen attempts to go 360, but limited to 180
374 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
375 |
376 | rotateStart.copy( rotateEnd );
377 |
378 | } else if ( state === STATE.DOLLY ) {
379 |
380 | if ( scope.noZoom === true ) return;
381 |
382 | dollyEnd.set( event.clientX, event.clientY );
383 | dollyDelta.subVectors( dollyEnd, dollyStart );
384 |
385 | if ( dollyDelta.y > 0 ) {
386 |
387 | scope.dollyIn();
388 |
389 | } else {
390 |
391 | scope.dollyOut();
392 |
393 | }
394 |
395 | dollyStart.copy( dollyEnd );
396 |
397 | } else if ( state === STATE.PAN ) {
398 |
399 | if ( scope.noPan === true ) return;
400 |
401 | panEnd.set( event.clientX, event.clientY );
402 | panDelta.subVectors( panEnd, panStart );
403 |
404 | scope.pan( panDelta.x, panDelta.y );
405 |
406 | panStart.copy( panEnd );
407 |
408 | }
409 |
410 | scope.update();
411 |
412 | }
413 |
414 | function onMouseUp( /* event */ ) {
415 |
416 | if ( scope.enabled === false ) return;
417 |
418 | scope.domElement.removeEventListener( 'mousemove', onMouseMove, false );
419 | scope.domElement.removeEventListener( 'mouseup', onMouseUp, false );
420 | scope.dispatchEvent( endEvent );
421 | state = STATE.NONE;
422 |
423 | }
424 |
425 | function onMouseWheel( event ) {
426 |
427 | if ( scope.enabled === false || scope.noZoom === true ) return;
428 |
429 | event.preventDefault();
430 | event.stopPropagation();
431 |
432 | var delta = 0;
433 |
434 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
435 |
436 | delta = event.wheelDelta;
437 |
438 | } else if ( event.detail !== undefined ) { // Firefox
439 |
440 | delta = - event.detail;
441 |
442 | }
443 |
444 | if ( delta > 0 ) {
445 |
446 | scope.dollyOut();
447 |
448 | } else {
449 |
450 | scope.dollyIn();
451 |
452 | }
453 |
454 | scope.update();
455 | scope.dispatchEvent( startEvent );
456 | scope.dispatchEvent( changeEvent );
457 | scope.dispatchEvent( endEvent );
458 |
459 | }
460 |
461 | function onKeyDown( event ) {
462 |
463 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return;
464 |
465 | switch ( event.keyCode ) {
466 |
467 | case scope.keys.UP:
468 | scope.pan( 0, scope.keyPanSpeed );
469 | scope.update();
470 | break;
471 |
472 | case scope.keys.BOTTOM:
473 | scope.pan( 0, - scope.keyPanSpeed );
474 | scope.update();
475 | break;
476 |
477 | case scope.keys.LEFT:
478 | scope.pan( scope.keyPanSpeed, 0 );
479 | scope.update();
480 | break;
481 |
482 | case scope.keys.RIGHT:
483 | scope.pan( - scope.keyPanSpeed, 0 );
484 | scope.update();
485 | break;
486 |
487 | }
488 |
489 | }
490 |
491 | function touchstart( event ) {
492 |
493 | if ( scope.enabled === false ) return;
494 |
495 | switch ( event.touches.length ) {
496 |
497 | case 1: // one-fingered touch: rotate
498 |
499 | if ( scope.noRotate === true ) return;
500 |
501 | state = STATE.TOUCH_ROTATE;
502 |
503 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
504 | break;
505 |
506 | case 2: // two-fingered touch: dolly
507 |
508 | if ( scope.noZoom === true ) return;
509 |
510 | state = STATE.TOUCH_DOLLY;
511 |
512 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
513 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
514 | var distance = Math.sqrt( dx * dx + dy * dy );
515 | dollyStart.set( 0, distance );
516 | break;
517 |
518 | case 3: // three-fingered touch: pan
519 |
520 | if ( scope.noPan === true ) return;
521 |
522 | state = STATE.TOUCH_PAN;
523 |
524 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
525 | break;
526 |
527 | default:
528 |
529 | state = STATE.NONE;
530 |
531 | }
532 |
533 | scope.dispatchEvent( startEvent );
534 |
535 | }
536 |
537 | function touchmove( event ) {
538 |
539 | if ( scope.enabled === false ) return;
540 |
541 | event.preventDefault();
542 | event.stopPropagation();
543 |
544 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
545 |
546 | switch ( event.touches.length ) {
547 |
548 | case 1: // one-fingered touch: rotate
549 |
550 | if ( scope.noRotate === true ) return;
551 | if ( state !== STATE.TOUCH_ROTATE ) return;
552 |
553 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
554 | rotateDelta.subVectors( rotateEnd, rotateStart );
555 |
556 | // rotating across whole screen goes 360 degrees around
557 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
558 | // rotating up and down along whole screen attempts to go 360, but limited to 180
559 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
560 |
561 | rotateStart.copy( rotateEnd );
562 |
563 | scope.update();
564 | break;
565 |
566 | case 2: // two-fingered touch: dolly
567 |
568 | if ( scope.noZoom === true ) return;
569 | if ( state !== STATE.TOUCH_DOLLY ) return;
570 |
571 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
572 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
573 | var distance = Math.sqrt( dx * dx + dy * dy );
574 |
575 | dollyEnd.set( 0, distance );
576 | dollyDelta.subVectors( dollyEnd, dollyStart );
577 |
578 | if ( dollyDelta.y > 0 ) {
579 |
580 | scope.dollyOut();
581 |
582 | } else {
583 |
584 | scope.dollyIn();
585 |
586 | }
587 |
588 | dollyStart.copy( dollyEnd );
589 |
590 | scope.update();
591 | break;
592 |
593 | case 3: // three-fingered touch: pan
594 |
595 | if ( scope.noPan === true ) return;
596 | if ( state !== STATE.TOUCH_PAN ) return;
597 |
598 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
599 | panDelta.subVectors( panEnd, panStart );
600 |
601 | scope.pan( panDelta.x, panDelta.y );
602 |
603 | panStart.copy( panEnd );
604 |
605 | scope.update();
606 | break;
607 |
608 | default:
609 |
610 | state = STATE.NONE;
611 |
612 | }
613 |
614 | }
615 |
616 | function touchend( /* event */ ) {
617 |
618 | if ( scope.enabled === false ) return;
619 |
620 | scope.dispatchEvent( endEvent );
621 | state = STATE.NONE;
622 |
623 | }
624 |
625 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
626 | this.domElement.addEventListener( 'mousedown', onMouseDown, false );
627 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
628 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
629 |
630 | this.domElement.addEventListener( 'touchstart', touchstart, false );
631 | this.domElement.addEventListener( 'touchend', touchend, false );
632 | this.domElement.addEventListener( 'touchmove', touchmove, false );
633 |
634 | window.addEventListener( 'keydown', onKeyDown, false );
635 |
636 | // force an update at start
637 | this.update();
638 |
639 | };
640 |
641 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
--------------------------------------------------------------------------------
/js/THREE.FBOHelper.js:
--------------------------------------------------------------------------------
1 | ;(function() {
2 |
3 | "use strict";
4 |
5 | var root = this
6 |
7 | var has_require = typeof require !== 'undefined'
8 |
9 | var THREE = root.THREE || has_require && require('three')
10 | if( !THREE )
11 | throw new Error( 'FBOHelper requires three.js' )
12 |
13 | "use strict";
14 |
15 | var layerCSS = `
16 | #fboh-fbos-list{
17 | all: unset;
18 | position: fixed;
19 | left: 0;
20 | top: 0;
21 | z-index: 1000000;
22 | width: 150px;
23 | }
24 | #fboh-fbos-list, #fboh-fbos-list *, #fboh-hotspot, #fboh-label, #fboh-info{
25 | box-sizing: border-box;
26 | font-family: 'Roboto Mono', 'courier new', courier, monospace;
27 | font-size: 11px;
28 | line-height: 1.4em;
29 | }
30 | #fboh-fbos-list li{
31 | cursor: pointer;
32 | color: white;
33 | width: 100%;
34 | padding: 4px 0;
35 | border-top: 1px solid #888;
36 | border-bottom: 1px solid black;
37 | background-color: #444;
38 | text-align: center;
39 | text-shadow: 0 -1px black;
40 | }
41 | #fboh-fbos-list li:hover{
42 | background-color: rgba( 158, 253, 56, .5 );
43 | }
44 | #fboh-fbos-list li.active{
45 | background-color: rgba( 158, 253, 56, .5 );
46 | color: white;
47 | text-shadow: 0 1px black;
48 | }
49 | #fboh-hotspot{
50 | position: absolute;
51 | left: 0;
52 | top: 0;
53 | background-color: rgba( 158, 253, 56,.5);
54 | pointer-events: none;
55 | }
56 | #fboh-label{
57 | position: absolute;
58 | left: 0;
59 | bottom: 0;
60 | transform-origin: bottom left;
61 | pointer-events: none;
62 | }
63 | #fboh-info{
64 | display: none;
65 | position: absolute;
66 | left: 160px;
67 | top: 10px;
68 | pointer-events: none;
69 | }
70 | .fboh-card{
71 | display: block;
72 | white-space: nowrap;
73 | color: black;
74 | padding: 10px;
75 | background-color: white;
76 | border: 1px solid black;
77 | }
78 | `;
79 |
80 | let formats = {}
81 | formats[ THREE.AlphaFormat ] = 'THREE.AlphaFormat';
82 | formats[ THREE.RGBFormat ] = 'THREE.RGBFormat';
83 | formats[ THREE.RGBAFormat ] = 'THREE.RGBAFormat';
84 | formats[ THREE.LuminanceFormat ] = 'THREE.LuminanceFormat';
85 | formats[ THREE.LuminanceAlphaFormat ] = 'THREE.LuminanceAlphaFormat';
86 | //formats[ THREE.RGBEFormat ] = 'THREE.RGBEFormat';
87 |
88 | let types = {}
89 | types[ THREE.UnsignedByteType ] = 'THREE.UnsignedByteType';
90 | types[ THREE.ByteType ] = 'THREE.ByteType';
91 | types[ THREE.ShortType ] = 'THREE.ShortType';
92 | types[ THREE.UnsignedShortType ] = 'THREE.UnsignedShortType';
93 | types[ THREE.IntType ] = 'THREE.IntType';
94 | types[ THREE.UnsignedIntType ] = 'THREE.UnsignedIntType';
95 | types[ THREE.FloatType ] = 'THREE.FloatType';
96 | types[ THREE.HalfFloatType ] = 'THREE.HalfFloatType';
97 | types[ THREE.UnsignedShort4444Type ] = 'THREE.UnsignedShort4444Type';
98 | types[ THREE.UnsignedShort5551Type ] = 'THREE.UnsignedShort5551Type';
99 | types[ THREE.UnsignedShort565Type ] = 'THREE.UnsignedShort565Type';
100 |
101 | class FBOHelper {
102 |
103 | constructor( renderer ) {
104 |
105 | this.renderer = renderer;
106 | this.autoUpdate = false;
107 | this.fbos = []
108 | this.list = document.createElement( 'ul' );
109 | this.list.setAttribute( 'id', 'fboh-fbos-list' );
110 | document.body.appendChild( this.list );
111 |
112 | this.scene = new THREE.Scene();
113 | this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, .000001, 1000 );
114 |
115 | this.raycaster = new THREE.Raycaster();
116 | this.mouse = new THREE.Vector2();
117 |
118 | this.grid = document.createElement( 'div' );
119 | this.grid.setAttribute( 'style', 'position: fixed; left: 50%; top: 50%; border: 1px solid #000000; transform: translate3d(-50%, -50%, 0 ); box-shadow: 0 0 50px black; display: none' );
120 | this.grid.setAttribute( 'id', 'bfoh-grid' );
121 | document.body.appendChild( this.grid );
122 |
123 | this.hotspot = document.createElement( 'div' );
124 | this.hotspot.setAttribute( 'id', 'fboh-hotspot' );
125 | this.grid.appendChild( this.hotspot );
126 |
127 | this.label = document.createElement( 'div' );
128 | this.label.setAttribute( 'id', 'fboh-label' );
129 | this.label.className = 'fboh-card';
130 | this.hotspot.appendChild( this.label );
131 |
132 | this.info = document.createElement( 'div' );
133 | this.info.setAttribute( 'id', 'fboh-info' );
134 | this.info.className = 'fboh-card';
135 | document.body.appendChild( this.info );
136 |
137 | this.currentObj = null;
138 | this.currentU = 0;
139 | this.currentV = 0;
140 |
141 | this.fboMap = new Map();
142 |
143 | this.offsetX = 0;
144 | this.offsetY = 0;
145 |
146 | this.grid.appendChild( this.hotspot );
147 |
148 | const head = window.document.head || window.document.getElementsByTagName('head')[0];
149 | const style = window.document.createElement('style');
150 |
151 | style.type = 'text/css';
152 | if (style.styleSheet){
153 | style.styleSheet.cssText = layerCSS;
154 | } else {
155 | style.appendChild(document.createTextNode(layerCSS));
156 | }
157 |
158 | head.appendChild(style);
159 |
160 | const ss = document.createElement( 'link' );
161 | ss.type = 'text/css';
162 | ss.rel = 'stylesheet';
163 | ss.href = 'https://fonts.googleapis.com/css?family=Roboto+Mono';
164 |
165 | head.appendChild( ss );
166 |
167 | this.grid.addEventListener( 'wheel', e => {
168 |
169 | var direction = ( e.deltaY < 0 ) ? 1 : -1;
170 |
171 | this.camera.zoom += direction / 50;
172 | this.camera.updateProjectionMatrix();
173 | this.grid.style.transform = `translate3d(-50%, -50%, 0 ) scale(${this.camera.zoom},${this.camera.zoom}) translate3d(${this.offsetX}px,${this.offsetY}px,0) `;
174 | this.label.style.transform = `scale(${1/this.camera.zoom},${1/this.camera.zoom})`;
175 | this.hotspot.style.transform = `scale(${1/this.camera.zoom},${1/this.camera.zoom})`;
176 | this.hotspot.style.borderWidth = `${1/this.camera.zoom}px`;
177 | this.readPixel( this.currentObj, this.currentU, this.currentV );
178 |
179 | } );
180 |
181 | let dragging = false;
182 | let mouseStart = { x: 0, y: 0 };
183 | let offsetStart = { x: 0, y: 0 };
184 |
185 | this.grid.addEventListener( 'mousedown', e => {
186 |
187 | dragging = true;
188 | mouseStart.x = e.clientX;
189 | mouseStart.y = e.clientY;
190 | offsetStart.x = this.offsetX;
191 | offsetStart.y = this.offsetY;
192 |
193 | } );
194 |
195 | this.grid.addEventListener( 'mouseup', e => {
196 |
197 | dragging = false;
198 |
199 | } );
200 |
201 | this.grid.addEventListener( 'mouseout', e => {
202 |
203 | this.label.style.display = 'none';
204 | dragging = false;
205 |
206 | } );
207 |
208 | this.grid.addEventListener( 'mouseover', e => {
209 |
210 | this.label.style.display = 'block';
211 |
212 | } );
213 |
214 | this.grid.addEventListener( 'mousemove', e => {
215 |
216 | if( dragging ) {
217 |
218 | this.offsetX = offsetStart.x + ( e.clientX - mouseStart.x ) / this.camera.zoom;
219 | this.offsetY = offsetStart.y + ( e.clientY - mouseStart.y ) / this.camera.zoom;
220 | this.camera.position.x = -this.offsetX;
221 | this.camera.position.y = this.offsetY;
222 |
223 | this.grid.style.transform = `translate3d(-50%, -50%, 0 ) scale(${this.camera.zoom},${this.camera.zoom}) translate3d(${this.offsetX}px,${this.offsetY}px,0)`;
224 |
225 | } else {
226 |
227 | this.mouse.x = ( e.clientX / renderer.domElement.clientWidth ) * 2 - 1;
228 | this.mouse.y = - ( e.clientY / renderer.domElement.clientHeight ) * 2 + 1;
229 | this.raycaster.setFromCamera( this.mouse, this.camera );
230 |
231 | const intersects = this.raycaster.intersectObject( this.currentObj.quad, true );
232 |
233 | if ( intersects.length > 0 ) {
234 |
235 | this.readPixel( this.fboMap.get( intersects[ 0 ].object ), intersects[ 0 ].uv.x, intersects[ 0 ].uv.y );
236 | this.label.style.display = 'block';
237 |
238 | } else {
239 |
240 | this.label.style.display = 'none';
241 |
242 | }
243 |
244 | }
245 |
246 | } );
247 |
248 | window.addEventListener( 'keydown', e => {
249 | if( e.keyCode === 27 ) {
250 | this.hide();
251 | }
252 | } );
253 |
254 | this.grid.addEventListener( 'keydown', e => {
255 | if( e.keyCode === 27 ) {
256 | this.hide();
257 | }
258 | } );
259 |
260 | }
261 |
262 | hide() {
263 |
264 | this.hideAll();
265 | this.info.style.display = 'none';
266 | this.grid.style.display = 'none';
267 | this.currentObj = null;
268 |
269 | }
270 |
271 | attach( fbo, name, formatter ) {
272 |
273 | var li = document.createElement( 'li' );
274 |
275 | li.textContent = name;
276 |
277 | if( fbo.image ) {
278 | fbo.width = fbo.image.width;
279 | fbo.height = fbo.image.height;
280 | }
281 |
282 | const width = 600;
283 | const height = fbo.height * width / fbo.width;
284 |
285 | const material = new THREE.MeshBasicMaterial( { map: fbo, side: THREE.DoubleSide } );
286 | const quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 1, 1 ), material );
287 | if( !fbo.flipY ) quad.rotation.x = Math.PI;
288 | quad.visible = false;
289 | quad.width = width;
290 | quad.height = height;
291 | quad.scale.set( width, height, 1. );
292 | this.scene.add( quad );
293 |
294 | var fboData = {
295 | width: width,
296 | height: height,
297 | name: name,
298 | fbo: fbo,
299 | flipY: fbo.flipY,
300 | li: li,
301 | visible: false,
302 | quad: quad,
303 | material: material,
304 | formatter: formatter
305 | };
306 | this.fbos.push( fboData );
307 | this.fboMap.set( quad, fboData );
308 |
309 | li.addEventListener( 'click', e => {
310 | quad.visible = !quad.visible;
311 | if( quad.visible ) {
312 | this.hideAll();
313 | quad.visible = true;
314 | li.classList.add( 'active' );
315 | this.info.style.display = 'block';
316 | this.grid.style.display = 'block';
317 | this.grid.style.width = ( fboData.width + 2 ) + 'px';
318 | this.grid.style.height = ( fboData.height + 2 ) + 'px';
319 | this.currentObj = fboData;
320 | this.info.innerHTML = `Width: ${fbo.width} Height: ${fbo.height} Format: ${formats[fbo.texture?fbo.texture.format:fbo.format]} Type: ${types[fbo.texture?fbo.texture.type:fbo.type]}`;
321 | } else {
322 | this.info.style.display = 'none';
323 | li.classList.remove( 'active' );
324 | this.grid.style.display = 'none';
325 | this.currentObj = null;
326 | }
327 | } );
328 |
329 | this.buildList();
330 |
331 | }
332 |
333 | detach( f ) {
334 |
335 | var p = 0;
336 | for( var fbo of this.fbos ) {
337 | if( fbo.fbo === f ) {
338 | this.fbos.splice( p, 1 )
339 | }
340 | p++;
341 | }
342 |
343 | this.buildList();
344 |
345 | }
346 |
347 | refreshFBO( f ) {
348 |
349 | for( var fbo of this.fbos ) {
350 | if( fbo.fbo === f ) {
351 | const width = 600;
352 | const height = f.height * width / f.width;
353 | fbo.width = width;
354 | fbo.height = height;
355 | fbo.quad.width = width;
356 | fbo.quad.height = height;
357 | fbo.quad.scale.set( width, height, 1. );
358 | }
359 | }
360 |
361 | }
362 |
363 | hideAll() {
364 |
365 | this.fbos.forEach( fbo => {
366 | fbo.quad.visible = false;
367 | fbo.li.classList.remove( 'active' );
368 | } );
369 |
370 | }
371 |
372 | buildList() {
373 |
374 | while( this.list.firstChild ) this.list.removeChild( this.list.firstChild );
375 |
376 | for( var fbo of this.fbos ) {
377 | this.list.appendChild( fbo.li );
378 | }
379 |
380 | }
381 |
382 | setSize( w, h ) {
383 |
384 | this.camera.left = w / - 2;
385 | this.camera.right = w / 2;
386 | this.camera.top = h / 2;
387 | this.camera.bottom = h / - 2;
388 |
389 | this.camera.updateProjectionMatrix();
390 |
391 | }
392 |
393 | readPixel( obj, u, v ) {
394 |
395 | this.currentU = u;
396 | this.currentV = v;
397 |
398 | if( this.currentObj === null ) return;
399 |
400 | const fbo = obj.fbo;
401 |
402 | const x = ~~( fbo.width * u );
403 | const y = ~~( fbo.height * v );
404 |
405 | let types = {}
406 | types[ THREE.UnsignedByteType ] = Uint8Array;
407 | types[ THREE.ByteType ] = Int8Array;
408 | types[ THREE.ShortType ] = Int16Array;
409 | types[ THREE.UnsignedShortType ] = Uint16Array;
410 | types[ THREE.IntType ] = Int32Array;
411 | types[ THREE.UnsignedIntType ] = Uint32Array;
412 | types[ THREE.FloatType ] = Float32Array;
413 | types[ THREE.HalfFloatType ] = null;
414 | types[ THREE.UnsignedShort4444Type ] = Uint16Array;
415 | types[ THREE.UnsignedShort5551Type ] = Uint16Array;
416 | types[ THREE.UnsignedShort565Type ] = Uint16Array;
417 |
418 | var type = types[ fbo.texture ? fbo.texture.type : fbo.type ];
419 | if( type === null ) {
420 | console.warning( fbo.texture ? fbo.texture.type : fbo.type + ' not supported' );
421 | return;
422 | }
423 |
424 | const pixelBuffer = new ( type )( 4 );
425 |
426 | this.renderer.readRenderTargetPixels( fbo, x, y, 1, 1, pixelBuffer );
427 | const posTxt = `X : ${x} Y: ${y} u: ${u} v: ${v}`;
428 | const dataTxt = obj.formatter ?
429 | obj.formatter( {
430 | x: x,
431 | y: y,
432 | u: u,
433 | v: v,
434 | r: pixelBuffer[ 0 ],
435 | g: pixelBuffer[ 1 ],
436 | b: pixelBuffer[ 2 ],
437 | a: pixelBuffer[ 3 ]
438 | } )
439 | :
440 | `R: ${pixelBuffer[ 0 ]} G: ${pixelBuffer[ 1 ]} B: ${pixelBuffer[ 2 ]} A: ${pixelBuffer[ 3 ]}`;
441 | this.label.innerHTML = `${posTxt} ${dataTxt}`;
442 |
443 | const ox = ~~( u * fbo.width ) * obj.quad.width / fbo.width;
444 | const oy = ~~( obj.flipY ? ( 1 - v ) * fbo.height : v * fbo.height ) * obj.quad.height / fbo.height;
445 | this.hotspot.style.width = `${obj.quad.width / fbo.width}px`;
446 | this.hotspot.style.height = `${obj.quad.height / fbo.height}px`;
447 | this.hotspot.style.transform = `translate3d(${ox}px,${oy}px,0)`;
448 | this.label.style.bottom = ( obj.quad.height / fbo.height ) + 'px';
449 |
450 | }
451 |
452 | update() {
453 |
454 | this.renderer.autoClear = false;
455 | this.renderer.render( this.scene, this.camera );
456 | this.renderer.autoClear = true;
457 | if( this.autoUpdate ) this.readPixel( this.currentObj, this.currentU, this.currentV );
458 |
459 | }
460 |
461 | }
462 |
463 | if( typeof exports !== 'undefined' ) {
464 | if( typeof module !== 'undefined' && module.exports ) {
465 | exports = module.exports = FBOHelper
466 | }
467 | exports.FBOHelper = FBOHelper
468 | }
469 | else {
470 | root.FBOHelper = FBOHelper
471 | }
472 |
473 | }).call(this);
474 |
475 |
--------------------------------------------------------------------------------
/js/isMobile.min.js:
--------------------------------------------------------------------------------
1 | !function(a){var b=/iPhone/i,c=/iPod/i,d=/iPad/i,e=/(?=.*\bAndroid\b)(?=.*\bMobile\b)/i,f=/Android/i,g=/(?=.*\bAndroid\b)(?=.*\bSD4930UR\b)/i,h=/(?=.*\bAndroid\b)(?=.*\b(?:KFOT|KFTT|KFJWI|KFJWA|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|KFARWI|KFASWI|KFSAWI|KFSAWA)\b)/i,i=/Windows Phone/i,j=/(?=.*\bWindows\b)(?=.*\bARM\b)/i,k=/BlackBerry/i,l=/BB10/i,m=/Opera Mini/i,n=/(CriOS|Chrome)(?=.*\bMobile\b)/i,o=/(?=.*\bFirefox\b)(?=.*\bMobile\b)/i,p=new RegExp("(?:Nexus 7|BNTV250|Kindle Fire|Silk|GT-P1000)","i"),q=function(a,b){return a.test(b)},r=function(a){var r=a||navigator.userAgent,s=r.split("[FBAN");if("undefined"!=typeof s[1]&&(r=s[0]),s=r.split("Twitter"),"undefined"!=typeof s[1]&&(r=s[0]),this.apple={phone:q(b,r),ipod:q(c,r),tablet:!q(b,r)&&q(d,r),device:q(b,r)||q(c,r)||q(d,r)},this.amazon={phone:q(g,r),tablet:!q(g,r)&&q(h,r),device:q(g,r)||q(h,r)},this.android={phone:q(g,r)||q(e,r),tablet:!q(g,r)&&!q(e,r)&&(q(h,r)||q(f,r)),device:q(g,r)||q(h,r)||q(e,r)||q(f,r)},this.windows={phone:q(i,r),tablet:q(j,r),device:q(i,r)||q(j,r)},this.other={blackberry:q(k,r),blackberry10:q(l,r),opera:q(m,r),firefox:q(o,r),chrome:q(n,r),device:q(k,r)||q(l,r)||q(m,r)||q(o,r)||q(n,r)},this.seven_inch=q(p,r),this.any=this.apple.device||this.android.device||this.windows.device||this.other.device||this.seven_inch,this.phone=this.apple.phone||this.android.phone||this.windows.phone,this.tablet=this.apple.tablet||this.android.tablet||this.windows.tablet,"undefined"==typeof window)return this},s=function(){var a=new r;return a.Class=r,a};"undefined"!=typeof module&&module.exports&&"undefined"==typeof window?module.exports=r:"undefined"!=typeof module&&module.exports&&"undefined"!=typeof window?module.exports=s():"function"==typeof define&&define.amd?define("isMobile",[],a.isMobile=s()):a.isMobile=s()}(this);
--------------------------------------------------------------------------------