├── 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 | 
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 |
39 |
--------------------------------------------------------------------------------
/app/assets/img/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
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 |
15 |
18 |
21 |
24 |
27 |
28 |
29 |
30 |
33 |
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 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
THANKS FOR WATCHING
73 |
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 |
--------------------------------------------------------------------------------