├── .gitignore ├── README.md ├── dst ├── assets │ ├── css │ │ └── styles.css │ ├── data │ │ ├── _s.txt │ │ └── s.txt │ ├── glsl │ │ ├── torus.frag │ │ └── torus.vert │ └── js │ │ ├── lib-head │ │ └── useragnt-all.min.js │ │ ├── lib │ │ └── OrbitControls.js │ │ └── main.min.js └── index.html ├── gulp └── tasks │ ├── browsersync.js │ ├── glsl.js │ ├── html.js │ ├── javascript.js │ └── sass.js ├── gulpfile.js ├── package.json ├── screenshot1.png ├── screenshot2.png └── src ├── assets ├── css │ ├── styles.scss │ └── vendor │ │ └── _initial.scss ├── data │ └── s.txt ├── glsl │ ├── torus.frag │ └── torus.vert └── js │ ├── lib-head │ └── useragnt-all.min.js │ ├── lib │ └── OrbitControls.js │ ├── main.js │ └── module │ ├── analyzer.js │ ├── audio.js │ ├── controls.js │ ├── resize-watch.js │ ├── torus.js │ └── webgl.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | 7 | # Thumbnails 8 | ._* 9 | 10 | # Files that might appear on external disk 11 | .Spotlight-V100 12 | .Trashes 13 | 14 | 15 | # node.js 16 | lib-cov 17 | *.seed 18 | *.log 19 | *.csv 20 | *.dat 21 | *.out 22 | *.pid 23 | *.gz 24 | pids 25 | logs 26 | results 27 | npm-debug.log 28 | node_modules 29 | npm-debug.log.* 30 | 31 | # Sass 32 | .sass-cache 33 | 34 | # SublimeText 35 | *.sublime-project 36 | *.sublime-workspace 37 | sftp-config.json 38 | 39 | #vim 40 | .*.s[a-w][a-z] 41 | *.un~ 42 | Session.vim 43 | .netrwhist 44 | *~ 45 | 46 | .idea 47 | .htaccess 48 | .htpasswd 49 | pull.php 50 | 51 | #dev 52 | build/* 53 | #dst/* 54 | build/deploy/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # audio-visualizer-torus 2 | 3 | ## Description 4 | The demo of audio visualizer using cubic interpolation. 5 | 6 | * [demo](https://mnmxmx.github.io/audio-visualizer-torus/dst/) 7 | * about [interpolation](http://paulbourke.net/miscellaneous/interpolation/) 8 | [](./screenshot1.png) 9 | [](./screenshot2.png) 10 | 11 | ## Usage 12 | * Clone repository 13 | * Install Node.js 14 | * Run following commands 15 | ``` 16 | npm install 17 | npm start 18 | ``` 19 | 20 | ## Thanks 21 | * The music : [Don't Talk About It](http://spoti.fi/2flLV1s) 22 | by [Michael Mar](https://www.facebook.com/michaelmarmusic/), feat. [Kyan Palmer](https://www.facebook.com/kyanpalmer/) 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /dst/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | html,body,article,section,nav,aside,h1,h2,h3,h4,h5,h6,header,footer,address,p,ol,ul,li,dl,dt,dd,div,a,strong,small,sup,span,img,iframe,embed,object,video,audio,table,tr,td,th,canvas,svg{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}html{cursor:default;line-height:1;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:transparent;font-family:arial}html.windows{-webkit-font-smoothing:antialiased}::-moz-selection{background-color:#ccc;color:#000;text-shadow:none}::selection{background-color:#ccc;color:#000;text-shadow:none}ol,ul{list-style:none}table{border-collapse:collapse;border-spacing:0}article,section,nav,aside,header,footer{display:block}video,audio,canvas{display:inline-block}audio:not([controls]){display:none;height:0}hr{box-sizing:content-box;height:0;overflow:visible}strong{font-weight:inherit}strong{font-weight:bolder}sub,sup{line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}svg{fill:currentColor}svg:not(:root){overflow:hidden}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}a{-ms-touch-action:manipulation;touch-action:manipulation}body{overflow:hidden}.stats{position:absolute;top:0;right:0;z-index:2}.file{position:absolute;top:15px;left:15px;padding:10px;box-sizing:border-box;border:1px solid #000;letter-spacing:.01em;color:#000;cursor:pointer;-webkit-transition:opacity .4s;transition:opacity .4s;font-size:12px;padding:15px 30px;letter-spacing:.15em;display:none}.file.isHidden{display:none}.file input{display:none}.credits{position:absolute;top:20px;left:20px;letter-spacing:.2em;font-size:18px}.credits p{margin-bottom:20px}.credits a{color:#ff52aa}.start{position:absolute;width:100%;height:100%;top:0;left:0;background:rgba(255,255,255,.8);letter-spacing:.4em;font-size:18px;z-index:1000000000;-webkit-transition:opacity .5s,visibility 0s .5s;transition:opacity .5s,visibility 0s .5s}.start.isHidden{opacity:0;visibility:hidden}.start .play,.start .loading{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.start .play.isHidden,.start .loading.isHidden{opacity:0;visibility:hidden}.start .play{border:2px solid #000;padding:20px 40px;cursor:pointer} -------------------------------------------------------------------------------- /dst/assets/data/_s.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnmxmx/audio-visualizer-torus/e48841d2532f2bee8546a582005df3b88d9d5db4/dst/assets/data/_s.txt -------------------------------------------------------------------------------- /dst/assets/data/s.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnmxmx/audio-visualizer-torus/e48841d2532f2bee8546a582005df3b88d9d5db4/dst/assets/data/s.txt -------------------------------------------------------------------------------- /dst/assets/glsl/torus.frag: -------------------------------------------------------------------------------- 1 | #define GLSLIFY 1 2 | varying vec2 vUv; 3 | varying vec3 vPos; 4 | varying float vFrequency; 5 | 6 | const vec3 objColor = vec3(1.0); 7 | 8 | const vec3 hemiLight_g_1 = vec3(0.8980392156862745,0.,0.3803921568627451); 9 | const vec3 hemiLight_s_1 = vec3(0.9372549019607843,0.796078431372549,0.011764705882352941); 10 | const vec3 hemiLight_g_2 = vec3(0.6352941176470588,0.043137254901960784,0.4745098039215686); 11 | const vec3 hemiLight_s_2 = vec3(0.6352941176470588,0.043137254901960784,0.1411764705882353); 12 | 13 | const vec3 dirLight = vec3(0.2); 14 | const vec3 dirLight_2 = vec3(0.15); 15 | 16 | const vec3 hemiLightPos_1 = vec3(1.0, 0.0, -1.0); 17 | const vec3 hemiLightPos_2 = vec3(-0.5, 0.5, 1.0); 18 | 19 | const vec3 dirLightPos = vec3(-30, 50, 50); 20 | const vec3 dirLightPos_2 = vec3(30, -50, -50); 21 | 22 | vec3 calcIrradiance_hemi(vec3 newNormal, vec3 lightPos, vec3 grd, vec3 sky){ 23 | float dotNL = dot(newNormal, normalize(lightPos)); 24 | float hemiDiffuseWeight = 0.5 * dotNL + 0.5; 25 | 26 | return mix(grd, sky, hemiDiffuseWeight); 27 | } 28 | 29 | vec3 calcIrradiance_dir(vec3 newNormal, vec3 lightPos, vec3 light){ 30 | float dotNL = dot(newNormal, normalize(lightPos)); 31 | 32 | return light * max(0.0, dotNL); 33 | } 34 | 35 | void main(){ 36 | 37 | vec3 _normal = normalize( cross(dFdx(vPos), dFdy(vPos)) ); 38 | 39 | vec3 hemiColor = vec3(0.0); 40 | hemiColor += calcIrradiance_hemi(_normal, hemiLightPos_1, hemiLight_g_1, hemiLight_s_1) * 0.7; 41 | hemiColor += calcIrradiance_hemi(_normal, hemiLightPos_2, hemiLight_g_2, hemiLight_s_2) * 0.8; 42 | 43 | vec3 dirColor = vec3(0.0); 44 | dirColor += calcIrradiance_dir(_normal, dirLightPos, dirLight); 45 | dirColor += calcIrradiance_dir(_normal, dirLightPos_2, dirLight_2); 46 | 47 | vec3 color = objColor * hemiColor; 48 | 49 | color += dirColor; 50 | 51 | vec3 offsetColor = vec3(vFrequency * 0.2, vFrequency * 1.5, -vFrequency * 0.7) * 0.008; 52 | 53 | color += offsetColor; 54 | color.g = min(0.9, color.g); 55 | 56 | color.r = (color.g == 0.9 && color.r > 0.94) ? 0.94 : color.r; 57 | 58 | color = min(vec3(1.0), color); 59 | 60 | float offsetColor2 = min(0.0, (vPos.x + vPos.y) / 2000.0); 61 | color -= vec3(offsetColor2) * vec3(-0.3, -1.0, 1.2); 62 | 63 | color = min(vec3(1.0), color + 0.1); 64 | 65 | gl_FragColor = vec4(color, 0.95); 66 | } -------------------------------------------------------------------------------- /dst/assets/glsl/torus.vert: -------------------------------------------------------------------------------- 1 | #define GLSLIFY 1 2 | attribute float aFrequency; 3 | attribute float aRadian; 4 | 5 | uniform float uRadius; 6 | uniform float uTick; 7 | 8 | varying vec2 vUv; 9 | varying vec3 vPos; 10 | varying float vFrequency; 11 | 12 | const float PI = 3.1415926; 13 | 14 | mat2 calcRotate2D(float _time){ 15 | float _sin = sin(_time); 16 | float _cos = cos(_time); 17 | return mat2(_cos, _sin, -_sin, _cos); 18 | } 19 | 20 | void main(){ 21 | vUv = uv; 22 | vFrequency = min(10.0, aFrequency); 23 | 24 | float time = uTick * 0.005; 25 | 26 | vec3 offset = vec3(cos(aRadian), sin(aRadian), 0.0) * uRadius; 27 | 28 | vec3 _position = position - offset; 29 | 30 | _position *= (1.0 + aFrequency); 31 | 32 | _position += offset; 33 | 34 | _position.xy = calcRotate2D(time * 1.2) * _position.xy; 35 | 36 | vec4 mvPos = vec4(_position, 1.0); 37 | 38 | mvPos = modelViewMatrix * mvPos; 39 | 40 | vPos = mvPos.xyz; 41 | 42 | gl_Position =projectionMatrix * mvPos; 43 | } -------------------------------------------------------------------------------- /dst/assets/js/lib-head/useragnt-all.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Useragnt 3 | * v0.3.1 4 | * 5 | * Copyright (c) 2016 Yuichiroh Arai 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/mit-license.php 8 | * 9 | * detects: mobile, tablet, pc, windows, mac, linux, ios, android, edge, ie, safari, chrome, firefox, opera 10 | !*/ 11 | !function(e,o){function i(e){return n.indexOf(e)!=-1}function r(e){var o=e.split("."),i={};return i.str=e,i.float=parseFloat(e)||0,i.major=o.length>0?parseInt(o[0])||0:0,i.minor=o.length>1?parseInt(o[1])||0:0,i.build=o.length>2?parseInt(o[2])||0:0,i.revision=o.length>3?parseInt(o[3])||0:0,i}var a={};a._detects=["mobile","tablet","pc","windows","mac","linux","ios","android","edge","ie","safari","chrome","firefox","opera"];var n=a.userAgent=e.navigator.userAgent.toLowerCase();a.mobile=i("iphone")||i("ipod")||i("android")&&i("mobile")||i("windows")&&i("phone")||i("firefox")&&i("mobile")||i("blackberry"),a.tablet=i("ipad")||i("android")&&!i("mobile")||i("windows")&&i("touch")&&!i("tablet pc")||i("firefox")&&i("tablet")||i("kindle")||i("silk")||i("playbook"),a.pc=!i("iphone")&&!i("ipod")&&!i("ipad")&&!i("android")&&(!i("windows")||!i("phone")&&(!i("touch")||i("tablet pc")))&&(!i("firefox")||!i("mobile")&&!i("tablet"))&&!i("blackberry")&&!i("kindle")&&!i("silk")&&!i("playbook"),a.windows=i("windows"),a.mac=i("mac os x")&&!i("iphone")&&!i("ipad")&&!i("ipod"),a.linux=i("linux")&&!i("android"),a.ios=i("iphone")||i("ipad")||i("ipod"),a.ios&&(a.ios=new Boolean(!0),n.match(/ os ([\d_]+)/g),a.ios.version=r(RegExp.$1.replace("_","."))),a.android=i("android"),a.android&&(a.android=new Boolean(!0),n.match(/android ([\d\.]+)/g),a.android.version=r(RegExp.$1)),a.edge=i("edge"),a.ie=i("trident")||i("msie"),a.safari=i("safari")&&!i("android")&&!i("edge")&&!i("opera")&&!i("opr")&&!i("chrome"),a.chrome=i("chrome")&&!i("edge")&&!i("opera")&&!i("opr"),a.chrome&&(a.chrome=new Boolean(!0),n.match(/chrome\/([\d.]+)/g),a.chrome.version=r(RegExp.$1)),a.firefox=i("firefox")&&!i("edge"),a.opera=i("opera")||i("opr");var d,t,s,l=a._classPrefix="",p=o.documentElement,c=p.className;for(t=a._detects.length,d=0;d EPS 189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 190 | 191 | if ( zoomChanged || 192 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 194 | 195 | scope.dispatchEvent( changeEvent ); 196 | 197 | lastPosition.copy( scope.object.position ); 198 | lastQuaternion.copy( scope.object.quaternion ); 199 | zoomChanged = false; 200 | 201 | return true; 202 | 203 | } 204 | 205 | return false; 206 | 207 | }; 208 | 209 | }(); 210 | 211 | this.dispose = function () { 212 | 213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 215 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 216 | 217 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 218 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 219 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 220 | 221 | document.removeEventListener( 'mousemove', onMouseMove, false ); 222 | document.removeEventListener( 'mouseup', onMouseUp, false ); 223 | 224 | window.removeEventListener( 'keydown', onKeyDown, false ); 225 | 226 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 227 | 228 | }; 229 | 230 | // 231 | // internals 232 | // 233 | 234 | var scope = this; 235 | 236 | var changeEvent = { type: 'change' }; 237 | var startEvent = { type: 'start' }; 238 | var endEvent = { type: 'end' }; 239 | 240 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; 241 | 242 | var state = STATE.NONE; 243 | 244 | var EPS = 0.000001; 245 | 246 | // current position in spherical coordinates 247 | var spherical = new THREE.Spherical(); 248 | var sphericalDelta = new THREE.Spherical(); 249 | 250 | var scale = 1; 251 | var panOffset = new THREE.Vector3(); 252 | var zoomChanged = false; 253 | 254 | var rotateStart = new THREE.Vector2(); 255 | var rotateEnd = new THREE.Vector2(); 256 | var rotateDelta = new THREE.Vector2(); 257 | 258 | var panStart = new THREE.Vector2(); 259 | var panEnd = new THREE.Vector2(); 260 | var panDelta = new THREE.Vector2(); 261 | 262 | var dollyStart = new THREE.Vector2(); 263 | var dollyEnd = new THREE.Vector2(); 264 | var dollyDelta = new THREE.Vector2(); 265 | 266 | function getAutoRotationAngle() { 267 | 268 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 269 | 270 | } 271 | 272 | function getZoomScale() { 273 | 274 | return Math.pow( 0.95, scope.zoomSpeed ); 275 | 276 | } 277 | 278 | function rotateLeft( angle ) { 279 | 280 | sphericalDelta.theta -= angle; 281 | 282 | } 283 | 284 | function rotateUp( angle ) { 285 | 286 | sphericalDelta.phi -= angle; 287 | 288 | } 289 | 290 | var panLeft = function () { 291 | 292 | var v = new THREE.Vector3(); 293 | 294 | return function panLeft( distance, objectMatrix ) { 295 | 296 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 297 | v.multiplyScalar( - distance ); 298 | 299 | panOffset.add( v ); 300 | 301 | }; 302 | 303 | }(); 304 | 305 | var panUp = function () { 306 | 307 | var v = new THREE.Vector3(); 308 | 309 | return function panUp( distance, objectMatrix ) { 310 | 311 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 312 | v.multiplyScalar( distance ); 313 | 314 | panOffset.add( v ); 315 | 316 | }; 317 | 318 | }(); 319 | 320 | // deltaX and deltaY are in pixels; right and down are positive 321 | var pan = function () { 322 | 323 | var offset = new THREE.Vector3(); 324 | 325 | return function pan( deltaX, deltaY ) { 326 | 327 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 328 | 329 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 330 | 331 | // perspective 332 | var position = scope.object.position; 333 | offset.copy( position ).sub( scope.target ); 334 | var targetDistance = offset.length(); 335 | 336 | // half of the fov is center to top of screen 337 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 338 | 339 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 340 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 341 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 342 | 343 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 344 | 345 | // orthographic 346 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 347 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 348 | 349 | } else { 350 | 351 | // camera neither orthographic nor perspective 352 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 353 | scope.enablePan = false; 354 | 355 | } 356 | 357 | }; 358 | 359 | }(); 360 | 361 | function dollyIn( dollyScale ) { 362 | 363 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 364 | 365 | scale /= dollyScale; 366 | 367 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 368 | 369 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 370 | scope.object.updateProjectionMatrix(); 371 | zoomChanged = true; 372 | 373 | } else { 374 | 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 376 | scope.enableZoom = false; 377 | 378 | } 379 | 380 | } 381 | 382 | function dollyOut( dollyScale ) { 383 | 384 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 385 | 386 | scale *= dollyScale; 387 | 388 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 389 | 390 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 391 | scope.object.updateProjectionMatrix(); 392 | zoomChanged = true; 393 | 394 | } else { 395 | 396 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 397 | scope.enableZoom = false; 398 | 399 | } 400 | 401 | } 402 | 403 | // 404 | // event callbacks - update the object state 405 | // 406 | 407 | function handleMouseDownRotate( event ) { 408 | 409 | //console.log( 'handleMouseDownRotate' ); 410 | 411 | rotateStart.set( event.clientX, event.clientY ); 412 | 413 | } 414 | 415 | function handleMouseDownDolly( event ) { 416 | 417 | //console.log( 'handleMouseDownDolly' ); 418 | 419 | dollyStart.set( event.clientX, event.clientY ); 420 | 421 | } 422 | 423 | function handleMouseDownPan( event ) { 424 | 425 | //console.log( 'handleMouseDownPan' ); 426 | 427 | panStart.set( event.clientX, event.clientY ); 428 | 429 | } 430 | 431 | function handleMouseMoveRotate( event ) { 432 | 433 | //console.log( 'handleMouseMoveRotate' ); 434 | 435 | rotateEnd.set( event.clientX, event.clientY ); 436 | rotateDelta.subVectors( rotateEnd, rotateStart ); 437 | 438 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 439 | 440 | // rotating across whole screen goes 360 degrees around 441 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 442 | 443 | // rotating up and down along whole screen attempts to go 360, but limited to 180 444 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 445 | 446 | rotateStart.copy( rotateEnd ); 447 | 448 | scope.update(); 449 | 450 | } 451 | 452 | function handleMouseMoveDolly( event ) { 453 | 454 | //console.log( 'handleMouseMoveDolly' ); 455 | 456 | dollyEnd.set( event.clientX, event.clientY ); 457 | 458 | dollyDelta.subVectors( dollyEnd, dollyStart ); 459 | 460 | if ( dollyDelta.y > 0 ) { 461 | 462 | dollyIn( getZoomScale() ); 463 | 464 | } else if ( dollyDelta.y < 0 ) { 465 | 466 | dollyOut( getZoomScale() ); 467 | 468 | } 469 | 470 | dollyStart.copy( dollyEnd ); 471 | 472 | scope.update(); 473 | 474 | } 475 | 476 | function handleMouseMovePan( event ) { 477 | 478 | //console.log( 'handleMouseMovePan' ); 479 | 480 | panEnd.set( event.clientX, event.clientY ); 481 | 482 | panDelta.subVectors( panEnd, panStart ); 483 | 484 | pan( panDelta.x, panDelta.y ); 485 | 486 | panStart.copy( panEnd ); 487 | 488 | scope.update(); 489 | 490 | } 491 | 492 | function handleMouseUp( event ) { 493 | 494 | // console.log( 'handleMouseUp' ); 495 | 496 | } 497 | 498 | function handleMouseWheel( event ) { 499 | 500 | // console.log( 'handleMouseWheel' ); 501 | 502 | if ( event.deltaY < 0 ) { 503 | 504 | dollyOut( getZoomScale() ); 505 | 506 | } else if ( event.deltaY > 0 ) { 507 | 508 | dollyIn( getZoomScale() ); 509 | 510 | } 511 | 512 | scope.update(); 513 | 514 | } 515 | 516 | function handleKeyDown( event ) { 517 | 518 | //console.log( 'handleKeyDown' ); 519 | 520 | switch ( event.keyCode ) { 521 | 522 | case scope.keys.UP: 523 | pan( 0, scope.keyPanSpeed ); 524 | scope.update(); 525 | break; 526 | 527 | case scope.keys.BOTTOM: 528 | pan( 0, - scope.keyPanSpeed ); 529 | scope.update(); 530 | break; 531 | 532 | case scope.keys.LEFT: 533 | pan( scope.keyPanSpeed, 0 ); 534 | scope.update(); 535 | break; 536 | 537 | case scope.keys.RIGHT: 538 | pan( - scope.keyPanSpeed, 0 ); 539 | scope.update(); 540 | break; 541 | 542 | } 543 | 544 | } 545 | 546 | function handleTouchStartRotate( event ) { 547 | 548 | //console.log( 'handleTouchStartRotate' ); 549 | 550 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 551 | 552 | } 553 | 554 | function handleTouchStartDolly( event ) { 555 | 556 | //console.log( 'handleTouchStartDolly' ); 557 | 558 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 559 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 560 | 561 | var distance = Math.sqrt( dx * dx + dy * dy ); 562 | 563 | dollyStart.set( 0, distance ); 564 | 565 | } 566 | 567 | function handleTouchStartPan( event ) { 568 | 569 | //console.log( 'handleTouchStartPan' ); 570 | 571 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 572 | 573 | } 574 | 575 | function handleTouchMoveRotate( event ) { 576 | 577 | //console.log( 'handleTouchMoveRotate' ); 578 | 579 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 580 | rotateDelta.subVectors( rotateEnd, rotateStart ); 581 | 582 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 583 | 584 | // rotating across whole screen goes 360 degrees around 585 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 586 | 587 | // rotating up and down along whole screen attempts to go 360, but limited to 180 588 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 589 | 590 | rotateStart.copy( rotateEnd ); 591 | 592 | scope.update(); 593 | 594 | } 595 | 596 | function handleTouchMoveDolly( event ) { 597 | 598 | //console.log( 'handleTouchMoveDolly' ); 599 | 600 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 601 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 602 | 603 | var distance = Math.sqrt( dx * dx + dy * dy ); 604 | 605 | dollyEnd.set( 0, distance ); 606 | 607 | dollyDelta.subVectors( dollyEnd, dollyStart ); 608 | 609 | if ( dollyDelta.y > 0 ) { 610 | 611 | dollyOut( getZoomScale() ); 612 | 613 | } else if ( dollyDelta.y < 0 ) { 614 | 615 | dollyIn( getZoomScale() ); 616 | 617 | } 618 | 619 | dollyStart.copy( dollyEnd ); 620 | 621 | scope.update(); 622 | 623 | } 624 | 625 | function handleTouchMovePan( event ) { 626 | 627 | //console.log( 'handleTouchMovePan' ); 628 | 629 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 630 | 631 | panDelta.subVectors( panEnd, panStart ); 632 | 633 | pan( panDelta.x, panDelta.y ); 634 | 635 | panStart.copy( panEnd ); 636 | 637 | scope.update(); 638 | 639 | } 640 | 641 | function handleTouchEnd( event ) { 642 | 643 | //console.log( 'handleTouchEnd' ); 644 | 645 | } 646 | 647 | // 648 | // event handlers - FSM: listen for events and reset state 649 | // 650 | 651 | function onMouseDown( event ) { 652 | 653 | if ( scope.enabled === false ) return; 654 | 655 | event.preventDefault(); 656 | 657 | if ( event.button === scope.mouseButtons.ORBIT ) { 658 | 659 | if ( scope.enableRotate === false ) return; 660 | 661 | handleMouseDownRotate( event ); 662 | 663 | state = STATE.ROTATE; 664 | 665 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 666 | 667 | if ( scope.enableZoom === false ) return; 668 | 669 | handleMouseDownDolly( event ); 670 | 671 | state = STATE.DOLLY; 672 | 673 | } else if ( event.button === scope.mouseButtons.PAN ) { 674 | 675 | if ( scope.enablePan === false ) return; 676 | 677 | handleMouseDownPan( event ); 678 | 679 | state = STATE.PAN; 680 | 681 | } 682 | 683 | if ( state !== STATE.NONE ) { 684 | 685 | document.addEventListener( 'mousemove', onMouseMove, false ); 686 | document.addEventListener( 'mouseup', onMouseUp, false ); 687 | 688 | scope.dispatchEvent( startEvent ); 689 | 690 | } 691 | 692 | } 693 | 694 | function onMouseMove( event ) { 695 | 696 | if ( scope.enabled === false ) return; 697 | 698 | event.preventDefault(); 699 | 700 | if ( state === STATE.ROTATE ) { 701 | 702 | if ( scope.enableRotate === false ) return; 703 | 704 | handleMouseMoveRotate( event ); 705 | 706 | } else if ( state === STATE.DOLLY ) { 707 | 708 | if ( scope.enableZoom === false ) return; 709 | 710 | handleMouseMoveDolly( event ); 711 | 712 | } else if ( state === STATE.PAN ) { 713 | 714 | if ( scope.enablePan === false ) return; 715 | 716 | handleMouseMovePan( event ); 717 | 718 | } 719 | 720 | } 721 | 722 | function onMouseUp( event ) { 723 | 724 | if ( scope.enabled === false ) return; 725 | 726 | handleMouseUp( event ); 727 | 728 | document.removeEventListener( 'mousemove', onMouseMove, false ); 729 | document.removeEventListener( 'mouseup', onMouseUp, false ); 730 | 731 | scope.dispatchEvent( endEvent ); 732 | 733 | state = STATE.NONE; 734 | 735 | } 736 | 737 | function onMouseWheel( event ) { 738 | 739 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 740 | 741 | event.preventDefault(); 742 | event.stopPropagation(); 743 | 744 | handleMouseWheel( event ); 745 | 746 | scope.dispatchEvent( startEvent ); // not sure why these are here... 747 | scope.dispatchEvent( endEvent ); 748 | 749 | } 750 | 751 | function onKeyDown( event ) { 752 | 753 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 754 | 755 | handleKeyDown( event ); 756 | 757 | } 758 | 759 | function onTouchStart( event ) { 760 | 761 | if ( scope.enabled === false ) return; 762 | 763 | switch ( event.touches.length ) { 764 | 765 | case 1: // one-fingered touch: rotate 766 | 767 | if ( scope.enableRotate === false ) return; 768 | 769 | handleTouchStartRotate( event ); 770 | 771 | state = STATE.TOUCH_ROTATE; 772 | 773 | break; 774 | 775 | case 2: // two-fingered touch: dolly 776 | 777 | if ( scope.enableZoom === false ) return; 778 | 779 | handleTouchStartDolly( event ); 780 | 781 | state = STATE.TOUCH_DOLLY; 782 | 783 | break; 784 | 785 | case 3: // three-fingered touch: pan 786 | 787 | if ( scope.enablePan === false ) return; 788 | 789 | handleTouchStartPan( event ); 790 | 791 | state = STATE.TOUCH_PAN; 792 | 793 | break; 794 | 795 | default: 796 | 797 | state = STATE.NONE; 798 | 799 | } 800 | 801 | if ( state !== STATE.NONE ) { 802 | 803 | scope.dispatchEvent( startEvent ); 804 | 805 | } 806 | 807 | } 808 | 809 | function onTouchMove( event ) { 810 | 811 | if ( scope.enabled === false ) return; 812 | 813 | event.preventDefault(); 814 | event.stopPropagation(); 815 | 816 | switch ( event.touches.length ) { 817 | 818 | case 1: // one-fingered touch: rotate 819 | 820 | if ( scope.enableRotate === false ) return; 821 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 822 | 823 | handleTouchMoveRotate( event ); 824 | 825 | break; 826 | 827 | case 2: // two-fingered touch: dolly 828 | 829 | if ( scope.enableZoom === false ) return; 830 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 831 | 832 | handleTouchMoveDolly( event ); 833 | 834 | break; 835 | 836 | case 3: // three-fingered touch: pan 837 | 838 | if ( scope.enablePan === false ) return; 839 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 840 | 841 | handleTouchMovePan( event ); 842 | 843 | break; 844 | 845 | default: 846 | 847 | state = STATE.NONE; 848 | 849 | } 850 | 851 | } 852 | 853 | function onTouchEnd( event ) { 854 | 855 | if ( scope.enabled === false ) return; 856 | 857 | handleTouchEnd( event ); 858 | 859 | scope.dispatchEvent( endEvent ); 860 | 861 | state = STATE.NONE; 862 | 863 | } 864 | 865 | function onContextMenu( event ) { 866 | 867 | event.preventDefault(); 868 | 869 | } 870 | 871 | // 872 | 873 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 874 | 875 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 876 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 877 | 878 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 879 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 880 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 881 | 882 | window.addEventListener( 'keydown', onKeyDown, false ); 883 | 884 | // force an update at start 885 | 886 | this.update(); 887 | 888 | }; 889 | 890 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 891 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 892 | 893 | Object.defineProperties( THREE.OrbitControls.prototype, { 894 | 895 | center: { 896 | 897 | get: function () { 898 | 899 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 900 | return this.target; 901 | 902 | } 903 | 904 | }, 905 | 906 | // backward compatibility 907 | 908 | noZoom: { 909 | 910 | get: function () { 911 | 912 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 913 | return ! this.enableZoom; 914 | 915 | }, 916 | 917 | set: function ( value ) { 918 | 919 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 920 | this.enableZoom = ! value; 921 | 922 | } 923 | 924 | }, 925 | 926 | noRotate: { 927 | 928 | get: function () { 929 | 930 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 931 | return ! this.enableRotate; 932 | 933 | }, 934 | 935 | set: function ( value ) { 936 | 937 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 938 | this.enableRotate = ! value; 939 | 940 | } 941 | 942 | }, 943 | 944 | noPan: { 945 | 946 | get: function () { 947 | 948 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 949 | return ! this.enablePan; 950 | 951 | }, 952 | 953 | set: function ( value ) { 954 | 955 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 956 | this.enablePan = ! value; 957 | 958 | } 959 | 960 | }, 961 | 962 | noKeys: { 963 | 964 | get: function () { 965 | 966 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 967 | return ! this.enableKeys; 968 | 969 | }, 970 | 971 | set: function ( value ) { 972 | 973 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 974 | this.enableKeys = ! value; 975 | 976 | } 977 | 978 | }, 979 | 980 | staticMoving: { 981 | 982 | get: function () { 983 | 984 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 985 | return ! this.enableDamping; 986 | 987 | }, 988 | 989 | set: function ( value ) { 990 | 991 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 992 | this.enableDamping = ! value; 993 | 994 | } 995 | 996 | }, 997 | 998 | dynamicDampingFactor: { 999 | 1000 | get: function () { 1001 | 1002 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1003 | return this.dampingFactor; 1004 | 1005 | }, 1006 | 1007 | set: function ( value ) { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1010 | this.dampingFactor = value; 1011 | 1012 | } 1013 | 1014 | } 1015 | 1016 | } ); 1017 | -------------------------------------------------------------------------------- /dst/assets/js/main.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Useragnt 3 | * v0.3.1 4 | * 5 | * Copyright (c) 2016 Yuichiroh Arai 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/mit-license.php 8 | * 9 | * detects: mobile, tablet, pc, windows, mac, linux, ios, android, edge, ie, safari, chrome, firefox, opera 10 | !*/ 11 | "use strict"; 12 | 13 | !(function (e, o) { 14 | function i(e) { 15 | return n.indexOf(e) != -1; 16 | }function r(e) { 17 | var o = e.split("."), 18 | i = {};return i.str = e, i.float = parseFloat(e) || 0, i.major = o.length > 0 ? parseInt(o[0]) || 0 : 0, i.minor = o.length > 1 ? parseInt(o[1]) || 0 : 0, i.build = o.length > 2 ? parseInt(o[2]) || 0 : 0, i.revision = o.length > 3 ? parseInt(o[3]) || 0 : 0, i; 19 | }var a = {};a._detects = ["mobile", "tablet", "pc", "windows", "mac", "linux", "ios", "android", "edge", "ie", "safari", "chrome", "firefox", "opera"];var n = a.userAgent = e.navigator.userAgent.toLowerCase();a.mobile = i("iphone") || i("ipod") || i("android") && i("mobile") || i("windows") && i("phone") || i("firefox") && i("mobile") || i("blackberry"), a.tablet = i("ipad") || i("android") && !i("mobile") || i("windows") && i("touch") && !i("tablet pc") || i("firefox") && i("tablet") || i("kindle") || i("silk") || i("playbook"), a.pc = !i("iphone") && !i("ipod") && !i("ipad") && !i("android") && (!i("windows") || !i("phone") && (!i("touch") || i("tablet pc"))) && (!i("firefox") || !i("mobile") && !i("tablet")) && !i("blackberry") && !i("kindle") && !i("silk") && !i("playbook"), a.windows = i("windows"), a.mac = i("mac os x") && !i("iphone") && !i("ipad") && !i("ipod"), a.linux = i("linux") && !i("android"), a.ios = i("iphone") || i("ipad") || i("ipod"), a.ios && (a.ios = new Boolean(!0), n.match(/ os ([\d_]+)/g), a.ios.version = r(RegExp.$1.replace("_", "."))), a.android = i("android"), a.android && (a.android = new Boolean(!0), n.match(/android ([\d\.]+)/g), a.android.version = r(RegExp.$1)), a.edge = i("edge"), a.ie = i("trident") || i("msie"), a.safari = i("safari") && !i("android") && !i("edge") && !i("opera") && !i("opr") && !i("chrome"), a.chrome = i("chrome") && !i("edge") && !i("opera") && !i("opr"), a.chrome && (a.chrome = new Boolean(!0), n.match(/chrome\/([\d.]+)/g), a.chrome.version = r(RegExp.$1)), a.firefox = i("firefox") && !i("edge"), a.opera = i("opera") || i("opr");var d, 20 | t, 21 | s, 22 | l = a._classPrefix = "", 23 | p = o.documentElement, 24 | c = p.className;for (t = a._detects.length, d = 0; d < t; d++) s = a._detects[d], c += a[s] ? " " + l + s : " " + l + "no-" + s;p.className = c, e.Useragnt = a; 25 | })(window, document); 26 | /** 27 | * @author qiao / https://github.com/qiao 28 | * @author mrdoob / http://mrdoob.com 29 | * @author alteredq / http://alteredqualia.com/ 30 | * @author WestLangley / http://github.com/WestLangley 31 | * @author erich666 / http://erichaines.com 32 | */ 33 | 34 | // This set of controls performs orbiting, dollying (zooming), and panning. 35 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 36 | // 37 | // Orbit - left mouse / touch: one finger move 38 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 39 | // Pan - right mouse, or arrow keys / touch: three finger swipe 40 | 41 | 'use strict'; 42 | 43 | THREE.OrbitControls = function (object, domElement) { 44 | 45 | this.object = object; 46 | 47 | this.domElement = domElement !== undefined ? domElement : document; 48 | 49 | // Set to false to disable this control 50 | this.enabled = true; 51 | 52 | // "target" sets the location of focus, where the object orbits around 53 | this.target = new THREE.Vector3(); 54 | 55 | // How far you can dolly in and out ( PerspectiveCamera only ) 56 | this.minDistance = 0; 57 | this.maxDistance = Infinity; 58 | 59 | // How far you can zoom in and out ( OrthographicCamera only ) 60 | this.minZoom = 0; 61 | this.maxZoom = Infinity; 62 | 63 | // How far you can orbit vertically, upper and lower limits. 64 | // Range is 0 to Math.PI radians. 65 | this.minPolarAngle = 0; // radians 66 | this.maxPolarAngle = Math.PI; // radians 67 | 68 | // How far you can orbit horizontally, upper and lower limits. 69 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 70 | this.minAzimuthAngle = -Infinity; // radians 71 | this.maxAzimuthAngle = Infinity; // radians 72 | 73 | // Set to true to enable damping (inertia) 74 | // If damping is enabled, you must call controls.update() in your animation loop 75 | this.enableDamping = false; 76 | this.dampingFactor = 0.25; 77 | 78 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 79 | // Set to false to disable zooming 80 | this.enableZoom = true; 81 | this.zoomSpeed = 1.0; 82 | 83 | // Set to false to disable rotating 84 | this.enableRotate = true; 85 | this.rotateSpeed = 1.0; 86 | 87 | // Set to false to disable panning 88 | this.enablePan = true; 89 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 90 | 91 | // Set to true to automatically rotate around the target 92 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 93 | this.autoRotate = false; 94 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 95 | 96 | // Set to false to disable use of the keys 97 | this.enableKeys = true; 98 | 99 | // The four arrow keys 100 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 101 | 102 | // Mouse buttons 103 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 104 | 105 | // for reset 106 | this.target0 = this.target.clone(); 107 | this.position0 = this.object.position.clone(); 108 | this.zoom0 = this.object.zoom; 109 | 110 | // 111 | // public methods 112 | // 113 | 114 | this.getPolarAngle = function () { 115 | 116 | return spherical.phi; 117 | }; 118 | 119 | this.getAzimuthalAngle = function () { 120 | 121 | return spherical.theta; 122 | }; 123 | 124 | this.reset = function () { 125 | 126 | scope.target.copy(scope.target0); 127 | scope.object.position.copy(scope.position0); 128 | scope.object.zoom = scope.zoom0; 129 | 130 | scope.object.updateProjectionMatrix(); 131 | scope.dispatchEvent(changeEvent); 132 | 133 | scope.update(); 134 | 135 | state = STATE.NONE; 136 | }; 137 | 138 | // this method is exposed, but perhaps it would be better if we can make it private... 139 | this.update = (function () { 140 | 141 | var offset = new THREE.Vector3(); 142 | 143 | // so camera.up is the orbit axis 144 | var quat = new THREE.Quaternion().setFromUnitVectors(object.up, new THREE.Vector3(0, 1, 0)); 145 | var quatInverse = quat.clone().inverse(); 146 | 147 | var lastPosition = new THREE.Vector3(); 148 | var lastQuaternion = new THREE.Quaternion(); 149 | 150 | return function update() { 151 | 152 | var position = scope.object.position; 153 | 154 | offset.copy(position).sub(scope.target); 155 | 156 | // rotate offset to "y-axis-is-up" space 157 | offset.applyQuaternion(quat); 158 | 159 | // angle from z-axis around y-axis 160 | spherical.setFromVector3(offset); 161 | 162 | if (scope.autoRotate && state === STATE.NONE) { 163 | 164 | rotateLeft(getAutoRotationAngle()); 165 | } 166 | 167 | spherical.theta += sphericalDelta.theta; 168 | spherical.phi += sphericalDelta.phi; 169 | 170 | // restrict theta to be between desired limits 171 | spherical.theta = Math.max(scope.minAzimuthAngle, Math.min(scope.maxAzimuthAngle, spherical.theta)); 172 | 173 | // restrict phi to be between desired limits 174 | spherical.phi = Math.max(scope.minPolarAngle, Math.min(scope.maxPolarAngle, spherical.phi)); 175 | 176 | spherical.makeSafe(); 177 | 178 | spherical.radius *= scale; 179 | 180 | // restrict radius to be between desired limits 181 | spherical.radius = Math.max(scope.minDistance, Math.min(scope.maxDistance, spherical.radius)); 182 | 183 | // move target to panned location 184 | scope.target.add(panOffset); 185 | 186 | offset.setFromSpherical(spherical); 187 | 188 | // rotate offset back to "camera-up-vector-is-up" space 189 | offset.applyQuaternion(quatInverse); 190 | 191 | position.copy(scope.target).add(offset); 192 | 193 | scope.object.lookAt(scope.target); 194 | 195 | if (scope.enableDamping === true) { 196 | 197 | sphericalDelta.theta *= 1 - scope.dampingFactor; 198 | sphericalDelta.phi *= 1 - scope.dampingFactor; 199 | } else { 200 | 201 | sphericalDelta.set(0, 0, 0); 202 | } 203 | 204 | scale = 1; 205 | panOffset.set(0, 0, 0); 206 | 207 | // update condition is: 208 | // min(camera displacement, camera rotation in radians)^2 > EPS 209 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 210 | 211 | if (zoomChanged || lastPosition.distanceToSquared(scope.object.position) > EPS || 8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) { 212 | 213 | scope.dispatchEvent(changeEvent); 214 | 215 | lastPosition.copy(scope.object.position); 216 | lastQuaternion.copy(scope.object.quaternion); 217 | zoomChanged = false; 218 | 219 | return true; 220 | } 221 | 222 | return false; 223 | }; 224 | })(); 225 | 226 | this.dispose = function () { 227 | 228 | scope.domElement.removeEventListener('contextmenu', onContextMenu, false); 229 | scope.domElement.removeEventListener('mousedown', onMouseDown, false); 230 | scope.domElement.removeEventListener('wheel', onMouseWheel, false); 231 | 232 | scope.domElement.removeEventListener('touchstart', onTouchStart, false); 233 | scope.domElement.removeEventListener('touchend', onTouchEnd, false); 234 | scope.domElement.removeEventListener('touchmove', onTouchMove, false); 235 | 236 | document.removeEventListener('mousemove', onMouseMove, false); 237 | document.removeEventListener('mouseup', onMouseUp, false); 238 | 239 | window.removeEventListener('keydown', onKeyDown, false); 240 | 241 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 242 | }; 243 | 244 | // 245 | // internals 246 | // 247 | 248 | var scope = this; 249 | 250 | var changeEvent = { type: 'change' }; 251 | var startEvent = { type: 'start' }; 252 | var endEvent = { type: 'end' }; 253 | 254 | var STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; 255 | 256 | var state = STATE.NONE; 257 | 258 | var EPS = 0.000001; 259 | 260 | // current position in spherical coordinates 261 | var spherical = new THREE.Spherical(); 262 | var sphericalDelta = new THREE.Spherical(); 263 | 264 | var scale = 1; 265 | var panOffset = new THREE.Vector3(); 266 | var zoomChanged = false; 267 | 268 | var rotateStart = new THREE.Vector2(); 269 | var rotateEnd = new THREE.Vector2(); 270 | var rotateDelta = new THREE.Vector2(); 271 | 272 | var panStart = new THREE.Vector2(); 273 | var panEnd = new THREE.Vector2(); 274 | var panDelta = new THREE.Vector2(); 275 | 276 | var dollyStart = new THREE.Vector2(); 277 | var dollyEnd = new THREE.Vector2(); 278 | var dollyDelta = new THREE.Vector2(); 279 | 280 | function getAutoRotationAngle() { 281 | 282 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 283 | } 284 | 285 | function getZoomScale() { 286 | 287 | return Math.pow(0.95, scope.zoomSpeed); 288 | } 289 | 290 | function rotateLeft(angle) { 291 | 292 | sphericalDelta.theta -= angle; 293 | } 294 | 295 | function rotateUp(angle) { 296 | 297 | sphericalDelta.phi -= angle; 298 | } 299 | 300 | var panLeft = (function () { 301 | 302 | var v = new THREE.Vector3(); 303 | 304 | return function panLeft(distance, objectMatrix) { 305 | 306 | v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix 307 | v.multiplyScalar(-distance); 308 | 309 | panOffset.add(v); 310 | }; 311 | })(); 312 | 313 | var panUp = (function () { 314 | 315 | var v = new THREE.Vector3(); 316 | 317 | return function panUp(distance, objectMatrix) { 318 | 319 | v.setFromMatrixColumn(objectMatrix, 1); // get Y column of objectMatrix 320 | v.multiplyScalar(distance); 321 | 322 | panOffset.add(v); 323 | }; 324 | })(); 325 | 326 | // deltaX and deltaY are in pixels; right and down are positive 327 | var pan = (function () { 328 | 329 | var offset = new THREE.Vector3(); 330 | 331 | return function pan(deltaX, deltaY) { 332 | 333 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 334 | 335 | if (scope.object instanceof THREE.PerspectiveCamera) { 336 | 337 | // perspective 338 | var position = scope.object.position; 339 | offset.copy(position).sub(scope.target); 340 | var targetDistance = offset.length(); 341 | 342 | // half of the fov is center to top of screen 343 | targetDistance *= Math.tan(scope.object.fov / 2 * Math.PI / 180.0); 344 | 345 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 346 | panLeft(2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix); 347 | panUp(2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix); 348 | } else if (scope.object instanceof THREE.OrthographicCamera) { 349 | 350 | // orthographic 351 | panLeft(deltaX * (scope.object.right - scope.object.left) / scope.object.zoom / element.clientWidth, scope.object.matrix); 352 | panUp(deltaY * (scope.object.top - scope.object.bottom) / scope.object.zoom / element.clientHeight, scope.object.matrix); 353 | } else { 354 | 355 | // camera neither orthographic nor perspective 356 | console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.'); 357 | scope.enablePan = false; 358 | } 359 | }; 360 | })(); 361 | 362 | function dollyIn(dollyScale) { 363 | 364 | if (scope.object instanceof THREE.PerspectiveCamera) { 365 | 366 | scale /= dollyScale; 367 | } else if (scope.object instanceof THREE.OrthographicCamera) { 368 | 369 | scope.object.zoom = Math.max(scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom * dollyScale)); 370 | scope.object.updateProjectionMatrix(); 371 | zoomChanged = true; 372 | } else { 373 | 374 | console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.'); 375 | scope.enableZoom = false; 376 | } 377 | } 378 | 379 | function dollyOut(dollyScale) { 380 | 381 | if (scope.object instanceof THREE.PerspectiveCamera) { 382 | 383 | scale *= dollyScale; 384 | } else if (scope.object instanceof THREE.OrthographicCamera) { 385 | 386 | scope.object.zoom = Math.max(scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom / dollyScale)); 387 | scope.object.updateProjectionMatrix(); 388 | zoomChanged = true; 389 | } else { 390 | 391 | console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.'); 392 | scope.enableZoom = false; 393 | } 394 | } 395 | 396 | // 397 | // event callbacks - update the object state 398 | // 399 | 400 | function handleMouseDownRotate(event) { 401 | 402 | //console.log( 'handleMouseDownRotate' ); 403 | 404 | rotateStart.set(event.clientX, event.clientY); 405 | } 406 | 407 | function handleMouseDownDolly(event) { 408 | 409 | //console.log( 'handleMouseDownDolly' ); 410 | 411 | dollyStart.set(event.clientX, event.clientY); 412 | } 413 | 414 | function handleMouseDownPan(event) { 415 | 416 | //console.log( 'handleMouseDownPan' ); 417 | 418 | panStart.set(event.clientX, event.clientY); 419 | } 420 | 421 | function handleMouseMoveRotate(event) { 422 | 423 | //console.log( 'handleMouseMoveRotate' ); 424 | 425 | rotateEnd.set(event.clientX, event.clientY); 426 | rotateDelta.subVectors(rotateEnd, rotateStart); 427 | 428 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 429 | 430 | // rotating across whole screen goes 360 degrees around 431 | rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed); 432 | 433 | // rotating up and down along whole screen attempts to go 360, but limited to 180 434 | rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed); 435 | 436 | rotateStart.copy(rotateEnd); 437 | 438 | scope.update(); 439 | } 440 | 441 | function handleMouseMoveDolly(event) { 442 | 443 | //console.log( 'handleMouseMoveDolly' ); 444 | 445 | dollyEnd.set(event.clientX, event.clientY); 446 | 447 | dollyDelta.subVectors(dollyEnd, dollyStart); 448 | 449 | if (dollyDelta.y > 0) { 450 | 451 | dollyIn(getZoomScale()); 452 | } else if (dollyDelta.y < 0) { 453 | 454 | dollyOut(getZoomScale()); 455 | } 456 | 457 | dollyStart.copy(dollyEnd); 458 | 459 | scope.update(); 460 | } 461 | 462 | function handleMouseMovePan(event) { 463 | 464 | //console.log( 'handleMouseMovePan' ); 465 | 466 | panEnd.set(event.clientX, event.clientY); 467 | 468 | panDelta.subVectors(panEnd, panStart); 469 | 470 | pan(panDelta.x, panDelta.y); 471 | 472 | panStart.copy(panEnd); 473 | 474 | scope.update(); 475 | } 476 | 477 | function handleMouseUp(event) { 478 | 479 | // console.log( 'handleMouseUp' ); 480 | 481 | } 482 | 483 | function handleMouseWheel(event) { 484 | 485 | // console.log( 'handleMouseWheel' ); 486 | 487 | if (event.deltaY < 0) { 488 | 489 | dollyOut(getZoomScale()); 490 | } else if (event.deltaY > 0) { 491 | 492 | dollyIn(getZoomScale()); 493 | } 494 | 495 | scope.update(); 496 | } 497 | 498 | function handleKeyDown(event) { 499 | 500 | //console.log( 'handleKeyDown' ); 501 | 502 | switch (event.keyCode) { 503 | 504 | case scope.keys.UP: 505 | pan(0, scope.keyPanSpeed); 506 | scope.update(); 507 | break; 508 | 509 | case scope.keys.BOTTOM: 510 | pan(0, -scope.keyPanSpeed); 511 | scope.update(); 512 | break; 513 | 514 | case scope.keys.LEFT: 515 | pan(scope.keyPanSpeed, 0); 516 | scope.update(); 517 | break; 518 | 519 | case scope.keys.RIGHT: 520 | pan(-scope.keyPanSpeed, 0); 521 | scope.update(); 522 | break; 523 | 524 | } 525 | } 526 | 527 | function handleTouchStartRotate(event) { 528 | 529 | //console.log( 'handleTouchStartRotate' ); 530 | 531 | rotateStart.set(event.touches[0].pageX, event.touches[0].pageY); 532 | } 533 | 534 | function handleTouchStartDolly(event) { 535 | 536 | //console.log( 'handleTouchStartDolly' ); 537 | 538 | var dx = event.touches[0].pageX - event.touches[1].pageX; 539 | var dy = event.touches[0].pageY - event.touches[1].pageY; 540 | 541 | var distance = Math.sqrt(dx * dx + dy * dy); 542 | 543 | dollyStart.set(0, distance); 544 | } 545 | 546 | function handleTouchStartPan(event) { 547 | 548 | //console.log( 'handleTouchStartPan' ); 549 | 550 | panStart.set(event.touches[0].pageX, event.touches[0].pageY); 551 | } 552 | 553 | function handleTouchMoveRotate(event) { 554 | 555 | //console.log( 'handleTouchMoveRotate' ); 556 | 557 | rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY); 558 | rotateDelta.subVectors(rotateEnd, rotateStart); 559 | 560 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 561 | 562 | // rotating across whole screen goes 360 degrees around 563 | rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed); 564 | 565 | // rotating up and down along whole screen attempts to go 360, but limited to 180 566 | rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed); 567 | 568 | rotateStart.copy(rotateEnd); 569 | 570 | scope.update(); 571 | } 572 | 573 | function handleTouchMoveDolly(event) { 574 | 575 | //console.log( 'handleTouchMoveDolly' ); 576 | 577 | var dx = event.touches[0].pageX - event.touches[1].pageX; 578 | var dy = event.touches[0].pageY - event.touches[1].pageY; 579 | 580 | var distance = Math.sqrt(dx * dx + dy * dy); 581 | 582 | dollyEnd.set(0, distance); 583 | 584 | dollyDelta.subVectors(dollyEnd, dollyStart); 585 | 586 | if (dollyDelta.y > 0) { 587 | 588 | dollyOut(getZoomScale()); 589 | } else if (dollyDelta.y < 0) { 590 | 591 | dollyIn(getZoomScale()); 592 | } 593 | 594 | dollyStart.copy(dollyEnd); 595 | 596 | scope.update(); 597 | } 598 | 599 | function handleTouchMovePan(event) { 600 | 601 | //console.log( 'handleTouchMovePan' ); 602 | 603 | panEnd.set(event.touches[0].pageX, event.touches[0].pageY); 604 | 605 | panDelta.subVectors(panEnd, panStart); 606 | 607 | pan(panDelta.x, panDelta.y); 608 | 609 | panStart.copy(panEnd); 610 | 611 | scope.update(); 612 | } 613 | 614 | function handleTouchEnd(event) {} 615 | 616 | //console.log( 'handleTouchEnd' ); 617 | 618 | // 619 | // event handlers - FSM: listen for events and reset state 620 | // 621 | 622 | function onMouseDown(event) { 623 | 624 | if (scope.enabled === false) return; 625 | 626 | event.preventDefault(); 627 | 628 | if (event.button === scope.mouseButtons.ORBIT) { 629 | 630 | if (scope.enableRotate === false) return; 631 | 632 | handleMouseDownRotate(event); 633 | 634 | state = STATE.ROTATE; 635 | } else if (event.button === scope.mouseButtons.ZOOM) { 636 | 637 | if (scope.enableZoom === false) return; 638 | 639 | handleMouseDownDolly(event); 640 | 641 | state = STATE.DOLLY; 642 | } else if (event.button === scope.mouseButtons.PAN) { 643 | 644 | if (scope.enablePan === false) return; 645 | 646 | handleMouseDownPan(event); 647 | 648 | state = STATE.PAN; 649 | } 650 | 651 | if (state !== STATE.NONE) { 652 | 653 | document.addEventListener('mousemove', onMouseMove, false); 654 | document.addEventListener('mouseup', onMouseUp, false); 655 | 656 | scope.dispatchEvent(startEvent); 657 | } 658 | } 659 | 660 | function onMouseMove(event) { 661 | 662 | if (scope.enabled === false) return; 663 | 664 | event.preventDefault(); 665 | 666 | if (state === STATE.ROTATE) { 667 | 668 | if (scope.enableRotate === false) return; 669 | 670 | handleMouseMoveRotate(event); 671 | } else if (state === STATE.DOLLY) { 672 | 673 | if (scope.enableZoom === false) return; 674 | 675 | handleMouseMoveDolly(event); 676 | } else if (state === STATE.PAN) { 677 | 678 | if (scope.enablePan === false) return; 679 | 680 | handleMouseMovePan(event); 681 | } 682 | } 683 | 684 | function onMouseUp(event) { 685 | 686 | if (scope.enabled === false) return; 687 | 688 | handleMouseUp(event); 689 | 690 | document.removeEventListener('mousemove', onMouseMove, false); 691 | document.removeEventListener('mouseup', onMouseUp, false); 692 | 693 | scope.dispatchEvent(endEvent); 694 | 695 | state = STATE.NONE; 696 | } 697 | 698 | function onMouseWheel(event) { 699 | 700 | if (scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE) return; 701 | 702 | event.preventDefault(); 703 | event.stopPropagation(); 704 | 705 | handleMouseWheel(event); 706 | 707 | scope.dispatchEvent(startEvent); // not sure why these are here... 708 | scope.dispatchEvent(endEvent); 709 | } 710 | 711 | function onKeyDown(event) { 712 | 713 | if (scope.enabled === false || scope.enableKeys === false || scope.enablePan === false) return; 714 | 715 | handleKeyDown(event); 716 | } 717 | 718 | function onTouchStart(event) { 719 | 720 | if (scope.enabled === false) return; 721 | 722 | switch (event.touches.length) { 723 | 724 | case 1: 725 | // one-fingered touch: rotate 726 | 727 | if (scope.enableRotate === false) return; 728 | 729 | handleTouchStartRotate(event); 730 | 731 | state = STATE.TOUCH_ROTATE; 732 | 733 | break; 734 | 735 | case 2: 736 | // two-fingered touch: dolly 737 | 738 | if (scope.enableZoom === false) return; 739 | 740 | handleTouchStartDolly(event); 741 | 742 | state = STATE.TOUCH_DOLLY; 743 | 744 | break; 745 | 746 | case 3: 747 | // three-fingered touch: pan 748 | 749 | if (scope.enablePan === false) return; 750 | 751 | handleTouchStartPan(event); 752 | 753 | state = STATE.TOUCH_PAN; 754 | 755 | break; 756 | 757 | default: 758 | 759 | state = STATE.NONE; 760 | 761 | } 762 | 763 | if (state !== STATE.NONE) { 764 | 765 | scope.dispatchEvent(startEvent); 766 | } 767 | } 768 | 769 | function onTouchMove(event) { 770 | 771 | if (scope.enabled === false) return; 772 | 773 | event.preventDefault(); 774 | event.stopPropagation(); 775 | 776 | switch (event.touches.length) { 777 | 778 | case 1: 779 | // one-fingered touch: rotate 780 | 781 | if (scope.enableRotate === false) return; 782 | if (state !== STATE.TOUCH_ROTATE) return; // is this needed?... 783 | 784 | handleTouchMoveRotate(event); 785 | 786 | break; 787 | 788 | case 2: 789 | // two-fingered touch: dolly 790 | 791 | if (scope.enableZoom === false) return; 792 | if (state !== STATE.TOUCH_DOLLY) return; // is this needed?... 793 | 794 | handleTouchMoveDolly(event); 795 | 796 | break; 797 | 798 | case 3: 799 | // three-fingered touch: pan 800 | 801 | if (scope.enablePan === false) return; 802 | if (state !== STATE.TOUCH_PAN) return; // is this needed?... 803 | 804 | handleTouchMovePan(event); 805 | 806 | break; 807 | 808 | default: 809 | 810 | state = STATE.NONE; 811 | 812 | } 813 | } 814 | 815 | function onTouchEnd(event) { 816 | 817 | if (scope.enabled === false) return; 818 | 819 | handleTouchEnd(event); 820 | 821 | scope.dispatchEvent(endEvent); 822 | 823 | state = STATE.NONE; 824 | } 825 | 826 | function onContextMenu(event) { 827 | 828 | event.preventDefault(); 829 | } 830 | 831 | // 832 | 833 | scope.domElement.addEventListener('contextmenu', onContextMenu, false); 834 | 835 | scope.domElement.addEventListener('mousedown', onMouseDown, false); 836 | scope.domElement.addEventListener('wheel', onMouseWheel, false); 837 | 838 | scope.domElement.addEventListener('touchstart', onTouchStart, false); 839 | scope.domElement.addEventListener('touchend', onTouchEnd, false); 840 | scope.domElement.addEventListener('touchmove', onTouchMove, false); 841 | 842 | window.addEventListener('keydown', onKeyDown, false); 843 | 844 | // force an update at start 845 | 846 | this.update(); 847 | }; 848 | 849 | THREE.OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype); 850 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 851 | 852 | Object.defineProperties(THREE.OrbitControls.prototype, { 853 | 854 | center: { 855 | 856 | get: function get() { 857 | 858 | console.warn('THREE.OrbitControls: .center has been renamed to .target'); 859 | return this.target; 860 | } 861 | 862 | }, 863 | 864 | // backward compatibility 865 | 866 | noZoom: { 867 | 868 | get: function get() { 869 | 870 | console.warn('THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.'); 871 | return !this.enableZoom; 872 | }, 873 | 874 | set: function set(value) { 875 | 876 | console.warn('THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.'); 877 | this.enableZoom = !value; 878 | } 879 | 880 | }, 881 | 882 | noRotate: { 883 | 884 | get: function get() { 885 | 886 | console.warn('THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.'); 887 | return !this.enableRotate; 888 | }, 889 | 890 | set: function set(value) { 891 | 892 | console.warn('THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.'); 893 | this.enableRotate = !value; 894 | } 895 | 896 | }, 897 | 898 | noPan: { 899 | 900 | get: function get() { 901 | 902 | console.warn('THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.'); 903 | return !this.enablePan; 904 | }, 905 | 906 | set: function set(value) { 907 | 908 | console.warn('THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.'); 909 | this.enablePan = !value; 910 | } 911 | 912 | }, 913 | 914 | noKeys: { 915 | 916 | get: function get() { 917 | 918 | console.warn('THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.'); 919 | return !this.enableKeys; 920 | }, 921 | 922 | set: function set(value) { 923 | 924 | console.warn('THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.'); 925 | this.enableKeys = !value; 926 | } 927 | 928 | }, 929 | 930 | staticMoving: { 931 | 932 | get: function get() { 933 | 934 | console.warn('THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.'); 935 | return !this.enableDamping; 936 | }, 937 | 938 | set: function set(value) { 939 | 940 | console.warn('THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.'); 941 | this.enableDamping = !value; 942 | } 943 | 944 | }, 945 | 946 | dynamicDampingFactor: { 947 | 948 | get: function get() { 949 | 950 | console.warn('THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.'); 951 | return this.dampingFactor; 952 | }, 953 | 954 | set: function set(value) { 955 | 956 | console.warn('THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.'); 957 | this.dampingFactor = value; 958 | } 959 | 960 | } 961 | 962 | }); 963 | "use strict"; 964 | 965 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 966 | 967 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 968 | 969 | var Analyzer = (function () { 970 | function Analyzer(audio, smoothTime, scale) { 971 | _classCallCheck(this, Analyzer); 972 | 973 | this.audio = audio; 974 | this.controls = new Controls(this); 975 | 976 | this.scale = scale; 977 | 978 | this.audioContext = this.audio.audioContext; 979 | this.analyser = this.audioContext.createAnalyser(); 980 | this.analyser.fftSize = 2048; 981 | this.frequencyNum = 1024; 982 | this.hz = 22028; 983 | this.analyser.smoothingTimeConstant = smoothTime; 984 | 985 | this.frequencyArray = []; 986 | 987 | this.distLength_1 = 120 * 2; 988 | this.distLength_2 = 60 * 2; 989 | 990 | this.setRange(); 991 | 992 | this.level = 5; 993 | 994 | this.avrBass = 0; 995 | } 996 | 997 | _createClass(Analyzer, [{ 998 | key: "setRange", 999 | value: function setRange() { 1000 | this.minHz = 1; 1001 | this.maxHz = this.controls.props.end; 1002 | 1003 | this.sourceStart = Math.ceil(this.frequencyNum * this.minHz / this.hz); 1004 | this.sourceEnd = Math.round(this.frequencyNum * this.maxHz / this.hz); 1005 | this.sourceLength = this.sourceEnd - this.sourceStart + 1; 1006 | 1007 | this.adjustOffset = Math.round(this.sourceLength * 0.12); 1008 | 1009 | this.interval_1 = (this.sourceLength - 1) / (this.distLength_1 - 1); 1010 | this.interval_2 = (this.sourceLength - 1) / (this.distLength_2 - 1); 1011 | } 1012 | }, { 1013 | key: "adjustFrequency", 1014 | value: function adjustFrequency(i, avr) { 1015 | var f = Math.max(0, this.spectrums[this.sourceStart + i] - avr) * this.scale; 1016 | var offset = i - this.sourceStart; 1017 | 1018 | var ratio = offset / this.adjustOffset; 1019 | 1020 | f *= Math.max(0, Math.min(1, 5 / 6 * (ratio - 1) * (ratio - 1) * (ratio - 1) + 1)); 1021 | 1022 | return f; 1023 | } 1024 | }, { 1025 | key: "update", 1026 | value: function update() { 1027 | this.frequencyArray = []; 1028 | 1029 | var spectrums = new Float32Array(this.frequencyNum); 1030 | 1031 | if (this.audio.isReady) { 1032 | this.analyser.getFloatFrequencyData(spectrums); 1033 | } 1034 | 1035 | this.spectrums = spectrums; 1036 | 1037 | this.avr = 0; 1038 | this.avrBass = 0; 1039 | 1040 | for (var i = this.sourceStart; i <= this.sourceEnd; i++) { 1041 | this.avr += this.spectrums[i]; 1042 | if (i < 6) this.avrBass += this.spectrums[i]; 1043 | } 1044 | 1045 | this.avr /= this.sourceLength; 1046 | this.avrBass /= 6; 1047 | // console.log(this.avrBass); 1048 | 1049 | this.avr = !this.audio.isReady || this.avr === 0 ? this.avr : Math.min(-40, Math.max(this.avr, -60)); 1050 | this.avrBass = !this.audio.isReady || this.avrBass === 0 ? this.avrBass : Math.max(-60, this.avrBass); 1051 | 1052 | this.createArray(this.distLength_1, this.interval_1); 1053 | this.createArray(this.distLength_2, this.interval_2, true); 1054 | } 1055 | }, { 1056 | key: "createArray", 1057 | value: function createArray(num, interval, isReverse) { 1058 | if (!isReverse) { 1059 | for (var i = 0; i < num; i++) { 1060 | this.calcFrequency(num, interval, i); 1061 | } 1062 | } else { 1063 | for (var i = num - 1; i >= 0; i--) { 1064 | this.calcFrequency(num, interval, i); 1065 | } 1066 | } 1067 | } 1068 | }, { 1069 | key: "calcFrequency", 1070 | value: function calcFrequency(num, interval, i) { 1071 | var n1 = Math.floor(i * interval); 1072 | var n2 = n1 + 1; 1073 | var n0 = Math.abs(n1 - 1); 1074 | var n3 = n1 + 2; 1075 | 1076 | n2 = n2 > this.sourceLength - 1 ? (this.sourceLength - 1) * 2 - n2 : n2; 1077 | n3 = n3 > this.sourceLength - 1 ? (this.sourceLength - 1) * 2 - n3 : n3; 1078 | 1079 | var p0 = this.adjustFrequency(n0, this.avr); 1080 | var p1 = this.adjustFrequency(n1, this.avr); 1081 | var p2 = this.adjustFrequency(n2, this.avr); 1082 | var p3 = this.adjustFrequency(n3, this.avr); 1083 | 1084 | var mu = i * interval - n1; 1085 | 1086 | var targetFrequency = undefined; 1087 | 1088 | // interpolation 1089 | if (this.controls.props.isCubic) { 1090 | targetFrequency = this.cubic(mu, p0, p1, p2, p3); 1091 | } else if (this.controls.props.isLinear) { 1092 | targetFrequency = this.linear(mu, p1, p2); 1093 | } else if (this.controls.props.isNo) { 1094 | targetFrequency = p1; 1095 | } else if (this.controls.props.isCosine) { 1096 | targetFrequency = this.cosine(mu, p1, p2); 1097 | } else if (this.controls.props.isHermite) { 1098 | targetFrequency = this.hermite(mu, p0, p1, p2, p3); 1099 | } 1100 | 1101 | targetFrequency = Math.max(0, targetFrequency); 1102 | this.frequencyArray.push(targetFrequency * this.controls.props.scaleSize / 10); 1103 | } 1104 | }, { 1105 | key: "cubic", 1106 | value: function cubic(mu, p0, p1, p2, p3) { 1107 | var mu2 = mu * mu; 1108 | 1109 | var a0 = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3; 1110 | var a1 = p0 - 2.5 * p1 + 2 * p2 - 0.5 * p3; 1111 | var a2 = -0.5 * p0 + 0.5 * p2; 1112 | 1113 | return a0 * mu * mu2 + a1 * mu2 + a2 * mu + p1; 1114 | } 1115 | }, { 1116 | key: "linear", 1117 | value: function linear(mu, p1, p2) { 1118 | return p1 * (1 - mu) + p2 * mu; 1119 | } 1120 | }, { 1121 | key: "cosine", 1122 | value: function cosine(mu, p1, p2) { 1123 | var mu2 = undefined; 1124 | 1125 | mu2 = (1 - Math.cos(mu * Math.PI)) / 2; 1126 | return p1 * (1 - mu2) + p2 * mu2; 1127 | } 1128 | }, { 1129 | key: "hermite", 1130 | value: function hermite(mu, p0, p1, p2, p3) { 1131 | var tension = this.controls.props.tension; 1132 | var bias = this.controls.props.bias; 1133 | 1134 | var m0 = undefined, 1135 | m1 = undefined, 1136 | mu2 = undefined, 1137 | mu3 = undefined; 1138 | var a0 = undefined, 1139 | a1 = undefined, 1140 | a2 = undefined, 1141 | a3 = undefined; 1142 | 1143 | mu2 = mu * mu; 1144 | mu3 = mu2 * mu; 1145 | 1146 | m0 = (p1 - p0) * (1 + bias) * (1 - tension) / 2; 1147 | m0 += (p2 - p1) * (1 - bias) * (1 - tension) / 2; 1148 | m1 = (p2 - p1) * (1 + bias) * (1 - tension) / 2; 1149 | m1 += (p3 - p2) * (1 - bias) * (1 - tension) / 2; 1150 | 1151 | a0 = 2 * mu3 - 3 * mu2 + 1; 1152 | a1 = mu3 - 2 * mu2 + mu; 1153 | a2 = mu3 - mu2; 1154 | a3 = -2 * mu3 + 3 * mu2; 1155 | 1156 | return a0 * p1 + a1 * m0 + a2 * m1 + a3 * p2; 1157 | } 1158 | }]); 1159 | 1160 | return Analyzer; 1161 | })(); 1162 | 'use strict'; 1163 | 1164 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 1165 | 1166 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 1167 | 1168 | var Audio = (function () { 1169 | function Audio(webgl) { 1170 | _classCallCheck(this, Audio); 1171 | 1172 | this.webgl = webgl; 1173 | this.torus = this.webgl.torus; 1174 | this.audioContext = window.AudioContext ? new AudioContext() : new webkitAudioContext(); 1175 | this.fileReader = new FileReader(); 1176 | this.isReady = false; 1177 | this.count = 0; 1178 | 1179 | this.startEle = document.getElementById('start'); 1180 | this.playEle = document.getElementById('play'); 1181 | this.loadingEle = document.getElementById('loading'); 1182 | 1183 | // this.init(); 1184 | } 1185 | 1186 | _createClass(Audio, [{ 1187 | key: 'init', 1188 | value: function init() { 1189 | this.analyzer = new Analyzer(this, 0.8, 3.5); 1190 | 1191 | this.render(); 1192 | 1193 | this.playEle.addEventListener("click", (function () { 1194 | if (this.isReady) return; 1195 | this.source.start(0); 1196 | this.isReady = true; 1197 | this.startEle.classList.add("isHidden"); 1198 | }).bind(this)); 1199 | 1200 | this.loadAudio(); 1201 | 1202 | // document.getElementById('file').addEventListener('change', function(e){ 1203 | // this.fileReader.readAsArrayBuffer(e.target.files[0]); 1204 | // }.bind(this)); 1205 | 1206 | // var _this = this; 1207 | 1208 | // this.fileReader.onload = function(){ 1209 | // _this.audioContext.decodeAudioData(_this.fileReader.result, function(buffer){ 1210 | // if(_this.source) { 1211 | // _this.source.stop(); 1212 | // } 1213 | // _this.source = _this.audioContext.createBufferSource(); 1214 | // _this.source.buffer = buffer; 1215 | 1216 | // _this.source.loop = true; 1217 | 1218 | // _this.connectNode(buffer); 1219 | 1220 | // _this.isReady = true; 1221 | // }); 1222 | // }; 1223 | } 1224 | }, { 1225 | key: 'loadAudio', 1226 | value: function loadAudio() { 1227 | var _this = this; 1228 | 1229 | var request = new XMLHttpRequest(); 1230 | 1231 | request.open('GET', "assets/data/s.txt", true); 1232 | request.responseType = 'arraybuffer'; 1233 | 1234 | request.onload = (function () { 1235 | _this.audioContext.decodeAudioData(request.response, function (buffer) { 1236 | _this.loadingEle.classList.add("isHidden"); 1237 | _this.playEle.classList.remove("isHidden"); 1238 | 1239 | _this.connectNode(buffer); 1240 | }); 1241 | }).bind(this); 1242 | 1243 | request.send(); 1244 | } 1245 | }, { 1246 | key: 'connectNode', 1247 | value: function connectNode(buffer) { 1248 | if (this.source) { 1249 | this.source.stop(); 1250 | } 1251 | 1252 | this.source = this.audioContext.createBufferSource(); 1253 | this.source.buffer = buffer; 1254 | this.source.loop = true; 1255 | 1256 | this.source.connect(this.analyzer.analyser); 1257 | 1258 | this.source.connect(this.audioContext.destination); 1259 | } 1260 | }, { 1261 | key: 'render', 1262 | value: function render() { 1263 | this.analyzer.update(); 1264 | this.webgl.render(); 1265 | requestAnimationFrame(this.render.bind(this)); 1266 | } 1267 | }]); 1268 | 1269 | return Audio; 1270 | })(); 1271 | "use strict"; 1272 | 1273 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 1274 | 1275 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 1276 | 1277 | var Controls = (function () { 1278 | function Controls(analyzer) { 1279 | _classCallCheck(this, Controls); 1280 | 1281 | this.analyzer = analyzer; 1282 | this.props = { 1283 | // start: 1, 1284 | end: 600, 1285 | scaleSize: 4.0, 1286 | 1287 | isNo: false, 1288 | isLinear: false, 1289 | isCubic: true, 1290 | isCosine: false, 1291 | isHermite: false, 1292 | tension: 0, 1293 | bias: 0 1294 | }; 1295 | 1296 | this.init(); 1297 | } 1298 | 1299 | _createClass(Controls, [{ 1300 | key: "init", 1301 | value: function init() { 1302 | 1303 | this.gui = new dat.GUI({ width: 400 }); 1304 | console.log(this.gui); 1305 | // this.gui.remember(this.controls); 1306 | 1307 | // this.gui.add(this.props, "start", 1, 90).name("start frequency").onChange(this.changeFunc.bind(this)); 1308 | this.gui.add(this.props, "end", 200, 2000).name("end frequency").onChange(this.changeFreq.bind(this)); 1309 | this.gui.add(this.props, "scaleSize", 1 / 7 * 10, 1 / 2 * 10).name("scale size"); 1310 | 1311 | // about interpolation : http://paulbourke.net/miscellaneous/interpolation/ 1312 | this.gui_type = this.gui.addFolder('type of interpolation'); 1313 | this.gui_type.open(); 1314 | this.gui_type.add(this.props, "isNo").name("no interpolation(stepped)").listen().onChange(this.changeType1.bind(this)); 1315 | this.gui_type.add(this.props, "isLinear").name("linear interpolation").listen().onChange(this.changeType2.bind(this)); 1316 | this.gui_type.add(this.props, "isCubic").name("Catmull–Rom spline(cubic)").listen().onChange(this.changeType3.bind(this)); 1317 | this.gui_type.add(this.props, "isCosine").name("cosine interpolation").listen().onChange(this.changeType4.bind(this)); 1318 | // this.gui_type.add(this.props, "isHermite").name("hermite interpolation").listen().onChange(this.changeType5.bind(this)); 1319 | } 1320 | }, { 1321 | key: "changeFreq", 1322 | value: function changeFreq(value) { 1323 | 1324 | this.analyzer.setRange(); 1325 | } 1326 | }, { 1327 | key: "changeType1", 1328 | value: function changeType1() { 1329 | this.props.isNo = true; 1330 | this.props.isLinear = false; 1331 | this.props.isCubic = false; 1332 | this.props.isCosine = false; 1333 | this.props.isHermite = false; 1334 | this.removeHermite(); 1335 | } 1336 | }, { 1337 | key: "changeType2", 1338 | value: function changeType2() { 1339 | this.props.isNo = false; 1340 | this.props.isLinear = true; 1341 | this.props.isCubic = false; 1342 | this.props.isCosine = false; 1343 | this.props.isHermite = false; 1344 | this.removeHermite(); 1345 | } 1346 | }, { 1347 | key: "changeType3", 1348 | value: function changeType3() { 1349 | this.props.isNo = false; 1350 | this.props.isLinear = false; 1351 | this.props.isCubic = true; 1352 | this.props.isCosine = false; 1353 | this.props.isHermite = false; 1354 | this.removeHermite(); 1355 | } 1356 | }, { 1357 | key: "changeType4", 1358 | value: function changeType4() { 1359 | this.props.isNo = false; 1360 | this.props.isLinear = false; 1361 | this.props.isCubic = false; 1362 | this.props.isCosine = true; 1363 | this.props.isHermite = false; 1364 | this.removeHermite(); 1365 | } 1366 | }, { 1367 | key: "changeType5", 1368 | value: function changeType5() { 1369 | this.props.isNo = false; 1370 | this.props.isLinear = false; 1371 | this.props.isCubic = false; 1372 | this.props.isCosine = false; 1373 | this.props.isHermite = true; 1374 | this.addHermite(); 1375 | } 1376 | }, { 1377 | key: "addHermite", 1378 | value: function addHermite() { 1379 | if (this.gui_hermite) { 1380 | this.gui_hermite.domElement.setAttribute("style", "display: block"); 1381 | return; 1382 | } 1383 | 1384 | this.gui_hermite = this.gui.addFolder('parms of hermite'); 1385 | // console.log(this.gui_hermite); 1386 | this.gui_hermite.open(); 1387 | 1388 | /* 1389 | Tension: 1 is high, 0 normal, -1 is low 1390 | Bias: 0 is even, 1391 | positive is towards first segment, 1392 | negative towards the other 1393 | */ 1394 | 1395 | this.gui_hermite.add(this.props, "tension", -1, 1).name("hermite tension"); 1396 | this.gui_hermite.add(this.props, "bias", -5, 5).name("hermite bias"); 1397 | } 1398 | }, { 1399 | key: "removeHermite", 1400 | value: function removeHermite() { 1401 | if (this.gui_hermite) this.gui_hermite.domElement.setAttribute("style", "display: none"); 1402 | } 1403 | }]); 1404 | 1405 | return Controls; 1406 | })(); 1407 | "use strict"; 1408 | 1409 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 1410 | 1411 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 1412 | 1413 | var ResizeWatch = (function () { 1414 | function ResizeWatch() { 1415 | _classCallCheck(this, ResizeWatch); 1416 | 1417 | this.instances = []; 1418 | 1419 | this.width = this._width = document.body.clientWidth; 1420 | this.height = this._height = window.innerHeight; 1421 | this.aspect = this.width / this.height; 1422 | 1423 | window.onresize = (function () { 1424 | if (this.instances.length === 0) return; 1425 | 1426 | this.width = document.body.clientWidth; 1427 | this.height = window.innerHeight; 1428 | this.aspect = this.width / this.height; 1429 | 1430 | for (var i = 0; i < this.instances.length; i++) { 1431 | this.instances[i].resizeUpdate(); 1432 | } 1433 | }).bind(this); 1434 | } 1435 | 1436 | _createClass(ResizeWatch, [{ 1437 | key: "register", 1438 | value: function register(instance) { 1439 | this.instances.push(instance); 1440 | } 1441 | }]); 1442 | 1443 | return ResizeWatch; 1444 | })(); 1445 | 1446 | window.ResizeWatch = new ResizeWatch(); 1447 | "use strict"; 1448 | 1449 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 1450 | 1451 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 1452 | 1453 | var Torus = (function () { 1454 | function Torus(webgl) { 1455 | _classCallCheck(this, Torus); 1456 | 1457 | this.webgl = webgl; 1458 | this.init(); 1459 | } 1460 | 1461 | _createClass(Torus, [{ 1462 | key: "init", 1463 | value: function init() { 1464 | var geometry = this.createGeometry(); 1465 | 1466 | this.uniforms = { 1467 | uRadius: { type: "f", value: this.radius }, 1468 | uTick: { type: "f", value: 0 } 1469 | }; 1470 | 1471 | var material = new THREE.ShaderMaterial({ 1472 | vertexShader: this.webgl.vertShader[0], 1473 | fragmentShader: this.webgl.fragShader[0], 1474 | uniforms: this.uniforms, 1475 | shading: THREE.FlatShading, 1476 | side: THREE.DoubleSide 1477 | 1478 | }); 1479 | 1480 | var torus = new THREE.Mesh(geometry, material); 1481 | torus.frustumCulled = false; 1482 | 1483 | this.webgl.scene.add(torus); 1484 | } 1485 | }, { 1486 | key: "createGeometry", 1487 | value: function createGeometry() { 1488 | this.geometry = new THREE.BufferGeometry(); 1489 | 1490 | this.radius = 180 * 1.1; 1491 | 1492 | var tube = 1.5 * 1.1; 1493 | 1494 | this.radialSegments = 18; 1495 | 1496 | this.tubularSegments = 360; 1497 | 1498 | this.orderNumArray = []; 1499 | var vertices = []; 1500 | var normals = []; 1501 | var radians = []; 1502 | var uvs = []; 1503 | var indices = []; 1504 | 1505 | for (var j = 0; j <= this.radialSegments; j++) { 1506 | for (var i = 0; i <= this.tubularSegments; i++) { 1507 | 1508 | this.orderNumArray.push(i); 1509 | 1510 | var u = i / this.tubularSegments * Math.PI * 2; 1511 | var v = j / this.radialSegments * Math.PI * 2; 1512 | 1513 | radians.push(u); 1514 | 1515 | // vertex 1516 | 1517 | var vertex = {}; 1518 | 1519 | vertex.x = (this.radius + tube * Math.cos(v)) * Math.cos(u); 1520 | vertex.y = (this.radius + tube * Math.cos(v)) * Math.sin(u); 1521 | vertex.z = tube * Math.sin(v); 1522 | 1523 | vertices.push(vertex.x, vertex.y, vertex.z); 1524 | 1525 | // normal 1526 | 1527 | var center = {}; 1528 | 1529 | center.x = this.radius * Math.cos(u); 1530 | center.y = this.radius * Math.sin(u); 1531 | 1532 | var normal = {}; 1533 | 1534 | normal.x = vertex.x - center.x; 1535 | normal.y = vertex.y - center.y; 1536 | normal.z = vertex.z - center.z; 1537 | 1538 | var normalRatio = Math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z); 1539 | 1540 | // normal.subVectors( , center ).normalize(); 1541 | 1542 | normals.push(normal.x / normalRatio, normal.y / normalRatio, normal.z / normalRatio); 1543 | 1544 | // uv 1545 | 1546 | uvs.push(i / this.tubularSegments); 1547 | uvs.push(j / this.radialSegments); 1548 | } 1549 | } 1550 | 1551 | // indices 1552 | for (var j = 1; j <= this.radialSegments; j++) { 1553 | for (var i = 1; i <= this.tubularSegments; i++) { 1554 | 1555 | // indices 1556 | 1557 | var a = (this.tubularSegments + 1) * j + i - 1; 1558 | var b = (this.tubularSegments + 1) * (j - 1) + i - 1; 1559 | var c = (this.tubularSegments + 1) * (j - 1) + i; 1560 | var d = (this.tubularSegments + 1) * j + i; 1561 | 1562 | // faces 1563 | indices.push(a, b, d); 1564 | indices.push(b, c, d); 1565 | } 1566 | } 1567 | 1568 | this.radians = new THREE.Float32BufferAttribute(radians, 1); 1569 | 1570 | this.indices = new (indices.length > 65535 ? THREE.Uint32BufferAttribute : THREE.Uint16BufferAttribute)(indices, 1); 1571 | this.positions = new THREE.Float32BufferAttribute(vertices, 3); 1572 | this.normals = new THREE.Float32BufferAttribute(normals, 3); 1573 | 1574 | this.uvs = new THREE.Float32BufferAttribute(uvs, 2); 1575 | 1576 | this.frequencies = new THREE.BufferAttribute(new Float32Array((this.radialSegments + 1) * (this.tubularSegments + 1)), 1); 1577 | 1578 | this.geometry.setIndex(this.indices); 1579 | this.geometry.addAttribute("position", this.positions); 1580 | this.geometry.addAttribute("normal", this.normals); 1581 | this.geometry.addAttribute("uv", this.uvs); 1582 | 1583 | this.geometry.addAttribute("aFrequency", this.frequencies); 1584 | this.geometry.addAttribute("aRadian", this.radians); 1585 | 1586 | return this.geometry; 1587 | } 1588 | }, { 1589 | key: "render", 1590 | value: function render() { 1591 | this.uniforms.uTick.value++; 1592 | var spectrums = this.webgl.audio.analyzer.frequencyArray; 1593 | 1594 | var aFrequency = this.geometry.attributes.aFrequency; 1595 | aFrequency.needsUpdate = true; 1596 | 1597 | for (var i = 0; i < aFrequency.count; i++) { 1598 | var num = this.orderNumArray[i]; 1599 | if (num === this.tubularSegments) num = 0; 1600 | var spectrum = spectrums[num]; 1601 | aFrequency.array[i] = spectrum; 1602 | } 1603 | } 1604 | }]); 1605 | 1606 | return Torus; 1607 | })(); 1608 | "use strict"; 1609 | 1610 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 1611 | 1612 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 1613 | 1614 | var Webgl = (function () { 1615 | function Webgl() { 1616 | _classCallCheck(this, Webgl); 1617 | 1618 | this.vertShader = ["assets/glsl/torus.vert"]; 1619 | 1620 | this.fragShader = ["assets/glsl/torus.frag"]; 1621 | 1622 | this.shaderLength = this.vertShader.length + this.fragShader.length; 1623 | this.shaderCount = 0; 1624 | 1625 | for (var i = 0; i < this.vertShader.length; i++) { 1626 | this.importShader_vert(i); 1627 | } 1628 | 1629 | for (var i = 0; i < this.fragShader.length; i++) { 1630 | this.importShader_frag(i); 1631 | } 1632 | } 1633 | 1634 | _createClass(Webgl, [{ 1635 | key: "importShader_vert", 1636 | value: function importShader_vert(i) { 1637 | 1638 | var myRequest = new XMLHttpRequest(); 1639 | 1640 | var _this = this; 1641 | myRequest.onreadystatechange = function () { 1642 | if (myRequest.readyState === 4) { 1643 | _this.vertShader[i] = myRequest.response; 1644 | _this.completeShaderLoad(); 1645 | } 1646 | }; 1647 | 1648 | myRequest.open("GET", this.vertShader[i], true); 1649 | myRequest.send(); 1650 | } 1651 | }, { 1652 | key: "importShader_frag", 1653 | value: function importShader_frag(i) { 1654 | 1655 | var myRequest = new XMLHttpRequest(); 1656 | 1657 | var _this = this; 1658 | myRequest.onreadystatechange = function () { 1659 | if (myRequest.readyState === 4) { 1660 | _this.fragShader[i] = myRequest.response; 1661 | 1662 | _this.completeShaderLoad(); 1663 | } 1664 | }; 1665 | 1666 | myRequest.open("GET", this.fragShader[i], true); 1667 | myRequest.send(); 1668 | } 1669 | }, { 1670 | key: "completeShaderLoad", 1671 | value: function completeShaderLoad() { 1672 | this.shaderCount++; 1673 | 1674 | if (this.shaderCount === this.shaderLength) { 1675 | this.isShaderComplete = true; 1676 | this.init(); 1677 | } 1678 | } 1679 | }, { 1680 | key: "init", 1681 | value: function init() { 1682 | // console.log(this.vertShader, this.fragShader); 1683 | ResizeWatch.register(this); 1684 | 1685 | this.width = 1600; 1686 | this.height = 1600; 1687 | this.aspect = this.width / this.height; 1688 | 1689 | this.scene = new THREE.Scene(); 1690 | 1691 | this.setProps(); 1692 | 1693 | this.camera = new THREE.PerspectiveCamera(this.props.fov, this.props.aspect, this.props.near, this.props.far); 1694 | // var cameraZ = (this.props.height / 2) / Math.tan((45 * Math.PI / 180) / 2); 1695 | 1696 | // this.camera.position.set(0, cameraZ/1.8, 0); 1697 | 1698 | // this.camera.lookAt(this.scene.position); 1699 | 1700 | this.renderer = new THREE.WebGLRenderer({ 1701 | antialias: true, 1702 | alpha: true 1703 | }); 1704 | 1705 | var ratio = 1.5; 1706 | 1707 | this.renderer.setPixelRatio(ratio); 1708 | 1709 | this.renderer.setClearColor(0xffffff, 1); 1710 | this.renderer.setSize(ResizeWatch.width, ResizeWatch.height); 1711 | 1712 | this.div = document.getElementById("wrapper"); 1713 | this.div.appendChild(this.renderer.domElement); 1714 | 1715 | this.torus = new Torus(this); 1716 | 1717 | var control = new THREE.OrbitControls(this.camera, this.renderer.domElement); 1718 | 1719 | this.audio = new Audio(this); 1720 | this.audio.init(); 1721 | this.resizeUpdate(); 1722 | } 1723 | }, { 1724 | key: "setProps", 1725 | value: function setProps() { 1726 | var width = ResizeWatch.width; 1727 | var height = ResizeWatch.height; 1728 | var aspect = width / height; 1729 | 1730 | this.props = { 1731 | width: width, 1732 | height: height, 1733 | aspect: aspect, 1734 | fov: 45, 1735 | left: -width / 2, 1736 | right: width / 2, 1737 | top: height / 2, 1738 | bottom: -height / 2, 1739 | near: 0.1, 1740 | far: 10000, 1741 | parent: document.getElementById("wrapper") 1742 | }; 1743 | } 1744 | }, { 1745 | key: "resizeUpdate", 1746 | value: function resizeUpdate() { 1747 | this.setProps(); 1748 | this.renderer.setSize(this.props.width, this.props.height); 1749 | 1750 | this.camera.aspect = this.props.aspect; 1751 | 1752 | var cameraZ = this.props.height / 2 / Math.tan(this.props.fov * Math.PI / 180 / 2); 1753 | 1754 | this.camera.position.set(0, -cameraZ / 2, -cameraZ / 7.0); 1755 | 1756 | this.camera.lookAt(this.scene.position); 1757 | 1758 | this.camera.updateProjectionMatrix(); 1759 | } 1760 | }, { 1761 | key: "render", 1762 | value: function render() { 1763 | if (this.uniforms) this.uniforms.uTick.value += 1; 1764 | this.renderer.render(this.scene, this.camera); 1765 | this.torus.render(); 1766 | // this.effect.render(); 1767 | } 1768 | }]); 1769 | 1770 | return Webgl; 1771 | })(); 1772 | "use strict"; 1773 | 1774 | window.onload = function () { 1775 | var webgl = new Webgl(); 1776 | }; -------------------------------------------------------------------------------- /dst/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | audio visualizer torus 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 36 | 37 | 38 |
39 |

MUSIC : Don't Talk About It

40 |

BY Michael Mar (FEAT. Kyan Palmer)

41 |
42 |
43 |
LOADING ...
44 |
PLAY
45 | 46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /gulp/tasks/browsersync.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserSync = require('browser-sync'); 3 | 4 | gulp.task('browserSync', function(){ 5 | browserSync({ 6 | open: 'external', 7 | reloadDebounce: 2000, 8 | ui: false, 9 | notify: false, 10 | startPath: "/", 11 | ghostMode: false, 12 | server: { 13 | baseDir: "dst/" 14 | }, 15 | files: [ 16 | "dst/**/*.obj", 17 | 18 | "dst/**/*.json", 19 | "dst/**/*.xml", 20 | 21 | "dst/**/*.mp4", 22 | "dst/**/*.webm", 23 | "dst/**/*.mp3", 24 | 25 | "dst/**/*.png", 26 | "dst/**/*.jpg", 27 | "dst/**/*.gif", 28 | "dst/**/*.svg", 29 | 30 | "dst/**/*.frag", 31 | "dst/**/*.vert", 32 | "dst/**/*.glsl", 33 | 34 | "dst/**/*.html", 35 | "dst/**/*.css", 36 | "dst/**/*.js" 37 | ] 38 | }); 39 | }); -------------------------------------------------------------------------------- /gulp/tasks/glsl.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var glslify = require("gulp-glslify"); 3 | 4 | // var path = "assets/test"; 5 | 6 | 7 | gulp.task("glsl", null, function() { 8 | gulp.src("src/**/*.{vert,frag}") 9 | .pipe(glslify()) 10 | .pipe(gulp.dest("dst")); 11 | }); -------------------------------------------------------------------------------- /gulp/tasks/html.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var plumber = require('gulp-plumber'); 3 | 4 | 5 | gulp.task('html', function() 6 | { 7 | 8 | gulp.src("src/**/*.html") 9 | .pipe(gulp.dest("dst")); 10 | }); -------------------------------------------------------------------------------- /gulp/tasks/javascript.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var changed = require("gulp-changed"); 3 | var concat = require("gulp-concat"); 4 | var runSequence = require('run-sequence'); 5 | var stripDebug = require('gulp-strip-debug'); 6 | var babel = require('gulp-babel'); 7 | var plumber = require('gulp-plumber'); 8 | 9 | 10 | 11 | gulp.task("js", function(){ 12 | return runSequence( 13 | "js.concat" 14 | ); 15 | }); 16 | 17 | 18 | 19 | gulp.task("js.concat", function(){ 20 | return gulp.src([ 21 | "src/assets/js/lib-head/*.js", 22 | "src/assets/js/lib/*.js", 23 | "src/assets/js/module/*.js", 24 | "src/assets/js/main.js" 25 | ], 26 | { base: "src" } 27 | ) 28 | .pipe(plumber()) 29 | .pipe(babel()) 30 | .pipe(concat("main.min.js")) 31 | .pipe(gulp.dest("dst/assets/js/")); 32 | }) -------------------------------------------------------------------------------- /gulp/tasks/sass.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var sass = require("gulp-sass"); 3 | var plumber = require('gulp-plumber'); 4 | var csswring = require('csswring'); 5 | 6 | 7 | gulp.task("css", function () { 8 | 9 | var postcss = require("gulp-postcss"); 10 | var postcssOptions = [ 11 | require('postcss-mixins'), 12 | require("autoprefixer")({ browsers: [ 13 | 'ie >= 9', 14 | 'ie_mob >= 10', 15 | 'ff >= 31', 16 | 'chrome >= 36', 17 | 'safari >= 8', 18 | 'opera >= 23', 19 | 'ios >= 8', 20 | 'android >= 4' 21 | ]}), 22 | require("css-mqpacker")({ sort: function (a, b) { return b.localeCompare(a); } }), 23 | require("postcss-flexbugs-fixes"), 24 | require("postcss-partial-import")() 25 | ]; 26 | 27 | postcssOptions.push(csswring); 28 | 29 | gulp.src("src/assets/css/styles.scss") 30 | .pipe(plumber()) 31 | .pipe(sass({outputStyle: "expanded"})) 32 | .pipe(postcss(postcssOptions)) 33 | .pipe(gulp.dest("dst/assets/css/")); 34 | }); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var requireDir = require('require-dir'); 3 | var runSequence = require('run-sequence'); 4 | 5 | 6 | requireDir('./gulp/tasks'); 7 | 8 | gulp.task('media', function() 9 | { 10 | 11 | gulp.src("src/**/*.txt") 12 | .pipe(gulp.dest("dst")); 13 | }); 14 | 15 | gulp.task('watch', function(){ 16 | gulp.watch(['src/assets/css/**/*.{scss,css}'], ['css']); 17 | gulp.watch('src/assets/js/**/*.js', ['js']); 18 | gulp.watch('src/assets/glsl/**/*.{vert,frag}', ['glsl']); 19 | gulp.watch('src/**/*.html', ['html']); 20 | gulp.watch('src/**/*.txt', ['media']); 21 | }); 22 | 23 | 24 | gulp.task('predefault', function(){ 25 | runSequence( 26 | 'css', 27 | 'html', 28 | 'glsl', 29 | 'js', 30 | 'media', 31 | 'browserSync', 32 | 'watch' 33 | ); 34 | }); 35 | 36 | 37 | gulp.task('default', ['predefault'], function(){ 38 | console.log('running default tasks...'); 39 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "audio-visualizer-torus", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "Misaki Nakano", 7 | "license": "GPL", 8 | "dependencies": {}, 9 | "scripts": { 10 | "start": "cross-env gulp" 11 | }, 12 | "devDependencies": { 13 | "autoprefixer": "^6.4.0", 14 | "babel-core": "^5.5.8", 15 | "babel-loader": "^5.1.4", 16 | "browser-sync": "^2.13.0", 17 | "cross-env": "^2.0.0", 18 | "css-loader": "^0.25.0", 19 | "css-mqpacker": "^5.0.1", 20 | "csswring": "^5.1.0", 21 | "del": "^2.2.2", 22 | "fs": "0.0.1-security", 23 | "glslify": "^6.0.2", 24 | "glslify-hex": "^2.1.1", 25 | "glslify-import": "^3.0.0", 26 | "gulp": "^3.9.1", 27 | "gulp-babel": "^5.1.0", 28 | "gulp-changed": "^1.3.2", 29 | "gulp-concat": "^2.6.0", 30 | "gulp-glslify": "git+https://github.com/yuichiroharai/gulp-glslify.git", 31 | "gulp-if": "^2.0.1", 32 | "gulp-load-plugins": "^1.2.4", 33 | "gulp-notify": "^2.2.0", 34 | "gulp-plumber": "^1.1.0", 35 | "gulp-postcss": "^6.1.1", 36 | "gulp-sass": "^2.3.2", 37 | "gulp-strip-debug": "^1.1.0", 38 | "gulp-uglify": "^2.0.0", 39 | "gulp-util": "^3.0.7", 40 | "gulp-watch": "^4.3.9", 41 | "gulp-webpack": "^1.5.0", 42 | "gulp.spritesmith": "^6.2.1", 43 | "html-loader": "^0.4.4", 44 | "postcss-flexbugs-fixes": "^2.0.0", 45 | "postcss-mixins": "^5.2.0", 46 | "postcss-partial-import": "^2.0.0", 47 | "require-dir": "^0.3.0", 48 | "run-sequence": "^1.2.2", 49 | "sass-variables-loader": "^0.1.3", 50 | "strip-loader": "^0.1.2", 51 | "style-loader": "^0.13.1", 52 | "through2": "^2.0.3", 53 | "vue-loader": "^9.7.0", 54 | "vue-style-loader": "^1.0.0", 55 | "webpack": "^2.2.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnmxmx/audio-visualizer-torus/e48841d2532f2bee8546a582005df3b88d9d5db4/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnmxmx/audio-visualizer-torus/e48841d2532f2bee8546a582005df3b88d9d5db4/screenshot2.png -------------------------------------------------------------------------------- /src/assets/css/styles.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import "vendor/initial"; 4 | 5 | body{ 6 | overflow: hidden; 7 | } 8 | 9 | .stats{ 10 | position:absolute; 11 | top: 0; 12 | right: 0; 13 | z-index: 2; 14 | } 15 | 16 | 17 | .file{ 18 | position: absolute; 19 | top: 15px; 20 | left: 15px; 21 | padding: 10px; 22 | box-sizing: border-box; 23 | border: 1px solid #000; 24 | // background: rgba(255, 255, 255, 0.2); 25 | 26 | letter-spacing: 0.01em; 27 | color: #000; 28 | cursor: pointer; 29 | transition: opacity .4s; 30 | 31 | font-size: 12px; 32 | padding: 15px 30px; 33 | letter-spacing: 0.15em; 34 | display: none; 35 | // opacity: 0; 36 | 37 | &.isHidden{ 38 | display: none; 39 | } 40 | 41 | 42 | 43 | 44 | // &:hover{ 45 | // opacity: .8; 46 | // } 47 | 48 | input{ 49 | display: none; 50 | } 51 | } 52 | 53 | 54 | .credits{ 55 | position: absolute; 56 | top: 20px; 57 | left: 20px; 58 | letter-spacing: 0.2em; 59 | font-size: 18px; 60 | p{ 61 | margin-bottom: 20px; 62 | } 63 | a{ 64 | color: #FF52AA 65 | 66 | } 67 | } 68 | 69 | .start{ 70 | position: absolute; 71 | width: 100%; 72 | height: 100%; 73 | top: 0; 74 | left: 0; 75 | background: rgba(#fff, 0.8); 76 | letter-spacing: 0.4em; 77 | font-size: 18px; 78 | z-index: 1000000000; 79 | 80 | transition: opacity 0.5s, visibility 0s 0.5s; 81 | 82 | &.isHidden{ 83 | opacity: 0; 84 | visibility: hidden; 85 | } 86 | 87 | .play, .loading{ 88 | position: absolute; 89 | top: 50%; 90 | left: 50%; 91 | transform: translate(-50%, -50%); 92 | // transition: opacity 0.5s, visibility 0s 0.5s; 93 | 94 | &.isHidden{ 95 | opacity: 0; 96 | visibility: hidden; 97 | 98 | } 99 | } 100 | 101 | .loading{ 102 | 103 | } 104 | 105 | .play{ 106 | // background: rgba(#000, 0.4); 107 | border: 2px solid #000; 108 | padding: 20px 40px; 109 | 110 | cursor: pointer; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/assets/css/vendor/_initial.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // -------------------- from reset.css 4 | html, 5 | body, article, section, nav, aside, 6 | h1, h2, h3, h4, h5, h6, 7 | header, footer, address, 8 | p, ol, ul, li, dl, dt, dd, div, 9 | a, strong, small, sup, sup, span, 10 | img, iframe, embed, object, video, audio, 11 | table, tr, td, th, 12 | canvas, 13 | svg { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | 22 | 23 | 24 | // -------------------- from normalize.css / sanitize.css 25 | html { 26 | cursor: default; 27 | line-height: 1; 28 | 29 | // http://ultimate-ez.com/2014/03/29/3572/ 30 | // http://xxmiz0rexx.tumblr.com/post/73393990520/webkit-text-size-adjust-100%E3%81%8C%E5%8A%B9%E3%81%8B%E3%81%AA%E3%81%8F%E3%81%A6%E7%84%A6%E3%81%A3%E3%81%9F 31 | -ms-text-size-adjust: 100%; 32 | -webkit-text-size-adjust: 100%; 33 | &.windows{ 34 | -webkit-font-smoothing: antialiased; 35 | } 36 | 37 | 38 | -webkit-overflow-scrolling: touch; 39 | -webkit-tap-highlight-color: transparent; 40 | font-family: arial; 41 | } 42 | 43 | 44 | 45 | // -------------------- from sanitize.css 46 | // https://developer.mozilla.org/ja/docs/Web/CSS/::selection 47 | //::-moz-selection, 48 | //::selection { 49 | // background-color: #ccc; 50 | // color: #000; 51 | // text-shadow: none; 52 | //} 53 | 54 | ::-moz-selection { 55 | background-color: #ccc; 56 | color: #000; 57 | text-shadow: none; 58 | } 59 | 60 | ::selection { 61 | background-color: #ccc; 62 | color: #000; 63 | text-shadow: none; 64 | } 65 | 66 | 67 | 68 | // -------------------- from reset.css 69 | ol, ul { 70 | list-style: none; 71 | } 72 | 73 | 74 | 75 | // -------------------- from reset.css / sanitize.css 76 | table { 77 | border-collapse: collapse; 78 | border-spacing: 0; 79 | } 80 | 81 | 82 | 83 | // -------------------- from reset.css / normalize.css / sanitize.css 84 | article, section, nav, aside, 85 | header, footer { 86 | display: block; 87 | } 88 | 89 | 90 | 91 | // -------------------- from normalize.css / sanitize.css 92 | video, audio, 93 | canvas { 94 | display: inline-block; 95 | } 96 | 97 | 98 | 99 | // -------------------- from normalize.css / sanitize.css 100 | audio:not([controls]) { 101 | display: none; 102 | height: 0; 103 | } 104 | 105 | 106 | 107 | // -------------------- from normalize.css / sanitize.css 108 | hr { 109 | box-sizing: content-box; 110 | height: 0; 111 | overflow: visible; 112 | } 113 | 114 | 115 | 116 | // -------------------- from normalize.css / sanitize.css 117 | strong { 118 | font-weight: inherit; 119 | } 120 | strong { 121 | font-weight: bolder; 122 | } 123 | 124 | 125 | 126 | // -------------------- from normalize.css / sanitize.css 127 | sub, 128 | sup { 129 | line-height: 0; 130 | position: relative; 131 | vertical-align: baseline; 132 | } 133 | sub { 134 | bottom: -.25em; 135 | } 136 | sup { 137 | top: -.5em; 138 | } 139 | 140 | 141 | 142 | // -------------------- from sanitize.css 143 | 144 | // http://www.02320.net/how-to-fill-svg-by-css/ 145 | svg { 146 | fill: currentColor; 147 | } 148 | 149 | 150 | 151 | // -------------------- from normalize.css / sanitize.css 152 | svg:not(:root) { 153 | overflow: hidden; 154 | } 155 | 156 | 157 | 158 | // -------------------- from normalize.css / sanitize.css 159 | a { 160 | background-color: transparent; 161 | 162 | // https://css-tricks.com/almanac/properties/t/text-decoration-skip/ 163 | -webkit-text-decoration-skip: objects; 164 | } 165 | 166 | 167 | 168 | // -------------------- from normalize.css / sanitize.css 169 | a:active, 170 | a:hover { 171 | outline-width: 0; 172 | } 173 | 174 | 175 | 176 | // -------------------- from normalize.css / sanitize.css 177 | // http://lealog.hateblo.jp/entry/2015/02/19/124748 178 | a { 179 | -ms-touch-action: manipulation; 180 | touch-action: manipulation; 181 | } -------------------------------------------------------------------------------- /src/assets/data/s.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnmxmx/audio-visualizer-torus/e48841d2532f2bee8546a582005df3b88d9d5db4/src/assets/data/s.txt -------------------------------------------------------------------------------- /src/assets/glsl/torus.frag: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | varying vec3 vPos; 3 | varying float vFrequency; 4 | 5 | const vec3 objColor = vec3(1.0); 6 | 7 | const vec3 hemiLight_g_1 = #e50061; 8 | const vec3 hemiLight_s_1 = #efcb03; 9 | const vec3 hemiLight_g_2 = #a20b79; 10 | const vec3 hemiLight_s_2 = #a20b24; 11 | 12 | 13 | const vec3 dirLight = vec3(0.2); 14 | const vec3 dirLight_2 = vec3(0.15); 15 | 16 | 17 | const vec3 hemiLightPos_1 = vec3(1.0, 0.0, -1.0); 18 | const vec3 hemiLightPos_2 = vec3(-0.5, 0.5, 1.0); 19 | 20 | 21 | const vec3 dirLightPos = vec3(-30, 50, 50); 22 | const vec3 dirLightPos_2 = vec3(30, -50, -50); 23 | 24 | 25 | vec3 calcIrradiance_hemi(vec3 newNormal, vec3 lightPos, vec3 grd, vec3 sky){ 26 | float dotNL = dot(newNormal, normalize(lightPos)); 27 | float hemiDiffuseWeight = 0.5 * dotNL + 0.5; 28 | 29 | return mix(grd, sky, hemiDiffuseWeight); 30 | } 31 | 32 | vec3 calcIrradiance_dir(vec3 newNormal, vec3 lightPos, vec3 light){ 33 | float dotNL = dot(newNormal, normalize(lightPos)); 34 | 35 | return light * max(0.0, dotNL); 36 | } 37 | 38 | 39 | void main(){ 40 | 41 | vec3 _normal = normalize( cross(dFdx(vPos), dFdy(vPos)) ); 42 | 43 | vec3 hemiColor = vec3(0.0); 44 | hemiColor += calcIrradiance_hemi(_normal, hemiLightPos_1, hemiLight_g_1, hemiLight_s_1) * 0.7; 45 | hemiColor += calcIrradiance_hemi(_normal, hemiLightPos_2, hemiLight_g_2, hemiLight_s_2) * 0.8; 46 | 47 | vec3 dirColor = vec3(0.0); 48 | dirColor += calcIrradiance_dir(_normal, dirLightPos, dirLight); 49 | dirColor += calcIrradiance_dir(_normal, dirLightPos_2, dirLight_2); 50 | 51 | 52 | vec3 color = objColor * hemiColor; 53 | 54 | color += dirColor; 55 | 56 | vec3 offsetColor = vec3(vFrequency * 0.2, vFrequency * 1.5, -vFrequency * 0.7) * 0.008; 57 | 58 | color += offsetColor; 59 | color.g = min(0.9, color.g); 60 | 61 | color.r = (color.g == 0.9 && color.r > 0.94) ? 0.94 : color.r; 62 | 63 | color = min(vec3(1.0), color); 64 | 65 | float offsetColor2 = min(0.0, (vPos.x + vPos.y) / 2000.0); 66 | color -= vec3(offsetColor2) * vec3(-0.3, -1.0, 1.2); 67 | 68 | color = min(vec3(1.0), color + 0.1); 69 | 70 | gl_FragColor = vec4(color, 0.95); 71 | } -------------------------------------------------------------------------------- /src/assets/glsl/torus.vert: -------------------------------------------------------------------------------- 1 | attribute float aFrequency; 2 | attribute float aRadian; 3 | 4 | uniform float uRadius; 5 | uniform float uTick; 6 | 7 | 8 | varying vec2 vUv; 9 | varying vec3 vPos; 10 | varying float vFrequency; 11 | 12 | 13 | const float PI = 3.1415926; 14 | 15 | 16 | mat2 calcRotate2D(float _time){ 17 | float _sin = sin(_time); 18 | float _cos = cos(_time); 19 | return mat2(_cos, _sin, -_sin, _cos); 20 | } 21 | 22 | 23 | void main(){ 24 | vUv = uv; 25 | vFrequency = min(10.0, aFrequency); 26 | 27 | float time = uTick * 0.005; 28 | 29 | vec3 offset = vec3(cos(aRadian), sin(aRadian), 0.0) * uRadius; 30 | 31 | vec3 _position = position - offset; 32 | 33 | _position *= (1.0 + aFrequency); 34 | 35 | _position += offset; 36 | 37 | _position.xy = calcRotate2D(time * 1.2) * _position.xy; 38 | 39 | 40 | 41 | vec4 mvPos = vec4(_position, 1.0); 42 | 43 | mvPos = modelViewMatrix * mvPos; 44 | 45 | vPos = mvPos.xyz; 46 | 47 | gl_Position =projectionMatrix * mvPos; 48 | } -------------------------------------------------------------------------------- /src/assets/js/lib-head/useragnt-all.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Useragnt 3 | * v0.3.1 4 | * 5 | * Copyright (c) 2016 Yuichiroh Arai 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/mit-license.php 8 | * 9 | * detects: mobile, tablet, pc, windows, mac, linux, ios, android, edge, ie, safari, chrome, firefox, opera 10 | !*/ 11 | !function(e,o){function i(e){return n.indexOf(e)!=-1}function r(e){var o=e.split("."),i={};return i.str=e,i.float=parseFloat(e)||0,i.major=o.length>0?parseInt(o[0])||0:0,i.minor=o.length>1?parseInt(o[1])||0:0,i.build=o.length>2?parseInt(o[2])||0:0,i.revision=o.length>3?parseInt(o[3])||0:0,i}var a={};a._detects=["mobile","tablet","pc","windows","mac","linux","ios","android","edge","ie","safari","chrome","firefox","opera"];var n=a.userAgent=e.navigator.userAgent.toLowerCase();a.mobile=i("iphone")||i("ipod")||i("android")&&i("mobile")||i("windows")&&i("phone")||i("firefox")&&i("mobile")||i("blackberry"),a.tablet=i("ipad")||i("android")&&!i("mobile")||i("windows")&&i("touch")&&!i("tablet pc")||i("firefox")&&i("tablet")||i("kindle")||i("silk")||i("playbook"),a.pc=!i("iphone")&&!i("ipod")&&!i("ipad")&&!i("android")&&(!i("windows")||!i("phone")&&(!i("touch")||i("tablet pc")))&&(!i("firefox")||!i("mobile")&&!i("tablet"))&&!i("blackberry")&&!i("kindle")&&!i("silk")&&!i("playbook"),a.windows=i("windows"),a.mac=i("mac os x")&&!i("iphone")&&!i("ipad")&&!i("ipod"),a.linux=i("linux")&&!i("android"),a.ios=i("iphone")||i("ipad")||i("ipod"),a.ios&&(a.ios=new Boolean(!0),n.match(/ os ([\d_]+)/g),a.ios.version=r(RegExp.$1.replace("_","."))),a.android=i("android"),a.android&&(a.android=new Boolean(!0),n.match(/android ([\d\.]+)/g),a.android.version=r(RegExp.$1)),a.edge=i("edge"),a.ie=i("trident")||i("msie"),a.safari=i("safari")&&!i("android")&&!i("edge")&&!i("opera")&&!i("opr")&&!i("chrome"),a.chrome=i("chrome")&&!i("edge")&&!i("opera")&&!i("opr"),a.chrome&&(a.chrome=new Boolean(!0),n.match(/chrome\/([\d.]+)/g),a.chrome.version=r(RegExp.$1)),a.firefox=i("firefox")&&!i("edge"),a.opera=i("opera")||i("opr");var d,t,s,l=a._classPrefix="",p=o.documentElement,c=p.className;for(t=a._detects.length,d=0;d EPS 189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 190 | 191 | if ( zoomChanged || 192 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 194 | 195 | scope.dispatchEvent( changeEvent ); 196 | 197 | lastPosition.copy( scope.object.position ); 198 | lastQuaternion.copy( scope.object.quaternion ); 199 | zoomChanged = false; 200 | 201 | return true; 202 | 203 | } 204 | 205 | return false; 206 | 207 | }; 208 | 209 | }(); 210 | 211 | this.dispose = function () { 212 | 213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 215 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 216 | 217 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 218 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 219 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 220 | 221 | document.removeEventListener( 'mousemove', onMouseMove, false ); 222 | document.removeEventListener( 'mouseup', onMouseUp, false ); 223 | 224 | window.removeEventListener( 'keydown', onKeyDown, false ); 225 | 226 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 227 | 228 | }; 229 | 230 | // 231 | // internals 232 | // 233 | 234 | var scope = this; 235 | 236 | var changeEvent = { type: 'change' }; 237 | var startEvent = { type: 'start' }; 238 | var endEvent = { type: 'end' }; 239 | 240 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; 241 | 242 | var state = STATE.NONE; 243 | 244 | var EPS = 0.000001; 245 | 246 | // current position in spherical coordinates 247 | var spherical = new THREE.Spherical(); 248 | var sphericalDelta = new THREE.Spherical(); 249 | 250 | var scale = 1; 251 | var panOffset = new THREE.Vector3(); 252 | var zoomChanged = false; 253 | 254 | var rotateStart = new THREE.Vector2(); 255 | var rotateEnd = new THREE.Vector2(); 256 | var rotateDelta = new THREE.Vector2(); 257 | 258 | var panStart = new THREE.Vector2(); 259 | var panEnd = new THREE.Vector2(); 260 | var panDelta = new THREE.Vector2(); 261 | 262 | var dollyStart = new THREE.Vector2(); 263 | var dollyEnd = new THREE.Vector2(); 264 | var dollyDelta = new THREE.Vector2(); 265 | 266 | function getAutoRotationAngle() { 267 | 268 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 269 | 270 | } 271 | 272 | function getZoomScale() { 273 | 274 | return Math.pow( 0.95, scope.zoomSpeed ); 275 | 276 | } 277 | 278 | function rotateLeft( angle ) { 279 | 280 | sphericalDelta.theta -= angle; 281 | 282 | } 283 | 284 | function rotateUp( angle ) { 285 | 286 | sphericalDelta.phi -= angle; 287 | 288 | } 289 | 290 | var panLeft = function () { 291 | 292 | var v = new THREE.Vector3(); 293 | 294 | return function panLeft( distance, objectMatrix ) { 295 | 296 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 297 | v.multiplyScalar( - distance ); 298 | 299 | panOffset.add( v ); 300 | 301 | }; 302 | 303 | }(); 304 | 305 | var panUp = function () { 306 | 307 | var v = new THREE.Vector3(); 308 | 309 | return function panUp( distance, objectMatrix ) { 310 | 311 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 312 | v.multiplyScalar( distance ); 313 | 314 | panOffset.add( v ); 315 | 316 | }; 317 | 318 | }(); 319 | 320 | // deltaX and deltaY are in pixels; right and down are positive 321 | var pan = function () { 322 | 323 | var offset = new THREE.Vector3(); 324 | 325 | return function pan( deltaX, deltaY ) { 326 | 327 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 328 | 329 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 330 | 331 | // perspective 332 | var position = scope.object.position; 333 | offset.copy( position ).sub( scope.target ); 334 | var targetDistance = offset.length(); 335 | 336 | // half of the fov is center to top of screen 337 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 338 | 339 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 340 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 341 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 342 | 343 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 344 | 345 | // orthographic 346 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 347 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 348 | 349 | } else { 350 | 351 | // camera neither orthographic nor perspective 352 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 353 | scope.enablePan = false; 354 | 355 | } 356 | 357 | }; 358 | 359 | }(); 360 | 361 | function dollyIn( dollyScale ) { 362 | 363 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 364 | 365 | scale /= dollyScale; 366 | 367 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 368 | 369 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 370 | scope.object.updateProjectionMatrix(); 371 | zoomChanged = true; 372 | 373 | } else { 374 | 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 376 | scope.enableZoom = false; 377 | 378 | } 379 | 380 | } 381 | 382 | function dollyOut( dollyScale ) { 383 | 384 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 385 | 386 | scale *= dollyScale; 387 | 388 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 389 | 390 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 391 | scope.object.updateProjectionMatrix(); 392 | zoomChanged = true; 393 | 394 | } else { 395 | 396 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 397 | scope.enableZoom = false; 398 | 399 | } 400 | 401 | } 402 | 403 | // 404 | // event callbacks - update the object state 405 | // 406 | 407 | function handleMouseDownRotate( event ) { 408 | 409 | //console.log( 'handleMouseDownRotate' ); 410 | 411 | rotateStart.set( event.clientX, event.clientY ); 412 | 413 | } 414 | 415 | function handleMouseDownDolly( event ) { 416 | 417 | //console.log( 'handleMouseDownDolly' ); 418 | 419 | dollyStart.set( event.clientX, event.clientY ); 420 | 421 | } 422 | 423 | function handleMouseDownPan( event ) { 424 | 425 | //console.log( 'handleMouseDownPan' ); 426 | 427 | panStart.set( event.clientX, event.clientY ); 428 | 429 | } 430 | 431 | function handleMouseMoveRotate( event ) { 432 | 433 | //console.log( 'handleMouseMoveRotate' ); 434 | 435 | rotateEnd.set( event.clientX, event.clientY ); 436 | rotateDelta.subVectors( rotateEnd, rotateStart ); 437 | 438 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 439 | 440 | // rotating across whole screen goes 360 degrees around 441 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 442 | 443 | // rotating up and down along whole screen attempts to go 360, but limited to 180 444 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 445 | 446 | rotateStart.copy( rotateEnd ); 447 | 448 | scope.update(); 449 | 450 | } 451 | 452 | function handleMouseMoveDolly( event ) { 453 | 454 | //console.log( 'handleMouseMoveDolly' ); 455 | 456 | dollyEnd.set( event.clientX, event.clientY ); 457 | 458 | dollyDelta.subVectors( dollyEnd, dollyStart ); 459 | 460 | if ( dollyDelta.y > 0 ) { 461 | 462 | dollyIn( getZoomScale() ); 463 | 464 | } else if ( dollyDelta.y < 0 ) { 465 | 466 | dollyOut( getZoomScale() ); 467 | 468 | } 469 | 470 | dollyStart.copy( dollyEnd ); 471 | 472 | scope.update(); 473 | 474 | } 475 | 476 | function handleMouseMovePan( event ) { 477 | 478 | //console.log( 'handleMouseMovePan' ); 479 | 480 | panEnd.set( event.clientX, event.clientY ); 481 | 482 | panDelta.subVectors( panEnd, panStart ); 483 | 484 | pan( panDelta.x, panDelta.y ); 485 | 486 | panStart.copy( panEnd ); 487 | 488 | scope.update(); 489 | 490 | } 491 | 492 | function handleMouseUp( event ) { 493 | 494 | // console.log( 'handleMouseUp' ); 495 | 496 | } 497 | 498 | function handleMouseWheel( event ) { 499 | 500 | // console.log( 'handleMouseWheel' ); 501 | 502 | if ( event.deltaY < 0 ) { 503 | 504 | dollyOut( getZoomScale() ); 505 | 506 | } else if ( event.deltaY > 0 ) { 507 | 508 | dollyIn( getZoomScale() ); 509 | 510 | } 511 | 512 | scope.update(); 513 | 514 | } 515 | 516 | function handleKeyDown( event ) { 517 | 518 | //console.log( 'handleKeyDown' ); 519 | 520 | switch ( event.keyCode ) { 521 | 522 | case scope.keys.UP: 523 | pan( 0, scope.keyPanSpeed ); 524 | scope.update(); 525 | break; 526 | 527 | case scope.keys.BOTTOM: 528 | pan( 0, - scope.keyPanSpeed ); 529 | scope.update(); 530 | break; 531 | 532 | case scope.keys.LEFT: 533 | pan( scope.keyPanSpeed, 0 ); 534 | scope.update(); 535 | break; 536 | 537 | case scope.keys.RIGHT: 538 | pan( - scope.keyPanSpeed, 0 ); 539 | scope.update(); 540 | break; 541 | 542 | } 543 | 544 | } 545 | 546 | function handleTouchStartRotate( event ) { 547 | 548 | //console.log( 'handleTouchStartRotate' ); 549 | 550 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 551 | 552 | } 553 | 554 | function handleTouchStartDolly( event ) { 555 | 556 | //console.log( 'handleTouchStartDolly' ); 557 | 558 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 559 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 560 | 561 | var distance = Math.sqrt( dx * dx + dy * dy ); 562 | 563 | dollyStart.set( 0, distance ); 564 | 565 | } 566 | 567 | function handleTouchStartPan( event ) { 568 | 569 | //console.log( 'handleTouchStartPan' ); 570 | 571 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 572 | 573 | } 574 | 575 | function handleTouchMoveRotate( event ) { 576 | 577 | //console.log( 'handleTouchMoveRotate' ); 578 | 579 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 580 | rotateDelta.subVectors( rotateEnd, rotateStart ); 581 | 582 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 583 | 584 | // rotating across whole screen goes 360 degrees around 585 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 586 | 587 | // rotating up and down along whole screen attempts to go 360, but limited to 180 588 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 589 | 590 | rotateStart.copy( rotateEnd ); 591 | 592 | scope.update(); 593 | 594 | } 595 | 596 | function handleTouchMoveDolly( event ) { 597 | 598 | //console.log( 'handleTouchMoveDolly' ); 599 | 600 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 601 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 602 | 603 | var distance = Math.sqrt( dx * dx + dy * dy ); 604 | 605 | dollyEnd.set( 0, distance ); 606 | 607 | dollyDelta.subVectors( dollyEnd, dollyStart ); 608 | 609 | if ( dollyDelta.y > 0 ) { 610 | 611 | dollyOut( getZoomScale() ); 612 | 613 | } else if ( dollyDelta.y < 0 ) { 614 | 615 | dollyIn( getZoomScale() ); 616 | 617 | } 618 | 619 | dollyStart.copy( dollyEnd ); 620 | 621 | scope.update(); 622 | 623 | } 624 | 625 | function handleTouchMovePan( event ) { 626 | 627 | //console.log( 'handleTouchMovePan' ); 628 | 629 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 630 | 631 | panDelta.subVectors( panEnd, panStart ); 632 | 633 | pan( panDelta.x, panDelta.y ); 634 | 635 | panStart.copy( panEnd ); 636 | 637 | scope.update(); 638 | 639 | } 640 | 641 | function handleTouchEnd( event ) { 642 | 643 | //console.log( 'handleTouchEnd' ); 644 | 645 | } 646 | 647 | // 648 | // event handlers - FSM: listen for events and reset state 649 | // 650 | 651 | function onMouseDown( event ) { 652 | 653 | if ( scope.enabled === false ) return; 654 | 655 | event.preventDefault(); 656 | 657 | if ( event.button === scope.mouseButtons.ORBIT ) { 658 | 659 | if ( scope.enableRotate === false ) return; 660 | 661 | handleMouseDownRotate( event ); 662 | 663 | state = STATE.ROTATE; 664 | 665 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 666 | 667 | if ( scope.enableZoom === false ) return; 668 | 669 | handleMouseDownDolly( event ); 670 | 671 | state = STATE.DOLLY; 672 | 673 | } else if ( event.button === scope.mouseButtons.PAN ) { 674 | 675 | if ( scope.enablePan === false ) return; 676 | 677 | handleMouseDownPan( event ); 678 | 679 | state = STATE.PAN; 680 | 681 | } 682 | 683 | if ( state !== STATE.NONE ) { 684 | 685 | document.addEventListener( 'mousemove', onMouseMove, false ); 686 | document.addEventListener( 'mouseup', onMouseUp, false ); 687 | 688 | scope.dispatchEvent( startEvent ); 689 | 690 | } 691 | 692 | } 693 | 694 | function onMouseMove( event ) { 695 | 696 | if ( scope.enabled === false ) return; 697 | 698 | event.preventDefault(); 699 | 700 | if ( state === STATE.ROTATE ) { 701 | 702 | if ( scope.enableRotate === false ) return; 703 | 704 | handleMouseMoveRotate( event ); 705 | 706 | } else if ( state === STATE.DOLLY ) { 707 | 708 | if ( scope.enableZoom === false ) return; 709 | 710 | handleMouseMoveDolly( event ); 711 | 712 | } else if ( state === STATE.PAN ) { 713 | 714 | if ( scope.enablePan === false ) return; 715 | 716 | handleMouseMovePan( event ); 717 | 718 | } 719 | 720 | } 721 | 722 | function onMouseUp( event ) { 723 | 724 | if ( scope.enabled === false ) return; 725 | 726 | handleMouseUp( event ); 727 | 728 | document.removeEventListener( 'mousemove', onMouseMove, false ); 729 | document.removeEventListener( 'mouseup', onMouseUp, false ); 730 | 731 | scope.dispatchEvent( endEvent ); 732 | 733 | state = STATE.NONE; 734 | 735 | } 736 | 737 | function onMouseWheel( event ) { 738 | 739 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 740 | 741 | event.preventDefault(); 742 | event.stopPropagation(); 743 | 744 | handleMouseWheel( event ); 745 | 746 | scope.dispatchEvent( startEvent ); // not sure why these are here... 747 | scope.dispatchEvent( endEvent ); 748 | 749 | } 750 | 751 | function onKeyDown( event ) { 752 | 753 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 754 | 755 | handleKeyDown( event ); 756 | 757 | } 758 | 759 | function onTouchStart( event ) { 760 | 761 | if ( scope.enabled === false ) return; 762 | 763 | switch ( event.touches.length ) { 764 | 765 | case 1: // one-fingered touch: rotate 766 | 767 | if ( scope.enableRotate === false ) return; 768 | 769 | handleTouchStartRotate( event ); 770 | 771 | state = STATE.TOUCH_ROTATE; 772 | 773 | break; 774 | 775 | case 2: // two-fingered touch: dolly 776 | 777 | if ( scope.enableZoom === false ) return; 778 | 779 | handleTouchStartDolly( event ); 780 | 781 | state = STATE.TOUCH_DOLLY; 782 | 783 | break; 784 | 785 | case 3: // three-fingered touch: pan 786 | 787 | if ( scope.enablePan === false ) return; 788 | 789 | handleTouchStartPan( event ); 790 | 791 | state = STATE.TOUCH_PAN; 792 | 793 | break; 794 | 795 | default: 796 | 797 | state = STATE.NONE; 798 | 799 | } 800 | 801 | if ( state !== STATE.NONE ) { 802 | 803 | scope.dispatchEvent( startEvent ); 804 | 805 | } 806 | 807 | } 808 | 809 | function onTouchMove( event ) { 810 | 811 | if ( scope.enabled === false ) return; 812 | 813 | event.preventDefault(); 814 | event.stopPropagation(); 815 | 816 | switch ( event.touches.length ) { 817 | 818 | case 1: // one-fingered touch: rotate 819 | 820 | if ( scope.enableRotate === false ) return; 821 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 822 | 823 | handleTouchMoveRotate( event ); 824 | 825 | break; 826 | 827 | case 2: // two-fingered touch: dolly 828 | 829 | if ( scope.enableZoom === false ) return; 830 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 831 | 832 | handleTouchMoveDolly( event ); 833 | 834 | break; 835 | 836 | case 3: // three-fingered touch: pan 837 | 838 | if ( scope.enablePan === false ) return; 839 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 840 | 841 | handleTouchMovePan( event ); 842 | 843 | break; 844 | 845 | default: 846 | 847 | state = STATE.NONE; 848 | 849 | } 850 | 851 | } 852 | 853 | function onTouchEnd( event ) { 854 | 855 | if ( scope.enabled === false ) return; 856 | 857 | handleTouchEnd( event ); 858 | 859 | scope.dispatchEvent( endEvent ); 860 | 861 | state = STATE.NONE; 862 | 863 | } 864 | 865 | function onContextMenu( event ) { 866 | 867 | event.preventDefault(); 868 | 869 | } 870 | 871 | // 872 | 873 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 874 | 875 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 876 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 877 | 878 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 879 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 880 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 881 | 882 | window.addEventListener( 'keydown', onKeyDown, false ); 883 | 884 | // force an update at start 885 | 886 | this.update(); 887 | 888 | }; 889 | 890 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 891 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 892 | 893 | Object.defineProperties( THREE.OrbitControls.prototype, { 894 | 895 | center: { 896 | 897 | get: function () { 898 | 899 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 900 | return this.target; 901 | 902 | } 903 | 904 | }, 905 | 906 | // backward compatibility 907 | 908 | noZoom: { 909 | 910 | get: function () { 911 | 912 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 913 | return ! this.enableZoom; 914 | 915 | }, 916 | 917 | set: function ( value ) { 918 | 919 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 920 | this.enableZoom = ! value; 921 | 922 | } 923 | 924 | }, 925 | 926 | noRotate: { 927 | 928 | get: function () { 929 | 930 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 931 | return ! this.enableRotate; 932 | 933 | }, 934 | 935 | set: function ( value ) { 936 | 937 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 938 | this.enableRotate = ! value; 939 | 940 | } 941 | 942 | }, 943 | 944 | noPan: { 945 | 946 | get: function () { 947 | 948 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 949 | return ! this.enablePan; 950 | 951 | }, 952 | 953 | set: function ( value ) { 954 | 955 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 956 | this.enablePan = ! value; 957 | 958 | } 959 | 960 | }, 961 | 962 | noKeys: { 963 | 964 | get: function () { 965 | 966 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 967 | return ! this.enableKeys; 968 | 969 | }, 970 | 971 | set: function ( value ) { 972 | 973 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 974 | this.enableKeys = ! value; 975 | 976 | } 977 | 978 | }, 979 | 980 | staticMoving: { 981 | 982 | get: function () { 983 | 984 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 985 | return ! this.enableDamping; 986 | 987 | }, 988 | 989 | set: function ( value ) { 990 | 991 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 992 | this.enableDamping = ! value; 993 | 994 | } 995 | 996 | }, 997 | 998 | dynamicDampingFactor: { 999 | 1000 | get: function () { 1001 | 1002 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1003 | return this.dampingFactor; 1004 | 1005 | }, 1006 | 1007 | set: function ( value ) { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1010 | this.dampingFactor = value; 1011 | 1012 | } 1013 | 1014 | } 1015 | 1016 | } ); 1017 | -------------------------------------------------------------------------------- /src/assets/js/main.js: -------------------------------------------------------------------------------- 1 | window.onload = function(){ 2 | var webgl = new Webgl(); 3 | }; 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/js/module/analyzer.js: -------------------------------------------------------------------------------- 1 | class Analyzer{ 2 | constructor(audio, smoothTime, scale){ 3 | this.audio = audio; 4 | this.controls = new Controls(this); 5 | 6 | this.scale = scale; 7 | 8 | this.audioContext = this.audio.audioContext; 9 | this.analyser = this.audioContext.createAnalyser(); 10 | this.analyser.fftSize = 2048; 11 | this.frequencyNum = 1024; 12 | this.hz = 22028; 13 | this.analyser.smoothingTimeConstant = smoothTime; 14 | 15 | this.frequencyArray = []; 16 | 17 | this.distLength_1 = 120 * 2; 18 | this.distLength_2 = 60 * 2; 19 | 20 | this.setRange(); 21 | 22 | this.level = 5; 23 | 24 | this.avrBass = 0; 25 | } 26 | 27 | 28 | setRange(){ 29 | this.minHz = 1; 30 | this.maxHz = this.controls.props.end; 31 | 32 | this.sourceStart = Math.ceil(this.frequencyNum * this.minHz / this.hz); 33 | this.sourceEnd = Math.round(this.frequencyNum * this.maxHz / this.hz); 34 | this.sourceLength = this.sourceEnd - this.sourceStart + 1; 35 | 36 | this.adjustOffset = Math.round(this.sourceLength * 0.12); 37 | 38 | this.interval_1 = (this.sourceLength - 1) / (this.distLength_1 - 1) ; 39 | this.interval_2 = (this.sourceLength - 1) / (this.distLength_2 - 1) ; 40 | } 41 | 42 | 43 | 44 | adjustFrequency(i, avr){ 45 | var f = Math.max(0, this.spectrums[this.sourceStart + i] - avr) * this.scale; 46 | var offset = i - this.sourceStart; 47 | 48 | var ratio = offset / this.adjustOffset; 49 | 50 | f *= Math.max(0, Math.min(1, 5 / 6 * (ratio - 1) * (ratio - 1) * (ratio - 1) + 1)); 51 | 52 | return f; 53 | } 54 | 55 | 56 | update(){ 57 | this.frequencyArray = []; 58 | 59 | var spectrums = new Float32Array(this.frequencyNum); 60 | 61 | if(this.audio.isReady) { 62 | this.analyser.getFloatFrequencyData(spectrums); 63 | } 64 | 65 | this.spectrums = spectrums; 66 | 67 | this.avr = 0; 68 | this.avrBass = 0; 69 | 70 | 71 | for(let i=this.sourceStart; i<=this.sourceEnd; i++){ 72 | this.avr += this.spectrums[i]; 73 | if(i < 6) this.avrBass += this.spectrums[i]; 74 | } 75 | 76 | this.avr /= this.sourceLength; 77 | this.avrBass /= 6; 78 | // console.log(this.avrBass); 79 | 80 | this.avr = (!this.audio.isReady || this.avr === 0) ? this.avr : Math.min(-40, Math.max(this.avr, -60)); 81 | this.avrBass = (!this.audio.isReady || this.avrBass === 0) ? this.avrBass : Math.max(-60, this.avrBass); 82 | 83 | this.createArray(this.distLength_1, this.interval_1); 84 | this.createArray(this.distLength_2, this.interval_2, true); 85 | } 86 | 87 | 88 | createArray(num, interval, isReverse){ 89 | if(!isReverse){ 90 | for(let i = 0; i < num; i++){ 91 | this.calcFrequency(num, interval, i); 92 | } 93 | } else { 94 | for(let i = num - 1; i >= 0; i--){ 95 | this.calcFrequency(num, interval, i); 96 | } 97 | } 98 | } 99 | 100 | calcFrequency(num, interval, i){ 101 | let n1 = Math.floor(i * interval); 102 | let n2 = n1 + 1; 103 | let n0 = Math.abs(n1 - 1); 104 | let n3 = n1 + 2; 105 | 106 | 107 | n2 = (n2 > this.sourceLength - 1) ? (this.sourceLength - 1) * 2 - n2 : n2; 108 | n3 = (n3 > this.sourceLength - 1) ? (this.sourceLength - 1) * 2 - n3 : n3; 109 | 110 | let p0 = this.adjustFrequency(n0, this.avr); 111 | let p1 = this.adjustFrequency(n1, this.avr); 112 | let p2 = this.adjustFrequency(n2, this.avr); 113 | let p3 = this.adjustFrequency(n3, this.avr); 114 | 115 | let mu = i * interval - n1; 116 | 117 | let targetFrequency; 118 | 119 | // interpolation 120 | if(this.controls.props.isCubic){ 121 | targetFrequency = this.cubic(mu, p0, p1, p2, p3); 122 | } else if(this.controls.props.isLinear){ 123 | targetFrequency = this.linear(mu, p1, p2); 124 | } else if(this.controls.props.isNo){ 125 | targetFrequency = p1; 126 | } else if(this.controls.props.isCosine){ 127 | targetFrequency = this.cosine(mu, p1, p2); 128 | } else if(this.controls.props.isHermite){ 129 | targetFrequency = this.hermite(mu, p0, p1, p2, p3); 130 | 131 | } 132 | 133 | targetFrequency = Math.max(0, targetFrequency); 134 | this.frequencyArray.push(targetFrequency * this.controls.props.scaleSize / 10); 135 | } 136 | 137 | cubic(mu, p0, p1, p2, p3){ 138 | let mu2 = mu * mu; 139 | 140 | 141 | let a0 = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3; 142 | let a1 = p0 - 2.5 * p1 + 2 * p2 - 0.5*p3; 143 | let a2 = -0.5 * p0 + 0.5 * p2; 144 | 145 | return a0 * mu * mu2 + a1 * mu2 + a2 * mu + p1; 146 | } 147 | 148 | linear(mu, p1, p2){ 149 | return p1 * (1 - mu) + p2 * mu; 150 | } 151 | 152 | cosine(mu, p1, p2){ 153 | let mu2; 154 | 155 | mu2 = (1 - Math.cos( mu * Math.PI)) / 2; 156 | return (p1 * (1 - mu2) + p2 * mu2); 157 | } 158 | 159 | hermite(mu, p0, p1, p2, p3){ 160 | const tension = this.controls.props.tension; 161 | const bias = this.controls.props.bias; 162 | 163 | let m0,m1,mu2,mu3; 164 | let a0,a1,a2,a3; 165 | 166 | mu2 = mu * mu; 167 | mu3 = mu2 * mu; 168 | 169 | m0 = (p1 - p0) * (1 + bias) * (1 - tension) / 2; 170 | m0 += (p2 - p1) * (1 - bias) * (1 - tension) / 2; 171 | m1 = (p2 - p1) * (1 + bias) * (1 - tension) / 2; 172 | m1 += (p3 - p2) * (1 - bias) * (1 - tension) / 2; 173 | 174 | a0 = 2 * mu3 - 3 * mu2 + 1; 175 | a1 = mu3 - 2 * mu2 + mu; 176 | a2 = mu3 - mu2; 177 | a3 = - 2 * mu3 + 3 * mu2; 178 | 179 | return a0 * p1 + a1 * m0 + a2 * m1 + a3 * p2; 180 | } 181 | 182 | } -------------------------------------------------------------------------------- /src/assets/js/module/audio.js: -------------------------------------------------------------------------------- 1 | class Audio{ 2 | constructor(webgl){ 3 | this.webgl = webgl; 4 | this.torus = this.webgl.torus; 5 | this.audioContext = (window.AudioContext) ? new AudioContext : new webkitAudioContext; 6 | this.fileReader = new FileReader; 7 | this.isReady = false; 8 | this.count = 0; 9 | 10 | this.startEle = document.getElementById('start'); 11 | this.playEle = document.getElementById('play'); 12 | this.loadingEle = document.getElementById('loading'); 13 | 14 | // this.init(); 15 | } 16 | 17 | init(){ 18 | this.analyzer = new Analyzer(this, 0.8, 3.5); 19 | 20 | this.render(); 21 | 22 | 23 | this.playEle.addEventListener("click", function(){ 24 | if(this.isReady) return; 25 | this.source.start(0); 26 | this.isReady = true; 27 | this.startEle.classList.add("isHidden"); 28 | }.bind(this)) 29 | 30 | this.loadAudio(); 31 | 32 | // document.getElementById('file').addEventListener('change', function(e){ 33 | // this.fileReader.readAsArrayBuffer(e.target.files[0]); 34 | // }.bind(this)); 35 | 36 | 37 | // var _this = this; 38 | 39 | // this.fileReader.onload = function(){ 40 | // _this.audioContext.decodeAudioData(_this.fileReader.result, function(buffer){ 41 | // if(_this.source) { 42 | // _this.source.stop(); 43 | // } 44 | // _this.source = _this.audioContext.createBufferSource(); 45 | // _this.source.buffer = buffer; 46 | 47 | // _this.source.loop = true; 48 | 49 | // _this.connectNode(buffer); 50 | 51 | // _this.isReady = true; 52 | // }); 53 | // }; 54 | }; 55 | 56 | 57 | loadAudio(){ 58 | var _this = this; 59 | 60 | var request = new XMLHttpRequest(); 61 | 62 | request.open('GET', "assets/data/s.txt", true); 63 | request.responseType = 'arraybuffer'; 64 | 65 | request.onload = function() { 66 | _this.audioContext.decodeAudioData(request.response, function(buffer){ 67 | _this.loadingEle.classList.add("isHidden"); 68 | _this.playEle.classList.remove("isHidden"); 69 | 70 | _this.connectNode(buffer); 71 | }); 72 | }.bind(this); 73 | 74 | request.send(); 75 | }; 76 | 77 | 78 | connectNode(buffer){ 79 | if(this.source) { 80 | this.source.stop(); 81 | } 82 | 83 | this.source = this.audioContext.createBufferSource(); 84 | this.source.buffer = buffer; 85 | this.source.loop = true; 86 | 87 | this.source.connect(this.analyzer.analyser); 88 | 89 | this.source.connect(this.audioContext.destination); 90 | }; 91 | 92 | 93 | render(){ 94 | this.analyzer.update(); 95 | this.webgl.render(); 96 | requestAnimationFrame(this.render.bind(this)); 97 | }; 98 | } -------------------------------------------------------------------------------- /src/assets/js/module/controls.js: -------------------------------------------------------------------------------- 1 | class Controls{ 2 | constructor(analyzer){ 3 | this.analyzer = analyzer; 4 | this.props = { 5 | // start: 1, 6 | end: 600, 7 | scaleSize: 4.0, 8 | 9 | isNo: false, 10 | isLinear: false, 11 | isCubic: true, 12 | isCosine: false, 13 | isHermite: false, 14 | tension: 0, 15 | bias: 0 16 | }; 17 | 18 | this.init(); 19 | } 20 | 21 | init(){ 22 | 23 | 24 | this.gui = new dat.GUI({width: 400}); 25 | console.log(this.gui); 26 | // this.gui.remember(this.controls); 27 | 28 | // this.gui.add(this.props, "start", 1, 90).name("start frequency").onChange(this.changeFunc.bind(this)); 29 | this.gui.add(this.props, "end", 200, 2000).name("end frequency").onChange(this.changeFreq.bind(this)); 30 | this.gui.add(this.props, "scaleSize", 1/7 * 10, 1/2 * 10).name("scale size"); 31 | 32 | 33 | // about interpolation : http://paulbourke.net/miscellaneous/interpolation/ 34 | this.gui_type = this.gui.addFolder('type of interpolation'); 35 | this.gui_type.open(); 36 | this.gui_type.add(this.props, "isNo").name("no interpolation(stepped)").listen().onChange(this.changeType1.bind(this)); 37 | this.gui_type.add(this.props, "isLinear").name("linear interpolation").listen().onChange(this.changeType2.bind(this)); 38 | this.gui_type.add(this.props, "isCubic").name("Catmull–Rom spline(cubic)").listen().onChange(this.changeType3.bind(this)); 39 | this.gui_type.add(this.props, "isCosine").name("cosine interpolation").listen().onChange(this.changeType4.bind(this)); 40 | // this.gui_type.add(this.props, "isHermite").name("hermite interpolation").listen().onChange(this.changeType5.bind(this)); 41 | } 42 | 43 | changeFreq(value){ 44 | 45 | this.analyzer.setRange(); 46 | 47 | } 48 | 49 | changeType1(){ 50 | this.props.isNo = true; 51 | this.props.isLinear = false; 52 | this.props.isCubic = false; 53 | this.props.isCosine = false; 54 | this.props.isHermite = false; 55 | this.removeHermite(); 56 | } 57 | 58 | changeType2(){ 59 | this.props.isNo = false; 60 | this.props.isLinear = true; 61 | this.props.isCubic = false; 62 | this.props.isCosine = false; 63 | this.props.isHermite = false; 64 | this.removeHermite(); 65 | 66 | } 67 | 68 | changeType3(){ 69 | this.props.isNo = false; 70 | this.props.isLinear = false; 71 | this.props.isCubic = true; 72 | this.props.isCosine = false; 73 | this.props.isHermite = false; 74 | this.removeHermite(); 75 | 76 | 77 | 78 | } 79 | 80 | changeType4(){ 81 | this.props.isNo = false; 82 | this.props.isLinear = false; 83 | this.props.isCubic = false; 84 | this.props.isCosine = true; 85 | this.props.isHermite = false; 86 | this.removeHermite(); 87 | } 88 | 89 | changeType5(){ 90 | this.props.isNo = false; 91 | this.props.isLinear = false; 92 | this.props.isCubic = false; 93 | this.props.isCosine = false; 94 | this.props.isHermite = true; 95 | this.addHermite(); 96 | } 97 | 98 | addHermite(){ 99 | if(this.gui_hermite){ 100 | this.gui_hermite.domElement.setAttribute("style", "display: block"); 101 | return; 102 | } 103 | 104 | this.gui_hermite = this.gui.addFolder('parms of hermite'); 105 | // console.log(this.gui_hermite); 106 | this.gui_hermite.open(); 107 | 108 | /* 109 | Tension: 1 is high, 0 normal, -1 is low 110 | Bias: 0 is even, 111 | positive is towards first segment, 112 | negative towards the other 113 | */ 114 | 115 | 116 | 117 | this.gui_hermite.add(this.props, "tension", -1, 1).name("hermite tension"); 118 | this.gui_hermite.add(this.props, "bias", -5, 5).name("hermite bias"); 119 | 120 | 121 | } 122 | 123 | removeHermite(){ 124 | if(this.gui_hermite) this.gui_hermite.domElement.setAttribute("style", "display: none"); 125 | } 126 | } -------------------------------------------------------------------------------- /src/assets/js/module/resize-watch.js: -------------------------------------------------------------------------------- 1 | class ResizeWatch{ 2 | constructor(){ 3 | this.instances = []; 4 | 5 | this.width = this._width = document.body.clientWidth; 6 | this.height = this._height = window.innerHeight; 7 | this.aspect = this.width / this.height; 8 | 9 | 10 | window.onresize = function(){ 11 | if(this.instances.length === 0) return; 12 | 13 | this.width = document.body.clientWidth; 14 | this.height = window.innerHeight; 15 | this.aspect = this.width / this.height; 16 | 17 | for(let i = 0; i < this.instances.length; i++){ 18 | this.instances[i].resizeUpdate(); 19 | } 20 | }.bind(this) 21 | } 22 | 23 | register(instance){ 24 | this.instances.push(instance); 25 | } 26 | } 27 | 28 | window.ResizeWatch = new ResizeWatch(); -------------------------------------------------------------------------------- /src/assets/js/module/torus.js: -------------------------------------------------------------------------------- 1 | class Torus{ 2 | constructor(webgl){ 3 | this.webgl = webgl; 4 | this.init(); 5 | } 6 | 7 | init(){ 8 | const geometry = this.createGeometry(); 9 | 10 | this.uniforms = { 11 | uRadius: {type: "f", value: this.radius}, 12 | uTick: {type: "f", value: 0} 13 | } 14 | 15 | const material = new THREE.ShaderMaterial( { 16 | vertexShader: this.webgl.vertShader[0], 17 | fragmentShader: this.webgl.fragShader[0], 18 | uniforms: this.uniforms, 19 | shading: THREE.FlatShading, 20 | side: THREE.DoubleSide, 21 | 22 | } ); 23 | 24 | const torus = new THREE.Mesh( geometry, material ); 25 | torus.frustumCulled = false; 26 | 27 | this.webgl.scene.add( torus ); 28 | } 29 | 30 | 31 | createGeometry(){ 32 | this.geometry = new THREE.BufferGeometry(); 33 | 34 | this.radius = 180 * 1.1; 35 | 36 | const tube = 1.5 * 1.1; 37 | 38 | this.radialSegments = 18; 39 | 40 | this.tubularSegments = 360; 41 | 42 | this.orderNumArray = []; 43 | const vertices = []; 44 | const normals = []; 45 | const radians = []; 46 | const uvs = []; 47 | const indices = []; 48 | 49 | for ( let j = 0; j <= this.radialSegments; j ++ ) { 50 | for ( let i = 0; i <= this.tubularSegments; i ++ ) { 51 | 52 | this.orderNumArray.push(i); 53 | 54 | var u = i / this.tubularSegments * Math.PI * 2; 55 | var v = j / this.radialSegments * Math.PI * 2; 56 | 57 | 58 | radians.push(u); 59 | 60 | // vertex 61 | 62 | var vertex = {}; 63 | 64 | vertex.x = ( this.radius + tube * Math.cos( v ) ) * Math.cos( u ); 65 | vertex.y = ( this.radius + tube * Math.cos( v ) ) * Math.sin( u ); 66 | vertex.z = tube * Math.sin( v ); 67 | 68 | vertices.push( vertex.x, vertex.y, vertex.z ); 69 | 70 | // normal 71 | 72 | var center = {}; 73 | 74 | center.x = this.radius * Math.cos( u ); 75 | center.y = this.radius * Math.sin( u ); 76 | 77 | var normal = {}; 78 | 79 | normal.x = vertex.x - center.x; 80 | normal.y = vertex.y - center.y; 81 | normal.z = vertex.z - center.z; 82 | 83 | var normalRatio = Math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z); 84 | 85 | // normal.subVectors( , center ).normalize(); 86 | 87 | normals.push( normal.x/normalRatio, normal.y/normalRatio, normal.z/normalRatio); 88 | 89 | // uv 90 | 91 | uvs.push( i / this.tubularSegments ); 92 | uvs.push( j / this.radialSegments ); 93 | 94 | } 95 | } 96 | 97 | // indices 98 | for ( let j = 1; j <= this.radialSegments; j ++ ) { 99 | for ( let i = 1; i <= this.tubularSegments; i ++ ) { 100 | 101 | // indices 102 | 103 | let a = ( this.tubularSegments + 1 ) * j + i - 1; 104 | let b = ( this.tubularSegments + 1 ) * ( j - 1 ) + i - 1; 105 | let c = ( this.tubularSegments + 1 ) * ( j - 1 ) + i; 106 | let d = ( this.tubularSegments + 1 ) * j + i; 107 | 108 | // faces 109 | indices.push( a, b, d ); 110 | indices.push( b, c, d ); 111 | } 112 | } 113 | 114 | this.radians = new THREE.Float32BufferAttribute( radians, 1 ); 115 | 116 | 117 | this.indices = new ( indices.length > 65535 ? THREE.Uint32BufferAttribute : THREE.Uint16BufferAttribute )( indices, 1 ); 118 | this.positions = new THREE.Float32BufferAttribute( vertices, 3 ); 119 | this.normals = new THREE.Float32BufferAttribute( normals, 3 ); 120 | 121 | 122 | this.uvs = new THREE.Float32BufferAttribute( uvs, 2 ); 123 | 124 | this.frequencies = new THREE.BufferAttribute( new Float32Array((this.radialSegments + 1) * (this.tubularSegments + 1)), 1 ); 125 | 126 | this.geometry.setIndex(this.indices); 127 | this.geometry.addAttribute("position", this.positions); 128 | this.geometry.addAttribute("normal", this.normals); 129 | this.geometry.addAttribute("uv", this.uvs); 130 | 131 | this.geometry.addAttribute("aFrequency", this.frequencies); 132 | this.geometry.addAttribute("aRadian", this.radians); 133 | 134 | return this.geometry; 135 | } 136 | 137 | 138 | 139 | render(){ 140 | this.uniforms.uTick.value++; 141 | var spectrums = this.webgl.audio.analyzer.frequencyArray; 142 | 143 | var aFrequency = this.geometry.attributes.aFrequency; 144 | aFrequency.needsUpdate = true; 145 | 146 | for(let i = 0; i < aFrequency.count; i++){ 147 | let num = this.orderNumArray[i]; 148 | if(num === this.tubularSegments) num = 0; 149 | let spectrum = spectrums[num]; 150 | aFrequency.array[i] = spectrum; 151 | } 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /src/assets/js/module/webgl.js: -------------------------------------------------------------------------------- 1 | class Webgl{ 2 | constructor(){ 3 | this.vertShader = [ 4 | "assets/glsl/torus.vert", 5 | ]; 6 | 7 | this.fragShader = [ 8 | "assets/glsl/torus.frag", 9 | ]; 10 | 11 | this.shaderLength = this.vertShader.length + this.fragShader.length; 12 | this.shaderCount = 0; 13 | 14 | 15 | for(var i = 0; i < this.vertShader.length; i++){ 16 | this.importShader_vert(i); 17 | } 18 | 19 | 20 | for(var i = 0; i < this.fragShader.length; i++){ 21 | this.importShader_frag(i); 22 | } 23 | } 24 | 25 | 26 | importShader_vert(i){ 27 | 28 | var myRequest = new XMLHttpRequest(); 29 | 30 | var _this = this; 31 | myRequest.onreadystatechange = function() { 32 | if ( myRequest.readyState === 4 ) { 33 | _this.vertShader[i] = myRequest.response; 34 | _this.completeShaderLoad(); 35 | } 36 | }; 37 | 38 | 39 | myRequest.open("GET", this.vertShader[i], true); 40 | myRequest.send(); 41 | }; 42 | 43 | 44 | importShader_frag(i){ 45 | 46 | var myRequest = new XMLHttpRequest(); 47 | 48 | var _this = this; 49 | myRequest.onreadystatechange = function() { 50 | if ( myRequest.readyState === 4 ) { 51 | _this.fragShader[i] = myRequest.response; 52 | 53 | 54 | _this.completeShaderLoad(); 55 | } 56 | }; 57 | 58 | myRequest.open("GET", this.fragShader[i], true); 59 | myRequest.send(); 60 | } 61 | 62 | 63 | 64 | completeShaderLoad(){ 65 | this.shaderCount++; 66 | 67 | if(this.shaderCount === this.shaderLength) { 68 | this.isShaderComplete = true; 69 | this.init(); 70 | } 71 | } 72 | 73 | 74 | 75 | init(){ 76 | // console.log(this.vertShader, this.fragShader); 77 | ResizeWatch.register(this); 78 | 79 | this.width = 1600; 80 | this.height = 1600; 81 | this.aspect = this.width / this.height; 82 | 83 | this.scene = new THREE.Scene(); 84 | 85 | this.setProps(); 86 | 87 | this.camera = new THREE.PerspectiveCamera(this.props.fov, this.props.aspect, this.props.near, this.props.far); 88 | // var cameraZ = (this.props.height / 2) / Math.tan((45 * Math.PI / 180) / 2); 89 | 90 | // this.camera.position.set(0, cameraZ/1.8, 0); 91 | 92 | // this.camera.lookAt(this.scene.position); 93 | 94 | this.renderer = new THREE.WebGLRenderer({ 95 | antialias: true, 96 | alpha: true 97 | }); 98 | 99 | var ratio = 1.5; 100 | 101 | this.renderer.setPixelRatio(ratio); 102 | 103 | this.renderer.setClearColor(0xffffff, 1); 104 | this.renderer.setSize(ResizeWatch.width, ResizeWatch.height); 105 | 106 | this.div = document.getElementById("wrapper"); 107 | this.div.appendChild(this.renderer.domElement); 108 | 109 | this.torus = new Torus(this); 110 | 111 | 112 | var control = new THREE.OrbitControls(this.camera, this.renderer.domElement); 113 | 114 | this.audio = new Audio(this); 115 | this.audio.init(); 116 | this.resizeUpdate(); 117 | } 118 | 119 | 120 | 121 | setProps(){ 122 | var width = ResizeWatch.width; 123 | var height = ResizeWatch.height; 124 | var aspect = width / height; 125 | 126 | this.props = { 127 | width: width, 128 | height: height, 129 | aspect: aspect, 130 | fov: 45, 131 | left: -width / 2, 132 | right: width / 2, 133 | top: height / 2, 134 | bottom: -height / 2, 135 | near: 0.1, 136 | far: 10000, 137 | parent: document.getElementById("wrapper") 138 | }; 139 | }; 140 | 141 | resizeUpdate(){ 142 | this.setProps(); 143 | this.renderer.setSize(this.props.width, this.props.height); 144 | 145 | this.camera.aspect = this.props.aspect; 146 | 147 | var cameraZ = (this.props.height / 2) / Math.tan((this.props.fov * Math.PI / 180) / 2); 148 | 149 | this.camera.position.set(0, -cameraZ/2, -cameraZ/7.0); 150 | 151 | this.camera.lookAt(this.scene.position); 152 | 153 | this.camera.updateProjectionMatrix(); 154 | } 155 | 156 | 157 | 158 | render(){ 159 | if(this.uniforms) this.uniforms.uTick.value += 1; 160 | this.renderer.render(this.scene, this.camera); 161 | this.torus.render(); 162 | // this.effect.render(); 163 | } 164 | } 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | audio visualizer torus 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 36 | 37 | 38 |
39 |

MUSIC : Don't Talk About It

40 |

BY Michael Mar (FEAT. Kyan Palmer)

41 |
42 |
43 |
LOADING ...
44 |
PLAY
45 | 46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | --------------------------------------------------------------------------------