├── .babelrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── SocialShareKit │ ├── css │ │ └── social-share-kit.css │ └── fonts │ │ ├── social-share-kit.eot │ │ ├── social-share-kit.svg │ │ ├── social-share-kit.ttf │ │ └── social-share-kit.woff ├── favicon.ico ├── fontello │ ├── LICENSE.txt │ ├── README.txt │ ├── config.json │ ├── css │ │ ├── animation.css │ │ ├── fontello-codes.css │ │ ├── fontello-embedded.css │ │ ├── fontello-ie7-codes.css │ │ ├── fontello-ie7.css │ │ └── fontello.css │ ├── demo.html │ └── font │ │ ├── fontello.eot │ │ ├── fontello.svg │ │ ├── fontello.ttf │ │ ├── fontello.woff │ │ └── fontello.woff2 ├── julia-set.png └── skeleton │ ├── normalize.css │ └── skeleton.css ├── deploy.sh ├── index.html ├── javascript ├── WebGL │ ├── createFrameRenderer.js │ ├── initializeWebGL.js │ └── utility │ │ ├── compileShader.js │ │ ├── configureProgram.js │ │ ├── createProgram.js │ │ ├── getAttribLocation.js │ │ ├── getUniformLocation.js │ │ ├── msaaCoordinates.js │ │ ├── prepareGeometry.js │ │ ├── programForShader.js │ │ └── setUniformValue.js ├── actions │ └── index.js ├── components │ ├── App.css │ ├── App.jsx │ ├── CanvasContainer.jsx │ ├── Header.css │ ├── Header.jsx │ ├── Menu.css │ ├── Menu.jsx │ ├── MenuItemRange.jsx │ ├── MenuItemSelect.jsx │ ├── MenuItemShaderSelect.jsx │ ├── MenuItemShareGroup.jsx │ └── index.js ├── config.js ├── index.js ├── reducers │ ├── createReducer.js │ ├── createShader.js │ ├── currentShader.js │ ├── index.js │ └── menuOpen.js ├── utility │ ├── configureStore.js │ ├── localStorage.js │ └── registerEvent.js └── viewport.js ├── package.json ├── shaders ├── burningShip.glsl ├── checkerboard.glsl ├── collatz.glsl ├── julia.glsl ├── spinningCube.glsl └── vertexShader.glsl └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | notable changes to this project will be documented in this file as I remember. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | 6 | ### 2016-08-26 7 | - Improves Canvas and WebGL context behavior on window resize 8 | - Replaces HashSubscriber with Redux for state management 9 | 10 | ### 2016-08-27 11 | - configuration values in the menu can now be set and reset by fractal 12 | 13 | ### 2016-08-30 14 | - rebuilt interface in React 15 | - switched to using [Standard](http://standardjs.com/) style 16 | 17 | ### 2016-09-02 18 | - rewrote Viewport so that its internal state held center and range rather than bounds 19 | - significantly simplified reducers 20 | - adds 'modified collatz' fractal to be selected 21 | - adds 'zoom in' and 'zoom out' buttons 22 | - adds basic google analytics script 23 | 24 | ### 2016-09-02 - 2016-10-02 25 | - allowed multiple shader files to be used 26 | - added 'burning ship' fractal 27 | - added 3D spinning cube 28 | 29 | ### 2016-10-04 30 | - adds sphere to 'spinning cube' shader 31 | - adds 'distance', 'check_size' to 'spinning cube' shader 32 | 33 | ### 2016-10-06 34 | - plays with coloration in 'spinning cube' shader 35 | - adds reflectivity to 'spinning cube' shader 36 | 37 | ### 2016-10-06 - 2016-10-24 38 | - adds pinch zoom capability for mobile 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jonathan Potter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WebGL Shaders 2 | -------- 3 | ![main image](http://jonathan-potter.github.io/webgl-shaders/assets/julia-set.png "Julia Set") 4 | 5 | Instructions: 6 | -------- 7 | 8 | run using: 9 | ``` 10 | npm start 11 | ``` 12 | open this url in your browser. 13 | ``` 14 | http://localhost:8080/ 15 | ``` 16 | 17 | Acknowledgement: 18 | -------- 19 | 20 | a big thanks to [Jamie Wong](http://jamie-wong.com/) for his post here: [http://jamie-wong.com/2016/07/06/metaballs-and-webgl/](http://jamie-wong.com/2016/07/06/metaballs-and-webgl/) 21 | 22 | Licenses: 23 | ________ 24 | 25 | [Social Share Kit](http://socialsharekit.com/) - [Creative Commons Attribution-NonCommercial 3.0](https://creativecommons.org/licenses/by-nc/3.0/) 26 | -------------------------------------------------------------------------------- /assets/SocialShareKit/css/social-share-kit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Social Share Kit v1.0.7 (http://socialsharekit.com) 3 | * Copyright 2015 Social Share Kit / Kaspars Sprogis. 4 | * Licensed under Creative Commons Attribution-NonCommercial 3.0 license: 5 | * https://github.com/darklow/social-share-kit/blob/master/LICENSE 6 | * --- 7 | */@font-face{font-family:'social-share-kit';src:url('../fonts/social-share-kit.eot');src:url('../fonts/social-share-kit.eot?#iefix') format('embedded-opentype'),url('../fonts/social-share-kit.woff') format('woff'),url('../fonts/social-share-kit.ttf') format('truetype'),url('../fonts/social-share-kit.svg#social-share-kit') format('svg');font-weight:normal;font-style:normal}.ssk:before{display:inline-block;font-family:"social-share-kit" !important;font-style:normal !important;font-weight:normal !important;font-variant:normal !important;text-transform:none !important;speak:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ssk-facebook:before{content:"a";text-indent:4px;margin-right:-4px}.ssk-twitter:before{content:"b"}.ssk-google-plus:before{content:"v"}.ssk-google-plus-old:before{content:"c"}.ssk-email:before{content:"d";top:-1px;position:relative}.ssk-pinterest:before{content:"e"}.ssk-tumblr:before{content:"f"}.ssk-linkedin:before{content:"g"}.ssk-github:before{content:"h"}.ssk-vk:before{content:"i"}.ssk-instagram:before{content:"j"}.ssk-amazon:before{content:"k"}.ssk-skype:before{content:"s"}.ssk-youtube:before{content:"x"}.ssk-vimeo:before{content:"u"}.ssk-ebay:before{content:"p"}.ssk-apple:before{content:"l"}.ssk-behance:before{content:"q"}.ssk-dribble:before{content:"n"}.ssk-android:before{content:"o"}.ssk-whatsapp:before{content:"m"}.ssk-reddit:before{content:"r"}.ssk-reddit2:before{content:"t"}.ssk{background-color:#757575;color:white;display:inline-block;font-size:22px;line-height:1px;margin-right:2px;margin-bottom:2px;padding:7px;text-align:center;text-decoration:none;transition:background-color .1s;-webkit-transition:background-color .1s;-moz-transition:background-color .1s;-ms-transition:background-color .1s;-o-transition:background-color .1s}.ssk:before,.ssk .glyphicon,.ssk .fa{position:relative;font-size:22px;top:0;vertical-align:middle}.ssk.ssk-xs,.ssk-xs>.ssk{padding:4px}.ssk.ssk-xs:before,.ssk-xs>.ssk:before,.ssk.ssk-xs .glyphicon,.ssk-xs>.ssk .glyphicon,.ssk.ssk-xs .fa,.ssk-xs>.ssk .fa{font-size:15px}.ssk.ssk-sm,.ssk-sm>.ssk{padding:5px}.ssk.ssk-sm:before,.ssk-sm>.ssk:before,.ssk.ssk-sm .glyphicon,.ssk-sm>.ssk .glyphicon,.ssk.ssk-sm .fa,.ssk-sm>.ssk .fa{font-size:20px}.ssk.ssk-lg,.ssk-lg>.ssk{padding:9px}.ssk.ssk-lg:before,.ssk-lg>.ssk:before,.ssk.ssk-lg .glyphicon,.ssk-lg>.ssk .glyphicon,.ssk.ssk-lg .fa,.ssk-lg>.ssk .fa{font-size:28px}.ssk:last-child{margin-right:0}.ssk:hover{background-color:#424242}.ssk:hover,.ssk:focus{color:#fff;text-decoration:none}.ssk.ssk-round,.ssk-round .ssk{border-radius:50%}.ssk.ssk-round:before,.ssk-round .ssk:before{text-indent:0;margin-right:0}.ssk.ssk-rounded,.ssk-rounded .ssk{border-radius:15%}.ssk.ssk-icon{color:#757575;padding:2px;font-size:24px}.ssk.ssk-icon,.ssk.ssk-icon:hover{background-color:transparent}.ssk.ssk-icon:hover{color:#424242}.ssk.ssk-icon.ssk-xs,.ssk-xs>.ssk.ssk-icon{font-size:16px}.ssk.ssk-icon.ssk-sm,.ssk-sm>.ssk.ssk-icon{font-size:20px}.ssk.ssk-icon.ssk-lg,.ssk-lg>.ssk.ssk-icon{font-size:28px}.ssk.ssk-text{overflow:hidden;font-size:17px;line-height:normal;padding-right:10px}.ssk.ssk-text:before,.ssk.ssk-text .glyphicon,.ssk.ssk-text .fa{margin:-7px 10px -7px -7px;padding:7px;background-color:rgba(0,0,0,0.15);vertical-align:bottom;text-indent:0}.ssk-block .ssk.ssk-text{display:block;margin-right:0;text-align:left}.ssk.ssk-text.ssk-xs,.ssk-xs>.ssk.ssk-text{font-size:12px;padding-right:6px}.ssk.ssk-text.ssk-xs:before,.ssk-xs>.ssk.ssk-text:before,.ssk.ssk-text.ssk-xs .glyphicon,.ssk-xs>.ssk.ssk-text .glyphicon,.ssk.ssk-text.ssk-xs .fa,.ssk-xs>.ssk.ssk-text .fa{margin:-4px 6px -4px -4px;padding:4px}.ssk.ssk-text.ssk-sm,.ssk-sm>.ssk.ssk-text{font-size:16px;padding-right:7px}.ssk.ssk-text.ssk-sm:before,.ssk-sm>.ssk.ssk-text:before,.ssk.ssk-text.ssk-sm .glyphicon,.ssk-sm>.ssk.ssk-text .glyphicon,.ssk.ssk-text.ssk-sm .fa,.ssk-sm>.ssk.ssk-text .fa{margin:-5px 7px -5px -5px;padding:5px}.ssk.ssk-text.ssk-lg,.ssk-lg>.ssk.ssk-text{font-size:22px;padding-right:13px}.ssk.ssk-text.ssk-lg:before,.ssk-lg>.ssk.ssk-text:before,.ssk.ssk-text.ssk-lg .glyphicon,.ssk-lg>.ssk.ssk-text .glyphicon,.ssk.ssk-text.ssk-lg .fa,.ssk-lg>.ssk.ssk-text .fa{margin:-9px 13px -9px -9px;padding:9px}.ssk-group,.ssk-sticky{font-size:0}.ssk-sticky{top:0;position:fixed;z-index:2000}.ssk-sticky .ssk{transition:padding .1s ease-out;-webkit-transition:padding .1s ease-out;-moz-transition:padding .1s ease-out;-ms-transition:padding .1s ease-out;-o-transition:padding .1s ease-out;margin:0}@media (min-width:768px){.ssk-sticky.ssk-left .ssk,.ssk-sticky.ssk-right .ssk{display:block;clear:both}.ssk-sticky.ssk-left.ssk-center,.ssk-sticky.ssk-right.ssk-center{top:50%;transform:translateY(-50%);-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-ms-transform:translateY(-50%);-o-transform:translateY(-50%)}.ssk-sticky.ssk-left{left:0}.ssk-sticky.ssk-left .ssk{float:left}.ssk-sticky.ssk-left .ssk:hover{padding-left:15px}.ssk-sticky.ssk-right{right:0}.ssk-sticky.ssk-right .ssk{float:right}.ssk-sticky.ssk-right .ssk:hover{padding-right:15px}}.ssk-sticky.ssk-bottom{font-size:0;top:auto;bottom:0}.ssk-sticky.ssk-bottom.ssk-center{left:50%;right:auto;transform:translateX(-50%);-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);-o-transform:translateX(-50%)}.ssk-sticky.ssk-bottom .ssk{vertical-align:bottom}.ssk-sticky.ssk-bottom .ssk:hover{padding-bottom:15px}.ssk-sticky.ssk-round.ssk-xs .ssk:hover{padding:8px}.ssk-sticky.ssk-round.ssk-sm .ssk:hover{padding:9px}.ssk-sticky.ssk-round .ssk:hover{padding:11px}.ssk-sticky.ssk-round.ssk-lg .ssk:hover{padding:13px}@media (max-width:767px){.ssk-sticky{left:0;right:0;bottom:0;top:auto;width:100%;display:flex !important;flex-direction:row;flex-wrap:nowrap}.ssk-sticky.ssk-sticky-hide-xs{display:none !important}.ssk-sticky .ssk{flex:1;width:auto}.ssk-sticky .ssk .ssk-num{display:none}}.ssk-count{padding-top:20px}.ssk-count .ssk{position:relative}.ssk-count .ssk-num{border-radius:4px;color:#8f8f8f;background-color:rgba(50,50,50,0.03);display:block;font-size:12px;left:0;line-height:20px;position:absolute;right:0;text-align:center;top:-20px}@media (min-width:768px){.ssk-count.ssk-sticky{padding-top:0}.ssk-count.ssk-sticky.ssk-left .ssk-num,.ssk-count.ssk-sticky.ssk-right .ssk-num{top:20%;background-color:transparent}.ssk-count.ssk-sticky.ssk-left .ssk-num{left:100%;margin-left:5px}.ssk-count.ssk-sticky.ssk-right .ssk-num{right:115%;margin-left:-100%;text-align:right}}.ssk-facebook{background-color:#255c95}.ssk-grayscale>.ssk-facebook{background-color:#757575}.ssk-facebook:hover{background-color:#1b436c}.ssk-facebook:hover{background-color:#1b436c}.ssk-grayscale>.ssk-facebook:hover{background-color:#255c95}.ssk-facebook.ssk-icon{color:#255c95}.ssk-facebook.ssk-icon:hover{color:#1b436c}.ssk-facebook.ssk-icon:before{text-indent:0;margin-right:0}.ssk-twitter{background-color:#00b4e0}.ssk-grayscale>.ssk-twitter{background-color:#757575}.ssk-twitter:hover{background-color:#008bad}.ssk-twitter:hover{background-color:#008bad}.ssk-grayscale>.ssk-twitter:hover{background-color:#00b4e0}.ssk-twitter.ssk-icon{color:#00b4e0}.ssk-twitter.ssk-icon:hover{color:#008bad}.ssk-google-plus{background-color:#f1403a}.ssk-grayscale>.ssk-google-plus{background-color:#757575}.ssk-google-plus:hover{background-color:#e81810}.ssk-google-plus:hover{background-color:#e81810}.ssk-grayscale>.ssk-google-plus:hover{background-color:#f1403a}.ssk-google-plus.ssk-icon{color:#f1403a}.ssk-google-plus.ssk-icon:hover{color:#e81810}.ssk-pinterest{background-color:#cb2027}.ssk-grayscale>.ssk-pinterest{background-color:#757575}.ssk-pinterest:hover{background-color:#9f191f}.ssk-pinterest:hover{background-color:#9f191f}.ssk-grayscale>.ssk-pinterest:hover{background-color:#cb2027}.ssk-pinterest.ssk-icon{color:#cb2027}.ssk-pinterest.ssk-icon:hover{color:#9f191f}.ssk-tumblr{background-color:#395773}.ssk-grayscale>.ssk-tumblr{background-color:#757575}.ssk-tumblr:hover{background-color:#283d51}.ssk-tumblr:hover{background-color:#283d51}.ssk-grayscale>.ssk-tumblr:hover{background-color:#395773}.ssk-tumblr.ssk-icon{color:#395773}.ssk-tumblr.ssk-icon:hover{color:#283d51}.ssk-email{background-color:#757575}.ssk-grayscale>.ssk-email{background-color:#757575}.ssk-email:hover{background-color:#5b5b5b}.ssk-email:hover{background-color:#5b5b5b}.ssk-grayscale>.ssk-email:hover{background-color:#757575}.ssk-grayscale>.ssk-email:hover{background-color:#5b5b5b}.ssk-email.ssk-icon{color:#757575}.ssk-email.ssk-icon:hover{color:#5b5b5b}.ssk-vk{background-color:#54769a}.ssk-grayscale>.ssk-vk{background-color:#757575}.ssk-vk:hover{background-color:#425d79}.ssk-vk:hover{background-color:#425d79}.ssk-grayscale>.ssk-vk:hover{background-color:#54769a}.ssk-vk.ssk-icon{color:#54769a}.ssk-vk.ssk-icon:hover{color:#425d79}.ssk-linkedin{background-color:#1c87bd}.ssk-grayscale>.ssk-linkedin{background-color:#757575}.ssk-linkedin:hover{background-color:#156791}.ssk-linkedin:hover{background-color:#156791}.ssk-grayscale>.ssk-linkedin:hover{background-color:#1c87bd}.ssk-linkedin.ssk-icon{color:#1c87bd}.ssk-linkedin.ssk-icon:hover{color:#156791}.ssk-whatsapp{background-color:#34AF23}.ssk-grayscale>.ssk-whatsapp{background-color:#757575}.ssk-whatsapp:hover{background-color:#27851a}.ssk-whatsapp:hover{background-color:#27851a}.ssk-grayscale>.ssk-whatsapp:hover{background-color:#34AF23}.ssk-whatsapp.ssk-icon{color:#34AF23}.ssk-whatsapp.ssk-icon:hover{color:#27851a}.ssk-reddit{background-color:#5f99cf}.ssk-grayscale>.ssk-reddit{background-color:#757575}.ssk-reddit:hover{background-color:#3a80c1}.ssk-reddit:hover{background-color:#3a80c1}.ssk-grayscale>.ssk-reddit:hover{background-color:#5f99cf}.ssk-reddit.ssk-icon{color:#5f99cf}.ssk-reddit.ssk-icon:hover{color:#3a80c1}.ssk-reddit2{background-color:#5f99cf}.ssk-grayscale>.ssk-reddit2{background-color:#757575}.ssk-reddit2:hover{background-color:#3a80c1}.ssk-reddit2:hover{background-color:#3a80c1}.ssk-grayscale>.ssk-reddit2:hover{background-color:#5f99cf}.ssk-reddit2.ssk-icon{color:#5f99cf}.ssk-reddit2.ssk-icon:hover{color:#3a80c1}.ssk-turquoise{background-color:#1abc9c}.ssk-turquoise:hover{background-color:#148f77}.ssk-emerald{background-color:#2ecc71}.ssk-emerald:hover{background-color:#25a25a}.ssk-peter-river{background-color:#3498db}.ssk-peter-river:hover{background-color:#217dbb}.ssk-belize-hole{background-color:#2980b9}.ssk-belize-hole:hover{background-color:#20638f}.ssk-amethyst{background-color:#9b59b6}.ssk-amethyst:hover{background-color:#804399}.ssk-wisteria{background-color:#8e44ad}.ssk-wisteria:hover{background-color:#703688}.ssk-wet-asphalt{background-color:#34495e}.ssk-wet-asphalt:hover{background-color:#222f3d}.ssk-midnight-blue{background-color:#2c3e50}.ssk-midnight-blue:hover{background-color:#1a242f}.ssk-green-sea{background-color:#16a085}.ssk-green-sea:hover{background-color:#107360}.ssk-nephritis{background-color:#27ae60}.ssk-nephritis:hover{background-color:#1e8449}.ssk-sunflower{background-color:#f1c40f}.ssk-sunflower:hover{background-color:#c29d0b}.ssk-orange{background-color:#f39c12}.ssk-orange:hover{background-color:#c87f0a}.ssk-carrot{background-color:#e67e22}.ssk-carrot:hover{background-color:#bf6516}.ssk-pumpkin{background-color:#d35400}.ssk-pumpkin:hover{background-color:#a04000}.ssk-alizarin{background-color:#e74c3c}.ssk-alizarin:hover{background-color:#d62c1a}.ssk-pomegranate{background-color:#c0392b}.ssk-pomegranate:hover{background-color:#962d22}.ssk-clouds{background-color:#cfd9db}.ssk-clouds:hover{background-color:#b1c2c6}.ssk-concrete{background-color:#95a5a6}.ssk-concrete:hover{background-color:#798d8f}.ssk-silver{background-color:#bdc3c7}.ssk-silver:hover{background-color:#a1aab0}.ssk-asbestos{background-color:#7f8c8d}.ssk-asbestos:hover{background-color:#667273}.ssk-dark-gray{background-color:#555}.ssk-dark-gray:hover{background-color:#3b3b3b}.ssk-black{background-color:#333}.ssk-black:hover{background-color:#1a1a1a} -------------------------------------------------------------------------------- /assets/SocialShareKit/fonts/social-share-kit.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/SocialShareKit/fonts/social-share-kit.eot -------------------------------------------------------------------------------- /assets/SocialShareKit/fonts/social-share-kit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 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 | -------------------------------------------------------------------------------- /assets/SocialShareKit/fonts/social-share-kit.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/SocialShareKit/fonts/social-share-kit.ttf -------------------------------------------------------------------------------- /assets/SocialShareKit/fonts/social-share-kit.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/SocialShareKit/fonts/social-share-kit.woff -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/favicon.ico -------------------------------------------------------------------------------- /assets/fontello/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Font license info 2 | 3 | 4 | ## Zocial 5 | 6 | Copyright (C) 2012 by Sam Collins 7 | 8 | Author: Sam Collins 9 | License: MIT (http://opensource.org/licenses/mit-license.php) 10 | Homepage: http://zocial.smcllns.com/ 11 | 12 | 13 | ## Font Awesome 14 | 15 | Copyright (C) 2016 by Dave Gandy 16 | 17 | Author: Dave Gandy 18 | License: SIL () 19 | Homepage: http://fortawesome.github.com/Font-Awesome/ 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/fontello/README.txt: -------------------------------------------------------------------------------- 1 | This webfont is generated by http://fontello.com open source project. 2 | 3 | 4 | ================================================================================ 5 | Please, note, that you should obey original font licenses, used to make this 6 | webfont pack. Details available in LICENSE.txt file. 7 | 8 | - Usually, it's enough to publish content of LICENSE.txt file somewhere on your 9 | site in "About" section. 10 | 11 | - If your project is open-source, usually, it will be ok to make LICENSE.txt 12 | file publicly available in your repository. 13 | 14 | - Fonts, used in Fontello, don't require a clickable link on your site. 15 | But any kind of additional authors crediting is welcome. 16 | ================================================================================ 17 | 18 | 19 | Comments on archive content 20 | --------------------------- 21 | 22 | - /font/* - fonts in different formats 23 | 24 | - /css/* - different kinds of css, for all situations. Should be ok with 25 | twitter bootstrap. Also, you can skip style and assign icon classes 26 | directly to text elements, if you don't mind about IE7. 27 | 28 | - demo.html - demo file, to show your webfont content 29 | 30 | - LICENSE.txt - license info about source fonts, used to build your one. 31 | 32 | - config.json - keeps your settings. You can import it back into fontello 33 | anytime, to continue your work 34 | 35 | 36 | Why so many CSS files ? 37 | ----------------------- 38 | 39 | Because we like to fit all your needs :) 40 | 41 | - basic file, .css - is usually enough, it contains @font-face 42 | and character code definitions 43 | 44 | - *-ie7.css - if you need IE7 support, but still don't wish to put char codes 45 | directly into html 46 | 47 | - *-codes.css and *-ie7-codes.css - if you like to use your own @font-face 48 | rules, but still wish to benefit from css generation. That can be very 49 | convenient for automated asset build systems. When you need to update font - 50 | no need to manually edit files, just override old version with archive 51 | content. See fontello source code for examples. 52 | 53 | - *-embedded.css - basic css file, but with embedded WOFF font, to avoid 54 | CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain. 55 | We strongly recommend to resolve this issue by `Access-Control-Allow-Origin` 56 | server headers. But if you ok with dirty hack - this file is for you. Note, 57 | that data url moved to separate @font-face to avoid problems with 2 | 3 | 4 | 278 | 279 | 291 | 292 | 293 |
294 |

295 | fontello 296 | font demo 297 |

298 | 301 |
302 |
303 |
304 |
icon-github-circled0xe800
305 |
icon-wikipedia0xe801
306 |
icon-twitter0xe802
307 |
icon-menu0xf0c9
308 |
309 |
310 | 311 | 312 | -------------------------------------------------------------------------------- /assets/fontello/font/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/fontello/font/fontello.eot -------------------------------------------------------------------------------- /assets/fontello/font/fontello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2016 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/fontello/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/fontello/font/fontello.ttf -------------------------------------------------------------------------------- /assets/fontello/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/fontello/font/fontello.woff -------------------------------------------------------------------------------- /assets/fontello/font/fontello.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/fontello/font/fontello.woff2 -------------------------------------------------------------------------------- /assets/julia-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathan-potter/webgl-shaders/d41c95b230be7c94f9664d9ad65abce4a4253bb2/assets/julia-set.png -------------------------------------------------------------------------------- /assets/skeleton/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } -------------------------------------------------------------------------------- /assets/skeleton/skeleton.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V2.0.4 3 | * Copyright 2014, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 12/29/2014 8 | */ 9 | 10 | 11 | /* Table of contents 12 | –––––––––––––––––––––––––––––––––––––––––––––––––– 13 | - Grid 14 | - Base Styles 15 | - Typography 16 | - Links 17 | - Buttons 18 | - Forms 19 | - Lists 20 | - Code 21 | - Tables 22 | - Spacing 23 | - Utilities 24 | - Clearing 25 | - Media Queries 26 | */ 27 | 28 | 29 | /* Grid 30 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 31 | .container { 32 | position: relative; 33 | width: 100%; 34 | max-width: 960px; 35 | margin: 0 auto; 36 | padding: 0 20px; 37 | box-sizing: border-box; } 38 | .column, 39 | .columns { 40 | width: 100%; 41 | float: left; 42 | box-sizing: border-box; } 43 | 44 | /* For devices larger than 400px */ 45 | @media (min-width: 400px) { 46 | .container { 47 | width: 85%; 48 | padding: 0; } 49 | } 50 | 51 | /* For devices larger than 550px */ 52 | @media (min-width: 550px) { 53 | .container { 54 | width: 80%; } 55 | .column, 56 | .columns { 57 | margin-left: 4%; } 58 | .column:first-child, 59 | .columns:first-child { 60 | margin-left: 0; } 61 | 62 | .one.column, 63 | .one.columns { width: 4.66666666667%; } 64 | .two.columns { width: 13.3333333333%; } 65 | .three.columns { width: 22%; } 66 | .four.columns { width: 30.6666666667%; } 67 | .five.columns { width: 39.3333333333%; } 68 | .six.columns { width: 48%; } 69 | .seven.columns { width: 56.6666666667%; } 70 | .eight.columns { width: 65.3333333333%; } 71 | .nine.columns { width: 74.0%; } 72 | .ten.columns { width: 82.6666666667%; } 73 | .eleven.columns { width: 91.3333333333%; } 74 | .twelve.columns { width: 100%; margin-left: 0; } 75 | 76 | .one-third.column { width: 30.6666666667%; } 77 | .two-thirds.column { width: 65.3333333333%; } 78 | 79 | .one-half.column { width: 48%; } 80 | 81 | /* Offsets */ 82 | .offset-by-one.column, 83 | .offset-by-one.columns { margin-left: 8.66666666667%; } 84 | .offset-by-two.column, 85 | .offset-by-two.columns { margin-left: 17.3333333333%; } 86 | .offset-by-three.column, 87 | .offset-by-three.columns { margin-left: 26%; } 88 | .offset-by-four.column, 89 | .offset-by-four.columns { margin-left: 34.6666666667%; } 90 | .offset-by-five.column, 91 | .offset-by-five.columns { margin-left: 43.3333333333%; } 92 | .offset-by-six.column, 93 | .offset-by-six.columns { margin-left: 52%; } 94 | .offset-by-seven.column, 95 | .offset-by-seven.columns { margin-left: 60.6666666667%; } 96 | .offset-by-eight.column, 97 | .offset-by-eight.columns { margin-left: 69.3333333333%; } 98 | .offset-by-nine.column, 99 | .offset-by-nine.columns { margin-left: 78.0%; } 100 | .offset-by-ten.column, 101 | .offset-by-ten.columns { margin-left: 86.6666666667%; } 102 | .offset-by-eleven.column, 103 | .offset-by-eleven.columns { margin-left: 95.3333333333%; } 104 | 105 | .offset-by-one-third.column, 106 | .offset-by-one-third.columns { margin-left: 34.6666666667%; } 107 | .offset-by-two-thirds.column, 108 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } 109 | 110 | .offset-by-one-half.column, 111 | .offset-by-one-half.columns { margin-left: 52%; } 112 | 113 | } 114 | 115 | 116 | /* Base Styles 117 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 118 | /* NOTE 119 | html is set to 62.5% so that all the REM measurements throughout Skeleton 120 | are based on 10px sizing. So basically 1.5rem = 15px :) */ 121 | html { 122 | font-size: 62.5%; } 123 | body { 124 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ 125 | line-height: 1.6; 126 | font-weight: 400; 127 | font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; 128 | color: #222; } 129 | 130 | 131 | /* Typography 132 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 133 | h1, h2, h3, h4, h5, h6 { 134 | margin-top: 0; 135 | margin-bottom: 2rem; 136 | font-weight: 300; } 137 | h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} 138 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } 139 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } 140 | h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } 141 | h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } 142 | h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } 143 | 144 | /* Larger than phablet */ 145 | @media (min-width: 550px) { 146 | h1 { font-size: 5.0rem; } 147 | h2 { font-size: 4.2rem; } 148 | h3 { font-size: 3.6rem; } 149 | h4 { font-size: 3.0rem; } 150 | h5 { font-size: 2.4rem; } 151 | h6 { font-size: 1.5rem; } 152 | } 153 | 154 | p { 155 | margin-top: 0; } 156 | 157 | 158 | /* Links 159 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 160 | a { 161 | color: #1EAEDB; } 162 | a:hover { 163 | color: #0FA0CE; } 164 | 165 | 166 | /* Buttons 167 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 168 | .button, 169 | button, 170 | input[type="submit"], 171 | input[type="reset"], 172 | input[type="button"] { 173 | display: inline-block; 174 | height: 38px; 175 | padding: 0 30px; 176 | color: #555; 177 | text-align: center; 178 | font-size: 11px; 179 | font-weight: 600; 180 | line-height: 38px; 181 | letter-spacing: .1rem; 182 | text-transform: uppercase; 183 | text-decoration: none; 184 | white-space: nowrap; 185 | background-color: transparent; 186 | border-radius: 4px; 187 | border: 1px solid #bbb; 188 | cursor: pointer; 189 | box-sizing: border-box; } 190 | .button:hover, 191 | button:hover, 192 | input[type="submit"]:hover, 193 | input[type="reset"]:hover, 194 | input[type="button"]:hover, 195 | .button:focus, 196 | button:focus, 197 | input[type="submit"]:focus, 198 | input[type="reset"]:focus, 199 | input[type="button"]:focus { 200 | color: #333; 201 | border-color: #888; 202 | outline: 0; } 203 | .button.button-primary, 204 | button.button-primary, 205 | input[type="submit"].button-primary, 206 | input[type="reset"].button-primary, 207 | input[type="button"].button-primary { 208 | color: #FFF; 209 | background-color: #33C3F0; 210 | border-color: #33C3F0; } 211 | .button.button-primary:hover, 212 | button.button-primary:hover, 213 | input[type="submit"].button-primary:hover, 214 | input[type="reset"].button-primary:hover, 215 | input[type="button"].button-primary:hover, 216 | .button.button-primary:focus, 217 | button.button-primary:focus, 218 | input[type="submit"].button-primary:focus, 219 | input[type="reset"].button-primary:focus, 220 | input[type="button"].button-primary:focus { 221 | color: #FFF; 222 | background-color: #1EAEDB; 223 | border-color: #1EAEDB; } 224 | 225 | 226 | /* Forms 227 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 228 | input[type="email"], 229 | input[type="number"], 230 | input[type="search"], 231 | input[type="text"], 232 | input[type="tel"], 233 | input[type="url"], 234 | input[type="password"], 235 | textarea, 236 | select { 237 | height: 38px; 238 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ 239 | background-color: #fff; 240 | border: 1px solid #D1D1D1; 241 | border-radius: 4px; 242 | box-shadow: none; 243 | box-sizing: border-box; } 244 | /* Removes awkward default styles on some inputs for iOS */ 245 | input[type="email"], 246 | input[type="number"], 247 | input[type="search"], 248 | input[type="text"], 249 | input[type="tel"], 250 | input[type="url"], 251 | input[type="password"], 252 | textarea { 253 | -webkit-appearance: none; 254 | -moz-appearance: none; 255 | appearance: none; } 256 | textarea { 257 | min-height: 65px; 258 | padding-top: 6px; 259 | padding-bottom: 6px; } 260 | input[type="email"]:focus, 261 | input[type="number"]:focus, 262 | input[type="search"]:focus, 263 | input[type="text"]:focus, 264 | input[type="tel"]:focus, 265 | input[type="url"]:focus, 266 | input[type="password"]:focus, 267 | textarea:focus, 268 | select:focus { 269 | border: 1px solid #33C3F0; 270 | outline: 0; } 271 | label, 272 | legend { 273 | display: block; 274 | margin-bottom: .5rem; 275 | font-weight: 600; } 276 | fieldset { 277 | padding: 0; 278 | border-width: 0; } 279 | input[type="checkbox"], 280 | input[type="radio"] { 281 | display: inline; } 282 | label > .label-body { 283 | display: inline-block; 284 | margin-left: .5rem; 285 | font-weight: normal; } 286 | 287 | 288 | /* Lists 289 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 290 | ul { 291 | list-style: circle inside; } 292 | ol { 293 | list-style: decimal inside; } 294 | ol, ul { 295 | padding-left: 0; 296 | margin-top: 0; } 297 | ul ul, 298 | ul ol, 299 | ol ol, 300 | ol ul { 301 | margin: 1.5rem 0 1.5rem 3rem; 302 | font-size: 90%; } 303 | li { 304 | margin-bottom: 1rem; } 305 | 306 | 307 | /* Code 308 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 309 | code { 310 | padding: .2rem .5rem; 311 | margin: 0 .2rem; 312 | font-size: 90%; 313 | white-space: nowrap; 314 | background: #F1F1F1; 315 | border: 1px solid #E1E1E1; 316 | border-radius: 4px; } 317 | pre > code { 318 | display: block; 319 | padding: 1rem 1.5rem; 320 | white-space: pre; } 321 | 322 | 323 | /* Tables 324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 325 | th, 326 | td { 327 | padding: 12px 15px; 328 | text-align: left; 329 | border-bottom: 1px solid #E1E1E1; } 330 | th:first-child, 331 | td:first-child { 332 | padding-left: 0; } 333 | th:last-child, 334 | td:last-child { 335 | padding-right: 0; } 336 | 337 | 338 | /* Spacing 339 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 340 | button, 341 | .button { 342 | margin-bottom: 1rem; } 343 | input, 344 | textarea, 345 | select, 346 | fieldset { 347 | margin-bottom: 1.5rem; } 348 | pre, 349 | blockquote, 350 | dl, 351 | figure, 352 | table, 353 | p, 354 | ul, 355 | ol, 356 | form { 357 | margin-bottom: 2.5rem; } 358 | 359 | 360 | /* Utilities 361 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 362 | .u-full-width { 363 | width: 100%; 364 | box-sizing: border-box; } 365 | .u-max-full-width { 366 | max-width: 100%; 367 | box-sizing: border-box; } 368 | .u-pull-right { 369 | float: right; } 370 | .u-pull-left { 371 | float: left; } 372 | 373 | 374 | /* Misc 375 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 376 | hr { 377 | margin-top: 3rem; 378 | margin-bottom: 3.5rem; 379 | border-width: 0; 380 | border-top: 1px solid #E1E1E1; } 381 | 382 | 383 | /* Clearing 384 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 385 | 386 | /* Self Clearing Goodness */ 387 | .container:after, 388 | .row:after, 389 | .u-cf { 390 | content: ""; 391 | display: table; 392 | clear: both; } 393 | 394 | 395 | /* Media Queries 396 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 397 | /* 398 | Note: The best way to structure the use of media queries is to create the queries 399 | near the relevant code. For example, if you wanted to change the styles for buttons 400 | on small devices, paste the mobile query code up in the buttons section and style it 401 | there. 402 | */ 403 | 404 | 405 | /* Larger than mobile */ 406 | @media (min-width: 400px) {} 407 | 408 | /* Larger than phablet (also point when grid becomes active) */ 409 | @media (min-width: 550px) {} 410 | 411 | /* Larger than tablet */ 412 | @media (min-width: 750px) {} 413 | 414 | /* Larger than desktop */ 415 | @media (min-width: 1000px) {} 416 | 417 | /* Larger than Desktop HD */ 418 | @media (min-width: 1200px) {} 419 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | git branch gh-pages 2 | git checkout gh-pages 3 | webpack -p 4 | git add . 5 | git commit -m "prep for deployment" 6 | git push -f origin gh-pages 7 | git checkout master 8 | git branch -D gh-pages 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGL Shaders 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 | 40 | 41 | WebGL Shaders 42 | 43 | 44 | 63 |
64 |
65 |
66 |
67 | 68 |
69 |
70 |
71 |
72 | 73 | 74 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /javascript/WebGL/createFrameRenderer.js: -------------------------------------------------------------------------------- 1 | import { getCurrentShader, getShaderConfig, getShaderViewport } from 'reducers' 2 | import { SHADER_ENUM } from 'javascript/config' 3 | import setUniformValue from 'webgl-utilities/setUniformValue' 4 | import msaaCoordinates from 'webgl-utilities/msaaCoordinates' 5 | 6 | import assign from 'lodash/assign' 7 | import forEach from 'lodash/forEach' 8 | 9 | const { requestAnimationFrame } = window 10 | 11 | let time = 0 12 | export default ({ canvas, context, shader, program, store }) => function renderFrame () { 13 | /* eslint-disable no-multi-spaces, key-spacing */ 14 | const state = store.getState() 15 | 16 | const currentShader = getCurrentShader(state) 17 | if (shader === currentShader) { 18 | const { center, range, rotation } = getShaderViewport(state, currentShader) 19 | const config = getShaderConfig(state, currentShader) 20 | 21 | if (config.speed) { 22 | time += parseFloat(config.speed) 23 | } else { 24 | /* only here for spinning cube */ 25 | time += 0.016 26 | } 27 | 28 | const ASPECT_RATIO = window.innerWidth / window.innerHeight 29 | 30 | const uniformValues = assign({}, config, { 31 | shader: SHADER_ENUM[currentShader], 32 | center: [center.x, center.y], 33 | /* range.x is intentionally ignored in favor of setting */ 34 | /* the window dimensions to dictate aspect ratio */ 35 | range: [range.y * ASPECT_RATIO, range.y], 36 | rotation: rotation || 0, 37 | resolution: [ 38 | window.innerWidth, 39 | window.innerHeight 40 | ], 41 | julia_c: [ 42 | -0.795 + Math.sin(time / 2000) / 40, 43 | 0.2321 + Math.cos(time / 1330) / 40 44 | ], 45 | msaa_coordinates: msaaCoordinates[config.supersamples], 46 | /* large times won't convert to float 32 well :( */ 47 | time: time 48 | }) 49 | 50 | context.useProgram(program) 51 | forEach(uniformValues, (uniformValue, uniformName) => { 52 | setUniformValue(uniformName, uniformValue, context, program) 53 | }) 54 | 55 | context.drawArrays(context.TRIANGLE_STRIP, 0, 4) 56 | 57 | resize({ canvas, context }) 58 | 59 | requestAnimationFrame(renderFrame) 60 | } 61 | /* eslint-enable no-multi-spaces, key-spacing */ 62 | } 63 | 64 | function resize ({ canvas, context }) { 65 | /* http://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html */ 66 | const WIDTH = window.innerWidth 67 | const HEIGHT = window.innerHeight 68 | 69 | if (canvas.width !== WIDTH || canvas.height !== HEIGHT) { 70 | canvas.width = WIDTH 71 | canvas.height = HEIGHT 72 | 73 | context.viewport(0, 0, WIDTH, HEIGHT) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /javascript/WebGL/initializeWebGL.js: -------------------------------------------------------------------------------- 1 | import { getCurrentShader } from 'reducers' 2 | import createFrameRenderer from 'webgl/createFrameRenderer' 3 | import programForShader from 'webgl-utilities/programForShader' 4 | 5 | const { requestAnimationFrame } = window 6 | 7 | export default ({ store }) => { 8 | const canvas = document.getElementById('main') 9 | 10 | const startRunLoop = createRunLoop({ 11 | canvas, 12 | context: canvas.getContext('webgl'), 13 | shader: getCurrentShader(store.getState()), 14 | store 15 | }) 16 | 17 | store.subscribe(startRunLoop) 18 | 19 | startRunLoop() 20 | } 21 | 22 | const createRunLoop = ({ canvas, context, shader, store, firstRun = true }) => () => { 23 | const state = store.getState() 24 | const currentShader = getCurrentShader(state) 25 | 26 | if (shader !== currentShader || firstRun) { 27 | shader = currentShader 28 | firstRun = false 29 | 30 | const program = programForShader({ context, shader }) 31 | 32 | context.useProgram(program) 33 | requestAnimationFrame(createFrameRenderer({ canvas, context, shader, program, store })) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/compileShader.js: -------------------------------------------------------------------------------- 1 | export default function ({ shaderSource, shaderType, context }) { 2 | const shader = context.createShader(shaderType) 3 | 4 | context.shaderSource(shader, shaderSource) 5 | context.compileShader(shader) 6 | 7 | if (!context.getShaderParameter(shader, context.COMPILE_STATUS)) { 8 | throw new Error('Shader compile failed with: ' + context.getShaderInfoLog(shader)) 9 | } 10 | 11 | return shader 12 | } 13 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/configureProgram.js: -------------------------------------------------------------------------------- 1 | /* utility */ 2 | import compileShader from 'webgl-utilities/compileShader' 3 | import createProgram from 'webgl-utilities/createProgram' 4 | import prepareGeometry from 'webgl-utilities/prepareGeometry' 5 | 6 | const { Float32Array } = window 7 | 8 | export default function ({ context, fragmentShaderSource, vertexShaderSource }) { 9 | const vertexShader = compileShader({ 10 | shaderSource: vertexShaderSource, 11 | shaderType: context.VERTEX_SHADER, 12 | context 13 | }) 14 | 15 | const fragmentShader = compileShader({ 16 | shaderSource: fragmentShaderSource, 17 | shaderType: context.FRAGMENT_SHADER, 18 | context 19 | }) 20 | 21 | const program = createProgram({ 22 | vertexShader: vertexShader, 23 | fragmentShader: fragmentShader, 24 | context 25 | }) 26 | 27 | context.linkProgram(program) 28 | 29 | /* eslint-disable no-multi-spaces, indent */ 30 | const vertices = new Float32Array([ 31 | -1.0, 1.0, // top left 32 | -1.0, -1.0, // bottom left 33 | 1.0, 1.0, // top right 34 | 1.0, -1.0 // bottom right 35 | ]) 36 | /* eslint-enable no-multi-spaces, indent */ 37 | 38 | prepareGeometry({ context, program: program, vertices }) 39 | 40 | return program 41 | } 42 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/createProgram.js: -------------------------------------------------------------------------------- 1 | export default function ({ context, vertexShader, fragmentShader }) { 2 | const program = context.createProgram() 3 | 4 | context.attachShader(program, vertexShader) 5 | context.attachShader(program, fragmentShader) 6 | 7 | return program 8 | } 9 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/getAttribLocation.js: -------------------------------------------------------------------------------- 1 | export default function ({ program, name, context }) { 2 | const attributeLocation = context.getAttribLocation(program, name) 3 | 4 | if (attributeLocation === -1) { 5 | throw new Error('Can not find attribute ' + name + '.') 6 | } 7 | 8 | return attributeLocation 9 | } 10 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/getUniformLocation.js: -------------------------------------------------------------------------------- 1 | export default function ({ program, name, context }) { 2 | const uniformLocation = context.getUniformLocation(program, name) 3 | 4 | if (uniformLocation === -1) { 5 | throw new Error('Can not find uniform ' + name + '.') 6 | } 7 | 8 | return uniformLocation 9 | } 10 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/msaaCoordinates.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-multi-spaces */ 2 | export default { 3 | 1: [ 4 | 0.5, 0.5 5 | ], 6 | 4: [ 7 | 0.25, 0.25, 8 | 0.25, 0.75, 9 | 0.75, 0.25, 10 | 0.75, 0.75 11 | ], 12 | 16: [ 13 | 0.125 + 0.0 * 0.25 + -0.0375, 0.125 + 0.0 * 0.25 + -0.0375, 14 | 0.125 + 0.0 * 0.25 + -0.0125, 0.125 + 1.0 * 0.25 + -0.0375, 15 | 0.125 + 0.0 * 0.25 + 0.0125, 0.125 + 2.0 * 0.25 + -0.0375, 16 | 0.125 + 0.0 * 0.25 + 0.0375, 0.125 + 3.0 * 0.25 + -0.0375, 17 | 0.125 + 1.0 * 0.25 + -0.0375, 0.125 + 0.0 * 0.25 + -0.0125, 18 | 0.125 + 1.0 * 0.25 + -0.0125, 0.125 + 1.0 * 0.25 + -0.0125, 19 | 0.125 + 1.0 * 0.25 + 0.0125, 0.125 + 2.0 * 0.25 + -0.0125, 20 | 0.125 + 1.0 * 0.25 + 0.0375, 0.125 + 3.0 * 0.25 + -0.0125, 21 | 0.125 + 2.0 * 0.25 + -0.0375, 0.125 + 0.0 * 0.25 + 0.0125, 22 | 0.125 + 2.0 * 0.25 + -0.0125, 0.125 + 1.0 * 0.25 + 0.0125, 23 | 0.125 + 2.0 * 0.25 + 0.0125, 0.125 + 2.0 * 0.25 + 0.0125, 24 | 0.125 + 2.0 * 0.25 + 0.0375, 0.125 + 3.0 * 0.25 + 0.0125, 25 | 0.125 + 3.0 * 0.25 + -0.0375, 0.125 + 0.0 * 0.25 + 0.0375, 26 | 0.125 + 3.0 * 0.25 + -0.0125, 0.125 + 1.0 * 0.25 + 0.0375, 27 | 0.125 + 3.0 * 0.25 + 0.0125, 0.125 + 2.0 * 0.25 + 0.0375, 28 | 0.125 + 3.0 * 0.25 + 0.0375, 0.125 + 3.0 * 0.25 + 0.0375 29 | ] 30 | } 31 | /* eslint-enable no-multi-spaces */ 32 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/prepareGeometry.js: -------------------------------------------------------------------------------- 1 | import getAttribLocation from 'webgl-utilities/getAttribLocation' 2 | 3 | export default function ({ context, program, vertices }) { 4 | const vertexDataBuffer = context.createBuffer() 5 | context.bindBuffer(context.ARRAY_BUFFER, vertexDataBuffer) 6 | context.bufferData(context.ARRAY_BUFFER, vertices, context.STATIC_DRAW) 7 | 8 | /** 9 | * Attribute setup 10 | */ 11 | 12 | // To make the geometry information available in the shader as attributes, we 13 | // need to tell WebGL what the layout of our data in the vertex buffer is. 14 | const positionHandle = getAttribLocation({program, name: 'position', context}) 15 | context.enableVertexAttribArray(positionHandle) 16 | context.vertexAttribPointer( 17 | positionHandle, 18 | 2, // position is a vec2 19 | context.FLOAT, // each component is a float 20 | context.FALSE, // don't normalize values 21 | 2 * 4, // two 4 byte float components per vertex 22 | 0 // offset into each span of vertex data 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/programForShader.js: -------------------------------------------------------------------------------- 1 | import configureProgram from 'webgl-utilities/configureProgram' 2 | 3 | /* shaders */ 4 | import vertexShaderSource from 'shaders/vertexShader.glsl' 5 | import julia from 'shaders/julia.glsl' 6 | import collatz from 'shaders/collatz.glsl' 7 | import burningShip from 'shaders/burningShip.glsl' 8 | import spinningCube from 'shaders/spinningCube.glsl' 9 | 10 | const SHADER_SOURCES = { 11 | 'julia set': julia, 12 | 'mandelbrot set': julia, 13 | 'modified collatz': collatz, 14 | 'burning ship': burningShip, 15 | 'spinning cube': spinningCube 16 | } 17 | 18 | const programs = {} 19 | export default ({ context, shader }) => ( 20 | programs[shader] = programs[shader] || configureProgram({ 21 | context, 22 | fragmentShaderSource: SHADER_SOURCES[shader], 23 | vertexShaderSource: vertexShaderSource 24 | }) 25 | ) 26 | -------------------------------------------------------------------------------- /javascript/WebGL/utility/setUniformValue.js: -------------------------------------------------------------------------------- 1 | import getUniformLocation from 'webgl-utilities/getUniformLocation' 2 | 3 | export default (name, value, context, program) => { 4 | let dataPointer = getUniformLocation({ 5 | program, 6 | name: name.toUpperCase(), 7 | context 8 | }) 9 | 10 | if (value instanceof Array) { 11 | return setUniformVec2(dataPointer, value, context) 12 | } else { 13 | return setUniformFloat(dataPointer, value, context) 14 | } 15 | } 16 | 17 | function setUniformFloat (dataPointer, value, context) { 18 | context.uniform1fv(dataPointer, new Float32Array([value])) 19 | } 20 | 21 | function setUniformVec2 (dataPointer, values, context) { 22 | context.uniform2fv(dataPointer, new Float32Array(values)) 23 | } 24 | -------------------------------------------------------------------------------- /javascript/actions/index.js: -------------------------------------------------------------------------------- 1 | import { getCurrentShader, getShaderViewport, getPinchStart } from 'reducers' 2 | import registerEvent from 'utility/registerEvent' 3 | import throttle from 'lodash/throttle' 4 | 5 | const throttledRegisterEvent = throttle(registerEvent, 1000) 6 | 7 | export const resetShader = () => (dispatch, getState) => { 8 | const currentShader = getCurrentShader(getState()) 9 | const action = 'RESET_SHADER_CONFIG' 10 | 11 | registerEvent({ 12 | category: currentShader, 13 | action: action 14 | }) 15 | 16 | dispatch({ 17 | type: action, 18 | shader: currentShader 19 | }) 20 | } 21 | 22 | export const zoomToLocation = ({ location, shader }) => (dispatch, getState) => { 23 | const currentShader = getCurrentShader(getState()) 24 | const action = 'ZOOM_TO_LOCATION' 25 | 26 | registerEvent({ 27 | category: currentShader, 28 | action: action 29 | }) 30 | 31 | dispatch({ 32 | type: action, 33 | shader: currentShader, 34 | location 35 | }) 36 | } 37 | 38 | export const zoomOut = () => (dispatch, getState) => { 39 | const currentShader = getCurrentShader(getState()) 40 | const action = 'ZOOM_OUT' 41 | 42 | registerEvent({ 43 | category: currentShader, 44 | action: action 45 | }) 46 | 47 | dispatch({ 48 | type: action, 49 | shader: currentShader 50 | }) 51 | } 52 | 53 | export const setConfigValue = ({ name, value }) => (dispatch, getState) => { 54 | const currentShader = getCurrentShader(getState()) 55 | const action = 'SET_CONFIG_VALUE' 56 | 57 | throttledRegisterEvent({ 58 | category: currentShader, 59 | action: action, 60 | label: name, 61 | value 62 | }) 63 | 64 | dispatch({ 65 | type: action, 66 | shader: getCurrentShader(getState()), 67 | name, 68 | value 69 | }) 70 | } 71 | 72 | export const setCurrentShader = ({ shader }) => (dispatch, getState) => { 73 | const currentShader = getCurrentShader(getState()) 74 | const action = 'SET_SHADER' 75 | 76 | throttledRegisterEvent({ 77 | category: currentShader, 78 | action: action, 79 | label: shader 80 | }) 81 | 82 | dispatch({ 83 | type: action, 84 | value: shader 85 | }) 86 | } 87 | 88 | export const toggleMenu = () => (dispatch, getState) => { 89 | const currentShader = getCurrentShader(getState()) 90 | const action = 'TOGGLE_MENU' 91 | 92 | throttledRegisterEvent({ 93 | category: currentShader, 94 | action: action 95 | }) 96 | 97 | dispatch({ 98 | type: action 99 | }) 100 | } 101 | 102 | export const setPinchStart = ({ center }) => (dispatch, getState) => { 103 | const state = getState() 104 | 105 | const currentShader = getCurrentShader(state) 106 | const viewport = getShaderViewport(state, currentShader) 107 | const action = 'SET_PINCH_START' 108 | 109 | registerEvent({ 110 | category: currentShader, 111 | action: action 112 | }) 113 | 114 | dispatch({ 115 | type: action, 116 | value: { 117 | center, 118 | viewport: { ...viewport } 119 | } 120 | }) 121 | } 122 | 123 | export const pinchZoom = ({ center, rotation, scale }) => (dispatch, getState) => { 124 | const state = getState() 125 | 126 | const currentShader = getCurrentShader(state) 127 | const pinchStart = getPinchStart(state) 128 | const action = 'PINCH_ZOOM' 129 | 130 | if (Object.keys(pinchStart).length > 0) { 131 | /* pinch start must have registered before firing this action */ 132 | 133 | dispatch({ 134 | type: action, 135 | shader: currentShader, 136 | pinchStart, 137 | pinchCurrent: { 138 | center, 139 | rotation, 140 | scale 141 | } 142 | }) 143 | } 144 | } 145 | 146 | export const resetPinchStart = () => (dispatch) => { 147 | dispatch({ 148 | type: 'RESET_PINCH_START' 149 | }) 150 | } 151 | -------------------------------------------------------------------------------- /javascript/components/App.css: -------------------------------------------------------------------------------- 1 | * { 2 | transition: 0.2s; 3 | } 4 | 5 | html, body, ul, li, menu, label, button { 6 | padding: 0; 7 | border: 0; 8 | margin: 0; 9 | list-style: none; 10 | text-align: left; 11 | 12 | font-weight: lighter; 13 | font-size: inherit; 14 | font-family: HelveticaNeue, arial, sans-serif; 15 | } 16 | 17 | html, body, #main { 18 | width: 100%; 19 | height: 100%; 20 | 21 | overflow: hidden; 22 | } 23 | 24 | .left { 25 | float: left; 26 | } 27 | 28 | body { 29 | background-color: black; 30 | text-align: center; 31 | } 32 | 33 | section { 34 | width: 100%; 35 | height:100%; 36 | position: relative; 37 | top: 0; 38 | left: 0; 39 | } 40 | 41 | section > .container { 42 | width: 100%; 43 | height: 100%; 44 | } 45 | 46 | #mandelbrot { 47 | width: 100%; 48 | height: 100%; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /javascript/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Header from 'components/Header' 3 | import Menu from 'components/Menu' 4 | import CanvasContainer from 'components/CanvasContainer' 5 | 6 | import './App.css' 7 | 8 | export default class App extends Component { 9 | componentDidMount () { 10 | this.props.initializeWebGL() 11 | } 12 | 13 | render () { 14 | return ( 15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /javascript/components/CanvasContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import * as actions from 'actions' 4 | 5 | const { hypot, PI: pi, sign } = Math 6 | 7 | class CanvasContainer extends Component { 8 | componentDidMount () { 9 | /* React onClick's SyntheticEvent does not contain all required properties */ 10 | const canvas = document.getElementById('main') 11 | 12 | canvas.addEventListener('click', this.onClick.bind(this)) 13 | canvas.addEventListener('touchstart', this.onTouchStart.bind(this)) 14 | canvas.addEventListener('touchmove', this.onTouchMove.bind(this)) 15 | canvas.addEventListener('touchend', this.onTouchEnd.bind(this)) 16 | canvas.addEventListener('wheel', this.onTrackpadPinchZoom.bind(this)) 17 | } 18 | 19 | onTouchMove (event) { 20 | event.preventDefault() 21 | 22 | const touches = Array.from(event.touches) 23 | 24 | this.props.pinchZoom({ 25 | scale: event.scale || 1, 26 | rotation: (event.rotation || 0) * pi / 180, 27 | center: { 28 | x: touches.reduce((sum, touch) => (sum + touch.clientX), 0) / touches.length / window.innerWidth, 29 | y: touches.reduce((sum, touch) => (sum + touch.clientY), 0) / touches.length / window.innerHeight 30 | } 31 | }) 32 | } 33 | 34 | onTrackpadPinchZoom (event) { 35 | event.preventDefault() 36 | const delta = hypot(event.deltaX, event.deltaY) * sign(event.deltaX + event.deltaY); 37 | const center = { 38 | x: event.clientX / window.innerWidth, 39 | y: event.clientY / window.innerHeight 40 | } 41 | 42 | this.props.setPinchStart({ 43 | center 44 | }) 45 | 46 | this.props.pinchZoom({ 47 | scale: 1 - delta / 200, 48 | rotation: 0, 49 | center 50 | }) 51 | } 52 | 53 | onTouchStart (event) { 54 | event.preventDefault() 55 | 56 | const touches = Array.from(event.touches) 57 | 58 | this.props.setPinchStart({ 59 | center: { 60 | x: touches.reduce((sum, touch) => (sum + touch.clientX), 0) / touches.length / window.innerWidth, 61 | y: touches.reduce((sum, touch) => (sum + touch.clientY), 0) / touches.length / window.innerHeight 62 | } 63 | }) 64 | } 65 | 66 | onTouchEnd (event) { 67 | event.preventDefault() 68 | 69 | this.props.resetPinchStart() 70 | } 71 | 72 | onClick (event) { 73 | const canvas = document.getElementById('main') 74 | 75 | this.props.zoomToLocation({ 76 | location: { 77 | x: event.offsetX / canvas.width, 78 | y: event.offsetY / canvas.height 79 | } 80 | }) 81 | } 82 | 83 | render () { 84 | return ( 85 | 86 | ) 87 | } 88 | } 89 | 90 | export default connect( 91 | () => ({}), 92 | actions 93 | )(CanvasContainer) 94 | -------------------------------------------------------------------------------- /javascript/components/Header.css: -------------------------------------------------------------------------------- 1 | /* originally written for Random Boggle Solver: http://jonathan-potter.github.io/boggle */ 2 | 3 | /* HEADER */ 4 | header { 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | z-index: 2; 9 | 10 | text-align: left; 11 | color: black; 12 | text-transform: uppercase; 13 | border-bottom: 1px solid #aaa; 14 | background-color: white; 15 | width: 100%; 16 | height: 60px; 17 | line-height: 60px; 18 | overflow: hidden; 19 | } 20 | 21 | header * { 22 | text-decoration: none; 23 | color: inherit; 24 | } 25 | 26 | heading { 27 | display: inline; 28 | font-weight: normal; 29 | letter-spacing: 2px; 30 | } 31 | 32 | nav { 33 | float: right; 34 | } 35 | 36 | nav > ul { 37 | height: inherit; 38 | } 39 | 40 | nav > ul > li { 41 | display: block; 42 | font-size: 20px; 43 | float: left; 44 | height: inherit; 45 | } 46 | 47 | .header-icon { 48 | position: relative; 49 | top: -8px; 50 | } 51 | 52 | .header-block-button { 53 | display: block; 54 | float: left; 55 | 56 | height: 0; 57 | line-height: 0; 58 | 59 | border: 0; 60 | 61 | padding: 30px 5px; 62 | } 63 | 64 | .reset-button { 65 | line-height: 0; /* huge bandaid */; 66 | padding-left: 10px; 67 | padding-right: 10px; 68 | } 69 | 70 | @media (min-width: 550px) { 71 | .reset-button { 72 | padding-left: 30px; 73 | padding-right: 30px; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /javascript/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import * as actions from 'actions' 4 | import classnames from 'classnames' 5 | 6 | import './Header.css' 7 | 8 | export default () => { 9 | return ( 10 |
11 |
12 | 13 | 14 |
15 |
16 | ) 17 | } 18 | const MenuGroup = connect(() => ({}), actions)(({ toggleMenu }) => { 19 | return ( 20 | 21 | 26 | 27 | WebGL Shaders 28 | 29 | 30 | ) 31 | }) 32 | 33 | const NavGroup = () => ( 34 | 50 | ) 51 | 52 | const NavGroupLink = ({ href, iconClass, title }) => { 53 | const className = classnames('header-icon', iconClass) 54 | 55 | return ( 56 |
  • 57 | 58 | 59 | 60 |
  • 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /javascript/components/Menu.css: -------------------------------------------------------------------------------- 1 | .slide-out-menu { 2 | position: fixed; 3 | top: 60px; 4 | left: -100%; 5 | 6 | overflow: scroll; 7 | 8 | z-index: 0; 9 | 10 | width: 100%; 11 | height: 100%; 12 | 13 | background-color: white; 14 | } 15 | 16 | .slide-out-menu.menu-open { 17 | left: 0; 18 | } 19 | 20 | @media (min-width: 550px) { 21 | .slide-out-menu { 22 | width: 400px; 23 | } 24 | } 25 | 26 | .content { 27 | z-index: 1; 28 | 29 | height: 100%; 30 | 31 | background-color: black; 32 | 33 | margin-left: 0; 34 | } 35 | 36 | .hamburger-menu { 37 | position: relative; 38 | 39 | width: 60px; 40 | } 41 | 42 | .menu-item { 43 | height: 60px; 44 | } 45 | 46 | .menu-item label, .menu-item input { 47 | width: 100%; 48 | } 49 | 50 | .menu-item-label { 51 | width: 30%; 52 | 53 | padding-right: 10px; 54 | 55 | line-height: 60px; 56 | vertical-align: middle; 57 | 58 | box-sizing: border-box; 59 | } 60 | 61 | .menu-item-label label { 62 | text-align: right; 63 | } 64 | 65 | .menu-item-range { 66 | width: 70%; 67 | 68 | line-height: 60px; 69 | 70 | padding-right: 10px; 71 | 72 | box-sizing: border-box; 73 | } 74 | 75 | datalist, datalist * { 76 | display: none; 77 | } 78 | 79 | .menu-size-mod { 80 | width: 20%; 81 | 82 | margin: 0; 83 | border: 0; 84 | padding: 5px 0 !important; 85 | } 86 | 87 | .zoom-button-group button { 88 | width: 25%; 89 | padding: 0; 90 | text-align: center; 91 | margin-left: 6.25%; 92 | font-size: 12px; 93 | } 94 | 95 | -------------------------------------------------------------------------------- /javascript/components/Menu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { DEFAULT_MENU_CONFIG } from 'javascript/config' 4 | import MenuItemShaderSelect from 'components/MenuItemShaderSelect' 5 | import MenuItemRange from 'components/MenuItemRange' 6 | import MenuItemSelect from 'components/MenuItemSelect' 7 | import MenuItemShareGroup from 'components/MenuItemShareGroup' 8 | import * as actions from 'actions' 9 | import map from 'lodash/map' 10 | import cn from 'classnames' 11 | 12 | import './menu.css' 13 | 14 | const mapStateToProps = ({ currentShader, menuOpen }) => ({ currentShader, menuOpen }) 15 | 16 | export default connect(mapStateToProps, actions)( 17 | ({ currentShader, menuOpen, resetShader, zoomOut, zoomToLocation }) => { 18 | const { menuOrder: MENU_ORDER, controls: CONTROLS } = DEFAULT_MENU_CONFIG[currentShader] 19 | 20 | const controls = map(MENU_ORDER, (name) => { 21 | const {min, max, options, type} = CONTROLS[name] 22 | 23 | switch (type) { 24 | case 'range': 25 | return 26 | case 'select': 27 | return 28 | } 29 | }) 30 | 31 | return ( 32 | 33 |
      34 | 35 |
    • 36 | 41 | 46 | 51 |
    • 52 | 53 | { controls } 54 |
    55 |
    56 | ) 57 | } 58 | ) 59 | -------------------------------------------------------------------------------- /javascript/components/MenuItemRange.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import * as actions from 'actions' 4 | import { getCurrentShader, getShaderConfig } from 'reducers' 5 | 6 | const mapStateToProps = (state) => ({ 7 | config: getShaderConfig(state, getCurrentShader(state)) 8 | }) 9 | 10 | export default connect(mapStateToProps, actions)(({ config, name, min, max, setConfigValue }) => { 11 | return ( 12 |
  • 13 |
    14 | 15 |
    16 |
    17 | setConfigValue({ 25 | value: event.currentTarget.value, 26 | name 27 | })} /> 28 |
    29 |
  • 30 | ) 31 | }) 32 | -------------------------------------------------------------------------------- /javascript/components/MenuItemSelect.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import * as actions from 'actions' 4 | import { getCurrentShader, getShaderConfig } from 'reducers' 5 | import map from 'lodash/map' 6 | 7 | const mapStateToProps = (state) => ({ 8 | config: getShaderConfig(state, getCurrentShader(state)) 9 | }) 10 | 11 | export default connect(mapStateToProps, actions)(({ config, name, options, setConfigValue }) => { 12 | const optionElements = map(options, (name, key) => ( 13 | 16 | )) 17 | 18 | return ( 19 |
  • 20 |
    21 | 22 |
    23 |
    24 | 35 |
    36 |
  • 37 | ) 38 | }) 39 | -------------------------------------------------------------------------------- /javascript/components/MenuItemShaderSelect.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import * as actions from 'actions' 4 | import { getCurrentShader } from 'reducers' 5 | import map from 'lodash/map' 6 | 7 | const mapStateToProps = (state) => ({ 8 | currentShader: getCurrentShader(state) 9 | }) 10 | 11 | export default connect(mapStateToProps, actions)( 12 | ({ currentShader, name, options, setCurrentShader }) => { 13 | const optionElements = map(options, name => ( 14 | 17 | )) 18 | 19 | return ( 20 |
  • 21 |
    22 | 23 |
    24 |
    25 | 35 |
    36 |
  • 37 | ) 38 | } 39 | ) 40 | -------------------------------------------------------------------------------- /javascript/components/MenuItemShareGroup.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import 'social-share-kit' // exposes global 3 | 4 | export default class MenuItemShareGroup extends Component { 5 | componentDidMount () { 6 | /* eslint-disable no-undef */ 7 | SocialShareKit.init({ 8 | url: 'https://jonathan-potter.github.io/webgl-shaders/' 9 | }) 10 | /* eslint-enable no-undef */ 11 | } 12 | 13 | render () { 14 | return ( 15 |
  • 16 | 23 |
  • 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /javascript/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import App from 'components/App' 4 | 5 | export default ({ store, initializeWebGL }) => { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /javascript/config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable key-spacing */ 2 | export const DEFAULT_MENU_CONFIG = { 3 | 'julia set': { 4 | menuOrder: ['colorset', 'brightness', 'speed', 'exponent', 'supersamples'], 5 | controls: { 6 | brightness: { type: 'range', min: 1, max: 8 }, 7 | colorset: { type: 'select', options: ['linear', 'squared periodic'] }, 8 | exponent: { type: 'range', min: 0, max: 10 }, 9 | speed: { type: 'range', min: 0, max: 320 }, 10 | supersamples: { type: 'select', options: { 1: '1x', 4: '4x', 16: '16x' } } 11 | } 12 | }, 13 | 'mandelbrot set': { 14 | menuOrder: ['colorset', 'brightness', 'exponent', 'supersamples'], 15 | controls: { 16 | brightness: { type: 'range', min: 1, max: 8 }, 17 | exponent: { type: 'range', min: 0, max: 10 }, 18 | colorset: { type: 'select', options: ['linear', 'squared periodic'] }, 19 | supersamples: { type: 'select', options: { 1: '1x', 4: '4x', 16: '16x' } } 20 | } 21 | }, 22 | 'burning ship': { 23 | menuOrder: ['colorset', 'brightness', 'exponent', 'supersamples'], 24 | controls: { 25 | brightness: { type: 'range', min: 1, max: 8 }, 26 | exponent: { type: 'range', min: 0, max: 10 }, 27 | colorset: { type: 'select', options: ['linear', 'squared periodic'] }, 28 | supersamples: { type: 'select', options: { 1: '1x', 4: '4x', 16: '16x' } } 29 | } 30 | }, 31 | 'modified collatz': { 32 | menuOrder: ['depth', 'constant_1', 'angle1', 'angle2', 'supersamples'], 33 | controls: { 34 | depth: { type: 'range', min: 1, max: 800 }, 35 | constant_1: { type: 'range', min: 1, max: 10 }, 36 | angle1: { type: 'range', min: 0, max: Math.PI * 2 }, 37 | angle2: { type: 'range', min: 0, max: Math.PI * 2 }, 38 | supersamples: { type: 'select', options: { 1: '1x', 4: '4x', 16: '16x' } } 39 | } 40 | }, 41 | 'spinning cube': { 42 | menuOrder: ['colorset', 'shape', 'distance', 'FOV', 'check_size', 'reflectivity', 'wobble'], 43 | controls: { 44 | colorset: { type: 'select', options: ['grey', 'colors'] }, 45 | shape: { type: 'select', options: ['cube', 'sphere'] }, 46 | distance: { type: 'range', min: 2, max: 20 }, 47 | FOV: { type: 'range', min: 0.001, max: 180 }, 48 | check_size: { type: 'range', min: 1, max: 50 }, 49 | reflectivity: { type: 'range', min: 0, max: 1 }, 50 | wobble: { type: 'range', min: 0, max: 1 } 51 | } 52 | } 53 | } 54 | 55 | export const DEFAULT_STORE = { 56 | 'julia set': { 57 | config: { 58 | brightness: 4, 59 | colorset: 0, 60 | exponent: 2, 61 | speed: 16, 62 | supersamples: 1 63 | }, 64 | viewport: { 65 | center: { x: 0, y: 0 }, 66 | range: { x: 4, y: 4 }, 67 | rotation: 0 68 | } 69 | }, 70 | 'mandelbrot set': { 71 | config: { 72 | brightness: 4, 73 | colorset: 0, 74 | exponent: 2, 75 | supersamples: 1 76 | }, 77 | viewport: { 78 | center: { x: 0, y: 0 }, 79 | range: { x: 4, y: 4 }, 80 | rotation: 0 81 | } 82 | }, 83 | 'burning ship': { 84 | config: { 85 | brightness: 4, 86 | colorset: 0, 87 | exponent: 2, 88 | supersamples: 1 89 | }, 90 | viewport: { 91 | center: { x: 0, y: 0 }, 92 | range: { x: 4, y: 4 }, 93 | rotation: 0 94 | } 95 | }, 96 | 'modified collatz': { 97 | config: { 98 | depth: 200, 99 | constant_1: 4, 100 | angle1: Math.PI, 101 | angle2: Math.PI 102 | }, 103 | viewport: { 104 | center: { x: 0, y: 0 }, 105 | range: { x: 100, y: 100 }, 106 | rotation: 0 107 | } 108 | }, 109 | 'spinning cube': { 110 | config: { 111 | colorset: 0, 112 | shape: 0, 113 | distance: 10, 114 | FOV: 90, 115 | check_size: 32, 116 | reflectivity: 0.45, 117 | wobble: 0.2 118 | }, 119 | viewport: { 120 | center: { x: 0.25, y: 0.25 }, 121 | range: { x: 1, y: 1 }, 122 | rotation: 0 123 | } 124 | } 125 | } 126 | 127 | export const SHADER_ENUM = { 128 | 'julia set': 0, 129 | 'mandelbrot set': 1, 130 | 'burning ship': 2, 131 | 'modified collatz': 3, 132 | 'spinning cube': 4 133 | } 134 | -------------------------------------------------------------------------------- /javascript/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Root from 'components' 4 | 5 | import configureStore from 'utility/configureStore' 6 | import initializeWebGL from 'webgl/initializeWebGL' 7 | 8 | const store = configureStore() 9 | 10 | ReactDOM.render( 11 | , 12 | document.getElementById('root') 13 | ) 14 | -------------------------------------------------------------------------------- /javascript/reducers/createReducer.js: -------------------------------------------------------------------------------- 1 | export default function (name, defaultValue, parentType, parentName) { 2 | const UPPERCASE_NAME = name.toUpperCase() 3 | 4 | return function (state = defaultValue, action) { 5 | if (parentName && parentName !== action[parentType]) { return state } 6 | 7 | switch (action.type) { 8 | case `SET_${UPPERCASE_NAME}`: 9 | return action.value 10 | case `RESET_${UPPERCASE_NAME}`: 11 | case 'RESET': 12 | return defaultValue 13 | default: 14 | return state 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /javascript/reducers/createShader.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import Viewport from 'javascript/Viewport' 3 | 4 | const { atan2, cos, PI: pi, sin, sqrt } = Math 5 | 6 | export default function (SHADER, DEFAULT_PROPERTIES) { 7 | return combineReducers({ 8 | viewport (state = DEFAULT_PROPERTIES.viewport, action) { 9 | if (action.shader !== SHADER) { return state } 10 | 11 | const viewport = Viewport.create(state) 12 | 13 | switch (action.type) { 14 | case 'RESET_SHADER_CONFIG': 15 | return DEFAULT_PROPERTIES.viewport 16 | case 'SET_VIEWPORT': 17 | return action.value 18 | case 'PINCH_ZOOM': 19 | const start = action.pinchStart 20 | const current = action.pinchCurrent 21 | 22 | const rotation = ((start.viewport.rotation || 0) + current.rotation) % (2 * pi) 23 | 24 | const cartesianCenter = start.viewport.center 25 | const startViewport = Viewport.create(start.viewport) 26 | const cartesianTouchCenter = startViewport.cartesianLocation(start.center) 27 | 28 | let newCenter = rotatePointAroundCenter({ 29 | point: cartesianCenter, 30 | center: cartesianTouchCenter, 31 | rotation: current.rotation 32 | }) 33 | 34 | newCenter = scalePointAroundCenter({ 35 | point: newCenter, 36 | center: cartesianTouchCenter, 37 | scale: current.scale 38 | }) 39 | 40 | /* range.x is intentionally ignored in favor of setting */ 41 | /* the window dimensions to dictate aspect ratio */ 42 | const ASPECT_RATIO = window.innerWidth / window.innerHeight 43 | const range = { 44 | x: start.viewport.range.y * ASPECT_RATIO, 45 | y: start.viewport.range.y 46 | } 47 | 48 | /* translate */ 49 | let dx = (start.center.x - current.center.x) * range.x 50 | let dy = (start.center.y - current.center.y) * range.y 51 | 52 | /* rotate */ 53 | const magnitude = sqrt(dx * dx + dy * dy) 54 | const angle = atan2(dy, dx) 55 | 56 | dx = magnitude * cos(angle - rotation) 57 | dy = magnitude * sin(angle - rotation) 58 | 59 | /* scale */ 60 | dx /= current.scale 61 | dy /= current.scale 62 | 63 | return { 64 | center: { 65 | x: newCenter.x + dx, 66 | y: newCenter.y - dy 67 | }, 68 | range: { 69 | x: range.x / current.scale, 70 | y: range.y / current.scale 71 | }, 72 | rotation: rotation 73 | } 74 | case 'ZOOM_TO_LOCATION': 75 | const location = viewport.cartesianLocation(action.location) 76 | 77 | return viewport.zoomToLocation(location).serialize() 78 | case 'ZOOM_OUT': 79 | return viewport.zoomOut(location).serialize() 80 | case 'ROTATE_VIEWPORT': { 81 | /* placed here for desktop debugging */ 82 | return { 83 | ...state, 84 | rotation: state.rotation + pi / 4 85 | } 86 | } 87 | default: 88 | return state 89 | } 90 | }, 91 | 92 | config (state = DEFAULT_PROPERTIES.config, action) { 93 | if (action.shader !== SHADER) { return state } 94 | 95 | switch (action.type) { 96 | case 'RESET_SHADER_CONFIG': 97 | return DEFAULT_PROPERTIES.config 98 | case 'SET_CONFIG_VALUE': 99 | return { 100 | ...state, 101 | [action.name]: action.value 102 | } 103 | default: 104 | return state 105 | } 106 | } 107 | }) 108 | } 109 | 110 | export const getShaderConfig = (state, shader) => state.shaders[shader].config 111 | export const getShaderViewport = (state, shader) => state.shaders[shader].viewport 112 | 113 | function rotatePointAroundCenter ({ point, center, rotation }) { 114 | const dx = point.x - center.x 115 | const dy = point.y - center.y 116 | 117 | const magnitude = sqrt(dx * dx + dy * dy) 118 | const angle = atan2(dy, dx) 119 | 120 | center = { 121 | x: center.x + magnitude * cos(angle + rotation), 122 | y: center.y + magnitude * sin(angle + rotation) 123 | } 124 | 125 | return center 126 | } 127 | 128 | function scalePointAroundCenter ({ point, center, scale }) { 129 | const dx = point.x - center.x 130 | const dy = point.y - center.y 131 | 132 | return { 133 | x: center.x + dx / scale, 134 | y: center.y + dy / scale 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /javascript/reducers/currentShader.js: -------------------------------------------------------------------------------- 1 | import createReducer from 'reducers/createReducer' 2 | 3 | export default createReducer('shader', 'julia set') 4 | 5 | export const getCurrentShader = (state) => state.currentShader 6 | 7 | -------------------------------------------------------------------------------- /javascript/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { DEFAULT_STORE } from 'javascript/config' 3 | import createReducer from 'reducers/createReducer' 4 | import currentShader, * as fromCurrentShader from 'reducers/currentShader' 5 | import createShader, * as Shader from 'reducers/createShader' 6 | import menuOpen from 'reducers/menuOpen' 7 | import mapValues from 'lodash/mapValues' 8 | 9 | export default combineReducers({ 10 | currentShader, 11 | pinchStart: createReducer('pinch_start', {}), 12 | menuOpen, 13 | shaders: combineReducers(mapValues(DEFAULT_STORE, (shaderConfig, shaderName) => ( 14 | createShader(shaderName, shaderConfig) 15 | ))) 16 | }) 17 | 18 | export const getCurrentShader = state => fromCurrentShader.getCurrentShader(state) 19 | export const getShaderConfig = (state, shader) => Shader.getShaderConfig(state, shader) 20 | export const getShaderViewport = (state, shader) => Shader.getShaderViewport(state, shader) 21 | export const getPinchStart = state => state.pinchStart 22 | -------------------------------------------------------------------------------- /javascript/reducers/menuOpen.js: -------------------------------------------------------------------------------- 1 | export default (state = false, action) => { 2 | switch (action.type) { 3 | case 'TOGGLE_MENU': 4 | return !state 5 | case 'RESET': 6 | return false 7 | default: 8 | return state 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /javascript/utility/configureStore.js: -------------------------------------------------------------------------------- 1 | import { loadState, saveState } from 'utility/localStorage' 2 | import rootReducer from 'reducers' 3 | 4 | import { applyMiddleware, createStore } from 'redux' 5 | import throttle from 'lodash/throttle' 6 | import createLogger from 'redux-logger' 7 | import thunk from 'redux-thunk' 8 | 9 | const CURRENT_VERSION = '0.2.3' 10 | 11 | export default function configureStore () { 12 | const middlewares = [thunk, createLogger()] 13 | 14 | const initialState = loadState() 15 | 16 | let version 17 | if (initialState && initialState.version) { 18 | version = initialState.version 19 | delete initialState['version'] 20 | } 21 | 22 | let store 23 | if (version && version >= CURRENT_VERSION) { 24 | store = createStore(rootReducer, initialState, applyMiddleware(...middlewares)) 25 | } else { 26 | store = createStore(rootReducer, applyMiddleware(...middlewares)) 27 | } 28 | 29 | store.subscribe(throttle(() => { 30 | const state = store.getState() 31 | 32 | saveState({ 33 | currentShader: state.currentShader, 34 | shaders: state.shaders, 35 | viewports: state.viewports, 36 | version: CURRENT_VERSION 37 | }) 38 | }, 1000)) 39 | 40 | return store 41 | } 42 | -------------------------------------------------------------------------------- /javascript/utility/localStorage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'WEBGL_STUFF!' 2 | 3 | const { localStorage } = window 4 | 5 | export function loadState () { 6 | try { 7 | const serializedState = localStorage.getItem(STORAGE_KEY) 8 | 9 | if (serializedState === null) { 10 | return undefined 11 | } 12 | 13 | return JSON.parse(serializedState) 14 | } catch (error) { 15 | return undefined 16 | } 17 | } 18 | 19 | export function saveState (state) { 20 | try { 21 | const serializedState = JSON.stringify(state) 22 | 23 | localStorage.setItem(STORAGE_KEY, serializedState) 24 | } catch (error) { 25 | /* eslint-disable no-console */ 26 | console.log('saveState error:', error) 27 | /* eslint-enable no-console */ 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /javascript/utility/registerEvent.js: -------------------------------------------------------------------------------- 1 | export default function registerEvent ({ category, action, label, value }) { 2 | if (!category || !action) { 3 | throw new Error('GA requires category and action') 4 | } 5 | 6 | const eventProperties = { 7 | eventCategory: category, 8 | eventAction: action 9 | } 10 | 11 | if (label) { eventProperties.eventLabel = label } 12 | if (value) { eventProperties.eventValue = value } 13 | 14 | /* eslint-disable no-undef */ 15 | ga('send', 'event', eventProperties) 16 | /* eslint-enable no-undef */ 17 | } 18 | -------------------------------------------------------------------------------- /javascript/viewport.js: -------------------------------------------------------------------------------- 1 | const ZOOM_SIZE = 0.5 2 | 3 | const VIEWPORT_PROTOTYPE = { 4 | init ({ center, range, rotation }) { 5 | this.center = center 6 | this.range = range 7 | this.rotation = rotation 8 | }, 9 | 10 | aspectRatio () { 11 | return window.innerWidth / window.innerHeight 12 | }, 13 | 14 | serialize () { 15 | return { 16 | center: this.center, 17 | range: this.range, 18 | rotation: this.rotation 19 | } 20 | }, 21 | 22 | setCenter (location) { 23 | return Viewport.create({ 24 | center: location, 25 | range: this.range, 26 | rotation: this.rotation 27 | }) 28 | }, 29 | 30 | cartesianLocation (normalizedLocation) { 31 | if (!normalizedLocation) { return } 32 | 33 | let { x, y } = normalizedLocation 34 | 35 | x -= 0.5 36 | y -= 0.5 37 | x *= this.aspectRatio() 38 | y *= -1 39 | 40 | const magnitude = Math.sqrt(x * x + y * y) 41 | const angle = Math.atan2(y, x) 42 | 43 | x = magnitude * Math.cos(angle + this.rotation) 44 | y = magnitude * Math.sin(angle + this.rotation) 45 | 46 | return { 47 | x: this.center.x + this.range.y * x, 48 | y: this.center.y + this.range.y * y 49 | } 50 | }, 51 | 52 | zoomToLocation (location = this.center) { 53 | const newRange = { 54 | x: this.range.x * ZOOM_SIZE, 55 | y: this.range.y * ZOOM_SIZE 56 | } 57 | 58 | return Viewport.create({ 59 | center: location, 60 | range: newRange, 61 | rotation: this.rotation 62 | }) 63 | }, 64 | 65 | zoomOut (location = this.center) { 66 | const newRange = { 67 | x: this.range.x / ZOOM_SIZE, 68 | y: this.range.y / ZOOM_SIZE 69 | } 70 | 71 | return Viewport.create({ 72 | center: location, 73 | range: newRange, 74 | rotation: this.rotation 75 | }) 76 | } 77 | } 78 | 79 | const Viewport = { 80 | create ({ center, range, rotation }) { 81 | const viewport = Object.create(VIEWPORT_PROTOTYPE) 82 | 83 | viewport.init({ center, range, rotation }) 84 | 85 | return viewport 86 | } 87 | } 88 | 89 | export default Viewport 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl_stuffs", 3 | "version": "0.2.0", 4 | "description": "doing stuff with webgl", 5 | "repository": { 6 | "type": "git", 7 | "url": null 8 | }, 9 | "author": "Jonathan Potter", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": null 13 | }, 14 | "homepage": null, 15 | "dependencies": { 16 | "classnames": "^2.2.5", 17 | "lodash": "^4.13.1", 18 | "react": "^15.3.1", 19 | "react-dom": "^15.3.1", 20 | "react-redux": "^4.4.5", 21 | "redux": "^3.5.2", 22 | "redux-logger": "^2.6.1", 23 | "redux-thunk": "^2.1.0", 24 | "social-share-kit": "^1.0.9" 25 | }, 26 | "devDependencies": { 27 | "babel-core": "^6.10.4", 28 | "babel-eslint": "^6.1.2", 29 | "babel-loader": "^6.2.4", 30 | "babel-preset-es2015": "^6.9.0", 31 | "babel-preset-react": "^6.11.1", 32 | "babel-preset-stage-2": "^6.13.0", 33 | "css-loader": "^0.24.0", 34 | "eslint": "^3.4.0", 35 | "eslint-plugin-react": "^6.2.0", 36 | "extract-text-webpack-plugin": "^1.0.1", 37 | "raw-loader": "^0.5.1", 38 | "standard": "^8.0.0", 39 | "style-loader": "^0.13.1", 40 | "webpack": "^1.13.2", 41 | "webpack-dev-server": "^1.15.0" 42 | }, 43 | "scripts": { 44 | "deploy": "source ./deploy.sh", 45 | "lint": "standard", 46 | "start": "webpack-dev-server" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /shaders/burningShip.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 RESOLUTION; 4 | uniform vec2 CENTER; 5 | uniform vec2 RANGE; 6 | uniform vec2 JULIA_C; 7 | uniform vec2 MSAA_COORDINATES[16]; 8 | 9 | uniform float FRACTAL; 10 | uniform float ROTATION; 11 | uniform float BRIGHTNESS; 12 | uniform float COLORSET; 13 | uniform float EXPONENT; 14 | uniform float SUPERSAMPLES; 15 | 16 | const int MAX_ITERATIONS = 255; 17 | const float pi = 3.1415926; 18 | 19 | vec2 PIXEL_SIZE = RANGE / RESOLUTION; 20 | float ASPECT_RATIO = RESOLUTION.x / RESOLUTION.y; 21 | 22 | float amd_atan (float y, float x) { 23 | /* this was written to make AMD cards happy */ 24 | 25 | float theta; 26 | if (x == 0.0) { 27 | theta = pi / 2.0 * sign(y); 28 | } else { 29 | theta = atan(y, x); 30 | } 31 | 32 | return theta; 33 | } 34 | 35 | vec2 lazy_cpow(vec2 z, float exponent) { 36 | /* lazy because the exponent is always real */ 37 | 38 | float magnitude = pow(length(z), exponent); 39 | float argument = amd_atan(z.y, z.x) * exponent; 40 | 41 | return vec2( 42 | magnitude * cos(argument), 43 | magnitude * sin(argument) 44 | ); 45 | } 46 | 47 | vec2 fractal(vec2 c, vec2 z) { 48 | for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) { 49 | 50 | // z <- z^2 + c 51 | z = lazy_cpow(abs(z), EXPONENT) + c; 52 | 53 | float magnitude = length(z); 54 | if (magnitude > 2.0) { 55 | return vec2(float(iteration), magnitude); 56 | } 57 | } 58 | 59 | return vec2(0.0, 0.0); 60 | } 61 | 62 | vec4 colorize(vec2 fractalValue) { 63 | float depth = fractalValue.x / 4.0; 64 | float value = pow(fractalValue.y, 2.0) / 4.0; 65 | 66 | float mu = (depth - log(log(sqrt(value))) / log(2.0)); 67 | 68 | mu = sin(mu / 20.0) * sin(mu / 20.0); 69 | 70 | return vec4(mu, mu, mu, 1.0); 71 | } 72 | 73 | vec2 rotate2D(vec2 point, vec2 center, float rotation) { 74 | vec2 delta = point - center; 75 | 76 | float magnitude = length(delta); 77 | float angle = atan(delta.y, delta.x); 78 | 79 | return center + vec2( 80 | magnitude * cos(angle + rotation), 81 | magnitude * sin(angle + rotation) 82 | ); 83 | } 84 | 85 | vec2 fragCoordToXY(vec4 fragCoord) { 86 | vec2 relativePosition = fragCoord.xy / RESOLUTION; 87 | 88 | vec2 cartesianPosition = CENTER + (relativePosition - 0.5) * RANGE; 89 | 90 | return rotate2D(cartesianPosition, CENTER, ROTATION); 91 | } 92 | 93 | vec2 msaa(vec2 coordinate) { 94 | vec2 fractalValue = vec2(0.0, 0.0); 95 | 96 | for (int index = 0; index < 16; index++) { 97 | vec2 msaaCoordinate = coordinate + PIXEL_SIZE * MSAA_COORDINATES[index]; 98 | 99 | fractalValue += fractal(msaaCoordinate, msaaCoordinate); 100 | 101 | if (SUPERSAMPLES <= float(index + 1)) { 102 | return fractalValue / SUPERSAMPLES; 103 | } 104 | } 105 | 106 | return fractalValue / 16.0; 107 | } 108 | 109 | void main() { 110 | vec2 coordinate = fragCoordToXY(gl_FragCoord); 111 | 112 | vec2 fractalValue = msaa(coordinate); 113 | 114 | if (COLORSET == 0.0) { 115 | float color = BRIGHTNESS * fractalValue.x / float(MAX_ITERATIONS); 116 | gl_FragColor = vec4(color, color, color, 1.0); 117 | } else if (COLORSET == 1.0) { 118 | gl_FragColor = BRIGHTNESS * colorize(fractalValue); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /shaders/checkerboard.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | const float CHECK_SIZE = 50.0; 4 | 5 | void main(){ 6 | float x_thing = step(CHECK_SIZE / 2.0, mod(gl_FragCoord.x, CHECK_SIZE)); 7 | float y_thing = step(CHECK_SIZE / 2.0, mod(gl_FragCoord.y, CHECK_SIZE)); 8 | 9 | bool condition1 = x_thing > 0.5 && y_thing < 0.5; 10 | bool condition2 = x_thing < 0.5 && y_thing > 0.5; 11 | 12 | float color; 13 | if (condition1 || condition2) { 14 | color = 1.0; 15 | } else { 16 | color = 0.0; 17 | } 18 | 19 | gl_FragColor = vec4(color, color, color, 1.0); 20 | } 21 | -------------------------------------------------------------------------------- /shaders/collatz.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 RESOLUTION; 4 | uniform vec2 CENTER; 5 | uniform vec2 RANGE; 6 | 7 | uniform float ROTATION; 8 | uniform float DEPTH; 9 | uniform float CONSTANT_1; 10 | uniform float ANGLE1; 11 | uniform float ANGLE2; 12 | 13 | const int MAX_ITERATIONS = 255; 14 | const float pi = 3.1415926; 15 | 16 | float ASPECT_RATIO = RESOLUTION.x / RESOLUTION.y; 17 | 18 | vec2 cmult(vec2 a, vec2 b) { 19 | return vec2( 20 | a.x * b.x - a.y * b.y, 21 | a.x * b.y + a.y * b.x 22 | ); 23 | } 24 | 25 | vec2 cexp(vec2 vector) { 26 | float magnitude = exp(vector.x); 27 | float argument = vector.y; 28 | 29 | float real = magnitude * cos(argument); 30 | float imag = magnitude * sin(argument); 31 | 32 | return vec2(real, imag); 33 | } 34 | 35 | vec2 rotate(vec2 vector, float theta) { 36 | float magnitude = length(vector); 37 | float argument = atan(vector.y, vector.x); 38 | 39 | float real = magnitude * cos(argument + theta); 40 | float imag = magnitude * sin(argument + theta); 41 | 42 | return vec2(real, imag); 43 | } 44 | 45 | vec2 ccos(vec2 c) { 46 | vec2 ci = cmult(c, vec2(0, 1)); 47 | vec2 c1 = rotate(ci, ANGLE1); 48 | vec2 c2 = -rotate(ci, ANGLE2); 49 | 50 | return (cexp(c1) + cexp(c2)) / 2.0; 51 | } 52 | 53 | float collatz(vec2 position) { 54 | vec2 z = position; 55 | 56 | for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) { 57 | z = (vec2(1.0, 0.0) + CONSTANT_1 * z - cmult(vec2(1.0,0.0) + 2.0 * z, ccos(pi * z))) / 4.0; 58 | 59 | if (length(z) > DEPTH) { 60 | return float(iteration); 61 | } 62 | } 63 | 64 | return 0.0; 65 | } 66 | 67 | vec2 rotate2D(vec2 point, vec2 center, float rotation) { 68 | vec2 delta = point - center; 69 | 70 | float magnitude = length(delta); 71 | float angle = atan(delta.y, delta.x); 72 | 73 | return center + vec2( 74 | magnitude * cos(angle + rotation), 75 | magnitude * sin(angle + rotation) 76 | ); 77 | } 78 | 79 | vec2 fragCoordToXY(vec4 fragCoord) { 80 | vec2 relativePosition = fragCoord.xy / RESOLUTION; 81 | 82 | vec2 cartesianPosition = CENTER + (relativePosition - 0.5) * RANGE; 83 | 84 | return rotate2D(cartesianPosition, CENTER, ROTATION); 85 | } 86 | 87 | float quickColorize(float value, float rate) { 88 | return pow(sin(value / rate), 2.0); 89 | } 90 | 91 | void main() { 92 | vec2 coordinate = fragCoordToXY(gl_FragCoord); 93 | 94 | float color = collatz(coordinate); 95 | 96 | gl_FragColor = vec4(quickColorize(color, 1.0), quickColorize(color, 3.0), quickColorize(color, 5.0), 1.0); 97 | } 98 | -------------------------------------------------------------------------------- /shaders/julia.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 RESOLUTION; 4 | uniform vec2 CENTER; 5 | uniform vec2 RANGE; 6 | uniform vec2 JULIA_C; 7 | uniform vec2 MSAA_COORDINATES[16]; 8 | 9 | uniform float SHADER; 10 | uniform float ROTATION; 11 | uniform float BRIGHTNESS; 12 | uniform float COLORSET; 13 | uniform float EXPONENT; 14 | uniform float SUPERSAMPLES; 15 | 16 | const int MAX_ITERATIONS = 255; 17 | const float pi = 3.1415926; 18 | 19 | vec2 PIXEL_SIZE = RANGE / RESOLUTION; 20 | float ASPECT_RATIO = RESOLUTION.x / RESOLUTION.y; 21 | 22 | float amd_atan (float y, float x) { 23 | /* this was written to make AMD cards happy */ 24 | 25 | float theta; 26 | if (x == 0.0) { 27 | theta = pi / 2.0 * sign(y); 28 | } else { 29 | theta = atan(y, x); 30 | } 31 | 32 | return theta; 33 | } 34 | 35 | vec2 lazy_cpow(vec2 z, float exponent) { 36 | /* lazy because the exponent is always real */ 37 | 38 | float magnitude = pow(length(z), exponent); 39 | float argument = amd_atan(z.y, z.x) * exponent; 40 | 41 | return vec2( 42 | magnitude * cos(argument), 43 | magnitude * sin(argument) 44 | ); 45 | } 46 | 47 | vec2 fractal(vec2 c, vec2 z) { 48 | for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) { 49 | 50 | // z <- z^2 + c 51 | z = lazy_cpow(z, EXPONENT) + c; 52 | 53 | float magnitude = length(z); 54 | if (magnitude > 2.0) { 55 | return vec2(float(iteration), magnitude); 56 | } 57 | } 58 | 59 | return vec2(0.0, 0.0); 60 | } 61 | 62 | vec4 colorize(vec2 fractalValue) { 63 | float depth = fractalValue.x / 4.0; 64 | float value = pow(fractalValue.y, 2.0) / 4.0; 65 | 66 | float mu = (depth - log(log(sqrt(value))) / log(2.0)); 67 | 68 | mu = sin(mu / 20.0) * sin(mu / 20.0); 69 | 70 | return vec4(mu, mu, mu, 1.0); 71 | } 72 | 73 | vec2 mandelbrot(vec2 coordinate) { 74 | return fractal(coordinate, vec2(0.0, 0.0)); 75 | } 76 | 77 | vec2 julia(vec2 coordinate, vec2 offset) { 78 | return fractal(offset, coordinate); 79 | } 80 | 81 | vec2 rotate2D(vec2 point, vec2 center, float rotation) { 82 | vec2 delta = point - center; 83 | 84 | float magnitude = length(delta); 85 | float angle = atan(delta.y, delta.x); 86 | 87 | return center + vec2( 88 | magnitude * cos(angle + rotation), 89 | magnitude * sin(angle + rotation) 90 | ); 91 | } 92 | 93 | vec2 fragCoordToXY(vec4 fragCoord) { 94 | vec2 relativePosition = fragCoord.xy / RESOLUTION; 95 | 96 | vec2 cartesianPosition = CENTER + (relativePosition - 0.5) * RANGE; 97 | 98 | return rotate2D(cartesianPosition, CENTER, ROTATION); 99 | } 100 | 101 | vec2 msaa(vec2 coordinate) { 102 | vec2 fractalValue = vec2(0.0, 0.0); 103 | 104 | for (int index = 0; index < 16; index++) { 105 | vec2 msaaCoordinate = coordinate + PIXEL_SIZE * MSAA_COORDINATES[index]; 106 | 107 | if (SHADER == 0.0) { 108 | fractalValue += julia(msaaCoordinate, JULIA_C); 109 | } else { 110 | fractalValue += mandelbrot(msaaCoordinate); 111 | } 112 | 113 | if (SUPERSAMPLES <= float(index + 1)) { 114 | return fractalValue / SUPERSAMPLES; 115 | } 116 | } 117 | 118 | return fractalValue / 16.0; 119 | } 120 | 121 | void main() { 122 | vec2 coordinate = fragCoordToXY(gl_FragCoord); 123 | 124 | vec2 fractalValue = msaa(coordinate); 125 | 126 | if (COLORSET == 0.0) { 127 | float color = BRIGHTNESS * fractalValue.x / float(MAX_ITERATIONS); 128 | gl_FragColor = vec4(color, color, color, 1.0); 129 | } else if (COLORSET == 1.0) { 130 | gl_FragColor = BRIGHTNESS * colorize(fractalValue); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /shaders/spinningCube.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform vec2 RESOLUTION; 4 | 5 | uniform float TIME; 6 | uniform float COLORSET; 7 | uniform float FOV; 8 | uniform float WOBBLE; 9 | uniform float CHECK_SIZE; 10 | uniform float SHAPE; 11 | uniform float DISTANCE; 12 | uniform float REFLECTIVITY; 13 | 14 | const float pi = 3.1415926; 15 | float CHECK_SIZE_1 = pi / CHECK_SIZE; 16 | float ASPECT_RATIO = RESOLUTION.x / RESOLUTION.y; 17 | 18 | const vec3 LOOK_AT = vec3(0.0, 0.0, 0.0); 19 | const vec3 UP = vec3(0.0, 0.0, 1.0); 20 | 21 | struct Ray { 22 | vec3 origin; 23 | vec3 direction; 24 | }; 25 | 26 | mat3 rotateX(float angle) { 27 | float sin = sin(angle); 28 | float cos = cos(angle); 29 | 30 | return mat3( 31 | 1.0, 0.0, 0.0, 32 | 0.0, cos, -sin, 33 | 0.0, sin, cos 34 | ); 35 | } 36 | 37 | mat3 rotateY(float angle) { 38 | float sin = sin(angle); 39 | float cos = cos(angle); 40 | 41 | return mat3( 42 | cos, 0.0, -sin, 43 | 0.0, 1.0, 0.0, 44 | sin, 0.0, cos 45 | ); 46 | } 47 | 48 | float sineSquared(float number) { 49 | return pow(sin(number), 2.0); 50 | } 51 | 52 | float cosineSquared(float number) { 53 | return pow(cos(number), 2.0); 54 | } 55 | 56 | vec3 checkerboard(vec3 direction) { 57 | float theta = atan(direction.y, direction.x); 58 | float phi = acos(direction.z); 59 | 60 | float x_thing = step(CHECK_SIZE_1 / 2.0, mod(theta, CHECK_SIZE_1)); 61 | float y_thing = step(CHECK_SIZE_1 / 2.0, mod(phi, CHECK_SIZE_1)); 62 | 63 | bool condition1 = x_thing > 0.5 && y_thing < 0.5; 64 | bool condition2 = x_thing < 0.5 && y_thing > 0.5; 65 | 66 | vec3 color; 67 | if (condition1 || condition2) { 68 | if (COLORSET == 0.0) { 69 | color = vec3(1.0, 1.0, 1.0); 70 | } else if (COLORSET == 1.0) { 71 | color = vec3(cosineSquared(phi * theta * 16.0), cosineSquared(phi * theta * 13.0), cosineSquared(phi * theta * 7.0)); 72 | } 73 | } else { 74 | color = vec3(0.0, 0.0, 0.0); 75 | } 76 | 77 | return color; 78 | } 79 | 80 | vec3 rayPlaneCollisionPoint(Ray ray, vec3 p0, vec3 pn) { 81 | // p0: point on plane, pn: plane normal 82 | 83 | vec3 l0 = ray.origin; 84 | vec3 l = ray.direction; 85 | 86 | /* https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection */ 87 | float d = dot(p0 - l0, pn) / dot(l, pn); 88 | return d * l + l0; 89 | } 90 | 91 | vec4 raySphereCollisionPoint(Ray ray, vec3 c, float r) { 92 | // c: center of sphere, r: radius of sphere 93 | 94 | vec3 l0 = ray.origin; 95 | vec3 l = ray.direction; 96 | 97 | /* https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection */ 98 | float collision = pow(dot(l, l0 - c), 2.0) - pow(length(l0 - c), 2.0) + pow(r, 2.0); 99 | float d = -dot(l, l0 - c) - sqrt(collision); 100 | 101 | return vec4(d * l + l0, float(collision > 0.0)); 102 | } 103 | 104 | vec4 boxCollisionAndNormal(Ray ray) { 105 | vec2 xy = vec2(1.0, 0.0); 106 | 107 | // w is negative flag for later 108 | vec4 colisionPoints[6]; 109 | colisionPoints[0] = vec4(rayPlaneCollisionPoint(ray, xy.xyy, xy.xyy), 0.0); 110 | colisionPoints[1] = vec4(rayPlaneCollisionPoint(ray, -xy.xyy, -xy.xyy), 1.0); 111 | colisionPoints[2] = vec4(rayPlaneCollisionPoint(ray, xy.yxy, xy.yxy), 0.0); 112 | colisionPoints[3] = vec4(rayPlaneCollisionPoint(ray, -xy.yxy, -xy.yxy), 1.0); 113 | colisionPoints[4] = vec4(rayPlaneCollisionPoint(ray, xy.yyx, xy.yyx), 0.0); 114 | colisionPoints[5] = vec4(rayPlaneCollisionPoint(ray, -xy.yyx, -xy.yyx), 1.0); 115 | 116 | float minDistance = 100000.0; 117 | bool collides = false; 118 | 119 | vec4 closestPoint = vec4(0.0); 120 | for (int i = 0; i < 6; i++) { 121 | vec4 cp = colisionPoints[i]; 122 | 123 | if (abs(cp.x) < 1.01 && abs(cp.y) < 1.01 && abs(cp.z) < 1.01) { 124 | float dist = length(ray.origin - cp.xyz); 125 | 126 | collides = true; 127 | if (dist < minDistance) { 128 | minDistance = dist; 129 | closestPoint = cp; 130 | } 131 | } 132 | } 133 | 134 | float x = abs(closestPoint.x); 135 | float y = abs(closestPoint.y); 136 | float z = abs(closestPoint.z); 137 | bool negative = closestPoint.w == 1.0; 138 | 139 | vec3 normal = vec3(0.0); 140 | if (collides) { 141 | if (x > y && x > z) { 142 | normal = vec3(1.0, 0.0, 0.0); 143 | } else if (y > x && y > z) { 144 | normal = vec3(0.0, 1.0, 0.0); 145 | } else { 146 | normal = vec3(0.0, 0.0, 1.0); 147 | } 148 | 149 | if (negative) { 150 | normal = -normal; 151 | } 152 | } 153 | 154 | return vec4(normal, float(collides)); 155 | } 156 | 157 | vec4 sphereCollisionAndNormal(Ray ray) { 158 | return raySphereCollisionPoint(ray, vec3(0.0), 1.0); 159 | } 160 | 161 | vec4 collisionAndNormal(Ray ray) { 162 | if (SHAPE == 0.0) { 163 | return boxCollisionAndNormal(ray); 164 | } else { 165 | return sphereCollisionAndNormal(ray); 166 | } 167 | } 168 | 169 | vec3 render(Ray ray) { 170 | vec4 cn = collisionAndNormal(ray); 171 | bool collides = cn.w == 1.0; 172 | 173 | if (collides) { 174 | vec3 normalVector = cn.xyz; 175 | 176 | vec3 reflectionVector = reflect(ray.direction, normalVector); 177 | vec3 reflectionColor = checkerboard(reflectionVector); 178 | 179 | float rf = (1.0 - abs(dot(ray.direction, normalVector))); 180 | 181 | return reflectionColor * rf * REFLECTIVITY; 182 | } 183 | 184 | return checkerboard(ray.direction); 185 | } 186 | 187 | Ray createRay(vec3 origin, vec3 lookVector, vec3 up, vec2 uv, float fov) { 188 | up = normalize(up - lookVector * dot(lookVector, up)); 189 | vec3 right = cross(lookVector, up); 190 | 191 | uv = 2.0 * uv - vec2(1.0, 1.0); 192 | 193 | vec3 direction = normalize( 194 | lookVector + 195 | tan(fov / 2.0) * right * uv.x + 196 | tan(fov / 2.0) / ASPECT_RATIO * up * uv.y 197 | ); 198 | 199 | return Ray(origin, direction); 200 | } 201 | 202 | float vignette(vec2 uv) { 203 | float x = sin(uv.x * pi); 204 | float y = sin(uv.y * pi); 205 | 206 | return 0.05 + x * y * 0.95; 207 | } 208 | 209 | vec3 cameraPosition() { 210 | return vec3( 211 | DISTANCE * sin(TIME / 6.0), 212 | DISTANCE * cos(TIME / 6.0), 213 | DISTANCE * sin(TIME / 2.0) 214 | ); 215 | } 216 | 217 | void main() { 218 | vec2 uv = gl_FragCoord.xy / RESOLUTION; 219 | 220 | // camera 221 | vec3 origin = cameraPosition(); 222 | vec3 lookVector = normalize(-origin); // look toward 0, 0, 0 223 | vec3 up = rotateX(WOBBLE * sin(TIME / 0.50)) * UP; // wobble camera 224 | 225 | Ray ray = createRay(origin, lookVector, up, uv, FOV * pi / 180.0); 226 | 227 | vec3 col = render(ray) * vignette(uv); 228 | 229 | gl_FragColor = vec4(col,1.0); 230 | } -------------------------------------------------------------------------------- /shaders/vertexShader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec2 position; 2 | 3 | void main() { 4 | // position specifies only x and y. 5 | // We set z to be 0.0, and w to be 1.0 6 | gl_Position = vec4(position, 0.0, 1.0); 7 | } 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 3 | 4 | module.exports = { 5 | entry: './javascript/index', 6 | output: { 7 | path: __dirname, 8 | filename: 'build/bundle.js', 9 | sourceMapFilename: 'sourcemap' 10 | }, 11 | resolve: { 12 | // Allow to omit extensions when requiring these files 13 | extensions: ['', '.js', '.jsx'], 14 | alias: { 15 | /* eslint-disable key-spacing */ 16 | assets: path.resolve(__dirname, 'assets'), 17 | css: path.resolve(__dirname, 'css'), 18 | shaders: path.resolve(__dirname, 'shaders'), 19 | javascript: path.resolve(__dirname, 'javascript'), 20 | actions: path.resolve(__dirname, 'javascript', 'actions'), 21 | components: path.resolve(__dirname, 'javascript', 'components'), 22 | reducers: path.resolve(__dirname, 'javascript', 'reducers'), 23 | utility: path.resolve(__dirname, 'javascript', 'utility'), 24 | 25 | webgl: path.resolve(__dirname, 'javascript', 'WebGL'), 26 | 'webgl-utilities': path.resolve(__dirname, 'javascript', 'WebGL', 'utility') 27 | /* eslint-enable key-spacing */ 28 | } 29 | }, 30 | module: { 31 | loaders: [ 32 | { 33 | test: /\.js$|.jsx$/, 34 | exclude: /node_modules/, 35 | loader: 'babel-loader' 36 | }, 37 | { 38 | test: /\.glsl$/, 39 | exclude: /node_modules/, 40 | loader: 'raw-loader' 41 | }, 42 | { 43 | test: /\.css$/, 44 | loader: ExtractTextPlugin.extract('style-loader', 'css-loader') 45 | } 46 | ] 47 | }, 48 | devtool: '#inline-source-map', 49 | plugins: [ 50 | new ExtractTextPlugin('build/style.css', { 51 | allChunks: true 52 | }) 53 | ] 54 | } 55 | --------------------------------------------------------------------------------