├── README.md ├── app ├── .DS_Store ├── assets │ ├── audio │ │ └── gramatik.mp3 │ └── img │ │ ├── codepen.svg │ │ ├── github.svg │ │ ├── sound_off.png │ │ ├── sound_on.png │ │ └── twitter.svg ├── index.css ├── index.html ├── index.js └── scripts │ ├── .gitignore │ ├── App.js │ ├── glsl │ └── shaders │ │ └── particles │ │ ├── particles.frag │ │ └── particles.vert │ ├── postprocessing │ └── glitch.js │ ├── shapes │ ├── cube.js │ ├── cylinder.js │ ├── final.js │ ├── hemisphere.js │ ├── initial.js │ ├── octa.js │ ├── particles.js │ ├── plane.js │ ├── sphere.js │ ├── tear.js │ └── torus.js │ └── utils │ ├── Animations.js │ ├── Camera.js │ ├── Colors.js │ ├── Controls.js │ ├── Sound.js │ └── StateManager.js ├── package-lock.json ├── package.json └── webpack.config.js /README.md: -------------------------------------------------------------------------------- 1 | # Particles danse 2 | ![alt text](http://antoineabbou.fr/particles.png) 3 | 4 | 5 | Particles danse is an interactive audio experiment using [Three](https://threejs.org/) and WebGL, based on the song Halcyion from Gramatik. 🎵 6 | 7 | ## Demo 8 | > http://particles.antoineabbou.fr 9 | 10 | ## Installation 11 | 12 | ### Requirements 13 | * npm 14 | 15 | Once the project is cloned, just run : 16 | 17 | `$ npm install` 18 | 19 | 20 | ## Development 21 | 22 | Launch development server. 23 | 24 | ``` 25 | $ npm run start 26 | ``` 27 | 28 | ## Build 29 | Prepare files for production. 30 | 31 | ``` 32 | $ npm run build 33 | ``` 34 | 35 | ## Contributing 36 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 37 | 38 | Please make sure to update tests as appropriate. 39 | 40 | ## License 41 | [MIT](https://choosealicense.com/licenses/mit/) 42 | -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoineabbou/particles-danseJS/e206ca6c9120fd8d23ba2f57515c8744ffde5efe/app/.DS_Store -------------------------------------------------------------------------------- /app/assets/audio/gramatik.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoineabbou/particles-danseJS/e206ca6c9120fd8d23ba2f57515c8744ffde5efe/app/assets/audio/gramatik.mp3 -------------------------------------------------------------------------------- /app/assets/img/codepen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/assets/img/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/assets/img/sound_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoineabbou/particles-danseJS/e206ca6c9120fd8d23ba2f57515c8744ffde5efe/app/assets/img/sound_off.png -------------------------------------------------------------------------------- /app/assets/img/sound_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoineabbou/particles-danseJS/e206ca6c9120fd8d23ba2f57515c8744ffde5efe/app/assets/img/sound_on.png -------------------------------------------------------------------------------- /app/assets/img/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | margin : 0; padding: 0; overflow: hidden; 6 | height: 100%; 7 | } 8 | 9 | body { 10 | color: #000; 11 | font-family: Monospace; 12 | font-size: 13px; 13 | background:#1E1D45; 14 | -webkit-animation: random 10s infinite; 15 | animation: random 10s infinite; 16 | } 17 | 18 | @keyframes random { 19 | 20% { background-color: #F5B076; } 20 | 40% { background-color: #EB8063; } 21 | 60% { background-color: #D3475B; } 22 | 80% { background-color: #474889; } 23 | 100% { background-color: #1E1D45; } 24 | } 25 | 26 | 27 | h1 { 28 | font-family: 'Montserrat', sans-serif; 29 | font-weight: black; 30 | color: #1a1a1a; 31 | text-transform: uppercase; 32 | font-size : 64px; 33 | text-align: center; 34 | margin:auto; 35 | letter-spacing: 4px; 36 | position:absolute; 37 | left:0; right:0; 38 | top:45%; 39 | opacity:0; 40 | } 41 | 42 | 43 | 44 | h3 { 45 | font-family: 'Source Sans Pro', sans-serif; 46 | color: #1a1a1a; 47 | font-size : 16px; 48 | text-align: center; 49 | margin:auto; 50 | letter-spacing: 1.3px; 51 | position:absolute; 52 | left:0; right:0; 53 | top:47%; 54 | opacity:0; 55 | font-weight:600; 56 | } 57 | 58 | h5 { 59 | font-family: 'Source Sans Pro', sans-serif; 60 | color: #1a1a1a; 61 | font-size : 14.5px; 62 | text-align: center; 63 | margin:auto; 64 | letter-spacing: 1.3px; 65 | position:absolute; 66 | left:0; right:0; 67 | top:53.5%; 68 | opacity:0; 69 | font-weight:600; 70 | } 71 | 72 | ul { 73 | list-style-type: none; 74 | display:flex; 75 | margin:auto; 76 | position:absolute; 77 | bottom:40px; 78 | width:246px; 79 | left:0; right:0; 80 | } 81 | 82 | 83 | canvas { 84 | background: #1a1a1a; 85 | opacity:0; 86 | } 87 | 88 | img{ 89 | width: 24px; 90 | } 91 | 92 | a { 93 | font-size: 12px; 94 | margin-left: 20px; 95 | line-height: 12px; 96 | letter-spacing: 1.5px; 97 | font-family: 'Source Sans Pro', sans-serif; 98 | text-decoration: none; 99 | outline:none; 100 | color:white; 101 | font-weight: bold; 102 | margin:0; padding:0; 103 | } 104 | 105 | a:focus { 106 | outline:none; 107 | color:white; 108 | } 109 | 110 | .song { 111 | color:white; 112 | left:24px; 113 | top:24px; 114 | display:flex; 115 | position: absolute; 116 | z-index: 1; 117 | height: 35px; 118 | opacity: 0; 119 | } 120 | 121 | .song p { 122 | font-size: 12px; 123 | line-height: 12px; 124 | letter-spacing: 2px; 125 | font-family: 'Source Sans Pro', sans-serif; 126 | 127 | } 128 | 129 | .song a { 130 | letter-spacing:2px; 131 | } 132 | 133 | #wrapper, #end-wrapper{ 134 | display: flex; 135 | height: 100%; 136 | align-items: center; 137 | } 138 | 139 | #end-wrapper { 140 | display:none; 141 | } 142 | 143 | #container, #end-container{ 144 | background: white; 145 | width:0%; 146 | height:1.5px; 147 | align-self: center; 148 | margin:auto; 149 | } 150 | 151 | .content, .end-content{ 152 | height:100%; 153 | align-items: center; 154 | position:relative; 155 | box-shadow: 6px 5px 30px -4px rgba(0,0,0,0.85); 156 | } 157 | 158 | .end-title { 159 | font-family: 'Montserrat', sans-serif; 160 | font-weight: black; 161 | font-size : 85px; 162 | text-align: center; 163 | margin:auto; 164 | letter-spacing: 4px; 165 | position:absolute; 166 | left:0; right:0; 167 | top:47%; 168 | opacity:0 169 | } 170 | 171 | .social{ 172 | width: 30px; 173 | margin: 0px 20px 174 | } 175 | 176 | .second-part { 177 | font-family: 'Source Sans Pro', sans-serif; 178 | } 179 | 180 | p.author { 181 | font-family: 'Source Sans Pro', sans-serif; 182 | position:absolute; 183 | bottom:24px; 184 | letter-spacing: 1.5; 185 | text-align: center; 186 | left:0; right:0; 187 | margin:0 auto; padding:0; 188 | opacity: 0; 189 | font-size:14px; 190 | } 191 | 192 | .github { 193 | position:absolute; 194 | color:white; 195 | bottom: 24px; 196 | width:100%; 197 | text-align: center; 198 | left:0; right:0; 199 | margin: 0 auto; padding:0; 200 | opacity:0; 201 | font-size:12px; 202 | letter-spacing: 1.5px 203 | } 204 | 205 | .link { 206 | font-size:12px; 207 | letter-spacing: 2px 208 | } 209 | 210 | .second-part a { 211 | color: #1a1a1a; 212 | font-size:14px; 213 | } 214 | 215 | 216 | 217 | a.btn { 218 | opacity:0; 219 | position: fixed; 220 | cursor: crosshair; 221 | display:none; 222 | font-family: 'Open-sans', sans-serif; 223 | top: 44vh; 224 | left: 50%; 225 | color: white; 226 | transform: translate3d(-50%, -50%, 0); 227 | padding: 1em calc(0.7em * 1.2); 228 | border: 2px solid transparent; 229 | position: relative; 230 | font-size: 18px; 231 | letter-spacing: 2.5px; 232 | } 233 | a.btn .text { 234 | color:#1a1a1a; 235 | display: block; 236 | transition: transform 0.4s cubic-bezier(0.2, 0, 0, 1) 0.4s; 237 | } 238 | a.btn:after { 239 | position: absolute; 240 | content: ''; 241 | bottom: -2px; 242 | left: calc(0.7em * 1.2); 243 | right: calc(0.7em * 1.2); 244 | height: 2px; 245 | background: #1a1a1a; 246 | z-index: -1; 247 | transition: transform 0.8s cubic-bezier(1, 0, 0.37, 1) 0.2s, right 0.2s cubic-bezier(0.04, 0.48, 0, 1) 0.6s, left 0.4s cubic-bezier(0.04, 0.48, 0, 1) 0.6s; 248 | transform-origin: left; 249 | } 250 | 251 | .line { 252 | position: absolute; 253 | background: #1a1a1a; 254 | } 255 | .line.-right, .line.-left { 256 | width: 2px; 257 | bottom: -2px; 258 | top: -2px; 259 | transform: scale3d(1, 0, 1); 260 | } 261 | .line.-top, .line.-bottom { 262 | height: 2px; 263 | left: -2px; 264 | right: -2px; 265 | transform: scale3d(0, 1, 1); 266 | } 267 | .line.-right { 268 | right: -2px; 269 | transition: transform 0.1s cubic-bezier(1, 0, 0.65, 1.01) 0.23s; 270 | transform-origin: top; 271 | } 272 | .line.-top { 273 | top: -2px; 274 | transition: transform 0.08s linear 0.43s; 275 | transform-origin: left; 276 | } 277 | .line.-left { 278 | left: -2px; 279 | transition: transform 0.08s linear 0.51s; 280 | transform-origin: bottom; 281 | } 282 | .line.-bottom { 283 | bottom: -2px; 284 | transition: transform 0.3s cubic-bezier(1, 0, 0.65, 1.01); 285 | transform-origin: right; 286 | } 287 | 288 | a.btn:hover .text, 289 | a.btn:active .text { 290 | transform: translate3d(0, 0, 0); 291 | transition: transform 0.6s cubic-bezier(0.2, 0, 0, 1) 0.4s; 292 | } 293 | a.btn:hover:after, 294 | a.btn:active:after { 295 | transform: scale3d(0, 1, 1); 296 | right: -2px; 297 | left: -2px; 298 | transform-origin: right; 299 | transition: transform 0.2s cubic-bezier(1, 0, 0.65, 1.01) 0.17s, right 0.2s cubic-bezier(1, 0, 0.65, 1.01), left 0s 0.3s; 300 | } 301 | a.btn:hover .line, 302 | a.btn:active .line { 303 | transform: scale3d(1, 1, 1); 304 | } 305 | a.btn:hover .line.-right, 306 | a.btn:active .line.-right { 307 | transition: transform 0.1s cubic-bezier(1, 0, 0.65, 1.01) 0.2s; 308 | transform-origin: bottom; 309 | } 310 | a:hover .line.-top, 311 | a:active .line.-top { 312 | transition: transform 0.08s linear 0.4s; 313 | transform-origin: right; 314 | } 315 | a.btn:hover .line.-left, 316 | a.btn:active .line.-left { 317 | transition: transform 0.08s linear 0.48s; 318 | transform-origin: top; 319 | } 320 | a.btn:hover .line.-bottom, 321 | a.btn:active .line.-bottom { 322 | transition: transform 0.5s cubic-bezier(0, 0.53, 0.29, 1) 0.56s; 323 | transform-origin: left; 324 | } 325 | 326 | 327 | .end-title { 328 | font-family: 'Montserrat', sans-serif; 329 | font-weight: black; 330 | font-size : 64px; 331 | text-align: center; 332 | margin:auto; 333 | letter-spacing: 4px; 334 | position:absolute; 335 | left:0; right:0; 336 | top:47%; 337 | opacity:0 338 | } 339 | ul { 340 | list-style-type: none; 341 | display:flex; 342 | margin:auto; 343 | position:absolute; 344 | bottom:40px; 345 | width:164px; 346 | padding:0px; 347 | left:0; right:0; 348 | } 349 | .social{ 350 | width: 24px; 351 | margin: 0px 16px 352 | } 353 | 354 | .full_screen { 355 | position: relative; 356 | overflow: hidden; 357 | display: inline-block; 358 | color:white; 359 | font-family: 'Source Sans Pro', sans-serif; 360 | font-size:12px; 361 | letter-spacing:1.2px; 362 | } 363 | 364 | .mute .sound_on { 365 | width:13px; 366 | } 367 | 368 | .tools { 369 | position:absolute; 370 | top:72px; 371 | left:24px; 372 | opacity:0; 373 | } 374 | .controls { 375 | margin-top:24px; 376 | } 377 | 378 | .sound_off, .sound_on{ 379 | cursor:pointer; 380 | left: 0; 381 | right: 0; 382 | margin: 0 auto; 383 | text-align: center; 384 | } 385 | 386 | .sound_off{ 387 | display:none; 388 | } 389 | 390 | .text-content { 391 | border: solid 1px white; 392 | padding: 10px; 393 | display: inline-block; 394 | cursor: pointer; 395 | } 396 | 397 | 398 | .loader { 399 | width: 100vw; 400 | height: 100vh; 401 | } 402 | 403 | .demo { 404 | width: 100px; 405 | height: 102px; 406 | border-radius: 100%; 407 | position: absolute; 408 | top: 45%; 409 | left: calc(50% - 50px); 410 | } 411 | 412 | .circle { 413 | width: 100%; 414 | height: 100%; 415 | position: absolute; 416 | } 417 | .circle .inner { 418 | width: 100%; 419 | height: 100%; 420 | border-radius: 100%; 421 | border: 5px solid rgba(255,255,255, 0.8); 422 | border-right: none; 423 | border-top: none; 424 | background-clip: padding; 425 | box-shadow: inset 0px 0px 10px rgba(255,255,255, 0.8) 426 | } 427 | 428 | @-webkit-keyframes spin { 429 | from { 430 | -webkit-transform: rotate(0deg); 431 | transform: rotate(0deg); 432 | } 433 | to { 434 | -webkit-transform: rotate(360deg); 435 | transform: rotate(360deg); 436 | } 437 | } 438 | 439 | @keyframes spin { 440 | from { 441 | -webkit-transform: rotate(0deg); 442 | transform: rotate(0deg); 443 | } 444 | to { 445 | -webkit-transform: rotate(360deg); 446 | transform: rotate(360deg); 447 | } 448 | } 449 | .circle:nth-of-type(0) { 450 | -webkit-transform: rotate(0deg); 451 | transform: rotate(0deg); 452 | } 453 | .circle:nth-of-type(0) .inner { 454 | -webkit-animation: spin 2s infinite linear; 455 | animation: spin 2s infinite linear; 456 | } 457 | 458 | .circle:nth-of-type(1) { 459 | -webkit-transform: rotate(70deg); 460 | transform: rotate(70deg); 461 | } 462 | .circle:nth-of-type(1) .inner { 463 | -webkit-animation: spin 2s infinite linear; 464 | animation: spin 2s infinite linear; 465 | } 466 | 467 | .circle:nth-of-type(2) { 468 | -webkit-transform: rotate(140deg); 469 | transform: rotate(140deg); 470 | } 471 | .circle:nth-of-type(2) .inner { 472 | -webkit-animation: spin 2s infinite linear; 473 | animation: spin 2s infinite linear; 474 | } 475 | 476 | .demo { 477 | -webkit-animation: spin 5s infinite linear; 478 | animation: spin 5s infinite linear; 479 | } 480 | 481 | .hidden { 482 | display:none; 483 | } 484 | 485 | .visible { 486 | display:inline-block; 487 | } 488 | 489 | 490 | /* Smartphones (portrait and landscape) ----------- */ 491 | @media only screen 492 | and (min-device-width : 320px) 493 | and (max-device-width : 480px) { 494 | h1{ 495 | font-size:32px; 496 | padding: 0px 24px; 497 | } 498 | h3{ 499 | font-size: 16px; 500 | padding: 0px 24px; 501 | top:47.5% 502 | } 503 | h5{ 504 | font-size: 12.5px; 505 | padding: 0px 24px; 506 | top: 57.5%; 507 | } 508 | 509 | .end-title { 510 | font-size:32px; 511 | padding: 0px 24px; 512 | } 513 | } 514 | 515 | @media only screen and (min-device-width : 320px) 516 | and (max-device-width : 767px) { 517 | .author { 518 | width:260px; 519 | } 520 | .song { 521 | top:12px; 522 | left:0; 523 | right:0; 524 | margin: auto; 525 | display:block; 526 | } 527 | .song p { 528 | text-align: center; 529 | } 530 | 531 | .tools { 532 | left: 0; 533 | right: 0; 534 | margin: 0 auto; 535 | display: block; 536 | text-align: center; 537 | top: 64px; 538 | } 539 | } 540 | 541 | 542 | 543 | 544 | @media only screen and (min-device-width : 767px) { 545 | .author { 546 | width:100%; 547 | } 548 | } 549 | 550 | @media only screen and (min-device-width : 481px) 551 | and (max-device-width : 767px) { 552 | h1{ 553 | font-size:40px; 554 | padding: 0px 24px; 555 | top:42%; 556 | } 557 | h3{ 558 | font-size: 18px; 559 | top:47.5%; 560 | padding: 0px 24px; 561 | } 562 | h5{ 563 | font-size: 14px; 564 | padding: 0px 24px; 565 | top: 57.5%; 566 | } 567 | 568 | .end-title{ 569 | font-size:40px; 570 | padding: 0px 24px; 571 | } 572 | 573 | } 574 | 575 | 576 | @media only screen 577 | and (min-device-width : 458px) 578 | and (max-device-width : 767px) { 579 | h1{ 580 | top:47%; 581 | font-size:32px; 582 | padding: 0 24px; 583 | } 584 | 585 | .end-title{ 586 | font-size:32px; 587 | padding: 0px 24px; 588 | } 589 | } 590 | 591 | 592 | @media only screen 593 | and (min-device-width : 607px) 594 | and (max-device-width : 767px) { 595 | h1{ 596 | top:48%; 597 | font-size:32px; 598 | padding: 0 24px; 599 | } 600 | h3 { 601 | top:48.5%; 602 | } 603 | 604 | .end-title{ 605 | font-size:32px; 606 | padding: 0px 24px; 607 | } 608 | } 609 | 610 | 611 | /* iPads (portrait and landscape) ----------- */ 612 | @media only screen 613 | and (min-device-width : 768px) 614 | and (max-device-width : 1024px) { 615 | h1 { 616 | font-size:48px; 617 | } 618 | h3{ 619 | font-size:20px; 620 | } 621 | h5 { 622 | top:55%; 623 | font-size:18px 624 | } 625 | .end-title{ 626 | font-size:48px; 627 | } 628 | } 629 | 630 | /* iPads (landscape) ----------- */ 631 | @media only screen 632 | and (min-device-width : 768px) 633 | and (max-device-width : 1024px) 634 | and (orientation : landscape) { 635 | /* STYLES GO HERE */ 636 | } 637 | 638 | /* iPads (portrait) ----------- */ 639 | @media only screen 640 | and (min-device-width : 768px) 641 | and (max-device-width : 1024px) 642 | and (orientation : portrait) { 643 | /* STYLES GO HERE */ 644 | } 645 | 646 | /* Desktops and laptops ----------- */ 647 | @media only screen 648 | and (min-width : 1224px) and (max-width: 1823px){ 649 | h1 { 650 | font-size:56px; 651 | top:46% 652 | } 653 | h3{ 654 | top:49%; 655 | } 656 | h5 { 657 | top:58%; 658 | } 659 | 660 | .end-title { 661 | font-size:56px; 662 | } 663 | } 664 | 665 | /* Large screens ----------- */ 666 | @media only screen 667 | and (min-width : 2000px) { 668 | h3{ 669 | font-size:20px; 670 | } 671 | h5 { 672 | font-size:18px; 673 | } 674 | } 675 | 676 | /* iPhone 5 (portrait & landscape)----------- */ 677 | @media only screen 678 | and (min-device-width : 320px) 679 | and (max-device-width : 568px) { 680 | /* STYLES GO HERE */ 681 | } 682 | 683 | /* iPhone 5 (landscape)----------- */ 684 | @media only screen 685 | and (min-device-width : 320px) 686 | and (max-device-width : 568px) 687 | and (orientation : landscape) { 688 | /* STYLES GO HERE */ 689 | } 690 | 691 | /* iPhone 5 (portrait)----------- */ 692 | @media only screen 693 | and (min-device-width : 320px) 694 | and (max-device-width : 568px) 695 | and (orientation : portrait) { 696 | /* STYLES GO HERE */ 697 | } 698 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Particles - Audiovisualization 5 | 6 | 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 |

Now playing : Halcyon by Gramatik

32 |
33 |
34 |
35 | 36 | 37 |
38 |
39 |
40 | Full screen 41 | 42 |
43 |
44 |
45 |

Created by Antoine Abbou | Resources available on GitHub

46 | 47 |
48 |
49 |
50 |
51 |

Particles danse

52 |

A sound experiment created with JavaScript and WebGL

53 |
Turn your audio on for a better experience
54 |
55 |
56 |

Created by Antoine Abbou | Resources available on GitHub

57 | 58 | Launch the experiment 59 | 60 | 61 | 62 | 63 | 64 |
65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 |

THANKS FOR WATCHING

73 |
    74 | 75 | 76 | 77 |
78 |
79 |
80 |
81 | 82 |
83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | import App from './scripts/App'; 4 | 5 | window.app = new App(); 6 | -------------------------------------------------------------------------------- /app/scripts/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoineabbou/particles-danseJS/e206ca6c9120fd8d23ba2f57515c8744ffde5efe/app/scripts/.gitignore -------------------------------------------------------------------------------- /app/scripts/App.js: -------------------------------------------------------------------------------- 1 | //import Utils 2 | import OrbitControls from 'imports-loader?THREE=three!exports-loader?THREE.OrbitControls!three/examples/js/controls/OrbitControls' 3 | import Glitch from './postprocessing/glitch.js' 4 | import Sound from './utils/Sound' 5 | import Audio from '../assets/audio/gramatik.mp3' 6 | import Colors from './utils/Colors' 7 | import StateManager from './utils/StateManager' 8 | import Camera from './utils/camera' 9 | import Animations from './utils/animations' 10 | import Controls from './utils/controls' 11 | 12 | //import Shaders 13 | import vertParticles from './glsl/shaders/particles/particles.vert' 14 | import fragParticles from './glsl/shaders/particles/particles.frag' 15 | 16 | // import first shape and stars 17 | import Initial from './shapes/initial' 18 | import Particles from './shapes/particles' 19 | import Final from './shapes/final' 20 | 21 | 22 | export default class App { 23 | 24 | constructor() { 25 | 26 | //Canvas 27 | this.container = document.querySelector( '#main' ); 28 | document.body.appendChild( this.container ); 29 | 30 | //Camera 31 | this.camera = new Camera(70, window.innerWidth / window.innerHeight, 0.1, this.resolutionZ, 250, 150, 1) 32 | 33 | //Scene 34 | this.scene = new THREE.Scene(); 35 | this.time = 0 36 | this.distance = 200 37 | this.resolutionX = window.innerWidth 38 | this.resolutionY = window.innerHeight 39 | this.resolutionZ = 10000 40 | 41 | 42 | //Renderer 43 | this.renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } ); 44 | this.renderer.setPixelRatio( window.devicePixelRatio ); 45 | this.renderer.setSize( window.innerWidth, window.innerHeight ); 46 | this.container.appendChild( this.renderer.domElement ); 47 | 48 | //Resize 49 | window.addEventListener('resize', this.onWindowResize.bind(this), false); 50 | this.onWindowResize(); 51 | 52 | this.renderer.animate( this.render.bind(this) ); 53 | 54 | //Utils stuff instanciation 55 | this.colors = new Colors() 56 | this.animations = new Animations() 57 | this.controls = new Controls() 58 | 59 | 60 | //Postprocessing 61 | this.initPostProcessing() 62 | 63 | //Audio 64 | this.audioManager() 65 | 66 | //particles / points 67 | this.nbParticles = 10000 68 | this.nbPoints = 80 69 | 70 | 71 | //state | shape Manager 72 | this.stateManager = new StateManager(this.nbParticles) 73 | 74 | //Ratio - Speed of particles when changing 75 | this.initialRatio = 0.0055 76 | this.ratio = 0.06 77 | 78 | //Instances of white particles 79 | this.particles = new Particles(this.nbPoints) 80 | for(var i = 0; i < this.nbPoints; i++){ 81 | this.scene.add(this.particles.particles[i]); 82 | } 83 | 84 | //instance of the first shape 85 | this.initial = new Initial(this.nbParticles) 86 | this.final = new Final(this.nbParticles) 87 | 88 | 89 | //First pattern is the sphere 90 | this.currentPattern = {type: "sphere", isActive: true, data: this.stateManager.sphereState.data} 91 | 92 | //Particles representing the shape - creation 93 | var uniforms = { 94 | u_time: { type: "f", value: 1.0 }, 95 | u_frequency: {type: "f", value: this.audio.arrAverage(this.audio.getSpectrum())}, 96 | u_resolution: {type: "f", value: window.innerWidth}, 97 | u_enter_anim: {type: "f", value: 0} 98 | } 99 | this.particlesMaterial = new THREE.ShaderMaterial( { 100 | uniforms : uniforms, 101 | vertexShader: vertParticles, 102 | fragmentShader: fragParticles 103 | } ); 104 | 105 | this.particlesField = new THREE.Points(this.initial.initialGeometry , this.particlesMaterial ); 106 | this.scene.add( this.particlesField ); 107 | 108 | 109 | // Launching first animation 110 | 111 | } 112 | 113 | initPostProcessing() { 114 | this.glitchMode = new Glitch(this.renderer, this.scene, this.camera.pov) 115 | } 116 | 117 | checkPattern(){ // Check if torus,sphere or plane, if it's the case, shape is noisy 118 | if((this.changingState) && ((this.currentPattern.type == 'sphere') || (this.currentPattern.type == 'torus'))){ 119 | this.particlesMaterial.uniforms.u_frequency.value = this.audio.arrAverage(this.audio.getSpectrum())/5 120 | } 121 | } 122 | 123 | //AudioManager instanciation 124 | audioManager() { 125 | var button = document.querySelector('.btn') 126 | var onSound = document.querySelector('.sound_on') 127 | var offSound = document.querySelector('.sound_off') 128 | 129 | this.kickTempo = 0; 130 | 131 | this.audio = new Sound(Audio, 103, .3, () => { 132 | this.audio._load(Audio, () => { 133 | this.animations.firstAnimation() 134 | button.addEventListener('click', ()=>{ 135 | this.audio.play() 136 | }) 137 | }); 138 | }, false); 139 | 140 | 141 | this.audio.between('first movement', 0, 18.5, () => { 142 | this.stateManager.displacement(this.nbParticles, this.stateManager.sphereState.data.points, this.initial.points, this.initialRatio) 143 | }) 144 | 145 | this.audio.between('first drop', 37, 46, () => { 146 | this.bass.on() 147 | }) 148 | this.audio.between('normal part', 46, 121, ()=> { 149 | this.bass.off() 150 | document.querySelector('canvas').style.background = '#1a1a1a' 151 | this.glitchMode.glitchPass.renderToScreen = false; 152 | 153 | }) 154 | 155 | this.audio.between('second drop', 121, 129.5, () => { 156 | this.bass.on(); 157 | }) 158 | this.audio.between('normal part', 129.5, 204.5, ()=> { 159 | this.bass.off() 160 | document.querySelector('canvas').style.background = '#1a1a1a' 161 | this.glitchMode.glitchPass.renderToScreen = false; 162 | }) 163 | 164 | this.audio.after('normal part', 205, ()=> { 165 | this.stateManager.displacement(this.nbParticles, this.final.points, this.currentPattern.data.points, this.initialRatio) 166 | }) 167 | 168 | //End of the sound 169 | this.audio.onceAt('end', 224, () => { 170 | this.audio.pause() 171 | this.animations.finalAnimation() 172 | }) 173 | 174 | //Bass check 175 | this.bass = this.audio.createKick({ 176 | frequency: 3, 177 | decay:1, 178 | threshold: 255, 179 | onKick: () => { 180 | if(this.kickTempo > 10){ 181 | this.kickTempo = 0 182 | document.querySelector('canvas').style.background = this.colors.getNewColor() 183 | this.glitchMode.glitchPass.renderToScreen = true; 184 | } 185 | } 186 | }) 187 | 188 | 189 | //Kick check 190 | this.kick = this.audio.createKick({ 191 | frequency: 200, 192 | decay:1, 193 | threshold: 5, 194 | onKick: () => { 195 | if(this.kickTempo > 25){ 196 | this.kickTempo = 0 197 | this.currentPattern = this.stateManager.getNewPattern(this.stateManager.states) 198 | this.changingState = true 199 | this.camera.speed = 2 200 | setTimeout(()=>{ 201 | this.camera.speed = -2 202 | }, 3000) 203 | } 204 | } 205 | }) 206 | this.kick.on() 207 | 208 | onSound.addEventListener('click', () => { 209 | this.audio.pause() 210 | onSound.style.display = 'none' 211 | offSound.style.display = 'inline-block' 212 | }) 213 | 214 | offSound.addEventListener('click', () => { 215 | this.audio.play() 216 | onSound.style.display = 'inline-block' 217 | offSound.style.display = 'none' 218 | }) 219 | } 220 | 221 | 222 | render() { 223 | 224 | this.kickTempo += 1 225 | this.time += 0.01; 226 | 227 | this.particlesMaterial.uniforms.u_time.value = this.time; 228 | this.particlesMaterial.uniforms.u_frequency.value = 1 229 | 230 | this.checkPattern() 231 | this.particles.moveParticles() //White particles constantly move 232 | 233 | this.initial.initialGeometry.verticesNeedUpdate = true 234 | 235 | if(this.changingState){ //If we are on a kick then we change shapes this way : 236 | this.stateManager.displacement(this.nbParticles, this.currentPattern.data.points, this.initial.points, this.ratio) 237 | } 238 | 239 | this.camera.rotate(this.particlesField.position, this.distance, this.time ) //Constantly rotate around the shape 240 | 241 | this.renderer.render( this.scene, this.camera.pov ); 242 | 243 | this.glitchMode.composer.render(); //Glitch mode on drop 244 | } 245 | 246 | onWindowResize() { //Resize stuff 247 | this.camera.pov.aspect = window.innerWidth / window.innerHeight; 248 | this.camera.pov.updateProjectionMatrix(); 249 | this.renderer.setSize( window.innerWidth, window.innerHeight ); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /app/scripts/glsl/shaders/particles/particles.frag: -------------------------------------------------------------------------------- 1 | uniform float u_time; 2 | uniform float u_frequency; 3 | 4 | vec3 colorA = vec3(0.31,0.30,0.67); 5 | vec3 colorB = vec3(0.51,0.44,0.64); 6 | vec3 mod289(vec3 x) { 7 | return x - floor(x * (1.0 / 289.0)) * 289.0; 8 | } 9 | 10 | vec4 mod289(vec4 x) { 11 | return x - floor(x * (1.0 / 289.0)) * 289.0; 12 | } 13 | 14 | vec4 permute(vec4 x) { 15 | return mod289(((x*34.0)+1.0)*x); 16 | } 17 | 18 | vec4 taylorInvSqrt(vec4 r) 19 | { 20 | return 1.79284291400159 - 0.85373472095314 * r; 21 | } 22 | 23 | float snoise(vec3 v) 24 | { 25 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 26 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 27 | 28 | // First corner 29 | vec3 i = floor(v + dot(v, C.yyy) ); 30 | vec3 x0 = v - i + dot(i, C.xxx) ; 31 | 32 | // Other corners 33 | vec3 g = step(x0.yzx, x0.xyz); 34 | vec3 l = 1.0 - g; 35 | vec3 i1 = min( g.xyz, l.zxy ); 36 | vec3 i2 = max( g.xyz, l.zxy ); 37 | 38 | vec3 x1 = x0 - i1 + C.xxx; 39 | vec3 x2 = x0 - i2 + C.yyy; 40 | vec3 x3 = x0 - D.yyy; 41 | 42 | // Permutations 43 | i = mod289(i); 44 | vec4 p = permute( permute( permute( 45 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 46 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 47 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 48 | 49 | 50 | float n_ = 0.142857142857; // 1.0/7.0 51 | vec3 ns = n_ * D.wyz - D.xzx; 52 | 53 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) 54 | 55 | vec4 x_ = floor(j * ns.z); 56 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 57 | 58 | vec4 x = x_ *ns.x + ns.yyyy; 59 | vec4 y = y_ *ns.x + ns.yyyy; 60 | vec4 h = 1.0 - abs(x) - abs(y); 61 | 62 | vec4 b0 = vec4( x.xy, y.xy ); 63 | vec4 b1 = vec4( x.zw, y.zw ); 64 | 65 | 66 | vec4 s0 = floor(b0)*2.0 + 1.0; 67 | vec4 s1 = floor(b1)*2.0 + 1.0; 68 | vec4 sh = -step(h, vec4(0.0)); 69 | 70 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 71 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 72 | 73 | vec3 p0 = vec3(a0.xy,h.x); 74 | vec3 p1 = vec3(a0.zw,h.y); 75 | vec3 p2 = vec3(a1.xy,h.z); 76 | vec3 p3 = vec3(a1.zw,h.w); 77 | 78 | //Normalise gradients 79 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 80 | p0 *= norm.x; 81 | p1 *= norm.y; 82 | p2 *= norm.z; 83 | p3 *= norm.w; 84 | 85 | // Mix final noise value 86 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 87 | m = m * m; 88 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 89 | dot(p2,x2), dot(p3,x3) ) ); 90 | } 91 | 92 | void main() { 93 | 94 | vec3 color = vec3(0.0); 95 | 96 | float pct = abs(sin(u_time)); 97 | 98 | color = mix(colorA, colorB, pct); 99 | 100 | gl_FragColor = vec4(color,0.1); 101 | 102 | // float color = snoise(vec3(u_time/u_frequency/4.)); 103 | // gl_FragColor = vec4(color/.5, color, color, 1.0); 104 | 105 | } -------------------------------------------------------------------------------- /app/scripts/glsl/shaders/particles/particles.vert: -------------------------------------------------------------------------------- 1 | uniform float u_time; 2 | uniform float u_frequency; 3 | uniform float u_resolution; 4 | uniform float u_enter_anim; 5 | 6 | vec3 mod289(vec3 x) { 7 | return x - floor(x * (1.0 / 289.0)) * 289.0; 8 | } 9 | 10 | vec4 mod289(vec4 x) { 11 | return x - floor(x * (1.0 / 289.0)) * 289.0; 12 | } 13 | 14 | vec4 permute(vec4 x) { 15 | return mod289(((x*34.0)+1.0)*x); 16 | } 17 | 18 | vec4 taylorInvSqrt(vec4 r) 19 | { 20 | return 1.79284291400159 - 0.85373472095314 * r; 21 | } 22 | 23 | float snoise(vec3 v) 24 | { 25 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 26 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 27 | 28 | // First corner 29 | vec3 i = floor(v + dot(v, C.yyy) ); 30 | vec3 x0 = v - i + dot(i, C.xxx) ; 31 | 32 | // Other corners 33 | vec3 g = step(x0.yzx, x0.xyz); 34 | vec3 l = 1.0 - g; 35 | vec3 i1 = min( g.xyz, l.zxy ); 36 | vec3 i2 = max( g.xyz, l.zxy ); 37 | 38 | vec3 x1 = x0 - i1 + C.xxx; 39 | vec3 x2 = x0 - i2 + C.yyy; 40 | vec3 x3 = x0 - D.yyy; 41 | 42 | // Permutations 43 | i = mod289(i); 44 | vec4 p = permute( permute( permute( 45 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 46 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 47 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 48 | float n_ = 0.142857142857; // 1.0/7.0 49 | vec3 ns = n_ * D.wyz - D.xzx; 50 | 51 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); 52 | 53 | vec4 x_ = floor(j * ns.z); 54 | vec4 y_ = floor(j - 7.0 * x_ ); 55 | 56 | vec4 x = x_ *ns.x + ns.yyyy; 57 | vec4 y = y_ *ns.x + ns.yyyy; 58 | vec4 h = 1.0 - abs(x) - abs(y); 59 | 60 | vec4 b0 = vec4( x.xy, y.xy ); 61 | vec4 b1 = vec4( x.zw, y.zw ); 62 | 63 | 64 | vec4 s0 = floor(b0)*2.0 + 1.0; 65 | vec4 s1 = floor(b1)*2.0 + 1.0; 66 | vec4 sh = -step(h, vec4(0.0)); 67 | 68 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 69 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 70 | 71 | vec3 p0 = vec3(a0.xy,h.x); 72 | vec3 p1 = vec3(a0.zw,h.y); 73 | vec3 p2 = vec3(a1.xy,h.z); 74 | vec3 p3 = vec3(a1.zw,h.w); 75 | 76 | 77 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 78 | p0 *= norm.x; 79 | p1 *= norm.y; 80 | p2 *= norm.z; 81 | p3 *= norm.w; 82 | 83 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 84 | m = m * m; 85 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 86 | dot(p2,x2), dot(p3,x3) ) ); 87 | } 88 | 89 | vec3 snoiseVec3( vec3 x ){ 90 | 91 | float s = snoise(vec3( x )); 92 | float s1 = snoise(vec3( x.y - 19.1 , x.z + 33.4 , x.x + 47.2 )); 93 | float s2 = snoise(vec3( x.z + 74.2 , x.x - 124.5 , x.y + 99.4 )); 94 | vec3 c = vec3( s , s1 , s2 ); 95 | return c; 96 | 97 | } 98 | 99 | 100 | vec3 curlNoise( vec3 p ){ 101 | 102 | const float e = .1; 103 | vec3 dx = vec3( e , 0.0 , 0.0 ); 104 | vec3 dy = vec3( 0.0 , e , 0.0 ); 105 | vec3 dz = vec3( 0.0 , 0.0 , e ); 106 | 107 | vec3 p_x0 = snoiseVec3( p - dx ); 108 | vec3 p_x1 = snoiseVec3( p + dx ); 109 | vec3 p_y0 = snoiseVec3( p - dy ); 110 | vec3 p_y1 = snoiseVec3( p + dy ); 111 | vec3 p_z0 = snoiseVec3( p - dz ); 112 | vec3 p_z1 = snoiseVec3( p + dz ); 113 | 114 | float x = p_y1.z - p_y0.z - p_z1.y + p_z0.y; 115 | float y = p_z1.x - p_z0.x - p_x1.z + p_x0.z; 116 | float z = p_x1.y - p_x0.y - p_y1.x + p_y0.x; 117 | 118 | const float divisor = 1.0 / ( 2.0 * e ); 119 | return normalize( vec3( x , y , z ) * divisor ); 120 | 121 | } 122 | 123 | void main() { 124 | gl_PointSize = 1.7; 125 | vec3 newPosition = position * (60.+u_frequency/2. + curlNoise(position+u_time/10.)*u_frequency/2.); 126 | //vec3 newPosition = position * (60. + curlNoise(position+u_time/10.)* 15.); 127 | 128 | gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0); 129 | } 130 | -------------------------------------------------------------------------------- /app/scripts/postprocessing/glitch.js: -------------------------------------------------------------------------------- 1 | import EffectComposer from 'imports-loader?THREE=three!exports-loader?THREE.EffectComposer!three/examples/js/postprocessing/EffectComposer' 2 | import RenderPass from 'imports-loader?THREE=three!exports-loader?THREE.RenderPass!three/examples/js/postprocessing/RenderPass' 3 | import MaskPass from 'imports-loader?THREE=three!exports-loader?THREE.MaskPass!three/examples/js/postprocessing/MaskPass' 4 | import ShaderPass from 'imports-loader?THREE=three!exports-loader?THREE.ShaderPass!three/examples/js/postprocessing/ShaderPass' 5 | import GlitchPass from 'imports-loader?THREE=three!exports-loader?THREE.GlitchPass!three/examples/js/postprocessing/GlitchPass' 6 | 7 | import CopyShader from 'imports-loader?THREE=three!exports-loader?THREE.CopyShader!three/examples/js/shaders/CopyShader' 8 | import DigitalGlitch from 'imports-loader?THREE=three!exports-loader?THREE.DigitalGlitch!three/examples/js/shaders/DigitalGlitch' 9 | 10 | export default class Glitch { 11 | 12 | constructor (renderer, scene, camera){ 13 | 14 | this.composer = new EffectComposer( renderer ) 15 | this.composer.setSize( window.innerWidth, window.innerHeight ) 16 | this.renderScene = new RenderPass(scene, camera) 17 | this.copyShader = new ShaderPass(THREE.CopyShader) 18 | this.composer.addPass( new RenderPass( scene, camera ) ) 19 | 20 | this.glitchPass = new GlitchPass() 21 | this.composer.addPass( this.glitchPass ) 22 | this.copyShader.renderToScreen = true 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /app/scripts/shapes/cube.js: -------------------------------------------------------------------------------- 1 | export default class Cube { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | for(var i = 0; i < number; i++){ 6 | var position = new THREE.Vector3() 7 | position.x = Math.random()*2-1 8 | position.y = Math.random()*2-1 9 | position.z = Math.random()*2-1 10 | this.points.push(position) 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /app/scripts/shapes/cylinder.js: -------------------------------------------------------------------------------- 1 | export default class Cylinder { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | for ( var i = 0; i < number; i ++ ) { 6 | var position = new THREE.Vector3() 7 | this.alpha = Math.random()*(Math.PI*2) 8 | this.theta = Math.random()*(Math.PI*2) 9 | 10 | position.x = Math.cos(this.alpha) 11 | position.y = Math.sin(this.alpha) 12 | position.z = -(Math.sin(this.theta)) 13 | this.points.push(position) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/scripts/shapes/final.js: -------------------------------------------------------------------------------- 1 | export default class Final { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | this.finalGeometry = new THREE.Geometry(); 6 | 7 | for ( var i = 0; i < number; i ++ ) { 8 | 9 | var position = new THREE.Vector3(); 10 | 11 | position.x = Math.random()*100-50 12 | position.y = Math.random()*100-50 13 | position.z = Math.random()*100-50 14 | 15 | this.finalGeometry.vertices.push( position ); 16 | this.points.push(position); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/scripts/shapes/hemisphere.js: -------------------------------------------------------------------------------- 1 | export default class Hemisphere { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | for ( var i = 0; i < number; i ++ ) { 6 | var position = new THREE.Vector3(); 7 | this.alpha = Math.random()*(Math.PI*2) 8 | this.theta = Math.random()*(Math.PI/2) 9 | 10 | position.x = Math.cos(this.alpha)*Math.sin(this.theta) 11 | position.y = Math.sin(this.alpha)*Math.sin(this.theta) 12 | position.z = Math.cos(this.theta) 13 | 14 | this.points.push(position) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/scripts/shapes/initial.js: -------------------------------------------------------------------------------- 1 | export default class Initial { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | this.initialGeometry = new THREE.Geometry(); 6 | 7 | for ( var i = 0; i < number; i ++ ) { 8 | 9 | var position = new THREE.Vector3(); 10 | 11 | position.x = Math.random()*100-50 12 | position.y = Math.random()*100-50 13 | position.z = Math.random()*100-50 14 | 15 | this.initialGeometry.vertices.push( position ); 16 | this.points.push(position); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/scripts/shapes/octa.js: -------------------------------------------------------------------------------- 1 | export default class Octa { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | for(var i = 0; i < number; i++){ 6 | var position = new THREE.Vector3() 7 | 8 | this.alpha = Math.random()*(Math.PI*2)-(Math.random()*Math.PI*2) 9 | this.theta = Math.random()*(Math.PI)-(Math.random()*Math.PI*2) 10 | 11 | position.x = Math.pow(Math.cos(this.alpha)*Math.cos(this.theta), 3) 12 | position.y = Math.pow(Math.sin(this.alpha)*Math.cos(this.theta), 3) 13 | position.z = Math.pow(Math.sin(this.theta), 3) 14 | 15 | this.points.push(position) 16 | 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/scripts/shapes/particles.js: -------------------------------------------------------------------------------- 1 | export default class Particles { 2 | 3 | constructor(number) { 4 | this.particles = [] 5 | for(var i = 0; i < number; i++){ 6 | var particle = new THREE.Vector3(); 7 | var geometry = new THREE.SphereGeometry( 0.4, 32, 32 ); 8 | var material = new THREE.MeshBasicMaterial( {color: 0xffffff} ); 9 | var particle = new THREE.Mesh( geometry, material ); 10 | particle.position.x = Math.random() * 1000 - 500; 11 | particle.position.y = Math.random() * 1000 - 500; 12 | particle.position.z = Math.random() * 1000 - 500; 13 | this.particles.push(particle) 14 | } 15 | } 16 | 17 | moveParticles() { 18 | for(var i=0; i1000) particle.position.z-=2000; 23 | 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/scripts/shapes/plane.js: -------------------------------------------------------------------------------- 1 | export default class Plane { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | for ( var i = 0; i < number; i ++ ) { 6 | var position = new THREE.Vector3() 7 | this.alpha = Math.random()*(Math.PI*2) 8 | this.theta = Math.random()*(Math.PI*2) 9 | 10 | position.x = Math.cos(this.alpha) 11 | position.y = Math.sin(this.theta) 12 | position.z = -(Math.sin(this.theta)) 13 | this.points.push(position) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/scripts/shapes/sphere.js: -------------------------------------------------------------------------------- 1 | export default class Sphere { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | for ( var i = 0; i < number; i ++ ) { 6 | var position = new THREE.Vector3(); 7 | this.alpha = Math.random()*(Math.PI) 8 | this.theta = Math.random()*(Math.PI*2) 9 | 10 | position.x = Math.cos(this.alpha)*Math.sin(this.theta) 11 | position.y = Math.sin(this.alpha)*Math.sin(this.theta) 12 | position.z = Math.cos(this.theta) 13 | 14 | this.points.push(position) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/scripts/shapes/tear.js: -------------------------------------------------------------------------------- 1 | export default class Tear { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | for ( var i = 0; i < number; i ++ ) { 6 | var position = new THREE.Vector3() 7 | 8 | this.alpha =Math.random()*2*Math.PI; 9 | this.theta = Math.random()*Math.PI; 10 | 11 | position.x = 0.7*(Math.cos(this.theta)*Math.sin(this.theta)*Math.cos(this.alpha))*2; 12 | position.y = -Math.sin(this.theta)*2+1; 13 | position.z = 0.7*(Math.cos(this.theta)*Math.sin(this.theta)*Math.sin(this.alpha))*2; 14 | 15 | this.points.push(position) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/scripts/shapes/torus.js: -------------------------------------------------------------------------------- 1 | export default class Torus { 2 | 3 | constructor(number) { 4 | this.points = [] 5 | for ( var i = 0; i < number; i ++ ) { 6 | var position = new THREE.Vector3() 7 | this.alpha = Math.random()*(Math.PI*2) 8 | this.theta = Math.random()*(Math.PI*2) 9 | position.x = (1+(1+Math.cos(this.theta)))*Math.cos(this.alpha) 10 | position.y = (1+(1+Math.cos(this.theta)))*Math.sin(this.alpha) 11 | position.z = Math.sin(this.theta) 12 | 13 | this.points.push(position) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/scripts/utils/Animations.js: -------------------------------------------------------------------------------- 1 | import {TweenMax, Power2, TimelineLite} from 'gsap' 2 | 3 | export default class Animations { 4 | 5 | constructor () { 6 | this.wrapper = document.getElementById('wrapper') 7 | this.container = document.getElementById('container') 8 | this.title = document.querySelector('.title') 9 | this.canvas = document.querySelector('canvas') 10 | this.subtitle = document.querySelector('h3') 11 | this.description = document.querySelector('h5') 12 | this.subtitles = document.querySelector('.first-part') 13 | this.author = document.querySelector('.author') 14 | this.button = document.querySelector('.btn') 15 | this.song = document.querySelector('.song') 16 | this.github = document.querySelector('.github') 17 | this.tools = document.querySelector('.tools') 18 | this.controls = document.querySelector('.controls') 19 | this.textContent = document.querySelector('.text-content') 20 | 21 | this.endWrapper = document.getElementById('end-wrapper') 22 | this.endContent = document.querySelector(".end-content") 23 | this.endContainer = document.getElementById("end-container") 24 | this.endTitle = document.querySelector(".end-title") 25 | 26 | this.loader = document.querySelector('.loader') 27 | 28 | this.tl = new TimelineMax(); 29 | this.endTl = new TimelineMax() 30 | 31 | } 32 | 33 | firstAnimation() { 34 | this.tl.to(this.loader, 0.3, {opacity:0}) 35 | setTimeout(()=>{ 36 | this.loader.style.display = 'none' 37 | },500) 38 | if(window.innerWidth<=500){ 39 | this.tl.to(this.container, 1, {width:'91%',ease: Expo.easeOut}, '+=1') 40 | this.tl.to(this.container, 1, {height:'95%',ease: Expo.easeOut}) 41 | } 42 | else if(window.innerWidth>500 && window.innerWidth<=767){ 43 | this.tl.to(this.container, 1, {width:'96%',ease: Expo.easeOut}, '+=1') 44 | this.tl.to(this.container, 1, {height:'96%',ease: Expo.easeOut}) 45 | } 46 | else if(window.innerWidth>767 && window.innerWidth<=1024){ 47 | this.tl.to(this.container, 1, {width:'95%',ease: Expo.easeOut}, '+=1') 48 | this.tl.to(this.container, 1, {height:'96%',ease: Expo.easeOut}) 49 | } 50 | else if(window.innerWidth>1024 && window.innerWidth<=1439){ 51 | this.tl.to(this.container, 1, {width:'96%',ease: Expo.easeOut}, '+=1') 52 | this.tl.to(this.container, 1, {height:'93%',ease: Expo.easeOut}) 53 | } 54 | else if(window.innerWidth>1439){ 55 | this.tl.to(this.container, 1, {width:'96%',ease: Expo.easeOut}, '+=1') 56 | this.tl.to(this.container, 1, {height:'93%',ease: Expo.easeOut}) 57 | } 58 | this.tl.to(this.title, 1, {opacity:1, y:'-90', ease: Expo.easeOut}) 59 | this.tl.to(this.subtitle, 1, {opacity:1, ease: Expo.easeOut}) 60 | this.tl.to(this.description, 1.2, {opacity:1, ease: Expo.easeOut}) 61 | this.tl.to(this.subtitles, 0.7, {opacity:0, ease: Expo.easeOut}, '+=0.8') 62 | this.tl.to(this.button, 1, {display: 'inline-block'}) 63 | this.tl.to(this.author, 1.5, {opacity:1, ease: Expo.easeOut}) 64 | this.tl.to(this.button, 1.5, {opacity:1, ease: Expo.easeOut}, '-=1.5') 65 | 66 | this.button.addEventListener('click', () => { 67 | this.tl.to(this.author, 1, {opacity:0, ease: Expo.easeOut}) 68 | this.tl.to(this.button, 1, {opacity:0, ease: Expo.easeOut}, '-=1') 69 | this.tl.to(this.container, 0.75, {height:'1.5px',ease: Expo.easeOut}) 70 | this.tl.to(this.container, 0.75, {width:'0%',ease: Expo.easeOut}) 71 | setTimeout(()=> { 72 | this.wrapper.style.display = 'none' 73 | this.tl.to(this.canvas, 1, {opacity: 1, ease: Expo.easeOut}, '+=0.5') 74 | this.tl.to(this.song, 1, {opacity: 1, ease: Expo.easeOut}, '+=1.5') 75 | this.tl.to(this.github, 1, {opacity: 1, ease: Expo.easeOut}, '-=1') 76 | this.tl.to(this.tools, 1, {opacity: 1, ease: Expo.easeOut}, '-=1') 77 | 78 | 79 | },2700) 80 | 81 | }) 82 | } 83 | 84 | finalAnimation() { 85 | this.endWrapper.style.background = '#1a1a1a' 86 | this.tl.to(this.song, 1, {opacity:0}) 87 | this.tl.to(this.github, 1, {opacity:0}, '-=1') 88 | this.tl.to(this.tools, 1, {opacity:0}, '-=1') 89 | this.endWrapper.style.display = 'flex' 90 | this.endContainer.style.display = 'block' 91 | 92 | 93 | if(window.innerWidth<=500){ 94 | this.endTl.to(this.endContainer, 1, {width:'91%',ease: Expo.easeOut}, '+=1') 95 | this.endTl.to(this.endContainer, 1, {height:'95%',ease: Expo.easeOut}) 96 | } 97 | else if(window.innerWidth>500 && window.innerWidth<=767){ 98 | this.endTl.to(this.endContainer, 1, {width:'96%',ease: Expo.easeOut}, '+=1') 99 | this.endTl.to(this.endContainer, 1, {height:'96%',ease: Expo.easeOut}) 100 | } 101 | else if(window.innerWidth>767 && window.innerWidth<=1024){ 102 | this.endTl.to(this.endContainer, 1, {width:'95%',ease: Expo.easeOut}, '+=1') 103 | this.endTl.to(this.endContainer, 1, {height:'96%',ease: Expo.easeOut}) 104 | } 105 | else if(window.innerWidth>1024 && window.innerWidth<=1439){ 106 | this.endTl.to(this.endContainer, 1, {width:'96%',ease: Expo.easeOut}, '+=1') 107 | this.endTl.to(this.endContainer, 1, {height:'93%',ease: Expo.easeOut}) 108 | } 109 | else if(window.innerWidth>1439){ 110 | this.endTl.to(this.endContainer, 1, {width:'96%',ease: Expo.easeOut}, '+=1') 111 | this.endTl.to(this.endContainer, 1, {height:'93%',ease: Expo.easeOut}) 112 | } 113 | this.endTl.to(this.endTitle, 1, {opacity:1, y:-40, ease:Power4.easeOut}) 114 | this.endTl.staggerFrom(".social", 1.5, {scale:0.5, opacity:0, ease:Elastic.easeOut, force3D:true}, 0.2) 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /app/scripts/utils/Camera.js: -------------------------------------------------------------------------------- 1 | export default class Camera { 2 | constructor (fov, aspect, near, far, positionY, positionZ, speed) { 3 | this.pov = new THREE.PerspectiveCamera(fov, aspect, near, far); 4 | this.pov.position.z = positionY; 5 | this.pov.position.y = positionZ; 6 | this.speed = speed 7 | } 8 | 9 | rotate(target, distance, time) { 10 | this.pov.position.x = target.x + distance * Math.cos( time*this.speed ); 11 | this.pov.position.y = target.y + distance * Math.sin( time*this.speed); 12 | this.pov.position.z = target.z + distance * Math.sin( time*this.speed); 13 | this.pov.lookAt( target ); 14 | } 15 | } -------------------------------------------------------------------------------- /app/scripts/utils/Colors.js: -------------------------------------------------------------------------------- 1 | export default class Colors { 2 | constructor() { 3 | this.colors = [] 4 | this.firstColor = { 5 | type : 'first', 6 | isActive : false, 7 | data: '#F5B076' 8 | } 9 | this.secondColor = { 10 | type : 'second', 11 | isActive : false, 12 | data: '#EB8063' 13 | } 14 | this.thirdColor = { 15 | type : 'third', 16 | isActive : false, 17 | data: '#D3475B' 18 | } 19 | this.fourthColor = { 20 | type : 'fourth', 21 | isActive : false, 22 | data: '#474889' 23 | } 24 | this.fifthColor = { 25 | type : 'fifth', 26 | isActive : false, 27 | data: '#1E1D45' 28 | } 29 | this.colors.push(this.firstColor, this.secondColor, this.thirdColor, this.fourthColor, this.fifthColor) 30 | } 31 | 32 | 33 | getNewColor() { 34 | let nextColors = this.colors.filter((color) => { 35 | return color.isActive != true; 36 | }); 37 | 38 | let currentColor = nextColors[Math.floor(Math.random() * nextColors.length)]; 39 | 40 | // Remove isActive 41 | this.colors.forEach((color) => { 42 | color.isActive = false; 43 | }); 44 | currentColor.isActive = true; 45 | this.currentColor= currentColor; 46 | return this.currentColor.data 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /app/scripts/utils/Controls.js: -------------------------------------------------------------------------------- 1 | export default class Controls { 2 | constructor() { 3 | let fullScreenButton = document.querySelector('.full_screen') 4 | fullScreenButton.addEventListener('click', () => { 5 | if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) { 6 | if (document.documentElement.requestFullscreen) { 7 | document.documentElement.requestFullscreen(); 8 | } else if (document.documentElement.mozRequestFullScreen) { 9 | document.documentElement.mozRequestFullScreen(); 10 | } else if (document.documentElement.webkitRequestFullscreen) { 11 | document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); 12 | } 13 | 14 | } else { 15 | if (document.cancelFullScreen) { 16 | document.cancelFullScreen(); 17 | } else if (document.mozCancelFullScreen) { 18 | document.mozCancelFullScreen(); 19 | } else if (document.webkitCancelFullScreen) { 20 | document.webkitCancelFullScreen(); 21 | } 22 | } 23 | }); 24 | 25 | 26 | 27 | } 28 | 29 | 30 | } -------------------------------------------------------------------------------- /app/scripts/utils/Sound.js: -------------------------------------------------------------------------------- 1 | // Based largely on https://github.com/jsantell/dancer.js/ 2 | 3 | export default class Sound { 4 | 5 | /** 6 | * src : path to mp3 7 | * bpm : beat per minute 8 | * offsetTime : remove blank sound at start for beat calculation (in seconds) 9 | * callback : ready callback 10 | * debug : enable debug display 11 | */ 12 | constructor(src, bpm, offsetTime, callback, debug = false) { 13 | 14 | // create context 15 | this.ctx; 16 | try { 17 | window.AudioContext = window.AudioContext || window.webkitAudioContext; 18 | this.ctx = new AudioContext(); 19 | } 20 | catch(e) { 21 | throw new Error('Web Audio API is not supported in this browser'); 22 | } 23 | 24 | // values 25 | this._bpm = bpm; 26 | this._beatDuration = 60 / this._bpm; 27 | this._offsetTime = offsetTime; 28 | this._sections = []; 29 | this._kicks = []; 30 | this._beats = []; 31 | this._startTime = 0; 32 | this._pauseTime = 0; 33 | this._isPlaying = false; 34 | this._isLoaded = false; 35 | this._progress = 0; 36 | 37 | // events 38 | this._onUpdate = this.onUpdate.bind(this); 39 | this._onEnded = this.onEnded.bind(this); 40 | 41 | // create gain 42 | this.gainNode = this.ctx.createGain(); 43 | this.gainNode.connect(this.ctx.destination); 44 | 45 | // create analyser 46 | this.analyserNode = this.ctx.createAnalyser(); 47 | this.analyserNode.connect(this.gainNode); 48 | this.analyserNode.smoothingTimeConstant = .8; 49 | this.analyserNode.fftSize = 512; 50 | let bufferLength = this.analyserNode.frequencyBinCount; 51 | this.frequencyDataArray = new Uint8Array(bufferLength); 52 | this.timeDomainDataArray = new Uint8Array(bufferLength); 53 | 54 | // create debug 55 | if (debug) this.debug = new Debug(this); 56 | 57 | // load 58 | this._load(src, callback); 59 | 60 | // update 61 | window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; 62 | window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame; 63 | } 64 | 65 | // load MP3 66 | 67 | _load(src, callback) { 68 | 69 | if (src) { 70 | 71 | this._isLoaded = false; 72 | this._progress = 0; 73 | 74 | // Load asynchronously 75 | let request = new XMLHttpRequest(); 76 | request.open("GET", src, true); 77 | request.responseType = "arraybuffer"; 78 | request.onprogress = (e) => { 79 | this._progress = e.loaded / e.total; 80 | }; 81 | request.onload = () => { 82 | this.ctx.decodeAudioData(request.response, (buffer) => { 83 | this._buffer = buffer; 84 | this._isLoaded = true; 85 | if (callback) callback(); 86 | }, function(e) { 87 | console.log(e); 88 | }); 89 | }; 90 | request.send(); 91 | } 92 | } 93 | 94 | get progress() { 95 | 96 | return this._progress; 97 | } 98 | 99 | get isLoaded() { 100 | 101 | return this._isLoaded; 102 | } 103 | 104 | // sound actions 105 | 106 | play(offset = 0) { 107 | 108 | if (this.req) cancelAnimationFrame(this.req); 109 | this._onUpdate(); 110 | 111 | this._isPlaying = true; 112 | let elapseTime = this._pauseTime - this._startTime + offset; 113 | this._startTime = this.ctx.currentTime - elapseTime; 114 | 115 | this.sourceNode = this.ctx.createBufferSource(); 116 | this.sourceNode.connect(this.analyserNode); 117 | this.sourceNode.buffer = this._buffer; 118 | this.sourceNode.start(0, elapseTime); 119 | this.sourceNode.addEventListener('ended', this._onEnded, false); 120 | } 121 | 122 | pause() { 123 | 124 | if (this.req) cancelAnimationFrame(this.req); 125 | 126 | if (this.sourceNode) { 127 | this.sourceNode.removeEventListener('ended', this._onEnded, false); 128 | this.sourceNode.stop(0); 129 | this.sourceNode.disconnect(); 130 | this.sourceNode = null; 131 | } 132 | 133 | this._pauseTime = this.ctx.currentTime; 134 | this._isPlaying = false; 135 | } 136 | 137 | get duration() { 138 | 139 | return this._isLoaded ? this._buffer.duration : 0; 140 | } 141 | 142 | get time() { 143 | 144 | return this.isPlaying ? this.ctx.currentTime - this._startTime : this._pauseTime - this._startTime; 145 | } 146 | 147 | set volume(value) { 148 | 149 | this.gainNode.gain.value = value; 150 | } 151 | 152 | get volume() { 153 | 154 | return this.gainNode.gain.value; 155 | } 156 | 157 | get isPlaying() { 158 | 159 | return this._isPlaying; 160 | } 161 | 162 | // callback at specific time 163 | 164 | before(label, time, callback) { 165 | 166 | let _this = this; 167 | this._sections.push({ 168 | label: label, 169 | condition : function () { 170 | return _this.time < time; 171 | }, 172 | callback : callback 173 | }); 174 | return this; 175 | } 176 | 177 | after(label, time, callback) { 178 | 179 | let _this = this; 180 | this._sections.push({ 181 | label: label, 182 | condition : function () { 183 | return _this.time > time; 184 | }, 185 | callback : callback 186 | }); 187 | return this; 188 | } 189 | 190 | between(label, startTime, endTime, callback) { 191 | 192 | let _this = this; 193 | this._sections.push({ 194 | label: label, 195 | condition : function () { 196 | return _this.time > startTime && _this.time < endTime; 197 | }, 198 | callback : callback 199 | }); 200 | return this; 201 | } 202 | 203 | onceAt(label, time, callback) { 204 | 205 | let _this = this; 206 | let thisSection = null; 207 | this._sections.push({ 208 | label: label, 209 | condition : function () { 210 | return _this.time > time && !this.called; 211 | }, 212 | callback : function () { 213 | //console.log('once :', thisSection.label) 214 | callback.call( this ); 215 | thisSection.called = true; 216 | }, 217 | called : false 218 | }); 219 | thisSection = this._sections[ this._sections.length - 1 ]; 220 | return this; 221 | } 222 | 223 | // sound analyser 224 | 225 | getSpectrum() { 226 | 227 | this.analyserNode.getByteFrequencyData(this.frequencyDataArray); 228 | 229 | return this.frequencyDataArray; 230 | } 231 | 232 | arrAverage(arr) { 233 | var sum = arr.reduce(function(a, b) { return a + b; }); 234 | return sum / arr.length; 235 | } 236 | 237 | getAverage() { 238 | var cumul = 0; 239 | var spectrum = this.getSpectrum() 240 | for(var i = 0; i < spectrum.length; i++){ 241 | cumul += spectrum[i]; 242 | } 243 | return cumul / spectrum.length 244 | } 245 | 246 | getWaveform() { 247 | 248 | this.analyserNode.getByteTimeDomainData(this.timeDomainDataArray); 249 | 250 | return this.timeDomainDataArray; 251 | } 252 | 253 | getFrequency(freq, endFreq = null) { 254 | 255 | let sum = 0; 256 | let spectrum = this.getSpectrum(); 257 | if ( endFreq !== undefined ) { 258 | for ( var i = freq; i <= endFreq; i++ ) { 259 | sum += spectrum[ i ]; 260 | } 261 | return sum / ( endFreq - freq + 1 ); 262 | } else { 263 | return spectrum[ freq ]; 264 | } 265 | } 266 | 267 | /** 268 | * Kicks are detected when the amplitude (normalized values between 0 and 1) of a specified frequency, or the max amplitude over a range, is greater than the minimum threshold, as well as greater than the previously registered kick's amplitude, which is decreased by the decay rate per frame. 269 | * frequency : the frequency (element of the spectrum) to check for a spike. Can be a single frequency (number) or a range (2 element array) that uses the frequency with highest amplitude. 270 | * threshold : the minimum amplitude of the frequency range in order for a kick to occur. 271 | * decay : the rate that the previously registered kick's amplitude is reduced by on every frame. 272 | * onKick : the callback to be called when a kick is detected. 273 | * offKick : the callback to be called when there is no kick on the current frame. 274 | */ 275 | 276 | createKick({frequency, threshold, decay, onKick, offKick}) { 277 | 278 | let kick = new Kick({frequency, threshold, decay, onKick, offKick}); 279 | this._kicks.push(kick); 280 | return kick; 281 | } 282 | 283 | /** 284 | * Beat are detected when the time correspond to duration of one beat (in second) multiplied by the factor 285 | * factor : the factor to multiply the duration of one beat 286 | * onBeat : the callback to be called when a beat is detected. 287 | */ 288 | 289 | createBeat({factor, onBeat}) { 290 | 291 | let beat = new Beat({factor, onBeat}); 292 | this._beats.push(beat); 293 | return beat; 294 | } 295 | 296 | get beatDuration() { 297 | 298 | return this._beatDuration; 299 | } 300 | 301 | // 302 | 303 | onUpdate() { 304 | 305 | this.req = requestAnimationFrame(this._onUpdate); 306 | 307 | for ( let i in this._sections ) { 308 | if ( this._sections[ i ].condition() ) 309 | this._sections[ i ].callback.call( this ); 310 | } 311 | 312 | let spectrum = this.getSpectrum(); 313 | for ( let i in this._kicks ) { 314 | this._kicks[i].calc(spectrum); 315 | } 316 | 317 | let time = Math.max(0, this.time - this._offsetTime); 318 | for ( let i in this._beats ) { 319 | this._beats[i].calc(time, this._beatDuration); 320 | } 321 | 322 | if (this.debug) this.debug.draw(); 323 | } 324 | 325 | onEnded() { 326 | 327 | //this.stop(); 328 | console.log('fin') 329 | } 330 | }; 331 | 332 | class Kick { 333 | 334 | constructor({frequency, threshold, decay, onKick, offKick}) { 335 | 336 | this.frequency = frequency !== undefined ? frequency : [ 0, 10 ]; 337 | this.threshold = threshold !== undefined ? threshold : 0.3; 338 | this.decay = decay !== undefined ? decay : 0.02; 339 | this.onKick = onKick; 340 | this.offKick = offKick; 341 | this.isOn = false; 342 | this.isKick = false; 343 | this.currentThreshold = this.threshold; 344 | } 345 | 346 | on() { 347 | 348 | this.isOn = true; 349 | } 350 | 351 | off() { 352 | 353 | this.isOn = false; 354 | } 355 | 356 | set({frequency, threshold, decay, onKick, offKick}) { 357 | 358 | this.frequency = frequency !== undefined ? frequency : this.frequency; 359 | this.threshold = threshold !== undefined ? threshold : this.threshold; 360 | this.decay = decay !== undefined ? decay : this.decay; 361 | this.onKick = onKick || this.onKick; 362 | this.offKick = offKick || this.offKick; 363 | } 364 | 365 | calc(spectrum) { 366 | 367 | if ( !this.isOn ) { return; } 368 | let magnitude = this.maxAmplitude(spectrum, this.frequency); 369 | if ( magnitude >= this.currentThreshold && magnitude >= this.threshold ) { 370 | this.currentThreshold = magnitude; 371 | this.onKick && this.onKick(magnitude); 372 | this.isKick = true; 373 | } else { 374 | this.offKick && this.offKick(magnitude); 375 | this.currentThreshold -= this.decay; 376 | this.isKick = false; 377 | } 378 | } 379 | 380 | maxAmplitude(fft, frequency) { 381 | 382 | let max = 0; 383 | 384 | // Sloppy array check 385 | if ( !frequency.length ) { 386 | return frequency < fft.length ? fft[ ~~frequency ] : null; 387 | } 388 | 389 | for ( var i = frequency[ 0 ], l = frequency[ 1 ]; i <= l; i++ ) { 390 | if ( fft[ i ] > max ) { max = fft[ i ]; } 391 | } 392 | 393 | return max; 394 | } 395 | }; 396 | 397 | class Beat { 398 | 399 | constructor({factor, onBeat}) { 400 | 401 | this.factor = factor !== undefined ? factor : 1; 402 | this.onBeat = onBeat; 403 | this.isOn = false; 404 | this.currentTime = 0; 405 | } 406 | 407 | on() { 408 | 409 | this.isOn = true; 410 | } 411 | 412 | off() { 413 | 414 | this.isOn = false; 415 | } 416 | 417 | set({factor, onBeat}) { 418 | 419 | this.factor = factor !== undefined ? factor : this.factor; 420 | this.onBeat = onBeat || this.onBeat; 421 | } 422 | 423 | calc(time, beatDuration) { 424 | if ( time == 0 ) { return; } 425 | let beatDurationFactored = beatDuration * this.factor; 426 | if (time >= this.currentTime + beatDurationFactored) { 427 | if ( this.isOn ) this.onBeat && this.onBeat(); 428 | this.currentTime += beatDurationFactored; 429 | } 430 | } 431 | } 432 | 433 | class Debug { 434 | 435 | constructor(sound) { 436 | 437 | this.sound = sound; 438 | 439 | this.canvas = document.createElement('canvas'); 440 | this.canvas.width = 512; 441 | this.canvas.height = 300; 442 | this.canvas.style.position = 'absolute'; 443 | this.canvas.style.bottom = 0; 444 | this.canvas.style.left = 0; 445 | this.canvas.style.zIndex = 3; 446 | document.body.appendChild(this.canvas); 447 | this.ctx = this.canvas.getContext('2d'); 448 | 449 | window.addEventListener('resize', this.resize.bind(this), false); 450 | this.resize(); 451 | } 452 | 453 | resize() { 454 | this.canvas.width = window.innerWidth; 455 | } 456 | 457 | draw() { 458 | 459 | let borderHeight = 10; 460 | 461 | // draw background 462 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 463 | this.ctx.beginPath(); 464 | this.ctx.rect(0, 0, this.canvas.width, this.canvas.height); 465 | this.ctx.fillStyle = '#000000'; 466 | this.ctx.fill(); 467 | this.ctx.strokeStyle = '#a1a1a1'; 468 | this.ctx.stroke(); 469 | 470 | // draw spectrum 471 | this.ctx.beginPath(); 472 | let spectrum = this.sound.getSpectrum(); 473 | let spectrumValue = null; 474 | let spectrumLength = spectrum.length; 475 | let spectrumWidth = this.canvas.width / spectrumLength; 476 | let spectrumHeight = this.canvas.height - borderHeight; 477 | for (let i = 0; i < spectrumLength; i++) { 478 | 479 | spectrumValue = spectrum[i] / 256; 480 | this.ctx.rect(i * spectrumWidth, spectrumHeight - spectrumHeight * spectrumValue, spectrumWidth / 2, spectrumHeight * spectrumValue); 481 | } 482 | this.ctx.fillStyle = '#ffffff'; 483 | this.ctx.fill(); 484 | 485 | // draw frequency 486 | this.ctx.beginPath(); 487 | this.ctx.font = "10px Arial"; 488 | this.ctx.textBaseline = 'middle'; 489 | this.ctx.textAlign = "left"; 490 | for (let i = 0, len = spectrumLength; i < len; i++) { 491 | 492 | if (i % 10 == 0) { 493 | this.ctx.rect(i * spectrumWidth, spectrumHeight, spectrumWidth / 2, borderHeight); 494 | this.ctx.fillText(i, i * spectrumWidth + 4, spectrumHeight + borderHeight * .5); 495 | } 496 | } 497 | this.ctx.fillStyle = '#ffffff'; 498 | this.ctx.fill(); 499 | 500 | // draw kick 501 | let kicks = this.sound._kicks; 502 | let kick = null; 503 | let kickLength = kicks.length; 504 | let kickFrequencyStart = null; 505 | let kickFrequencyLength = null; 506 | for (let i = 0, len = kickLength; i < len; i++) { 507 | 508 | kick = kicks[i]; 509 | if (kick.isOn) { 510 | kickFrequencyStart = (kick.frequency.length ? kick.frequency[0] : kick.frequency); 511 | kickFrequencyLength = (kick.frequency.length ? kick.frequency[1] - kick.frequency[0] + 1 : 1); 512 | this.ctx.beginPath(); 513 | this.ctx.rect(kickFrequencyStart * spectrumWidth, spectrumHeight - spectrumHeight * (kick.threshold / 256), kickFrequencyLength * spectrumWidth - (spectrumWidth * .5), 2); 514 | this.ctx.rect(kickFrequencyStart * spectrumWidth, spectrumHeight - spectrumHeight * (kick.currentThreshold / 256), kickFrequencyLength * spectrumWidth - (spectrumWidth * .5), 5); 515 | this.ctx.fillStyle = kick.isKick ? '#00ff00' : '#ff0000'; 516 | this.ctx.fill(); 517 | } 518 | } 519 | 520 | // draw waveform 521 | this.ctx.beginPath(); 522 | let waveform = this.sound.getWaveform(); 523 | let waveformValue = null; 524 | let waveformLength = waveform.length; 525 | let waveformWidth = this.canvas.width / waveformLength; 526 | let waveformHeight = this.canvas.height - borderHeight; 527 | for (let i = 0; i < waveformLength; i++) { 528 | 529 | waveformValue = waveform[i] / 256; 530 | if (i == 0) this.ctx.moveTo(i * waveformWidth, waveformHeight * waveformValue); 531 | else this.ctx.lineTo(i * waveformWidth, waveformHeight * waveformValue); 532 | } 533 | this.ctx.strokeStyle = '#0000ff'; 534 | this.ctx.stroke(); 535 | 536 | // draw time 537 | this.ctx.beginPath(); 538 | this.ctx.textAlign = "right"; 539 | this.ctx.textBaseline = 'top'; 540 | this.ctx.font = "15px Arial"; 541 | this.ctx.fillStyle = '#ffffff'; 542 | this.ctx.fillText((Math.round(this.sound.time * 10) / 10) + ' / ' + (Math.round(this.sound.duration * 10) / 10), this.canvas.width - 5, 5); 543 | 544 | // draw section 545 | this.ctx.beginPath(); 546 | let sections = this.sound._sections; 547 | let section = null; 548 | let sectionLength = sections.length; 549 | let sectionLabels = ''; 550 | for (let i = 0, len = sectionLength; i < len; i++) { 551 | 552 | section = sections[i]; 553 | if ( section.condition() ) { 554 | sectionLabels += section.label + ' - '; 555 | } 556 | } 557 | if (sectionLabels.length > 0) sectionLabels = sectionLabels.substr(0, sectionLabels.length - 3); 558 | this.ctx.fillText(sectionLabels, this.canvas.width - 5, 25); 559 | this.ctx.fill(); 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /app/scripts/utils/StateManager.js: -------------------------------------------------------------------------------- 1 | import Cube from '../shapes/cube' 2 | import Hemisphere from '../shapes/hemisphere' 3 | import Octa from '../shapes/octa' 4 | import Plane from '../shapes/plane' 5 | import Cylinder from '../shapes/cylinder' 6 | import Sphere from '../shapes/sphere' 7 | import Tear from '../shapes/tear' 8 | import Torus from '../shapes/torus' 9 | 10 | export default class StateManager { 11 | constructor(nb) { 12 | 13 | //Instanciation of my shapes 14 | this.cube = new Cube(nb) 15 | this.cylinder = new Cylinder(nb) 16 | this.hemisphere = new Hemisphere(nb) 17 | this.octa = new Octa(nb) 18 | this.plane = new Plane(nb) 19 | this.sphere = new Sphere(nb) 20 | this.tear = new Tear(nb) 21 | this.torus = new Torus(nb) 22 | 23 | //We are putting all the shapes in an array to manage them 24 | this.states = [] 25 | this.cubeState = { 26 | type : 'cube', 27 | isActive : false, 28 | data: this.cube 29 | } 30 | this.cylinderState = { 31 | type : 'cylinder', 32 | isActive : false, 33 | data: this.cylinder 34 | } 35 | this.hemisphereState = { 36 | type : 'hemisphere', 37 | isActive : false, 38 | data: this.hemisphere 39 | } 40 | this.planeState = { 41 | type : 'plane', 42 | isActive : false, 43 | data: this.plane 44 | } 45 | this.octaState = { 46 | type : 'octa', 47 | isActive : false, 48 | data: this.octa 49 | } 50 | this.sphereState = { 51 | type : 'sphere', 52 | isActive : false, 53 | data: this.sphere 54 | } 55 | this.tearState = { 56 | type : 'tear', 57 | isActive : false, 58 | data: this.tear 59 | } 60 | this.torusState = { 61 | type : 'torus', 62 | isActive : false, 63 | data: this.torus 64 | } 65 | this.states.push(this.cubeState, this.cylinderState, this.hemisphereState, this.planeState, this.octaState, this.sphereState, this.tearState, this.torusState) 66 | } 67 | 68 | displacement(nb, departure, arrival, ratio) { 69 | for(var i=0; i< nb; i++){ 70 | arrival[i].x += (departure[i].x - arrival[i].x) * ratio 71 | arrival[i].y += (departure[i].y - arrival[i].y) * ratio 72 | arrival[i].z += (departure[i].z - arrival[i].z) * ratio 73 | } 74 | 75 | } 76 | 77 | getNewPattern(arrStates) { 78 | let nextPatterns = arrStates.filter((state) => { 79 | return state.isActive != true; 80 | }); 81 | 82 | let currentPattern = nextPatterns[Math.floor(Math.random() * nextPatterns.length)]; 83 | 84 | arrStates.forEach((state) => { 85 | state.isActive = false; 86 | }); 87 | 88 | currentPattern.isActive = true; 89 | 90 | return currentPattern; 91 | 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threejs-boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server --env=dev --open", 9 | "build": "webpack --env=prod" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel-core": "^6.26.0", 15 | "babel-loader": "^7.0.0", 16 | "babel-preset-env": "^1.6.1", 17 | "babel-preset-es2015": "^6.24.1", 18 | "clean-webpack-plugin": "^0.1.16", 19 | "copy-webpack-plugin": "^4.0.1", 20 | "css-loader": "^0.28.1", 21 | "exports-loader": "^0.6.4", 22 | "extract-text-webpack-plugin": "^2.1.0", 23 | "file-loader": "^0.11.1", 24 | "gsap": "^1.20.3", 25 | "html-webpack-plugin": "^2.28.0", 26 | "imports-loader": "^0.7.1", 27 | "raw-loader": "^0.5.1", 28 | "script-loader": "^0.7.0", 29 | "uglify-js": "v2.8.29", 30 | "uglifyjs-webpack-plugin": "^0.4.6", 31 | "webpack": "^2.7.0", 32 | "webpack-dev-server": "^2.4.5", 33 | "webpack-glsl-loader": "^1.0.1" 34 | }, 35 | "dependencies": { 36 | "three": "^0.87.1", 37 | "three-orbit-controls": "^82.1.0", 38 | "webvr-polyfill": "^0.9.38" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 7 | 8 | module.exports = function(env) { 9 | let plugins = [ 10 | new webpack.ProvidePlugin({ 11 | THREE: 'three', 12 | }), 13 | // clean export folder 14 | new CleanWebpackPlugin('dist', { 15 | root: __dirname 16 | }), 17 | // create styles css 18 | new ExtractTextPlugin(env == 'prod' ? '[name].[contenthash].css' : '[name].css'), 19 | // create vendor bundle with all imported node_modules 20 | new webpack.optimize.CommonsChunkPlugin({ 21 | name: 'vendor', 22 | minChunks: function (module) { 23 | return module.context && module.context.indexOf('node_modules') !== -1; 24 | } 25 | }), 26 | // create webpack manifest separately 27 | new webpack.optimize.CommonsChunkPlugin({ 28 | name: 'manifest' 29 | }), 30 | // create html 31 | new HtmlWebpackPlugin({ 32 | template: 'index.html', 33 | chunksSortMode: 'dependency' 34 | }), 35 | ]; 36 | if (env == 'dev') { 37 | 38 | 39 | } 40 | else { 41 | 42 | // uglify 43 | plugins.push(new UglifyJSPlugin({ 44 | sourceMap: false, 45 | compress: { 46 | warnings: false, 47 | }, 48 | })); 49 | } 50 | 51 | return { 52 | context: path.resolve(__dirname, 'app'), 53 | devServer: { 54 | host: "0.0.0.0", 55 | disableHostCheck: true 56 | }, 57 | entry: { 58 | main: './index.js' 59 | }, 60 | output: { 61 | path: path.resolve(__dirname, 'dist'), 62 | filename: env == 'prod' ? '[name].[chunkhash].js' : '[name].js', 63 | }, 64 | module: { 65 | rules: [{ 66 | test: /\.js$/, 67 | exclude: /(node_modules|bower_components)/, 68 | use: { 69 | loader: 'babel-loader', 70 | options: { 71 | presets: ['env'] 72 | } 73 | } 74 | },{ 75 | test: /\.css$/, 76 | use: ExtractTextPlugin.extract({ 77 | use: 'css-loader' 78 | }) 79 | },{ 80 | test: [/\.mp3$/, /\.dae$/, /\.jpg$/, /\.obj$/, /\.fbx$/], 81 | use: ['file-loader?name=[path][name].[hash].[ext]'] 82 | }, 83 | { 84 | test:[/\.vert$/,/\.frag$/], 85 | loader: 'webpack-glsl-loader' 86 | } 87 | ] 88 | }, 89 | devtool: env == 'dev' ? 'cheap-eval-source-map' : '', 90 | plugins: plugins, 91 | } 92 | }; 93 | --------------------------------------------------------------------------------