├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .nvmrc ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── babel.config.js ├── build └── index.js ├── demo └── src │ ├── App.js │ └── index.js ├── docs ├── 52f4fcf052d40d121e7ad596cd758164.jpg ├── 813d08cc533261b5422bbac3b67deb19.jpg ├── index.bundle.js └── index.html ├── jest.config.js ├── jest ├── __mocks__ │ └── file-mock.js ├── preprocess.js └── setup.js ├── package-lock.json ├── package.json ├── public ├── assets │ └── images │ │ └── screenshot.png ├── favicon.ico └── index.html ├── scripts ├── deploy_demo.sh ├── getPackageVersion.js ├── publish.sh ├── tag_exists.sh └── tag_release.sh ├── server ├── jormungandr-config.yaml ├── package.json ├── restart.sh └── server.js ├── src ├── assets │ ├── globeBW.jpg │ └── globeBWe.jpg ├── components │ ├── Config.js │ ├── Main.css │ ├── Main.js │ └── classes │ │ ├── AmbientLightClass.js │ │ ├── BaseClass.js │ │ ├── CameraClass.js │ │ ├── ControlsClass.js │ │ ├── FBOClass.js │ │ ├── GlobeClass.js │ │ ├── GlobeSceneClass.js │ │ ├── IcosaSceneClass.js │ │ ├── IcosahedronClass.js │ │ ├── MarkersClass.js │ │ ├── MouseClass.js │ │ ├── ParticlesClass.js │ │ ├── PathsClass.js │ │ ├── PickerSceneClass.js │ │ ├── PickersClass.js │ │ ├── PointLightClass.js │ │ ├── QuadCameraClass.js │ │ ├── RendererClass.js │ │ └── TouchClass.js ├── data │ └── test.js ├── helpers │ ├── TextureHelper.js │ ├── math.js │ └── utility.js ├── index.js ├── libs │ ├── Detector.js │ └── post │ │ ├── CopyShader.js │ │ ├── EffectComposer.js │ │ └── Vignette.js ├── post │ ├── BlendLighten.js │ ├── BrightnessContrast.js │ ├── CopyShader.js │ ├── EffectComposer.js │ ├── FXAAShader.js │ ├── Film.js │ └── Vignette.js └── shaders │ ├── applyQuaternionToVector.glsl │ ├── blur.frag │ ├── curlNoise.glsl │ ├── edgeDetect.frag │ ├── empty.frag │ ├── markers.frag │ ├── markers.vert │ ├── mousePos.frag │ ├── particles.frag │ ├── particles.vert │ ├── passThrough.frag │ ├── passThrough.vert │ ├── paths.frag │ ├── paths.vert │ ├── pickers.frag │ ├── pickers.vert │ ├── position.frag │ └── rotationMatrix.glsl ├── webpack.prod.config.js ├── webpack_demo.config.js └── webpack_demo.prod.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | executors: 3 | main: 4 | docker: 5 | - image: 'circleci/node:10' 6 | 7 | jobs: 8 | install_dependencies: 9 | executor: main 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | keys: 14 | - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 15 | - node-v1-{{ .Branch }}- 16 | - node-v1- 17 | - run: npm ci 18 | - save_cache: 19 | paths: 20 | - ./node_modules 21 | key: node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 22 | build: 23 | executor: main 24 | steps: 25 | - checkout 26 | - restore_cache: 27 | keys: 28 | - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 29 | - run: 30 | name: Building src 31 | command: npm run build 32 | - persist_to_workspace: 33 | root: build 34 | paths: 35 | - "*" 36 | deploy: 37 | executor: main 38 | steps: 39 | - checkout 40 | - restore_cache: 41 | keys: 42 | - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 43 | - run: 44 | name: Publish to NPM 45 | command: ./scripts/publish.sh 46 | release: 47 | executor: main 48 | steps: 49 | - checkout 50 | - add_ssh_keys: 51 | fingerprints: 52 | - "3c:17:52:8b:7b:72:62:10:86:bf:ec:ab:fc:63:7b:0e" 53 | - run: 54 | name: Setup git config 55 | command: | 56 | git config user.email "$GIT_EMAIL" 57 | git config user.name "$GIT_USERNAME" 58 | - run: 59 | name: Tag release 60 | command: ./scripts/tag_release.sh 61 | test: 62 | executor: main 63 | steps: 64 | - checkout 65 | - restore_cache: 66 | keys: 67 | - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 68 | - run: 69 | name: Linting javascript 70 | command: npm run lint 71 | build_demo: 72 | executor: main 73 | steps: 74 | - checkout 75 | - restore_cache: 76 | keys: 77 | - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 78 | - attach_workspace: 79 | at: build 80 | - run: 81 | name: Building demo 82 | command: npm run build:demo 83 | - persist_to_workspace: 84 | root: public/build 85 | paths: 86 | - "*" 87 | deploy_demo: 88 | executor: main 89 | steps: 90 | - checkout 91 | - restore_cache: 92 | keys: 93 | - node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} 94 | - attach_workspace: 95 | at: public/build 96 | - add_ssh_keys: 97 | fingerprints: 98 | - "3c:17:52:8b:7b:72:62:10:86:bf:ec:ab:fc:63:7b:0e" 99 | - run: 100 | name: Setup git config 101 | command: | 102 | git config user.email "$GIT_EMAIL" 103 | git config user.name "$GIT_USERNAME" 104 | - run: 105 | name: Deploying to GitHub pages 106 | command: npm run deploy:demo 107 | 108 | workflows: 109 | version: 2 110 | main: 111 | jobs: 112 | - install_dependencies 113 | - test: 114 | requires: 115 | - install_dependencies 116 | - build: 117 | requires: 118 | - install_dependencies 119 | - build_demo: 120 | requires: 121 | - build 122 | - deploy: 123 | requires: 124 | - build 125 | filters: 126 | branches: 127 | only: master 128 | - release: 129 | requires: 130 | - deploy 131 | filters: 132 | branches: 133 | only: master 134 | - deploy_demo: 135 | requires: 136 | - build_demo 137 | filters: 138 | branches: 139 | only: master 140 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack*.js 2 | scripts/* 3 | jest/* 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', // Specifies the ESLint parser 3 | extends: [ 4 | 'standard', 5 | 'eslint:recommended', 6 | 'plugin:import/errors', 7 | 'plugin:import/warnings', 8 | 'plugin:react/recommended' // Uses the recommended rules from @eslint-plugin-react 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 2018, 12 | sourceType: 'module', // Allows for the use of imports 13 | ecmaFeatures: { 14 | jsx: true, // Allows for the parsing of JSX 15 | } 16 | }, 17 | rules: { 18 | indent: 'off', 19 | 'import/no-unresolved': 'off', 20 | 'import/named': 'error', 21 | 'import/namespace': 'error', 22 | 'import/default': 'error', 23 | 'import/export': 'error' 24 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 25 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 26 | }, 27 | settings: { 28 | react: { 29 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 30 | }, 31 | 'import/ignore': [ 'node_modules/*' ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | * **Please check if the PR fulfills these requirements** 2 | - [ ] Commit messages are concise and descriptive 3 | - [ ] Tests for the changes have been added (for bug fixes / features) 4 | - [ ] Docs have been added / updated (for bug fixes / features) 5 | - [ ] Demos have been added / updated (for bug fixes / features) 6 | - [ ] Removed any tech debt where applicable 7 | 8 | 9 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 10 | 11 | 12 | 13 | * **What is the current behavior?** (You can also link to an open issue here) 14 | 15 | 16 | 17 | * **What is the new behavior (if this is a feature change)?** 18 | 19 | 20 | 21 | * **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?) 22 | 23 | 24 | 25 | * **Other information**: -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # Mac files 58 | .DS_Store 59 | 60 | # Yarn 61 | yarn-error.log 62 | .pnp/ 63 | .pnp.js 64 | yarn.lock 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | .vscode 69 | 70 | # Build 71 | .cache/ 72 | 73 | # firebase auth 74 | server/auth -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Scott Darby 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Cardano Shelley Node Map 3 |

4 | 5 | ![Shelley Node Map](https://raw.githubusercontent.com/input-output-hk/shelley-node-map/master/public/assets/images/screenshot.png) 6 | 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const presets = [ 2 | '@babel/env', 3 | '@babel/preset-react' 4 | ] 5 | 6 | module.exports = { 7 | presets 8 | } 9 | -------------------------------------------------------------------------------- /demo/src/App.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react' 3 | import Main from '../../src/components/Main' 4 | 5 | class App extends Component { 6 | render () { 7 | return ( 8 |
// es-lint 9 | ) 10 | } 11 | } 12 | 13 | export default App 14 | -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(( 6 | 7 | ), document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /docs/52f4fcf052d40d121e7ad596cd758164.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/shelley-node-map/069e9e464f422530a998090abe57ab6f44c283e2/docs/52f4fcf052d40d121e7ad596cd758164.jpg -------------------------------------------------------------------------------- /docs/813d08cc533261b5422bbac3b67deb19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/shelley-node-map/069e9e464f422530a998090abe57ab6f44c283e2/docs/813d08cc533261b5422bbac3b67deb19.jpg -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Shelley Node Map 6 | 7 | 8 | 9 | 10 | 11 | 39 | 40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.(t|j)sx?$': `/jest/preprocess.js` 4 | }, 5 | moduleNameMapper: { 6 | '.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`, 7 | '.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `/jest/__mocks__/file-mock.js` 8 | }, 9 | testPathIgnorePatterns: [`node_modules`, `.cache`], 10 | transformIgnorePatterns: [`node_modules/(?!(gatsby)/)`], 11 | globals: { 12 | __PATH_PREFIX__: `` 13 | }, 14 | testURL: 'https://www.something.com/', 15 | setupFilesAfterEnv: ['/jest/setup.js'], 16 | snapshotSerializers: ['enzyme-to-json/serializer'] 17 | } 18 | -------------------------------------------------------------------------------- /jest/__mocks__/file-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub' 2 | -------------------------------------------------------------------------------- /jest/preprocess.js: -------------------------------------------------------------------------------- 1 | const babelConfig = require('../babel.config.js') 2 | module.exports = require('babel-jest').createTransformer(babelConfig) 3 | -------------------------------------------------------------------------------- /jest/setup.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme' 2 | import Adapter from 'enzyme-adapter-react-16' 3 | import 'jest-styled-components' 4 | 5 | Enzyme.configure({ adapter: new Adapter() }) 6 | 7 | global.windowEventListeners = {} 8 | global.triggerWindowEvent = (name, event) => global.windowEventListeners[name].forEach(listener => listener(event)) 9 | 10 | global.window = window 11 | global.window.innerWidth = 1920 12 | global.window.addEventListener = (name, listener) => { 13 | windowEventListeners[name] = windowEventListeners[name] || [] 14 | if (windowEventListeners[name].includes(listener)) return 15 | windowEventListeners[name].push(listener) 16 | } 17 | global.window.removeEventListener = (name, listener) => { 18 | if (!windowEventListeners[name]) return 19 | windowEventListeners[name] = windowEventListeners[name].splice(windowEventListeners[name].indexOf(listener), 1) 20 | if (windowEventListeners[name].length < 1) delete windowEventListeners[name] 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shelley-node-map", 3 | "version": "0.0.1", 4 | "description": "Shelley Node Map", 5 | "main": "build/index.js", 6 | "files": [ 7 | "/build", 8 | "/src" 9 | ], 10 | "repository": { 11 | "type": "git" 12 | }, 13 | "browserify": { 14 | "transform": [ 15 | "glslify" 16 | ] 17 | }, 18 | "scripts": { 19 | "build": "npm run build:js", 20 | "build:demo": "webpack --config webpack_demo.prod.config.js", 21 | "build:js": "webpack --config webpack.prod.config.js", 22 | "deploy:demo": "./scripts/deploy_demo.sh", 23 | "lint": "eslint src/**/*.js demo/src/**/*.js", 24 | "prepublish": "npm run build", 25 | "prepublishOnly": "npm run lint", 26 | "dev:demo": "webpack-dev-server --config webpack_demo.config.js --open", 27 | "watch:js": "npm run build:js -- --watch" 28 | }, 29 | "peerDependencies": { 30 | "react": "^16.8" 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.4.4", 34 | "@babel/core": "^7.4.5", 35 | "@babel/preset-env": "^7.4.5", 36 | "@babel/preset-react": "^7.0.0", 37 | "@tweenjs/tween.js": "^17.4.0", 38 | "@types/jest": "^24.0.15", 39 | "@types/react": "^16.8.22", 40 | "@types/styled-components": "^4.1.16", 41 | "babel-eslint": "^10.0.2", 42 | "babel-jest": "^24.8.0", 43 | "babel-loader": "^8.0.6", 44 | "babel-plugin-react-css-modules": "^5.2.6", 45 | "babel-plugin-styled-components": "^1.10.0", 46 | "coveralls": "^3.0.4", 47 | "css-loader": "^3.0.0", 48 | "dat.gui": "^0.7.6", 49 | "enzyme": "^3.9.0", 50 | "enzyme-adapter-react-16": "^1.12.1", 51 | "enzyme-to-json": "^3.3.5", 52 | "eslint": "^5.16.0", 53 | "eslint-config-standard": "^12.0.0", 54 | "eslint-plugin-import": "^2.18.0", 55 | "eslint-plugin-node": "^9.1.0", 56 | "eslint-plugin-promise": "^4.2.1", 57 | "eslint-plugin-react": "^7.14.2", 58 | "eslint-plugin-standard": "^4.0.0", 59 | "file-loader": "^4.2.0", 60 | "gh-pages": "^2.0.1", 61 | "html-loader": "^0.5.5", 62 | "html-webpack-plugin": "^3.2.0", 63 | "identity-obj-proxy": "^3.0.0", 64 | "jest": "^24.8.0", 65 | "jest-styled-components": "^6.3.3", 66 | "markdown-loader": "^5.0.0", 67 | "node-sass": "^4.12.0", 68 | "prop-types": "^15.7.2", 69 | "react": "^16.8.6", 70 | "react-dom": "^16.8.6", 71 | "sass-loader": "^7.1.0", 72 | "standard": "^12.0.1", 73 | "style-loader": "^0.23.1", 74 | "styled-components": "^4.3.2", 75 | "three": "^0.112.1", 76 | "three-orbitcontrols": "^2.102.2", 77 | "url-loader": "^2.2.0", 78 | "webpack": "^4.35.2", 79 | "webpack-cli": "^3.3.5", 80 | "webpack-dev-server": "^3.8.0" 81 | }, 82 | "author": "Scott Darby ", 83 | "license": "MIT", 84 | "dependencies": { 85 | "@input-output-hk/react-preloader": "^1.0.5", 86 | "d3-geo": "^1.11.9", 87 | "deep-assign": "^3.0.0", 88 | "firebase": "^7.5.0", 89 | "glsl-blend": "^1.0.3", 90 | "glsl-edge-detection": "^1.1.0", 91 | "glsl-noise": "^0.0.0", 92 | "glslify-loader": "^2.0.0", 93 | "mixin": "^0.2.0", 94 | "raw-loader": "^3.1.0", 95 | "tween.js": "^16.6.0" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /public/assets/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/shelley-node-map/069e9e464f422530a998090abe57ab6f44c283e2/public/assets/images/screenshot.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/shelley-node-map/069e9e464f422530a998090abe57ab6f44c283e2/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Shelley Node Map 6 | 7 | 8 | 9 | 10 | 11 | 39 | 40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /scripts/deploy_demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RED='\033[0;31m' 4 | NC='\033[0m' 5 | if [ "$CI" == "true" ]; then 6 | echo "On CI, continuing with deployment" 7 | ./node_modules/.bin/gh-pages -d public -b gh-pages -m "[skip ci] Updated demo" 8 | else 9 | echo -e "${RED}---------------------------------" 10 | echo "------------ ERROR ------------" 11 | echo "---------------------------------" 12 | echo "" 13 | echo "Can only run deploy script on CI" 14 | echo "" 15 | echo -e "---------------------------------${NC}" 16 | exit 1 17 | fi 18 | -------------------------------------------------------------------------------- /scripts/getPackageVersion.js: -------------------------------------------------------------------------------- 1 | const package = require('../package.json') 2 | console.log(package.version) 3 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$CI" != "true" ]; then 4 | echo "" 5 | echo "Can only use the publish script on CI" 6 | echo "" 7 | exit 1 8 | fi 9 | 10 | PACKAGE_VERSION=$(node ./scripts/getPackageVersion.js) 11 | TAG_EXISTS=$(./scripts/tag_exists.sh v$PACKAGE_VERSION) 12 | if [[ $TAG_EXISTS == "false" ]]; then 13 | echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > ./.npmrc 14 | npm publish --access public 15 | fi 16 | -------------------------------------------------------------------------------- /scripts/tag_exists.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TAG_FOUND=$(git tag | grep "$1") 4 | if [[ $TAG_FOUND == $1 ]]; then 5 | echo "true" 6 | else 7 | echo "false" 8 | fi 9 | -------------------------------------------------------------------------------- /scripts/tag_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$CI" != "true" ]; then 4 | echo "" 5 | echo "Can only use the tag release script on CI" 6 | echo "" 7 | exit 1 8 | fi 9 | 10 | PACKAGE_VERSION=$(node ./scripts/getPackageVersion.js) 11 | TAG_EXISTS=$(./scripts/tag_exists.sh v$PACKAGE_VERSION) 12 | if [[ $TAG_EXISTS == "false" ]]; then 13 | git tag v$PACKAGE_VERSION 14 | git push origin --tags 15 | fi 16 | -------------------------------------------------------------------------------- /server/jormungandr-config.yaml: -------------------------------------------------------------------------------- 1 | { 2 | "log": [ 3 | { 4 | "format": "json", 5 | "level": "info", 6 | "output": "stdout" 7 | } 8 | ], 9 | "p2p": { 10 | "topics_of_interest": { 11 | "blocks": "high", 12 | "messages": "low" 13 | }, 14 | "trusted_peers": [ 15 | { 16 | "address": "/ip4/13.230.137.72/tcp/3000", 17 | "id": "fe3332044877b2034c8632a08f08ee47f3fbea6c64165b3b" 18 | }, 19 | { 20 | "address": "/ip4/13.230.48.191/tcp/3000", 21 | "id": "c38aabb936944776ef15bbe4b5b02454c46a8a80d871f873" 22 | }, 23 | { 24 | "address": "/ip4/18.196.168.220/tcp/3000", 25 | "id": "7e2222179e4f3622b31037ede70949d232536fdc244ca3d9" 26 | }, 27 | { 28 | "address": "/ip4/3.124.132.123/tcp/3000", 29 | "id": "9085fa5caeb39eace748a7613438bd2a62c8c8ee00040b71" 30 | }, 31 | { 32 | "address": "/ip4/18.184.181.30/tcp/3000", 33 | "id": "f131b71d65c49116f3c23c8f1dd7ceaa98f5962979133404" 34 | }, 35 | { 36 | "address": "/ip4/184.169.162.15/tcp/3000", 37 | "id": "fdb88d08c7c759b5d30e854492cb96f8203c2d875f6f3e00" 38 | }, 39 | { 40 | "address": "/ip4/52.52.67.33/tcp/3000", 41 | "id": "3d1f8891bf53eb2946a18fb46cf99309649f0163b4f71b34" 42 | } 43 | ] 44 | }, 45 | "rest": { 46 | "listen": "127.0.0.1:3100" 47 | }, 48 | "storage": "./storage" 49 | } -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shelley-node-map-server", 3 | "version": "0.0.1", 4 | "description": "Parse Cardano Shelley node log file and update firebase", 5 | "author": "Scott Darby ", 6 | "license": "ISC", 7 | "scripts": { 8 | "server": "nodemon server.js", 9 | "sim": "nodemon sim.js" 10 | }, 11 | "dependencies": { 12 | "express": "^4.16.2", 13 | "firebase-admin": "8.8.0", 14 | "node-fetch": "^2.2.0", 15 | "node-watch": "^0.6.3", 16 | "nodemon": "^2.0.1", 17 | "read-last-lines": "^1.7.1", 18 | "ws": "^7.2.0" 19 | }, 20 | "devDependencies": { 21 | "concurrently": "^4.0.1", 22 | "standard": "^11.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # kill and restart jormungandr node (run on cron to ensure logs don't get too large and we get a new selection of peers) 3 | 4 | HOME_DIR="/home/scott/" 5 | SELF_NODE_DIR="/home/scott/self-node/" 6 | 7 | killall -q "jormungandr" 8 | rm ${SELF_NODE_DIR}blocks.log 9 | ${HOME_DIR}.cargo/bin/jormungandr --config ${SELF_NODE_DIR}config.yaml --genesis-block-hash 65a9b15f82619fffd5a7571fdbf973a18480e9acf1d2fddeb606ebb53ecca839 --secret ${SELF_NODE_DIR}pool-secret1.yaml | grep --line-buffered -i "block_events" | tee -a ${SELF_NODE_DIR}blocks.log -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const readLastLines = require('read-last-lines') 2 | const watch = require('node-watch') 3 | // const express = require('express') 4 | const fetch = require('node-fetch') 5 | 6 | // const app = express() 7 | // const port = process.env.PORT || 5000 8 | 9 | const config = { 10 | FBFilename: 'webgl-gource-1da99-firebase-adminsdk-2gak7-c2e824de64.json', 11 | collection: 'shelley-node-log' 12 | } 13 | 14 | // firebase 15 | const admin = require('firebase-admin') 16 | const serviceAccount = require('./auth/' + config.FBFilename) 17 | 18 | admin.initializeApp({ 19 | credential: admin.credential.cert(serviceAccount) 20 | }) 21 | 22 | const firebaseDB = admin.firestore() 23 | 24 | const filePath = '/home/scott/self-node/blocks.log' 25 | const geoLocationURL = 'https://api.ipdata.co/' 26 | const geoLocationConfig = require('./auth/ipdata.json') 27 | 28 | // app.get('/test', (req, res) => { 29 | watch(filePath, { recursive: true }, function (evt, name) { 30 | readLastLines.read(filePath, 1) 31 | .then(async (lines) => { 32 | try { 33 | const msg = JSON.parse(lines) 34 | if (typeof msg.peer_addr !== 'undefined') { 35 | let url = new URL('http://' + msg.peer_addr) 36 | url.port = '' 37 | 38 | const ipAddress = url.host 39 | 40 | // check if geodata is in db 41 | let docRef = firebaseDB.collection(config.collection).doc(ipAddress) 42 | let snapshot = await docRef.get().catch(err => { 43 | console.log(err) 44 | }) 45 | 46 | if (!snapshot.exists) { 47 | // get lat/long for ip 48 | fetch(geoLocationURL + url.host + '?api-key=' + geoLocationConfig.APIKey, { 49 | method: 'GET' 50 | }) 51 | .then(res => res.text()) 52 | .then((body) => { 53 | let geoData = JSON.parse(body) 54 | 55 | if (typeof geoData.latitude !== 'undefined') { 56 | // save geolocation in db 57 | let saveData = { 58 | ip: ipAddress, 59 | lat: geoData.latitude, 60 | long: geoData.longitude, 61 | region: geoData.region, 62 | country: geoData.country_name, 63 | city: geoData.city, 64 | timestamp: admin.firestore.Timestamp.fromDate(new Date()) 65 | } 66 | 67 | docRef.set(saveData, { merge: true }).then(() => { 68 | console.log('Geolocation data saved for IP: ' + ipAddress) 69 | }) 70 | } else { 71 | let saveData = { 72 | ip: ipAddress, 73 | timestamp: admin.firestore.Timestamp.fromDate(new Date()) 74 | } 75 | 76 | docRef.set(saveData, { merge: true }).then(() => { 77 | console.log('Could not get geolocation data for: ' + ipAddress) 78 | }) 79 | } 80 | }) 81 | .catch(error => console.error(error)) 82 | } else { 83 | // just store updated time 84 | let saveData = { 85 | timestamp: admin.firestore.Timestamp.fromDate(new Date()) 86 | } 87 | 88 | docRef.set(saveData, { merge: true }).then(() => { 89 | console.log('Updated node timestamp for IP: ' + ipAddress) 90 | }) 91 | .catch(error => { 92 | console.log(error) 93 | }) 94 | } 95 | } 96 | } catch (error) { 97 | console.log(error) 98 | } 99 | }) 100 | }) 101 | 102 | // res.send({ express: 'Check console' }) 103 | // }) 104 | 105 | // app.listen(port, () => console.log(`Listening on port ${port}`)) 106 | -------------------------------------------------------------------------------- /src/assets/globeBW.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/shelley-node-map/069e9e464f422530a998090abe57ab6f44c283e2/src/assets/globeBW.jpg -------------------------------------------------------------------------------- /src/assets/globeBWe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/input-output-hk/shelley-node-map/069e9e464f422530a998090abe57ab6f44c283e2/src/assets/globeBWe.jpg -------------------------------------------------------------------------------- /src/components/Config.js: -------------------------------------------------------------------------------- 1 | import { 2 | HalfFloatType, 3 | FloatType, 4 | Color 5 | } from 'three' 6 | 7 | import Detector from '../libs/Detector' 8 | 9 | class Config { 10 | constructor () { 11 | if (!Config.instance) { 12 | this.init() 13 | Config.instance = this 14 | } 15 | 16 | return Config.instance 17 | } 18 | 19 | init () { 20 | this.data = { 21 | curveMinAltitude: 0, 22 | curveMaxAltitude: 1.8, 23 | curveSegments: 32, 24 | particleScene: { 25 | width: 2100, 26 | height: 2100, 27 | downScaleFactor: Detector.isMobile ? 0.4 : 0.5 28 | }, 29 | scene: { 30 | lowBandwidth: false, 31 | fullScreen: true, 32 | width: window.innerWidth, 33 | height: window.innerHeight, 34 | bgColor: new Color(0x000000), 35 | canvasID: 'stage', // ID of webgl canvas element 36 | ambientLightColor: 0xffffff, 37 | ambientLightIntensity: 1.0, 38 | sphereRadius: 2, 39 | globeRadius: 2.1, 40 | particleLifeMax: 1000, 41 | showAnnotations: true 42 | }, 43 | post: { 44 | enabled: false, 45 | vignette: true, 46 | blendLighten: true, 47 | tranparentBackground: false, 48 | blendColor: new Color(0x000000) // 121326 49 | }, 50 | camera: { 51 | fov: 60, 52 | initPos: { x: 0, y: 0, z: 7 }, 53 | near: 0.1, 54 | far: 20, 55 | enableZoom: true // enable camera zoom on mousewheel/pinch gesture 56 | }, 57 | dev: { 58 | debugPicker: false 59 | }, 60 | fireBase: { 61 | collection: 'shelley-node-log-ITN', 62 | apiKey: 'AIzaSyCwfdzrjQ5GRqyz-napBM29T7Zel_6KIUY', 63 | authDomain: 'webgl-gource-1da99.firebaseapp.com', 64 | databaseURL: 'https://webgl-gource-1da99.firebaseio.com', 65 | projectId: 'webgl-gource-1da99', 66 | storageBucket: 'webgl-gource-1da99.appspot.com', 67 | messagingSenderId: '532264380396' 68 | }, 69 | detector: Detector, 70 | floatType: Detector.isIOS ? HalfFloatType : FloatType 71 | } 72 | 73 | this.data.particleScene.width *= this.data.particleScene.downScaleFactor 74 | this.data.particleScene.height *= this.data.particleScene.downScaleFactor 75 | } 76 | 77 | get (id) { 78 | return this.data[id] 79 | } 80 | } 81 | 82 | const instance = new Config() 83 | Object.freeze(instance) 84 | 85 | export default Config 86 | -------------------------------------------------------------------------------- /src/components/Main.css: -------------------------------------------------------------------------------- 1 | :local(.container) { 2 | font-family: 'Open Sans', sans-serif; 3 | height: 100vh; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | } 8 | 9 | :local(.tooltip) { 10 | text-transform: uppercase; 11 | font-size: 12px; 12 | text-shadow: 0px 0px 3px #000; 13 | position: fixed; 14 | top: 0; 15 | left: 0; 16 | color: #fff; 17 | opacity: 1.0; 18 | transition: opacity 0.3s; 19 | padding-left: 5px; 20 | margin-top: -5px; 21 | pointer-events: none; 22 | -webkit-touch-callout: none; 23 | -webkit-user-select: none; 24 | -ms-user-select: none; 25 | user-select: none; 26 | visibility: visible; 27 | } 28 | 29 | :local(.tooltipHide) { 30 | transition: opacity 0.3s, visibility 0.3s; 31 | visibility: hidden; 32 | composes: tooltip; 33 | opacity: 0.0; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------ 2 | 3rd Party 3 | ------------------------------------------ */ 4 | import React, { Component } from 'react' 5 | import { 6 | Clock, 7 | Vector2, 8 | Color 9 | } from 'three' 10 | 11 | import EventEmitter from 'eventemitter3' 12 | import mixin from 'mixin' 13 | import TWEEN from 'tween.js' 14 | import firebase from 'firebase/app' 15 | import 'firebase/firestore' 16 | import 'firebase/auth' 17 | import { getUrlParameter } from '../helpers/utility' 18 | 19 | /* ------------------------------------------ 20 | Config 21 | ------------------------------------------ */ 22 | import Config from './Config' 23 | 24 | /* ------------------------------------------ 25 | Classes 26 | ------------------------------------------ */ 27 | import RendererClass from './classes/RendererClass' 28 | import GlobeSceneClass from './classes/GlobeSceneClass' 29 | import IcosaSceneClass from './classes/IcosaSceneClass' 30 | import PickerSceneClass from './classes/PickerSceneClass' 31 | import FBOClass from './classes/FBOClass' 32 | import CameraClass from './classes/CameraClass' 33 | import ControlsClass from './classes/ControlsClass' 34 | import MouseClass from './classes/MouseClass' 35 | import TouchClass from './classes/TouchClass' 36 | import IcosahedronClass from './classes/IcosahedronClass' 37 | import AmbientLightClass from './classes/AmbientLightClass' 38 | import PointLightClass from './classes/PointLightClass' 39 | import ParticlesClass from './classes/ParticlesClass' 40 | import GlobeClass from './classes/GlobeClass' 41 | import MarkersClass from './classes/MarkersClass' 42 | import PickersClass from './classes/PickersClass' 43 | import PathsClass from './classes/PathsClass' 44 | 45 | /* ------------------------------------------ 46 | Styles 47 | ------------------------------------------ */ 48 | import styles from './Main.css' 49 | 50 | class Main extends mixin(EventEmitter, Component) { 51 | constructor (props) { 52 | super(props) 53 | 54 | this.config = new Config().data 55 | this.clock = new Clock() 56 | this.modifiedQueue = [] 57 | this.processingQueue = false 58 | this.data = [] 59 | 60 | this.state = { 61 | tooltipPos: new Vector2(), 62 | tooltipCountry: null, 63 | tooltipCity: null, 64 | tooltipHide: true, 65 | loading: true 66 | } 67 | } 68 | 69 | componentDidMount () { 70 | this.initStage() 71 | } 72 | 73 | initFireBase () { 74 | return new Promise((resolve, reject) => { 75 | try { 76 | firebase.initializeApp(this.config.fireBase) 77 | firebase.firestore() 78 | this.firebaseDB = firebase.firestore() 79 | } catch (error) { 80 | console.log(error) 81 | } 82 | this.docRef = this.firebaseDB.collection(this.config.fireBase.collection) 83 | 84 | let coords = [] 85 | 86 | let that = this 87 | 88 | firebase.auth().signInAnonymously() 89 | .then(() => { 90 | // setup live data listener 91 | this.docRef.onSnapshot(function (querySnapshot) { 92 | querySnapshot.docChanges().forEach(function (change) { 93 | if (change.type === 'added') { 94 | that.emit('added', change.doc.data()) 95 | } 96 | if (change.type === 'modified') { 97 | that.emit('modified', change.doc.data()) 98 | } 99 | if (change.type === 'removed') { 100 | that.emit('removed', change.doc.data()) 101 | } 102 | }) 103 | }) 104 | 105 | this.docRef.orderBy('timestamp', 'desc').get().then(function (querySnapshot) { 106 | querySnapshot.forEach(function (doc) { 107 | coords.push(doc.data()) 108 | }) 109 | resolve(coords) 110 | }) 111 | }) 112 | .catch(function (error) { 113 | console.log(error.code) 114 | console.log(error.message) 115 | }) 116 | }) 117 | } 118 | 119 | setConfigFromURLParams () { 120 | const blendColor = parseInt(getUrlParameter('blendColor')) 121 | if (!isNaN(blendColor)) { 122 | this.config.post.blendColor = new Color(blendColor) 123 | } 124 | 125 | const showAnnotations = parseInt(getUrlParameter('showAnnotations')) 126 | if (!isNaN(showAnnotations)) { 127 | this.config.scene.showAnnotations = !!showAnnotations 128 | } 129 | 130 | const transparentBackground = parseInt(getUrlParameter('transparentBackground')) 131 | if (!isNaN(transparentBackground)) { 132 | this.config.post.transparentBackground = !!transparentBackground 133 | } 134 | 135 | const lowBandwidth = parseInt(getUrlParameter('lowBandwidth')) 136 | if (!isNaN(lowBandwidth)) { 137 | this.config.scene.lowBandwidth = !!lowBandwidth 138 | } 139 | } 140 | 141 | initStage () { 142 | this.setConfigFromURLParams() 143 | 144 | GlobeSceneClass.getInstance().init() 145 | IcosaSceneClass.getInstance().init() 146 | PickerSceneClass.getInstance().init() 147 | CameraClass.getInstance().init() 148 | 149 | RendererClass.getInstance().init() 150 | 151 | const numPoints = this.config.particleScene.width * this.config.scene.height 152 | ParticlesClass.getInstance().init(numPoints) 153 | 154 | FBOClass.getInstance().init({ 155 | width: this.config.particleScene.width, 156 | height: this.config.scene.height, 157 | transparentBackground: this.config.post.transparentBackground 158 | }) 159 | ControlsClass.getInstance().init() 160 | MouseClass.getInstance().init() 161 | TouchClass.getInstance().init() 162 | IcosahedronClass.getInstance().init() 163 | GlobeClass.getInstance().init() 164 | AmbientLightClass.getInstance().init() 165 | PointLightClass.getInstance().init() 166 | 167 | this.initFireBase().then((data) => { 168 | this.data = data 169 | 170 | MarkersClass.getInstance().init(data) 171 | PickersClass.getInstance().init(data) 172 | PathsClass.getInstance().init(data) 173 | 174 | this.buildScene() 175 | this.addEvents() 176 | this.animate() 177 | 178 | this.setState({ 179 | loading: false 180 | }) 181 | 182 | this.highlightLatestNode() 183 | }) 184 | } 185 | 186 | highlightLatestNode () { 187 | this.emit('modified', this.data[0]) 188 | } 189 | 190 | buildScene () { 191 | IcosaSceneClass.getInstance().scene.add(IcosahedronClass.getInstance().mesh) 192 | IcosaSceneClass.getInstance().scene.add(IcosahedronClass.getInstance().mesh2) 193 | IcosaSceneClass.getInstance().scene.add(AmbientLightClass.getInstance().light) 194 | IcosaSceneClass.getInstance().scene.add(PointLightClass.getInstance().light) 195 | IcosaSceneClass.getInstance().scene.add(MarkersClass.getInstance().mesh) 196 | IcosaSceneClass.getInstance().scene.add(PathsClass.getInstance().mesh) 197 | 198 | PickerSceneClass.getInstance().scene.add(PickersClass.getInstance().mesh) 199 | 200 | GlobeSceneClass.getInstance().scene.add(GlobeClass.getInstance().mesh) 201 | } 202 | 203 | animate () { 204 | window.requestAnimationFrame(this.animate.bind(this)) 205 | this.renderFrame() 206 | } 207 | 208 | renderFrame () { 209 | const dt = this.clock.getDelta() 210 | 211 | TWEEN.update() 212 | 213 | this.setState({ 214 | tooltipPos: MarkersClass.getInstance().selectedNodePosScreen 215 | }) 216 | 217 | MouseClass.getInstance().renderFrame({ dt: dt }) 218 | TouchClass.getInstance().renderFrame({ dt: dt }) 219 | ControlsClass.getInstance().renderFrame({ dt: dt }) 220 | MarkersClass.getInstance().renderFrame({ dt: dt }) 221 | PickersClass.getInstance().renderFrame({ dt: dt }) 222 | PathsClass.getInstance().renderFrame({ dt: dt }) 223 | ParticlesClass.getInstance().renderFrame({ dt: dt }) 224 | FBOClass.getInstance().renderFrame({ dt: dt }) 225 | } 226 | 227 | addNewNode (data) { 228 | this.data.push(data) 229 | this.addToModifiedQueue(data) 230 | MarkersClass.getInstance().addNode(data) 231 | PickersClass.getInstance().addNode(data) 232 | PathsClass.getInstance().addNode(data) 233 | } 234 | 235 | addEvents () { 236 | window.addEventListener('resize', this.resize.bind(this), false) 237 | this.resize() 238 | 239 | RendererClass.getInstance().renderer.domElement.addEventListener('mousemove', (e) => { 240 | MouseClass.getInstance().onMouseMove(e) 241 | }, false) 242 | 243 | RendererClass.getInstance().renderer.domElement.addEventListener('touchmove', (e) => { 244 | TouchClass.getInstance().onTouchMove(e) 245 | }, false) 246 | 247 | RendererClass.getInstance().renderer.domElement.addEventListener('wheel', () => { 248 | MarkersClass.getInstance().stopUpdateCamPos() 249 | }) 250 | 251 | RendererClass.getInstance().renderer.domElement.addEventListener('mousedown', () => { 252 | MarkersClass.getInstance().stopUpdateCamPos() 253 | 254 | // const data = { 255 | // city: 'Ashburn', 256 | // country: 'United States', 257 | // ip: '54.242.227.95', 258 | // lat: 0.0, 259 | // long: 0.0, 260 | // region: 'Virginia', 261 | // timestamp: { seconds: 1575282866, nanoseconds: 504000000 } 262 | // } 263 | 264 | // this.addNewNode(data) 265 | }) 266 | 267 | PickersClass.getInstance().on('nodeMouseOver', (data) => { 268 | clearTimeout(this.hoverTimeout) 269 | this.showGeoData(data) 270 | }) 271 | 272 | PickersClass.getInstance().on('nodeMouseOut', () => { 273 | this.setState({ 274 | tooltipHide: true 275 | }) 276 | }) 277 | 278 | // on node data changes 279 | this.on('modified', (data) => { 280 | this.addToModifiedQueue(data) 281 | this.processModifiedQueue() 282 | }) 283 | 284 | this.on('added', (data) => { 285 | this.addNewNode(data) 286 | 287 | this.processModifiedQueue() 288 | 289 | // console.log('Added: ', data) 290 | }) 291 | 292 | this.on('removed', (data) => { 293 | // console.log('Removed: ', data) 294 | }) 295 | } 296 | 297 | addToModifiedQueue (data) { 298 | this.modifiedQueue.push(data) 299 | } 300 | 301 | showGeoData (data) { 302 | if (typeof data === 'undefined') { 303 | return 304 | } 305 | 306 | if (data.city === null && data.country === null) { 307 | data.country = 'Unknown' 308 | } 309 | 310 | this.hoverTimeout = setTimeout(() => { 311 | this.setState({ 312 | tooltipHide: true 313 | }) 314 | }, 4000) 315 | 316 | this.setState({ 317 | tooltipCountry: data.country, 318 | tooltipCity: data.city, 319 | tooltipHide: false, 320 | tooltipLastBlockTime: new Intl.DateTimeFormat('default', { 321 | year: '2-digit', 322 | month: 'numeric', 323 | day: 'numeric', 324 | hour: 'numeric', 325 | minute: 'numeric', 326 | second: 'numeric', 327 | hour12: false 328 | }).format(data.timestamp.toDate()) 329 | }) 330 | } 331 | 332 | processModifiedQueue () { 333 | if (PickersClass.getInstance().isHovering) { 334 | return 335 | } 336 | 337 | if (this.modifiedQueue.length === 0) { 338 | return 339 | } 340 | 341 | if (this.processingQueue) { 342 | return 343 | } 344 | 345 | this.processingQueue = true 346 | 347 | const data = this.modifiedQueue.shift() 348 | 349 | if (!PickersClass.getInstance().isHovering) { 350 | this.showGeoData(data) 351 | } 352 | 353 | MarkersClass.getInstance().highlight(data) 354 | .then(() => { 355 | // console.log('Updated: ', data) 356 | this.processingQueue = false 357 | this.processModifiedQueue() 358 | }) 359 | } 360 | 361 | resize () { 362 | this.width = window.innerWidth 363 | this.height = window.innerHeight 364 | 365 | CameraClass.getInstance().resize(this.width, this.height) 366 | RendererClass.getInstance().resize(this.width, this.height) 367 | FBOClass.getInstance().resize(this.width, this.height) 368 | ParticlesClass.getInstance().resize(this.width, this.height) 369 | MarkersClass.getInstance().resize(this.width, this.height) 370 | PickersClass.getInstance().resize(this.width, this.height) 371 | 372 | if (this.config.post.enabled) { 373 | this.composer.setSize(this.width, this.height) 374 | } 375 | } 376 | 377 | destroy () { 378 | RendererClass.getInstance().dispose() 379 | GlobeSceneClass.getInstance().destroy() 380 | ControlsClass.getInstance().destroy() 381 | FBOClass.getInstance().destroy() 382 | 383 | if (this.composer) { 384 | delete this.composer 385 | } 386 | 387 | window.cancelAnimationFrame(this.animate) 388 | this.running = false 389 | } 390 | 391 | render () { 392 | var tooltipStyle = { 393 | left: this.state.tooltipPos.x, 394 | top: this.state.tooltipPos.y 395 | } 396 | 397 | let className = styles.tooltip 398 | 399 | if (this.state.tooltipHide) { 400 | className = styles.tooltipHide 401 | } 402 | 403 | if (this.config.scene.showAnnotations === false) { 404 | className = styles.tooltipHide 405 | } 406 | 407 | return ( 408 |
409 | 410 |
411 |

{this.state.tooltipCity}

412 |

{this.state.tooltipCountry}

413 |

Last Block Time: {this.state.tooltipLastBlockTime}

414 |
415 |
416 | ) 417 | } 418 | } 419 | 420 | export default Main 421 | -------------------------------------------------------------------------------- /src/components/classes/AmbientLightClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | AmbientLight 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | 7 | class AmbientLightClass extends BaseClass { 8 | init () { 9 | this.light = new AmbientLight(this.config.scene.ambientLightColor, this.config.scene.ambientLightIntensity) 10 | 11 | super.init() 12 | } 13 | } 14 | 15 | export default AmbientLightClass 16 | -------------------------------------------------------------------------------- /src/components/classes/BaseClass.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3' 2 | 3 | import Config from '../Config' 4 | 5 | /** 6 | * Base Singleton Class 7 | */ 8 | class BaseClass extends EventEmitter { 9 | /** 10 | * Create new singleton class instance 11 | */ 12 | constructor () { 13 | super() 14 | this.config = new Config().data 15 | 16 | if (!this.constructor.instance) { 17 | this.constructor.instance = this 18 | } 19 | 20 | return this.constructor.instance 21 | } 22 | 23 | /** 24 | * Return singleton instance 25 | */ 26 | static getInstance () { 27 | return new this() 28 | } 29 | 30 | /** 31 | * Initialize class properties 32 | */ 33 | init () { 34 | const instance = this.constructor.getInstance() 35 | Object.freeze(instance) 36 | } 37 | 38 | /** 39 | * Run on window resize 40 | * 41 | * @param {int} width 42 | * @param {int} height 43 | */ 44 | resize (width, height) {} 45 | 46 | /** 47 | * Run on each renderered frame 48 | */ 49 | renderFrame () {} 50 | 51 | /** 52 | * Tear down 53 | */ 54 | destroy () {} 55 | 56 | /** 57 | * On mouse move 58 | */ 59 | onMouseMove () {} 60 | } 61 | 62 | export default BaseClass 63 | -------------------------------------------------------------------------------- /src/components/classes/CameraClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | PerspectiveCamera 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | 7 | class CameraClass extends BaseClass { 8 | init () { 9 | this.camera = new PerspectiveCamera( 10 | this.config.camera.fov, 11 | window.innerWidth / window.innerHeight, 12 | this.config.camera.near, 13 | this.config.camera.far 14 | ) 15 | this.camera.position.x = this.config.camera.initPos.x 16 | this.camera.position.y = this.config.camera.initPos.y 17 | this.camera.position.z = this.config.camera.initPos.z 18 | 19 | this.camera.updateMatrixWorld() 20 | } 21 | 22 | resize (width, height) { 23 | this.camera.aspect = width / height 24 | 25 | this.camera.updateProjectionMatrix() 26 | super.resize() 27 | } 28 | } 29 | 30 | export default CameraClass 31 | -------------------------------------------------------------------------------- /src/components/classes/ControlsClass.js: -------------------------------------------------------------------------------- 1 | import OrbitControls from 'three-orbitcontrols' 2 | 3 | import CameraClass from './CameraClass' 4 | import RendererClass from './RendererClass' 5 | import BaseClass from './BaseClass' 6 | 7 | class ControlsClass extends BaseClass { 8 | init () { 9 | this.controls = new OrbitControls(CameraClass.getInstance().camera, RendererClass.getInstance().renderer.domElement.parentNode) 10 | this.controls.minDistance = 2.4 11 | this.controls.maxDistance = 8 12 | this.controls.enablePan = false 13 | this.controls.enableZoom = this.config.camera.enableZoom 14 | this.controls.zoomSpeed = 0.7 15 | this.controls.rotateSpeed = 0.25 16 | this.controls.autoRotateSpeed = 0.3 17 | this.controls.autoRotate = false 18 | this.controls.enableDamping = false 19 | this.controls.dampingFactor = 0.01 20 | super.init() 21 | } 22 | 23 | destroy () { 24 | this.controls.dispose() 25 | super.destroy() 26 | } 27 | 28 | renderFrame () { 29 | this.controls.update() 30 | super.renderFrame() 31 | } 32 | } 33 | 34 | export default ControlsClass 35 | -------------------------------------------------------------------------------- /src/components/classes/FBOClass.js: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------------------------------ 3 | 3rd Party 4 | ------------------------------------------ */ 5 | import { 6 | WebGLRenderTarget, 7 | LinearFilter, 8 | RGBAFormat, 9 | PlaneBufferGeometry, 10 | Mesh, 11 | ShaderMaterial, 12 | Scene, 13 | PerspectiveCamera, 14 | Vector2, 15 | OrthographicCamera 16 | } from 'three' 17 | 18 | /* ------------------------------------------ 19 | Post 20 | ------------------------------------------ */ 21 | import { EffectComposer, ShaderPass, RenderPass, UnrealBloomPass } from '../../post/EffectComposer' 22 | import BrightnessContrastShader from '../../post/BrightnessContrast' 23 | import FXAAShader from '../../post/FXAAShader' 24 | import BlendShader from '../../post/BlendLighten' 25 | import VignetteShader from '../../post/Vignette' 26 | 27 | /* ------------------------------------------ 28 | Classes 29 | ------------------------------------------ */ 30 | import BaseClass from './BaseClass' 31 | import GlobeSceneClass from './GlobeSceneClass' 32 | import IcosaSceneClass from './IcosaSceneClass' 33 | import RendererClass from './RendererClass' 34 | import CameraClass from './CameraClass' 35 | import ParticlesClass from './ParticlesClass' 36 | 37 | /* ------------------------------------------ 38 | Shaders 39 | ------------------------------------------ */ 40 | import PassThroughVert from '../../shaders/passThrough.vert' 41 | import MousePosFrag from '../../shaders/mousePos.frag' 42 | import MouseClass from './MouseClass' 43 | 44 | import TouchClass from './TouchClass' 45 | 46 | class FBOClass extends BaseClass { 47 | init ({ 48 | width, 49 | height, 50 | transparentBackground 51 | } = {}) { 52 | this.frame = 0 53 | this.width = width 54 | this.height = height 55 | 56 | this.initRenderTargets() 57 | this.initMousePos() 58 | this.addMesh() 59 | 60 | this.composer = new EffectComposer(RendererClass.getInstance().renderer) 61 | 62 | this.renderPassMain = new RenderPass(IcosaSceneClass.getInstance().scene, CameraClass.getInstance().camera) 63 | this.composer.addPass(this.renderPassMain) 64 | 65 | this.renderPassParticles = new RenderPass(this.particleScene, this.particleCamera) 66 | this.renderPassParticles.clear = false 67 | this.renderPassParticles.alpha = true 68 | this.renderPassParticles.transparent = true 69 | this.composer.addPass(this.renderPassParticles) 70 | 71 | this.BrightnessContrastPass = new ShaderPass(BrightnessContrastShader) 72 | this.composer.addPass(this.BrightnessContrastPass) 73 | 74 | const alphaSum = transparentBackground ? 0.0 : 1.0 75 | 76 | this.bloomPass = new UnrealBloomPass(new Vector2(this.width, this.height), 0.8, 2, 0.1, alphaSum) // 1.0, 9, 0.5, 512); 77 | this.composer.addPass(this.bloomPass) 78 | 79 | if (this.config.post.vignette) { 80 | this.VignettePass = new ShaderPass(VignetteShader) 81 | this.composer.addPass(this.VignettePass) 82 | } 83 | 84 | if (this.config.post.blendLighten && !transparentBackground) { 85 | this.BlendPass = new ShaderPass(BlendShader) 86 | this.BlendPass.material.uniforms['blendColor'].value = this.config.post.blendColor 87 | this.composer.addPass(this.BlendPass) 88 | } 89 | 90 | this.FXAAPass = new ShaderPass(FXAAShader) 91 | this.FXAAPass.material.uniforms['resolution'].value.x = 1 / (window.innerWidth) 92 | this.FXAAPass.material.uniforms['resolution'].value.y = 1 / (window.innerHeight) 93 | this.FXAAPass.renderToScreen = true 94 | this.composer.addPass(this.FXAAPass) 95 | } 96 | 97 | initRenderTargets () { 98 | this.RTGlobe = new WebGLRenderTarget( 99 | this.width, 100 | this.height, 101 | { 102 | minFilter: LinearFilter, 103 | magFilter: LinearFilter, 104 | format: RGBAFormat, 105 | type: this.config.floatType, 106 | depthWrite: false, 107 | depthBuffer: false, 108 | stencilBuffer: false 109 | } 110 | ) 111 | 112 | this.RTParticles = this.RTGlobe.clone() 113 | this.rt3 = this.RTGlobe.clone() 114 | this.rt4 = this.RTGlobe.clone() 115 | } 116 | 117 | addMesh () { 118 | this.particleScene = new Scene() 119 | this.particleScene.add(ParticlesClass.getInstance().mesh) 120 | 121 | this.particleCamera = new PerspectiveCamera( 122 | this.config.camera.fov, 123 | 1.0, 124 | this.config.camera.near, 125 | this.config.camera.far 126 | ) 127 | this.particleCamera.position.x = 0 128 | this.particleCamera.position.y = 0 129 | this.particleCamera.position.z = 0.9 130 | this.particleCamera.updateMatrixWorld() 131 | } 132 | 133 | initMousePos () { 134 | this.mousePosRT1 = new WebGLRenderTarget( 135 | this.width, 136 | this.height, 137 | { 138 | minFilter: LinearFilter, 139 | magFilter: LinearFilter, 140 | format: RGBAFormat, 141 | type: this.config.floatType, 142 | depthWrite: false, 143 | depthBuffer: false, 144 | stencilBuffer: false 145 | }) 146 | 147 | this.mousePosRT2 = this.mousePosRT1.clone() 148 | 149 | this.mousePosScene = new Scene() 150 | this.mousePosMaterial = new ShaderMaterial({ 151 | uniforms: { 152 | uMousePosTexture: { 153 | type: 't', 154 | value: null 155 | }, 156 | uMousePos: { 157 | type: 'v2', 158 | value: new Vector2(0, 0) 159 | }, 160 | uPrevMousePos: { 161 | type: 'v2', 162 | value: new Vector2(0, 0) 163 | }, 164 | uDir: { 165 | type: 'v2', 166 | value: new Vector2(0, 0) 167 | }, 168 | uAspect: { 169 | type: 'f', 170 | value: 1.0 171 | } 172 | }, 173 | vertexShader: PassThroughVert, 174 | fragmentShader: MousePosFrag 175 | }) 176 | this.mousePosMesh = new Mesh(new PlaneBufferGeometry(2, 2), this.mousePosMaterial) 177 | this.mousePosMesh.frustumCulled = false 178 | this.mousePosScene.add(this.mousePosMesh) 179 | 180 | this.mousePosCamera = new OrthographicCamera() 181 | this.mousePosCamera.position.z = 1 182 | this.mousePosCamera.updateMatrixWorld() 183 | 184 | this.mousePosTexture = null 185 | } 186 | 187 | resize (width, height) { 188 | this.RTGlobe.setSize(width, height) 189 | this.RTParticles.setSize(width, height) 190 | this.composer.setSize(width, height) 191 | this.bloomPass.setSize(width, height) 192 | this.FXAAPass.material.uniforms[ 'resolution' ].value.x = 1 / (width) 193 | this.FXAAPass.material.uniforms[ 'resolution' ].value.y = 1 / (height) 194 | this.mousePosMaterial.uniforms.uAspect.value = CameraClass.getInstance().camera.aspect 195 | 196 | super.resize() 197 | } 198 | 199 | renderFrame (args) { 200 | this.frame++ 201 | 202 | // this.FilmPass.uniforms[ 'time' ].value += args.dt * 0.1 203 | 204 | // standard scene 205 | RendererClass.getInstance().renderer.setRenderTarget(this.RTGlobe) 206 | // RendererClass.getInstance().renderer.autoClear = false 207 | RendererClass.getInstance().renderer.render(GlobeSceneClass.getInstance().scene, CameraClass.getInstance().camera) 208 | 209 | // particles scene 210 | ParticlesClass.getInstance().mesh.material.uniforms.uTexture.value = this.RTGlobe.texture 211 | 212 | // debug picker 213 | // RendererClass.getInstance().renderer.setRenderTarget(null) 214 | // RendererClass.getInstance().renderer.render(PickerSceneClass.getInstance().scene, CameraClass.getInstance().camera) 215 | 216 | this.composer.render() 217 | 218 | // mouse position 219 | if (this.config.detector.isMobile) { 220 | this.mousePosMaterial.uniforms.uMousePos.value = TouchClass.getInstance().normalizedTouchPos 221 | this.mousePosMaterial.uniforms.uPrevMousePos.value = TouchClass.getInstance().prevNormalizedTouchPos 222 | } else { 223 | this.mousePosMaterial.uniforms.uMousePos.value = MouseClass.getInstance().normalizedMousePos 224 | this.mousePosMaterial.uniforms.uPrevMousePos.value = MouseClass.getInstance().prevNormalizedMousePos 225 | } 226 | 227 | let inputPositionRenderTarget = this.mousePosRT1 228 | this.outputPositionRenderTarget = this.mousePosRT2 229 | if (this.frame % 2 === 0) { 230 | inputPositionRenderTarget = this.mousePosRT2 231 | this.outputPositionRenderTarget = this.mousePosRT1 232 | } 233 | 234 | this.mousePosMaterial.uniforms.uMousePosTexture.value = inputPositionRenderTarget.texture 235 | 236 | RendererClass.getInstance().renderer.setRenderTarget(this.outputPositionRenderTarget) 237 | RendererClass.getInstance().renderer.render(this.mousePosScene, this.mousePosCamera) 238 | 239 | this.mousePosTexture = this.outputPositionRenderTarget.texture 240 | 241 | // RendererClass.getInstance().renderer.setRenderTarget(null) 242 | // RendererClass.getInstance().renderer.render(this.mousePosScene, this.mousePosCamera) 243 | 244 | super.renderFrame() 245 | } 246 | } 247 | 248 | export default FBOClass 249 | -------------------------------------------------------------------------------- /src/components/classes/GlobeClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Mesh, 3 | SphereBufferGeometry, 4 | MeshBasicMaterial, 5 | Color, 6 | TextureLoader 7 | } from 'three' 8 | 9 | import BaseClass from './BaseClass' 10 | 11 | // textures 12 | import map from '../../assets/globeBW.jpg' 13 | import mapE from '../../assets/globeBWe.jpg' 14 | 15 | class GlobeClass extends BaseClass { 16 | init () { 17 | if (this.config.scene.lowBandwidth) { 18 | this.map = new TextureLoader().load(mapE) 19 | } else { 20 | this.map = new TextureLoader().load(map) 21 | } 22 | 23 | this.geometry = new SphereBufferGeometry(this.config.scene.sphereRadius, 32, 32) 24 | this.material = new MeshBasicMaterial({ 25 | color: new Color(0xffffff), 26 | opacity: 1.0, 27 | map: this.map 28 | }) 29 | this.mesh = new Mesh(this.geometry, this.material) 30 | 31 | super.init() 32 | } 33 | } 34 | 35 | export default GlobeClass 36 | -------------------------------------------------------------------------------- /src/components/classes/GlobeSceneClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Scene 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | 7 | class GlobeClass extends BaseClass { 8 | init () { 9 | this.scene = new Scene() 10 | // this.scene.background = this.config.scene.bgColor 11 | 12 | super.init() 13 | } 14 | 15 | destroy () { 16 | this.scene.traverse(function (object) { 17 | if (object.geometry) { 18 | object.geometry.dispose() 19 | } 20 | if (object.material) { 21 | object.material.dispose() 22 | } 23 | }) 24 | 25 | super.destroy() 26 | } 27 | } 28 | 29 | export default GlobeClass 30 | -------------------------------------------------------------------------------- /src/components/classes/IcosaSceneClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Scene 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | 7 | class IcosaSceneClass extends BaseClass { 8 | init () { 9 | this.scene = new Scene() 10 | 11 | super.init() 12 | } 13 | 14 | destroy () { 15 | this.scene.traverse(function (object) { 16 | if (object.geometry) { 17 | object.geometry.dispose() 18 | } 19 | if (object.material) { 20 | object.material.dispose() 21 | } 22 | }) 23 | 24 | super.destroy() 25 | } 26 | } 27 | 28 | export default IcosaSceneClass 29 | -------------------------------------------------------------------------------- /src/components/classes/IcosahedronClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Mesh, 3 | IcosahedronBufferGeometry, 4 | MeshBasicMaterial, 5 | Color, 6 | AdditiveBlending, 7 | FrontSide, 8 | BackSide 9 | } from 'three' 10 | 11 | import BaseClass from './BaseClass' 12 | 13 | class IcosahedronClass extends BaseClass { 14 | init () { 15 | this.geometry = new IcosahedronBufferGeometry(this.config.scene.sphereRadius * 1.08, 1) 16 | this.material = new MeshBasicMaterial({ 17 | color: new Color(0x711111), 18 | wireframe: true, 19 | opacity: 0.1, 20 | transparent: true, 21 | blending: AdditiveBlending, 22 | depthWrite: false, 23 | side: FrontSide 24 | }) 25 | this.mesh = new Mesh(this.geometry, this.material) 26 | 27 | this.mesh.frustumCulled = false 28 | 29 | this.mesh2 = this.mesh.clone() 30 | this.mesh2.scale.set(0.98, 0.98, 0.98) 31 | this.mesh2.material = new MeshBasicMaterial({ 32 | color: new Color(0x000000), 33 | side: BackSide, 34 | opacity: 0 35 | }) 36 | 37 | super.init() 38 | } 39 | } 40 | 41 | export default IcosahedronClass 42 | -------------------------------------------------------------------------------- /src/components/classes/MarkersClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Mesh, 3 | BufferGeometry, 4 | CylinderGeometry, 5 | InstancedBufferGeometry, 6 | InstancedBufferAttribute, 7 | ShaderLib, 8 | Color, 9 | Object3D, 10 | Vector3, 11 | Vector2, 12 | MeshBasicMaterial 13 | } from 'three' 14 | 15 | import TWEEN from 'tween.js' 16 | 17 | import BaseClass from './BaseClass' 18 | 19 | import { latLongToCartesian } from '../../helpers/math' 20 | 21 | // import { coords } from '../../data/test' 22 | 23 | // shaders 24 | import fragmentShader from '../../shaders/markers.frag' 25 | import vertexShader from '../../shaders/markers.vert' 26 | import CameraClass from './CameraClass' 27 | import IcosaSceneClass from './IcosaSceneClass' 28 | 29 | class MarkersClass extends BaseClass { 30 | init (data) { 31 | let coords = data 32 | 33 | this.camTween = null 34 | this.updateCamPos = true 35 | this.selectionRef = new Object3D() 36 | this.selectionRefPos = new Vector3() 37 | this.selectedNodePosScreen = new Vector3() 38 | IcosaSceneClass.getInstance().scene.add(this.selectionRef) 39 | 40 | this.nodeCount = coords.length 41 | this.instanceTotal = 1000 // max number of instances 42 | 43 | this.material = new MarkersMaterial({ 44 | color: new Color(0x888888), 45 | flatShading: true, 46 | wireframe: true 47 | }) 48 | 49 | const tubeGeo = new CylinderGeometry(0.0, 0.005, 0.06, 3) 50 | const tubeBufferGeo = new BufferGeometry().fromGeometry(tubeGeo) 51 | this.geometry = new InstancedBufferGeometry().copy(tubeBufferGeo) 52 | this.geometry.rotateX(Math.PI / 2) 53 | 54 | this.ipMap = [] 55 | this.offsetsAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal * 3).fill(99999), 3) 56 | this.idAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal), 1) 57 | this.scalesAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal), 1) 58 | this.quaternionsAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal * 4), 4) 59 | this.isHoveredAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal), 1) 60 | this.isSelectedAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal), 1) 61 | 62 | this.geometry.setAttribute('offset', this.offsetsAttr) 63 | this.geometry.setAttribute('scale', this.scalesAttr) 64 | this.geometry.setAttribute('quaternion', this.quaternionsAttr) 65 | this.geometry.setAttribute('isHovered', this.isHoveredAttr) 66 | this.geometry.setAttribute('isSelected', this.isSelectedAttr) 67 | this.geometry.setAttribute('id', this.idAttr) 68 | 69 | for (let index = 0; index < this.nodeCount; index++) { 70 | if (typeof coords[index] !== 'undefined') { 71 | this.addNodeGeoData(coords[index], index) 72 | } 73 | } 74 | 75 | this.mesh = new Mesh(this.geometry, this.material) 76 | this.mesh.frustumCulled = false 77 | } 78 | 79 | addNodeGeoData (data, index) { 80 | const pos = latLongToCartesian(data.lat, data.long, this.config.scene.sphereRadius * 1.05) 81 | 82 | const x = this.offsetsAttr.array[index * 3 + 0] = pos.x 83 | const y = this.offsetsAttr.array[index * 3 + 1] = pos.y 84 | const z = this.offsetsAttr.array[index * 3 + 2] = pos.z 85 | 86 | const dummyObject = new Object3D() 87 | dummyObject.position.set(x, y, z) 88 | dummyObject.lookAt(0, 0, 0) 89 | 90 | this.quaternionsAttr.array[index * 4 + 0] = dummyObject.quaternion.x 91 | this.quaternionsAttr.array[index * 4 + 1] = dummyObject.quaternion.y 92 | this.quaternionsAttr.array[index * 4 + 2] = dummyObject.quaternion.z 93 | this.quaternionsAttr.array[index * 4 + 3] = dummyObject.quaternion.w 94 | 95 | this.scalesAttr.array[index] = 1.0 96 | 97 | this.idAttr.array[index] = index 98 | 99 | data.pos = pos 100 | this.ipMap[index] = data 101 | 102 | this.geometry.attributes.offset.needsUpdate = true 103 | this.geometry.attributes.scale.needsUpdate = true 104 | this.geometry.attributes.quaternion.needsUpdate = true 105 | this.geometry.attributes.isHovered.needsUpdate = true 106 | this.geometry.attributes.isSelected.needsUpdate = true 107 | this.geometry.attributes.id.needsUpdate = true 108 | } 109 | 110 | addNode (data) { 111 | this.nodeCount += 1 112 | this.addNodeGeoData(data, this.nodeCount) 113 | } 114 | 115 | getArcFromCoords (camPos, endPos, steps) { 116 | // get normal of both points 117 | let cb = new Vector3() 118 | let ab = new Vector3() 119 | let normal = new Vector3() 120 | cb.subVectors(new Vector3(), endPos) 121 | ab.subVectors(camPos, endPos) 122 | cb.cross(ab) 123 | normal.copy(cb).normalize() 124 | 125 | const angle = camPos.angleTo(endPos) // get the angle between vectors 126 | const angleDelta = angle / (steps) 127 | 128 | let points = [] 129 | for (var i = 0; i <= steps; i++) { 130 | points.push(camPos.clone().applyAxisAngle(normal, angleDelta * i)) 131 | } 132 | 133 | return points 134 | } 135 | 136 | stopUpdateCamPos () { 137 | this.updateCamPos = false 138 | } 139 | 140 | setNodePosInScreenSpace () { 141 | this.selectionRefPos.setFromMatrixPosition(this.selectionRef.matrixWorld) 142 | this.selectionRefPos.project(CameraClass.getInstance().camera) 143 | 144 | this.selectedNodePosScreen = new Vector2( 145 | (this.selectionRefPos.x + 1) * this.width * 0.5, 146 | (1 - this.selectionRefPos.y) * this.height * 0.5 147 | ) 148 | } 149 | 150 | setSelectionRef (nodePos) { 151 | this.selectionRef.position.set(nodePos.x, nodePos.y, nodePos.z) 152 | } 153 | 154 | highlight (data) { 155 | return new Promise((resolve, reject) => { 156 | let that = this 157 | 158 | this.ipMap.forEach((nodeData, index) => { 159 | if (nodeData.ip === data.ip) { 160 | if (that.camTween) { 161 | that.camTween.stop() 162 | } 163 | 164 | const nodePos = new Vector3( 165 | that.offsetsAttr.array[index * 3 + 0], 166 | that.offsetsAttr.array[index * 3 + 1], 167 | that.offsetsAttr.array[index * 3 + 2] 168 | ) 169 | 170 | this.setSelectionRef(nodePos) 171 | 172 | const steps = 25 173 | let points = this.getArcFromCoords(CameraClass.getInstance().camera.position, nodePos, steps) 174 | 175 | that.camTween = new TWEEN.Tween({ step: 0 }) 176 | .to({ step: steps }, 3000) 177 | .onUpdate(function () { 178 | if (!that.updateCamPos) { 179 | return 180 | } 181 | 182 | // lerp between points on arc 183 | const pos1 = points[Math.floor(this.step)] 184 | const pos2 = points[Math.floor(this.step + 1)] 185 | if (typeof pos2 !== 'undefined') { 186 | const pos = pos1.clone().lerp(pos2, this.step % 1) 187 | CameraClass.getInstance().camera.position.set(pos.x, pos.y, pos.z) 188 | } 189 | }) 190 | .onComplete(() => { 191 | that.updateCamPos = true 192 | const properties = { scale: 5.0 } 193 | new TWEEN.Tween(properties) 194 | .to({ scale: 1.0 }, 2000) 195 | .onUpdate(function () { 196 | that.scalesAttr.array[index] = properties.scale 197 | that.scalesAttr.needsUpdate = true 198 | }) 199 | .onComplete(() => { 200 | resolve() 201 | }) 202 | .easing(TWEEN.Easing.Quadratic.InOut) 203 | .start() 204 | }) 205 | .easing(TWEEN.Easing.Quadratic.InOut) 206 | .start() 207 | } 208 | }) 209 | }) 210 | } 211 | 212 | resize (width, height) { 213 | this.width = width 214 | this.height = height 215 | } 216 | 217 | renderFrame (args) { 218 | this.material.uniforms.uTime.value += args.dt 219 | this.material.uniforms.uDTime.value = args.dt 220 | 221 | this.setNodePosInScreenSpace() 222 | 223 | super.renderFrame() 224 | } 225 | } 226 | 227 | class MarkersMaterial extends MeshBasicMaterial { 228 | constructor (config) { 229 | super(config) 230 | this.type = 'ShaderMaterial' 231 | 232 | this.uniforms = ShaderLib.basic.uniforms 233 | 234 | this.uniforms.uTime = { 235 | type: 'f', 236 | value: 0.0 237 | } 238 | 239 | this.uniforms.uDTime = { 240 | type: 'f', 241 | value: 0.0 242 | } 243 | 244 | this.vertexShader = vertexShader 245 | this.fragmentShader = fragmentShader 246 | this.lights = true 247 | } 248 | } 249 | 250 | export default MarkersClass 251 | -------------------------------------------------------------------------------- /src/components/classes/MouseClass.js: -------------------------------------------------------------------------------- 1 | // 3rd party 2 | import { 3 | Vector2 4 | } from 'three' 5 | 6 | // classes 7 | import BaseClass from './BaseClass' 8 | import RendererClass from './RendererClass' 9 | 10 | class MouseClass extends BaseClass { 11 | init () { 12 | this.prevMousePos = new Vector2(0, 0) 13 | this.mouseDelta = new Vector2(0, 0) 14 | this.movement = new Vector2() 15 | this.mousePos = new Vector2() 16 | this.normalizedMousePos = new Vector2() 17 | this.prevNormalizedMousePos = new Vector2() 18 | } 19 | 20 | onMouseMove (e) { 21 | this.prevNormalizedMousePos.x = this.normalizedMousePos.x 22 | this.prevNormalizedMousePos.y = this.normalizedMousePos.y 23 | 24 | this.prevMousePos.x = this.mousePos.x 25 | this.prevMousePos.y = this.mousePos.y 26 | 27 | this.mousePos.x = e.clientX - RendererClass.getInstance().renderer.domElement.offsetLeft 28 | this.mousePos.y = e.clientY - RendererClass.getInstance().renderer.domElement.offsetTop 29 | 30 | this.mouseDelta = this.mousePos.clone().sub(this.prevMousePos) 31 | 32 | this.movement.x = e.movementX 33 | this.movement.y = e.movementY 34 | 35 | const x = e.clientX - RendererClass.getInstance().renderer.domElement.offsetLeft 36 | const y = e.clientY - RendererClass.getInstance().renderer.domElement.offsetTop 37 | 38 | this.normalizedMousePos.x = x / RendererClass.getInstance().renderer.domElement.width 39 | this.normalizedMousePos.y = 1 - y / RendererClass.getInstance().renderer.domElement.height 40 | 41 | super.onMouseMove() 42 | } 43 | 44 | renderFrame ({ dt } = {}) { 45 | super.renderFrame() 46 | } 47 | } 48 | 49 | export default MouseClass 50 | -------------------------------------------------------------------------------- /src/components/classes/ParticlesClass.js: -------------------------------------------------------------------------------- 1 | // 3rd party 2 | import { 3 | Vector2, 4 | InstancedBufferGeometry, 5 | InstancedBufferAttribute, 6 | Mesh, 7 | ShaderLib, 8 | ShaderMaterial, 9 | PlaneBufferGeometry, 10 | AdditiveBlending, 11 | Scene, 12 | WebGLRenderTarget, 13 | ClampToEdgeWrapping, 14 | NearestFilter, 15 | RGBAFormat, 16 | OrthographicCamera, 17 | Vector3 18 | } from 'three' 19 | 20 | import BaseClass from './BaseClass' 21 | import MouseClass from './MouseClass' 22 | import TouchClass from './TouchClass' 23 | 24 | // shaders 25 | import fragmentShader from '../../shaders/particles.frag' 26 | import vertexShader from '../../shaders/particles.vert' 27 | import PassThroughVert from '../../shaders/passThrough.vert' 28 | import PositionFrag from '../../shaders/position.frag' 29 | import PassThroughFrag from '../../shaders/passThrough.frag' 30 | 31 | // classes 32 | import TextureHelper from '../../helpers/TextureHelper' 33 | import RendererClass from './RendererClass' 34 | import FBOClass from './FBOClass' 35 | import CameraClass from './CameraClass' 36 | 37 | class ParticlesClass extends BaseClass { 38 | init (numPoints) { 39 | this.mouseMoved = 1 40 | this.frame = 0 41 | let step = 4 42 | 43 | this.particleCount = Math.round(numPoints / (step * step)) 44 | 45 | this.textureHelper = new TextureHelper({ 46 | config: this.config 47 | }) 48 | this.textureHelper.setTextureSize(this.particleCount) 49 | 50 | this.material = new ParticlesMaterial({ 51 | transparent: true, 52 | blending: AdditiveBlending 53 | }) 54 | this.material.uniforms.uTextureSize = { value: new Vector2(this.config.particleScene.width, this.config.particleScene.height) } 55 | this.material.uniforms.uAspect = { value: CameraClass.getInstance().camera.aspect } 56 | 57 | this.geometry = new InstancedBufferGeometry() 58 | const refGeo = new PlaneBufferGeometry(1, 1) 59 | this.geometry.setAttribute('position', refGeo.attributes.position) 60 | 61 | this.geometry.setAttribute('uv', refGeo.attributes.uv) 62 | this.geometry.setIndex(refGeo.index) 63 | 64 | this.offsets = new Float32Array(this.particleCount * 2) 65 | 66 | for (let i = 0; i < this.particleCount; i++) { 67 | this.offsets[i * 2 + 0] = (i % (this.config.particleScene.width / step)) * step 68 | this.offsets[i * 2 + 1] = Math.floor(i / (this.config.particleScene.width / step)) * step 69 | } 70 | 71 | this.geometry.setAttribute('offset', new InstancedBufferAttribute(this.offsets, 2, false)) 72 | 73 | const positionArray = new Float32Array(this.particleCount * 3) 74 | 75 | this.setTextureLocations( 76 | this.particleCount, 77 | positionArray 78 | ) 79 | 80 | const tPosition = new InstancedBufferAttribute(positionArray, 3) 81 | this.geometry.setAttribute('tPosition', tPosition) 82 | 83 | this.mesh = new Mesh(this.geometry, this.material) 84 | 85 | this.mesh.position.z = 0.1 86 | 87 | this.positionMaterial = new ShaderMaterial({ 88 | uniforms: { 89 | positionTexture: { 90 | type: 't', 91 | value: null 92 | }, 93 | defaultPositionTexture: { 94 | type: 't', 95 | value: null 96 | }, 97 | initialPositionTexture: { 98 | type: 't', 99 | value: null 100 | }, 101 | uNoiseMix: { 102 | type: 'f', 103 | value: 1.0 104 | }, 105 | uFrame: { 106 | type: 'f', 107 | value: 0.0 108 | }, 109 | uMousePos: { 110 | type: 'v2', 111 | value: new Vector2(0, 0) 112 | }, 113 | uPrevMousePos: { 114 | type: 'v2', 115 | value: new Vector2(0, 0) 116 | } 117 | }, 118 | vertexShader: PassThroughVert, 119 | fragmentShader: PositionFrag 120 | }) 121 | 122 | this.initCamera() 123 | this.initPassThrough() 124 | this.initRenderTargets() 125 | this.initPositions() 126 | } 127 | 128 | initPassThrough () { 129 | this.passThroughScene = new Scene() 130 | this.passThroughMaterial = new ShaderMaterial({ 131 | uniforms: { 132 | texture: { 133 | type: 't', 134 | value: null 135 | } 136 | }, 137 | vertexShader: PassThroughVert, 138 | fragmentShader: PassThroughFrag 139 | }) 140 | const mesh = new Mesh(new PlaneBufferGeometry(2, 2), this.passThroughMaterial) 141 | mesh.frustumCulled = false 142 | this.passThroughScene.add(mesh) 143 | } 144 | 145 | initRenderTargets () { 146 | this.positionRenderTarget1 = new WebGLRenderTarget(this.textureHelper.textureWidth, this.textureHelper.textureHeight, { 147 | wrapS: ClampToEdgeWrapping, 148 | wrapT: ClampToEdgeWrapping, 149 | minFilter: NearestFilter, 150 | magFilter: NearestFilter, 151 | format: RGBAFormat, 152 | type: this.config.floatType, 153 | depthWrite: false, 154 | depthBuffer: false, 155 | stencilBuffer: false 156 | }) 157 | 158 | this.positionRenderTarget2 = this.positionRenderTarget1.clone() 159 | 160 | this.outputPositionRenderTarget = this.positionRenderTarget1 161 | } 162 | 163 | initPositions () { 164 | this.renderer = RendererClass.getInstance().renderer 165 | 166 | const positionData = this.textureHelper.createPositionTexture() 167 | this.defaultPositionTexture = positionData.positionTexture 168 | this.initialPositionTexture = positionData.initialPositionTexture 169 | 170 | this.positionMaterial.uniforms.defaultPositionTexture.value = this.defaultPositionTexture 171 | this.material.uniforms.defaultPositionTexture.value = this.defaultPositionTexture 172 | 173 | this.positionMaterial.uniforms.initialPositionTexture.value = this.initialPositionTexture 174 | this.material.uniforms.initialPositionTexture.value = this.initialPositionTexture 175 | 176 | this.positionScene = new Scene() 177 | 178 | this.positionMesh = new Mesh(new PlaneBufferGeometry(2, 2), this.positionMaterial) 179 | this.positionMesh.frustumCulled = false 180 | this.positionScene.add(this.positionMesh) 181 | } 182 | 183 | initCamera () { 184 | this.quadCamera = new OrthographicCamera() 185 | this.quadCamera.position.z = 1 186 | } 187 | 188 | passThroughTexture (input, output) { 189 | this.passThroughMaterial.uniforms.texture.value = input 190 | this.renderer.setRenderTarget(output) 191 | this.renderer.render(this.passThroughScene, this.quadCamera) 192 | } 193 | 194 | updatePositions () { 195 | let inputPositionRenderTarget = this.positionRenderTarget1 196 | this.outputPositionRenderTarget = this.positionRenderTarget2 197 | if (this.frame % 2 === 0) { 198 | inputPositionRenderTarget = this.positionRenderTarget2 199 | this.outputPositionRenderTarget = this.positionRenderTarget1 200 | } 201 | this.positionMaterial.uniforms.positionTexture.value = inputPositionRenderTarget.texture 202 | 203 | this.renderer.setRenderTarget(this.outputPositionRenderTarget) 204 | this.renderer.render(this.positionScene, this.quadCamera) 205 | 206 | this.material.uniforms.positionTexture.value = this.outputPositionRenderTarget.texture 207 | } 208 | 209 | setTextureLocations ( 210 | nodeCount, 211 | positionArray 212 | ) { 213 | for (let i = 0; i < nodeCount; i++) { 214 | const textureLocation = this.textureHelper.getNodeTextureLocation(i) 215 | positionArray[i * 3 + 0] = textureLocation.x 216 | positionArray[i * 3 + 1] = textureLocation.y 217 | } 218 | } 219 | 220 | resize (width, height) { 221 | this.material.uniforms.uAspect = { value: CameraClass.getInstance().camera.aspect } 222 | this.material.uniforms.uTextureSize = { value: new Vector2(width * this.config.particleScene.downScaleFactor, height * this.config.particleScene.downScaleFactor) } 223 | 224 | this.mesh.scale.set(1.0, CameraClass.getInstance().camera.aspect, 1.0) 225 | } 226 | 227 | renderFrame (args) { 228 | this.frame++ 229 | 230 | this.positionMaterial.uniforms.uFrame.value = this.frame 231 | this.positionMaterial.uniforms.uMousePos.value = MouseClass.getInstance().normalizedMousePos 232 | this.positionMaterial.uniforms.uPrevMousePos.value = MouseClass.getInstance().prevNormalizedMousePos 233 | 234 | this.material.uniforms.uTime.value += args.dt 235 | this.material.uniforms.uMousePos.value = MouseClass.getInstance().normalizedMousePos 236 | this.material.uniforms.uPrevMousePos.value = MouseClass.getInstance().prevNormalizedMousePos 237 | this.material.uniforms.uMousePosTexture.value = FBOClass.getInstance().mousePosTexture 238 | this.material.uniforms.uCamPos.value = CameraClass.getInstance().camera.position 239 | this.material.uniforms.uIsMobile.value = this.config.detector.isMobile 240 | 241 | this.updatePositions() 242 | 243 | if (this.config.detector.isMobile) { 244 | if (Math.abs(TouchClass.getInstance().touchDelta.x) + Math.abs(TouchClass.getInstance().touchDelta.y) > 1.0) { 245 | this.mouseMoved = 1.0 246 | } 247 | } else { 248 | if (Math.abs(MouseClass.getInstance().mouseDelta.x) + Math.abs(MouseClass.getInstance().mouseDelta.y) > 1.0) { 249 | this.mouseMoved = 1.0 250 | } 251 | } 252 | 253 | if (this.mouseMoved > 0) { 254 | this.mouseMoved -= args.dt * 0.7 255 | } 256 | if (this.mouseMoved < 0) { 257 | this.mouseMoved = 0 258 | } 259 | 260 | this.material.uniforms.uNoiseMix.value = this.mouseMoved 261 | this.positionMaterial.uniforms.uNoiseMix.value = this.mouseMoved 262 | 263 | super.renderFrame() 264 | } 265 | } 266 | 267 | class ParticlesMaterial extends ShaderMaterial { 268 | constructor (config) { 269 | super(config) 270 | 271 | this.type = 'ShaderMaterial' 272 | 273 | this.uniforms = ShaderLib.standard.uniforms 274 | 275 | this.uniforms.uTexture = { 276 | type: 't', 277 | value: null 278 | } 279 | 280 | this.uniforms.uMousePosTexture = { 281 | type: 't', 282 | value: null 283 | } 284 | 285 | this.uniforms.uTime = { 286 | type: 'f', 287 | value: 0.0 288 | } 289 | 290 | this.uniforms.positionTexture = { 291 | type: 't', 292 | value: null 293 | } 294 | 295 | this.uniforms.initialPositionTexture = { 296 | type: 't', 297 | value: null 298 | } 299 | 300 | this.uniforms.defaultPositionTexture = { 301 | type: 't', 302 | value: null 303 | } 304 | 305 | this.uniforms.uMousePos = { 306 | type: 'v2', 307 | value: new Vector2(0, 0) 308 | } 309 | 310 | this.uniforms.uPrevMousePos = { 311 | type: 'v2', 312 | value: new Vector2(0, 0) 313 | } 314 | 315 | this.uniforms.uNoiseMix = { 316 | type: 'f', 317 | value: 0.0 318 | } 319 | 320 | this.uniforms.uAspect = { 321 | type: 'f', 322 | value: 1.0 323 | } 324 | 325 | this.uniforms.uCamPos = { 326 | type: 'v3', 327 | value: new Vector3(0, 0, 0) 328 | } 329 | 330 | this.uniforms.uIsMobile = { 331 | type: 'f', 332 | value: 0.0 333 | } 334 | 335 | this.vertexShader = vertexShader 336 | this.fragmentShader = fragmentShader 337 | this.lights = true 338 | } 339 | } 340 | 341 | export default ParticlesClass 342 | -------------------------------------------------------------------------------- /src/components/classes/PathsClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Mesh, 3 | BufferGeometry, 4 | Color, 5 | MeshBasicMaterial, 6 | CubicBezierCurve3, 7 | BufferAttribute, 8 | AdditiveBlending, 9 | Line 10 | } from 'three' 11 | 12 | import { geoInterpolate } from 'd3-geo' 13 | 14 | import BaseClass from './BaseClass' 15 | 16 | import { latLongToCartesian, clamp } from '../../helpers/math' 17 | 18 | // test data 19 | // import { coords } from '../../data/test' 20 | 21 | class PathsClass extends BaseClass { 22 | getSplineFromCoords (coords) { 23 | const startLat = coords[0] 24 | const startLng = coords[1] 25 | const endLat = coords[2] 26 | const endLng = coords[3] 27 | 28 | // start and end points 29 | const start = latLongToCartesian(startLat, startLng, this.config.scene.globeRadius) 30 | const end = latLongToCartesian(endLat, endLng, this.config.scene.globeRadius) 31 | 32 | // altitude 33 | const altitude = clamp(start.distanceTo(end) * 0.75, this.config.curveMinAltitude, this.config.curveMaxAltitude) 34 | 35 | // 2 control points 36 | const interpolate = geoInterpolate([startLng, startLat], [endLng, endLat]) 37 | const midCoord1 = interpolate(0.25 + (Math.random() * 0.1)) 38 | const midCoord2 = interpolate(0.75 + (Math.random() * 0.1)) 39 | const mid1 = latLongToCartesian(midCoord1[1], midCoord1[0], this.config.scene.globeRadius + altitude) 40 | const mid2 = latLongToCartesian(midCoord2[1], midCoord2[0], this.config.scene.globeRadius + altitude) 41 | 42 | return { 43 | start, 44 | end, 45 | spline: new CubicBezierCurve3(start, mid1, mid2, end) 46 | } 47 | } 48 | 49 | init (data) { 50 | this.coords = data 51 | this.material = new MeshBasicMaterial({ 52 | blending: AdditiveBlending, 53 | opacity: 0.4, 54 | transparent: true, 55 | depthWrite: false, 56 | color: new Color(0x003a62) 57 | }) 58 | 59 | this.mesh = new Mesh() 60 | 61 | this.lineCount = 700 62 | 63 | this.counters = [] 64 | 65 | for (let index = 0; index < this.lineCount; index++) { 66 | const randIndex1 = Math.floor(Math.random() * this.coords.length) 67 | const randIndex2 = Math.floor(Math.random() * this.coords.length) 68 | this.addLine(randIndex1, randIndex2) 69 | } 70 | 71 | super.init() 72 | } 73 | 74 | addLine (index1, index2) { 75 | this.counters.push(Math.floor(Math.random() * this.config.curveSegments)) 76 | 77 | const start = this.coords[index1] 78 | const end = this.coords[index2] 79 | 80 | if (typeof start === 'undefined' || typeof end === 'undefined') { 81 | return 82 | } 83 | 84 | const { spline } = this.getSplineFromCoords([ 85 | start.lat, 86 | start.long, 87 | end.lat, 88 | end.long 89 | ]) 90 | 91 | // add curve geometry 92 | const curveGeometry = new BufferGeometry() 93 | const points = new Float32Array(this.config.curveSegments * 3) 94 | const vertices = spline.getPoints(this.config.curveSegments - 1) 95 | 96 | for (let i = 0, j = 0; i < vertices.length; i++) { 97 | const vertex = vertices[i] 98 | points[j++] = vertex.x 99 | points[j++] = vertex.y 100 | points[j++] = vertex.z 101 | } 102 | 103 | curveGeometry.setAttribute('position', new BufferAttribute(points, 3)) 104 | curveGeometry.setDrawRange(0, 0) 105 | 106 | let mesh = new Line(curveGeometry, this.material) 107 | 108 | this.mesh.add(mesh) 109 | } 110 | 111 | addNode (data) { 112 | this.coords.push(data) 113 | for (let index = 0; index < 10; index++) { 114 | this.addLine(this.coords.length - 1, Math.floor(Math.random() * this.coords.length)) 115 | } 116 | } 117 | 118 | renderFrame (args) { 119 | this.mesh.children.forEach((line, index) => { 120 | this.counters[index] += (args.dt * 30.0) 121 | 122 | if (this.counters[index] > this.config.curveSegments) { 123 | this.counters[index] = Math.floor(Math.random() * this.config.curveSegments) 124 | } 125 | 126 | line.geometry.setDrawRange(0, this.counters[index]) 127 | }) 128 | 129 | super.renderFrame() 130 | } 131 | } 132 | 133 | export default PathsClass 134 | -------------------------------------------------------------------------------- /src/components/classes/PickerSceneClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Scene 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | 7 | class PickerSceneClass extends BaseClass { 8 | init () { 9 | this.scene = new Scene() 10 | 11 | super.init() 12 | } 13 | 14 | destroy () { 15 | this.scene.traverse(function (object) { 16 | if (object.geometry) { 17 | object.geometry.dispose() 18 | } 19 | if (object.material) { 20 | object.material.dispose() 21 | } 22 | }) 23 | 24 | super.destroy() 25 | } 26 | } 27 | 28 | export default PickerSceneClass 29 | -------------------------------------------------------------------------------- /src/components/classes/PickersClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Mesh, 3 | BufferGeometry, 4 | CylinderGeometry, 5 | InstancedBufferGeometry, 6 | InstancedBufferAttribute, 7 | Color, 8 | Object3D, 9 | ShaderMaterial, 10 | WebGLRenderTarget, 11 | LinearFilter 12 | } from 'three' 13 | 14 | import BaseClass from './BaseClass' 15 | 16 | import { latLongToCartesian } from '../../helpers/math' 17 | 18 | // shaders 19 | import fragmentShader from '../../shaders/pickers.frag' 20 | import vertexShader from '../../shaders/pickers.vert' 21 | 22 | import RendererClass from './RendererClass' 23 | import CameraClass from './CameraClass' 24 | import PickerSceneClass from './PickerSceneClass' 25 | import MouseClass from './MouseClass' 26 | import MarkersClass from './MarkersClass' 27 | 28 | class PickersClass extends BaseClass { 29 | init (data) { 30 | this.lastHoveredID = -1 31 | this.lastSelectedID = -1 32 | this.hoveredIP = '' 33 | this.isHovering = false 34 | 35 | this.pickingTexture = new WebGLRenderTarget(window.innerWidth, window.innerHeight) 36 | this.pickingTexture.texture.minFilter = LinearFilter 37 | this.pickingTexture.texture.generateMipmaps = false 38 | 39 | let coords = data 40 | 41 | this.nodeCount = coords.length 42 | this.instanceTotal = 1000 // max number of instances 43 | 44 | this.material = new PickersMaterial({}) 45 | 46 | const tubeGeo = new CylinderGeometry(0.0, 0.005, 0.06, 3) 47 | const tubeBufferGeo = new BufferGeometry().fromGeometry(tubeGeo) 48 | this.geometry = new InstancedBufferGeometry().copy(tubeBufferGeo) 49 | this.geometry.rotateX(Math.PI / 2) 50 | 51 | this.offsetsAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal * 3).fill(99999), 3) 52 | this.idAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal), 1) 53 | this.scalesAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal), 1) 54 | this.quaternionsAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal * 4), 4) 55 | this.pickingColorsAttr = new InstancedBufferAttribute(new Float32Array(this.instanceTotal * 3), 3) 56 | 57 | this.geometry.setAttribute('offset', this.offsetsAttr) 58 | this.geometry.setAttribute('scale', this.scalesAttr) 59 | this.geometry.setAttribute('quaternion', this.quaternionsAttr) 60 | this.geometry.setAttribute('id', this.idAttr) 61 | this.geometry.setAttribute('pickerColor', this.pickingColorsAttr) 62 | 63 | this.pickColor = new Color(0x999999) 64 | 65 | for (let index = 0; index < this.nodeCount; index++) { 66 | if (typeof coords[index] !== 'undefined') { 67 | this.addNodeGeoData(coords[index], index) 68 | } 69 | } 70 | 71 | this.mesh = new Mesh(this.geometry, this.material) 72 | this.mesh.frustumCulled = false 73 | } 74 | 75 | addNodeGeoData (data, index) { 76 | this.pickColor.setHex(index + 1) 77 | this.pickingColorsAttr.array[index * 3 + 0] = this.pickColor.r 78 | this.pickingColorsAttr.array[index * 3 + 1] = this.pickColor.g 79 | this.pickingColorsAttr.array[index * 3 + 2] = this.pickColor.b 80 | 81 | const pos = latLongToCartesian(data.lat, data.long, this.config.scene.sphereRadius * 1.05) 82 | 83 | const x = this.offsetsAttr.array[index * 3 + 0] = pos.x 84 | const y = this.offsetsAttr.array[index * 3 + 1] = pos.y 85 | const z = this.offsetsAttr.array[index * 3 + 2] = pos.z 86 | 87 | const dummyObject = new Object3D() 88 | dummyObject.position.set(x, y, z) 89 | dummyObject.lookAt(0, 0, 0) 90 | 91 | this.quaternionsAttr.array[index * 4 + 0] = dummyObject.quaternion.x 92 | this.quaternionsAttr.array[index * 4 + 1] = dummyObject.quaternion.y 93 | this.quaternionsAttr.array[index * 4 + 2] = dummyObject.quaternion.z 94 | this.quaternionsAttr.array[index * 4 + 3] = dummyObject.quaternion.w 95 | 96 | this.scalesAttr.array[index] = 2.0 97 | 98 | this.idAttr.array[index] = index 99 | 100 | this.geometry.attributes.offset.needsUpdate = true 101 | this.geometry.attributes.scale.needsUpdate = true 102 | this.geometry.attributes.quaternion.needsUpdate = true 103 | this.geometry.attributes.id.needsUpdate = true 104 | this.geometry.attributes.pickerColor.needsUpdate = true 105 | } 106 | 107 | addNode (data) { 108 | this.nodeCount += 1 109 | this.addNodeGeoData(data, this.nodeCount) 110 | } 111 | 112 | resize (width, height) { 113 | this.width = width 114 | this.height = height 115 | 116 | this.pickingTexture.setSize(this.width, this.height) 117 | } 118 | 119 | renderFrame () { 120 | RendererClass.getInstance().renderer.autoClear = true 121 | RendererClass.getInstance().renderer.setRenderTarget(this.pickingTexture) 122 | RendererClass.getInstance().renderer.render(PickerSceneClass.getInstance().scene, CameraClass.getInstance().camera) 123 | 124 | const pixelBuffer = new Uint8Array(4) 125 | pixelBuffer[3] = 255 126 | 127 | RendererClass.getInstance().renderer.readRenderTargetPixels( 128 | this.pickingTexture, 129 | MouseClass.getInstance().mousePos.x, 130 | this.pickingTexture.height - (MouseClass.getInstance().mousePos.y), 131 | 1, 132 | 1, 133 | pixelBuffer 134 | ) 135 | 136 | const id = (pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | (pixelBuffer[2] - 1) 137 | 138 | if (this.lastHoveredID !== id) { 139 | this.lastHoveredID = id 140 | 141 | if (typeof MarkersClass.getInstance().ipMap[id] !== 'undefined') { 142 | this.hoveredData = MarkersClass.getInstance().ipMap[id] 143 | MarkersClass.getInstance().setSelectionRef(this.hoveredData.pos) 144 | 145 | this.emit('nodeMouseOver', this.hoveredData) 146 | 147 | this.isHovering = true 148 | 149 | document.body.style.cursor = 'pointer' 150 | } else { 151 | this.isHovering = false 152 | 153 | this.emit('nodeMouseOut', {}) 154 | 155 | // MarkersClass.getInstance().setSelectionRef(new Vector3(0, 0, 0)) 156 | 157 | document.body.style.cursor = 'default' 158 | } 159 | } 160 | 161 | super.renderFrame() 162 | } 163 | } 164 | 165 | class PickersMaterial extends ShaderMaterial { 166 | constructor (config) { 167 | super(config) 168 | this.type = 'ShaderMaterial' 169 | 170 | this.uniforms = {} 171 | 172 | this.vertexShader = vertexShader 173 | this.fragmentShader = fragmentShader 174 | } 175 | } 176 | 177 | export default PickersClass 178 | -------------------------------------------------------------------------------- /src/components/classes/PointLightClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | PointLight 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | 7 | class PointLightClass extends BaseClass { 8 | init () { 9 | this.light = new PointLight(0xffffff, 3.0) 10 | this.light.position.set(0, 5, 0) 11 | 12 | super.init() 13 | } 14 | } 15 | 16 | export default PointLightClass 17 | -------------------------------------------------------------------------------- /src/components/classes/QuadCameraClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | OrthographicCamera 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | 7 | class QuadCameraClass extends BaseClass { 8 | init () { 9 | this.camera = new OrthographicCamera() 10 | this.camera.position.z = 1 11 | 12 | super.init() 13 | } 14 | 15 | resize (width, height) { 16 | this.camera.aspect = width / height 17 | this.camera.updateProjectionMatrix() 18 | super.resize() 19 | } 20 | } 21 | 22 | export default QuadCameraClass 23 | -------------------------------------------------------------------------------- /src/components/classes/RendererClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | WebGLRenderer 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | import CameraClass from './CameraClass' 7 | import GlobeSceneClass from './GlobeSceneClass' 8 | 9 | class RendererClass extends BaseClass { 10 | init () { 11 | this.canvas = document.querySelector('#' + this.config.scene.canvasID) 12 | this.renderer = new WebGLRenderer({ 13 | antialias: false, 14 | canvas: this.canvas, 15 | powerPreference: 'high-performance', 16 | alpha: true 17 | }) 18 | 19 | this.renderer.setClearColor(0xffffff, 0) 20 | 21 | super.init() 22 | } 23 | 24 | resize (width, height) { 25 | this.renderer.setSize(width, height, false) 26 | 27 | super.resize() 28 | } 29 | 30 | renderFrame ({ 31 | renderTarget = null, 32 | scene = GlobeSceneClass.getInstance().scene, 33 | camera = CameraClass.getInstance().camera 34 | } = {}) { 35 | this.renderer.setRenderTarget(renderTarget) 36 | this.renderer.render(scene, camera) 37 | 38 | super.renderFrame() 39 | } 40 | 41 | destroy () { 42 | this.renderer.dispose() 43 | 44 | super.destroy() 45 | } 46 | } 47 | 48 | export default RendererClass 49 | -------------------------------------------------------------------------------- /src/components/classes/TouchClass.js: -------------------------------------------------------------------------------- 1 | import { 2 | Vector2 3 | } from 'three' 4 | 5 | import BaseClass from './BaseClass' 6 | import RendererClass from './RendererClass' 7 | 8 | class TouchClass extends BaseClass { 9 | init () { 10 | this.touchPos = new Vector2() 11 | this.prevTouchPos = new Vector2(0, 0) 12 | this.normalizedTouchPos = new Vector2() 13 | 14 | this.touchDelta = new Vector2(0, 0) 15 | this.movement = new Vector2() 16 | this.touchPos = new Vector2() 17 | this.normalizedTouchPos = new Vector2() 18 | this.prevNormalizedTouchPos = new Vector2() 19 | } 20 | 21 | onTouchMove (e) { 22 | if (typeof e.touches[0] === 'undefined') { 23 | return 24 | } else { 25 | e = e.touches[0] 26 | } 27 | 28 | this.prevNormalizedTouchPos.x = this.normalizedTouchPos.x 29 | this.prevNormalizedTouchPos.y = this.normalizedTouchPos.y 30 | 31 | this.prevTouchPos.x = this.touchPos.x 32 | this.prevTouchPos.y = this.touchPos.y 33 | 34 | this.touchPos.x = e.clientX - RendererClass.getInstance().renderer.domElement.offsetLeft 35 | this.touchPos.y = e.clientY - RendererClass.getInstance().renderer.domElement.offsetTop 36 | 37 | this.touchDelta = this.touchPos.clone().sub(this.prevTouchPos) 38 | 39 | this.movement.x = this.touchDelta.x 40 | this.movement.y = this.touchDelta.y 41 | 42 | const x = e.clientX - RendererClass.getInstance().renderer.domElement.offsetLeft 43 | const y = e.clientY - RendererClass.getInstance().renderer.domElement.offsetTop 44 | 45 | this.normalizedTouchPos.x = x / RendererClass.getInstance().renderer.domElement.width 46 | this.normalizedTouchPos.y = 1 - y / RendererClass.getInstance().renderer.domElement.height 47 | } 48 | 49 | renderFrame ({ dt } = {}) { 50 | super.renderFrame() 51 | } 52 | } 53 | 54 | export default TouchClass 55 | -------------------------------------------------------------------------------- /src/data/test.js: -------------------------------------------------------------------------------- 1 | export const coords = [ 2 | { lat: 51.229424, long: -2.322441 }, 3 | { lat: 31.0449, long: 121.4012 }, 4 | { lat: 38.6583, long: -77.2481 }, 5 | { lat: 37.5112, long: 126.9741 }, 6 | { lat: 32.3485, long: -97.3311 }, 7 | { lat: 50.1188, long: 8.6843 }, 8 | { lat: 40.4143, long: -3.7016 }, 9 | { lat: 51.2993, long: 9.491 }, 10 | { lat: 35.1731, long: 149.107 }, 11 | { lat: -27.4833, long: 153.0053 }, 12 | { lat: 35.6882, long: 139.7532 }, 13 | { lat: 35.6882, long: 139.7532 }, 14 | { lat: 37.3388, long: -121.8914 }, 15 | { lat: 37.3388, long: -121.8914 }, 16 | { lat: 39.9653, long: -83.0235 }, 17 | { lat: 34.8224, long: 135.4301 }, 18 | { lat: 52.3902, long: 4.6568 }, 19 | { lat: 38.7095, long: -78.1539 }, 20 | { lat: 53.3338, long: -6.2488 }, 21 | { lat: 56.752, long: -111.4398 }, 22 | { lat: 43.6547, long: -79.3623 }, 23 | { lat: -27.4737, long: 153.0169 }, 24 | { lat: -33.8591, long: 151.2002 }, 25 | { lat: 37.3387, long: -121.8914 }, 26 | { lat: 51.5353, long: -0.6658 }, 27 | { lat: 51.2993, long: 9.491 }, 28 | { lat: 49.405, long: 11.1617 }, 29 | { lat: 49.405, long: 11.1617 }, 30 | { lat: 37.751, long: -97.822 }, 31 | { lat: 51.2993, long: 9.491 }, 32 | { lat: 49.405, long: 11.1617 }, 33 | { lat: 49.405, long: 11.1617 }, 34 | { lat: 37.751, long: -97.822 }, 35 | { lat: 51.2993, long: 9.491 }, 36 | { lat: 35.7965, long: -78.7981 }, 37 | { lat: 45.4995, long: -73.5848 }, 38 | { lat: 52.2777, long: -0.8284 }, 39 | { lat: 56.1174, long: -3.5345 }, 40 | { lat: 37.751, long: -97.822 }, 41 | { lat: 50.4671, long: -104.541 }, 42 | { lat: 48.8582, long: 2.3387 }, 43 | { lat: 37.3417, long: -121.9753 }, 44 | { lat: 40.8364, long: -74.1403 }, 45 | { lat: 50.1084, long: 8.6841 }, 46 | { lat: 49.405, long: 11.1617 }, 47 | { lat: 17.3846, long: 78.4574 }, 48 | { lat: 51.7047, long: -0.4213 }, 49 | { lat: 52.5258, long: 5.7235 }, 50 | { lat: 47.3848, long: 9.9014 }, 51 | { lat: 61.4545, long: 5.8395 }, 52 | { lat: 47.3664, long: 8.5546 }, 53 | { lat: -3.1145, long: -60.0278 }, 54 | { lat: 1.2929, long: 103.8547 }, 55 | { lat: 35.6882, long: 139.7532 }, 56 | { lat: 50.1188, long: 8.6843 }, 57 | { lat: 50.1188, long: 8.6843 }, 58 | { lat: 50.1188, long: 8.6843 }, 59 | { lat: 50.1188, long: 8.6843 }, 60 | { lat: 53.3338, long: -6.2488 }, 61 | { lat: 39.9653, long: -83.0235 }, 62 | { lat: 37.3388, long: -121.8914 }, 63 | { lat: 25.2925, long: 51.5321 }, 64 | { lat: 46.6682, long: 11.1595 }, 65 | { lat: 46.6682, long: 11.1595 }, 66 | { lat: 42.7, long: 23.3333 }, 67 | { lat: 52.4855, long: 13.4392 }, 68 | { lat: 51.2993, long: 9.491 }, 69 | { lat: 59.9308, long: 30.1918 }, 70 | { lat: 55.7386, long: 37.6068 }, 71 | { lat: 55.7386, long: 37.6068 }, 72 | { lat: 55.6059, long: 13.0007 }, 73 | { lat: -32.4833, long: -58.2283 }, 74 | { lat: -31.4015, long: -64.1803 }, 75 | { lat: 19.4065, long: -99.0837 }, 76 | { lat: -20.3857, long: -40.3206 }, 77 | { lat: 39.0448, long: -77.6042 }, 78 | { lat: 36.1682, long: -115.2166 }, 79 | { lat: 45.4168, long: -73.4992 }, 80 | { lat: 47.8008, long: 0.0443 }, 81 | { lat: 49.405, long: 11.1617 }, 82 | { lat: -33.9165, long: 18.4155 }, 83 | { lat: 32.7787, long: -96.8217 }, 84 | { lat: 47.6859, long: -122.2994 }, 85 | { lat: 52.3824, long: 4.8995 }, 86 | { lat: 48.8582, long: 2.3387 }, 87 | { lat: 37.3417, long: -121.9753 }, 88 | { lat: 49.405, long: 11.1617 }, 89 | { lat: 43.9982, long: -79.4625 }, 90 | { lat: 56.1663, long: 10.1987 }, 91 | { lat: 47.4925, long: 19.0514 }, 92 | { lat: 47.3664, long: 8.5546 }, 93 | { lat: 43.2001, long: -79.5663 }, 94 | { lat: 59.3307, long: 18.0718 }, 95 | { lat: 52.3534, long: 4.9087 }, 96 | { lat: 64.7805, long: -147.3694 }, 97 | { lat: 36.0861, long: -115.2541 }, 98 | { lat: 45.4978, long: -73.5485 }, 99 | { lat: 35.6882, long: 139.7532 }, 100 | { lat: 50.1188, long: 8.6843 }, 101 | { lat: 50.1188, long: 8.6843 }, 102 | { lat: 50.1188, long: 8.6843 }, 103 | { lat: 50.1188, long: 8.6843 }, 104 | { lat: 50.1188, long: 8.6843 }, 105 | { lat: 50.1188, long: 8.6843 }, 106 | { lat: 50.1188, long: 8.6843 }, 107 | { lat: 50.1188, long: 8.6843 }, 108 | { lat: 50.1188, long: 8.6843 }, 109 | { lat: 39.9653, long: -83.0235 }, 110 | { lat: 39.0481, long: -77.4728 }, 111 | { lat: 46.3682, long: 17.7945 }, 112 | { lat: 53.3338, long: -6.2488 }, 113 | { lat: 38.6583, long: -77.2481 }, 114 | { lat: 37.751, long: -97.822 }, 115 | { lat: 37.751, long: -97.822 }, 116 | { lat: 37.751, long: -97.822 }, 117 | { lat: 37.751, long: -97.822 }, 118 | { lat: 27.8485, long: -82.7944 }, 119 | { lat: 38.6583, long: -77.2481 }, 120 | { lat: 35, long: 105 }, 121 | { lat: 50.941, long: 5.8016 }, 122 | { lat: 48.8582, long: 2.3387 }, 123 | { lat: 41.3891, long: 2.1611 }, 124 | { lat: 37.3388, long: -121.8914 }, 125 | { lat: -26.2309, long: 28.0583 }, 126 | { lat: 41.6006, long: -93.6112 }, 127 | { lat: 41.6006, long: -93.6112 }, 128 | { lat: 52.3534, long: 4.9087 }, 129 | { lat: 45.8491, long: -119.7143 }, 130 | { lat: 45.8491, long: -119.7143 }, 131 | { lat: 60.1756, long: 24.9342 }, 132 | { lat: 42.7, long: 23.3333 }, 133 | { lat: 42.7, long: 23.3333 }, 134 | 135 | { lat: 42.7, long: 23.3333 }, 136 | { lat: 42.7, long: 23.3333 }, 137 | { lat: 32.7787, long: -96.8217 }, 138 | { lat: 40.5511, long: -74.4606 }, 139 | { lat: 49.05, long: 14.4333 }, 140 | { lat: 33.5631, long: -117.2738 }, 141 | { lat: 37.5112, long: 126.9741 }, 142 | { lat: 33.7485, long: -84.3871 }, 143 | { lat: 50.9266, long: -113.9726 }, 144 | { lat: 51.5132, long: -0.0961 }, 145 | { lat: 48.8582, long: 2.3387 }, 146 | { lat: 52.3615, long: 4.6419 }, 147 | { lat: 52.3824, long: 4.8995 }, 148 | { lat: 48.8607, long: 2.3281 }, 149 | { lat: 48.8607, long: 2.3281 }, 150 | { lat: 43.6319, long: -79.3716 }, 151 | { lat: 48.8582, long: 2.3387 }, 152 | { lat: 35.6882, long: 139.7532 }, 153 | { lat: 35.6882, long: 139.7532 }, 154 | { lat: 1.2929, long: 103.8547 }, 155 | { lat: 50.1188, long: 8.6843 }, 156 | { lat: 37.3388, long: -121.8914 }, 157 | { lat: 37.3388, long: -121.8914 }, 158 | { lat: 50.1188, long: 8.6843 }, 159 | { lat: 35.6882, long: 139.7532 }, 160 | { lat: 37.3388, long: -121.8914 }, 161 | { lat: 37.3388, long: -121.8914 }, 162 | { lat: 37.3388, long: -121.8914 }, 163 | { lat: 37.3388, long: -121.8914 }, 164 | { lat: 37.3388, long: -121.8914 }, 165 | { lat: 37.3388, long: -121.8914 }, 166 | { lat: 39.0481, long: -77.4728 }, 167 | { lat: 45.5063, long: -73.5794 }, 168 | { lat: 39.0481, long: -77.4728 }, 169 | { lat: 35.6882, long: 139.7532 }, 170 | { lat: 35.69, long: 139.69 }, 171 | { lat: 35.7138, long: 0.7484 }, 172 | { lat: -33.8591, long: 151.2002 }, 173 | { lat: 51.4476, long: 7.0122 }, 174 | { lat: 45.4017, long: -74.0335 }, 175 | { lat: 53.2158, long: -6.6669 }, 176 | { lat: 40.2342, long: -111.6442 }, 177 | { lat: 41.7579, long: -88.2934 }, 178 | { lat: 40.8364, long: -74.1403 }, 179 | { lat: 44.1035, long: -102.0159 }, 180 | { lat: 40.739, long: -74.1697 }, 181 | { lat: 37.045, long: -76.4067 }, 182 | { lat: 34.185, long: -118.7669 }, 183 | { lat: 50.8696, long: 3.8105 }, 184 | { lat: 51.2993, long: 9.491 }, 185 | { lat: 52.5739, long: 13.3224 }, 186 | { lat: 51.2993, long: 9.491 }, 187 | { lat: 51.2993, long: 9.491 }, 188 | { lat: 51.2993, long: 9.491 }, 189 | { lat: 51.2993, long: 9.491 }, 190 | { lat: 51.2993, long: 9.491 }, 191 | { lat: 38.9942, long: -1.8564 }, 192 | { lat: 52.2524, long: 6.1595 }, 193 | { lat: 49.85, long: 18.3667 }, 194 | { lat: 49.85, long: 18.3667 }, 195 | { lat: 52.2859, long: 4.8667 }, 196 | { lat: 48.8543, long: 2.3527 }, 197 | { lat: 45.7537, long: 21.2257 }, 198 | { lat: 41.3891, long: 2.1611 }, 199 | { lat: 52.0626, long: 5.1191 }, 200 | { lat: 50.8168, long: 5.1865 }, 201 | { lat: 52.5174, long: 13.3985 }, 202 | { lat: 46.5189, long: 6.636 }, 203 | { lat: 51.2993, long: 9.491 }, 204 | { lat: 51.4476, long: 7.0122 }, 205 | { lat: 51.6004, long: -0.1168 }, 206 | { lat: 51.6, long: -0.2167 }, 207 | { lat: 51.5167, long: -0.0333 }, 208 | { lat: 52.4854, long: 6.1145 }, 209 | { lat: 51.6573, long: 4.8688 }, 210 | { lat: 51.2993, long: 9.491 }, 211 | { lat: 42.5499, long: -3.3232 }, 212 | { lat: 66.9122, long: 13.6296 }, 213 | { lat: 46.0503, long: 14.5046 }, 214 | { lat: 48.7662, long: 9.1833 }, 215 | { lat: 44.1392, long: 4.8079 }, 216 | { lat: 51.197, long: 3.1789 }, 217 | { lat: 34.6874, long: 33.0366 }, 218 | { lat: 43.5611, long: -5.9191 }, 219 | { lat: 45.5534, long: 8.9792 }, 220 | { lat: 51.0955, long: 4.509 }, 221 | { lat: 60.1708, long: 24.9375 }, 222 | { lat: 60.1708, long: 24.9375 }, 223 | { lat: 60.1708, long: 24.9375 }, 224 | { lat: 60.1708, long: 24.9375 }, 225 | { lat: 60.1708, long: 24.9375 }, 226 | { lat: 60.1708, long: 24.9375 }, 227 | { lat: 60.1708, long: 24.9375 }, 228 | { lat: 32.8137, long: -96.8704 }, 229 | { lat: 35.3869, long: -119.1745 }, 230 | { lat: 40.76, long: -73.5318 }, 231 | { lat: 43.0351, long: -108.2024 }, 232 | { lat: 21.3133, long: -157.823 } 233 | 234 | ] 235 | -------------------------------------------------------------------------------- /src/helpers/TextureHelper.js: -------------------------------------------------------------------------------- 1 | import { 2 | Vector3, 3 | DataTexture, 4 | RGBAFormat, 5 | FloatType, 6 | NearestFilter 7 | } from 'three' 8 | 9 | export default class TextureHelper { 10 | constructor (args) { 11 | this.config = args.config 12 | } 13 | 14 | setPointCount (pointCount) { 15 | this.pointCount = pointCount 16 | } 17 | 18 | setTextureSize (pointCount) { 19 | this.setPointCount(pointCount) 20 | 21 | let width = 1 22 | let height = 1 23 | 24 | while (height * width < this.pointCount) { 25 | width *= 2 26 | if (height * width >= this.pointCount) { 27 | break 28 | } 29 | height *= 2 30 | } 31 | 32 | this.textureWidth = width 33 | this.textureHeight = height 34 | } 35 | 36 | getNodeTextureLocation (nodeID) { 37 | return { 38 | x: (nodeID % this.textureWidth) * (1 / this.textureWidth) + (1 / (this.textureWidth * 2)), 39 | y: Math.floor(nodeID / this.textureWidth) * (1 / this.textureHeight) + (1 / (this.textureHeight * 2)) 40 | } 41 | } 42 | 43 | createPositionTexture ({ 44 | defaultPositions = new Float32Array() 45 | } = {}) { 46 | let initialTextureArray = new Float32Array(this.textureWidth * this.textureHeight * 4) 47 | let textureArray = new Float32Array(this.textureWidth * this.textureHeight * 4) 48 | let lifeArray = [] 49 | let step = 4 50 | 51 | for (let i = 0; i < this.pointCount; i++) { 52 | let location = new Vector3( 53 | ((i) % (this.config.particleScene.width / step)) * step, 54 | (Math.floor((i) / (this.config.particleScene.width / step))) * step, 55 | 0 56 | ) 57 | 58 | let lifeDuration = Math.ceil(Math.random() * this.config.scene.particleLifeMax) 59 | 60 | textureArray[i * 4 + 0] = location.x 61 | textureArray[i * 4 + 1] = location.y 62 | textureArray[i * 4 + 2] = location.z 63 | textureArray[i * 4 + 3] = lifeDuration 64 | 65 | initialTextureArray[i * 4 + 0] = Math.random() * this.config.particleScene.width 66 | initialTextureArray[i * 4 + 1] = Math.random() * this.config.particleScene.height 67 | initialTextureArray[i * 4 + 2] = Math.random() * this.config.particleScene.height 68 | initialTextureArray[i * 4 + 3] = 0 69 | } 70 | 71 | let positionTexture = new DataTexture( 72 | textureArray, 73 | this.textureWidth, 74 | this.textureHeight, 75 | RGBAFormat, 76 | FloatType 77 | ) 78 | positionTexture.minFilter = NearestFilter 79 | positionTexture.magFilter = NearestFilter 80 | positionTexture.generateMipmaps = false 81 | positionTexture.needsUpdate = true 82 | 83 | let initialPositionTexture = new DataTexture( 84 | initialTextureArray, 85 | this.textureWidth, 86 | this.textureHeight, 87 | RGBAFormat, 88 | FloatType 89 | ) 90 | initialPositionTexture.minFilter = NearestFilter 91 | initialPositionTexture.magFilter = NearestFilter 92 | initialPositionTexture.generateMipmaps = false 93 | initialPositionTexture.needsUpdate = true 94 | 95 | return { 96 | positionTexture: positionTexture, 97 | initialPositionTexture: initialPositionTexture, 98 | lifeArray: lifeArray 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/helpers/math.js: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three' 2 | 3 | export function lerp (v0, v1, t) { 4 | return v0 * (1 - t) + v1 * t 5 | } 6 | 7 | export function latLongToCartesian (lat, long, radius) { 8 | const phi = (90 - lat) * (Math.PI / 180) 9 | const theta = (long + 180) * (Math.PI / 180) 10 | const x = radius * Math.sin(phi) * Math.cos(theta) * -1 11 | const z = radius * Math.sin(phi) * Math.sin(theta) 12 | const y = radius * Math.cos(phi) 13 | return new Vector3(x, y, z) 14 | } 15 | 16 | export function clamp (num, min, max) { 17 | return num <= min ? min : (num >= max ? max : num) 18 | } 19 | -------------------------------------------------------------------------------- /src/helpers/utility.js: -------------------------------------------------------------------------------- 1 | export function getUrlParameter (name) { 2 | name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]') 3 | var regex = new RegExp('[\\?&]' + name + '=([^&#]*)') 4 | var results = regex.exec(window.location.search) 5 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')) 6 | }; 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Main from './components/Main' 2 | 3 | export { 4 | Main 5 | } 6 | -------------------------------------------------------------------------------- /src/libs/Detector.js: -------------------------------------------------------------------------------- 1 | const ua = (navigator.userAgent || navigator.vendor || window.opera).toLowerCase() 2 | 3 | const Detector = { 4 | isRetina: window.devicePixelRatio && window.devicePixelRatio >= 1.5, 5 | isChrome: ua.indexOf('chrome') > -1, 6 | isFirefox: ua.indexOf('firefox') > -1, 7 | isSafari: ua.indexOf('safari') > -1, 8 | isEdge: ua.indexOf('edge') > -1, 9 | isIE: ua.indexOf('msie') > -1, 10 | isMobile: /(iPad|iPhone|Android)/i.test(ua), 11 | isIOS: /(iPad|iPhone)/i.test(ua) 12 | } 13 | 14 | export default Detector 15 | -------------------------------------------------------------------------------- /src/libs/post/CopyShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Full-screen textured quad shader 5 | */ 6 | 7 | const CopyShader = { 8 | 9 | uniforms: { 10 | 11 | 'tDiffuse': { value: null }, 12 | 'opacity': { value: 1.0 } 13 | 14 | }, 15 | 16 | vertexShader: [ 17 | 18 | 'varying vec2 vUv;', 19 | 20 | 'void main() {', 21 | 22 | 'vUv = uv;', 23 | 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 24 | 25 | '}' 26 | 27 | ].join('\n'), 28 | 29 | fragmentShader: [ 30 | 31 | 'uniform float opacity;', 32 | 33 | 'uniform sampler2D tDiffuse;', 34 | 35 | 'varying vec2 vUv;', 36 | 37 | 'void main() {', 38 | 39 | 'vec4 texel = texture2D( tDiffuse, vUv );', 40 | 'gl_FragColor = opacity * texel;', 41 | 42 | '}' 43 | 44 | ].join('\n') 45 | 46 | } 47 | 48 | export default CopyShader 49 | -------------------------------------------------------------------------------- /src/libs/post/EffectComposer.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | ShaderMaterial, 4 | LinearFilter, 5 | WebGLRenderTarget, 6 | RGBAFormat, 7 | UniformsUtils, 8 | OrthographicCamera, 9 | Scene, 10 | Mesh, 11 | PlaneBufferGeometry, 12 | Vector2 13 | } from 'three' 14 | 15 | import CopyShader from './CopyShader' 16 | 17 | /** 18 | * @author alteredq / http://alteredqualia.com/ 19 | */ 20 | 21 | const EffectComposer = function (renderer, renderTarget) { 22 | this.renderer = renderer 23 | 24 | if (renderTarget === undefined) { 25 | var parameters = { 26 | minFilter: LinearFilter, 27 | magFilter: LinearFilter, 28 | format: RGBAFormat, 29 | stencilBuffer: false 30 | } 31 | 32 | var size = renderer.getDrawingBufferSize(new Vector2()) 33 | renderTarget = new WebGLRenderTarget(size.width, size.height, parameters) 34 | renderTarget.texture.name = 'EffectComposer.rt1' 35 | } 36 | 37 | this.renderTarget1 = renderTarget 38 | this.renderTarget2 = renderTarget.clone() 39 | this.renderTarget2.texture.name = 'EffectComposer.rt2' 40 | 41 | this.writeBuffer = this.renderTarget1 42 | this.readBuffer = this.renderTarget2 43 | 44 | this.passes = [] 45 | 46 | // dependencies 47 | 48 | if (CopyShader === undefined) { 49 | console.error('EffectComposer relies on CopyShader') 50 | } 51 | 52 | if (ShaderPass === undefined) { 53 | console.error('EffectComposer relies on ShaderPass') 54 | } 55 | 56 | this.copyPass = new ShaderPass(CopyShader) 57 | } 58 | 59 | Object.assign(EffectComposer.prototype, { 60 | 61 | swapBuffers: function () { 62 | var tmp = this.readBuffer 63 | this.readBuffer = this.writeBuffer 64 | this.writeBuffer = tmp 65 | }, 66 | 67 | addPass: function (pass) { 68 | this.passes.push(pass) 69 | 70 | var size = this.renderer.getDrawingBufferSize(new Vector2()) 71 | pass.setSize(size.width, size.height) 72 | }, 73 | 74 | insertPass: function (pass, index) { 75 | this.passes.splice(index, 0, pass) 76 | }, 77 | 78 | render: function (delta) { 79 | var maskActive = false 80 | 81 | var pass 82 | var i 83 | var il = this.passes.length 84 | 85 | for (i = 0; i < il; i++) { 86 | pass = this.passes[i] 87 | 88 | if (pass.enabled === false) continue 89 | 90 | pass.render(this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive) 91 | 92 | if (pass.needsSwap) { 93 | if (maskActive) { 94 | var context = this.renderer.context 95 | 96 | context.stencilFunc(context.NOTEQUAL, 1, 0xffffffff) 97 | 98 | this.copyPass.render(this.renderer, this.writeBuffer, this.readBuffer, delta) 99 | 100 | context.stencilFunc(context.EQUAL, 1, 0xffffffff) 101 | } 102 | 103 | this.swapBuffers() 104 | } 105 | 106 | /* if (MaskPass !== undefined) { 107 | if (pass instanceof MaskPass) { 108 | maskActive = true 109 | } else if (pass instanceof ClearMaskPass) { 110 | maskActive = false 111 | } 112 | } */ 113 | } 114 | }, 115 | 116 | reset: function (renderTarget) { 117 | if (renderTarget === undefined) { 118 | var size = this.renderer.getDrawingBufferSize(new Vector2()) 119 | 120 | renderTarget = this.renderTarget1.clone() 121 | renderTarget.setSize(size.width, size.height) 122 | } 123 | 124 | this.renderTarget1.dispose() 125 | this.renderTarget2.dispose() 126 | this.renderTarget1 = renderTarget 127 | this.renderTarget2 = renderTarget.clone() 128 | 129 | this.writeBuffer = this.renderTarget1 130 | this.readBuffer = this.renderTarget2 131 | }, 132 | 133 | setSize: function (width, height) { 134 | this.renderTarget1.setSize(width, height) 135 | this.renderTarget2.setSize(width, height) 136 | 137 | for (var i = 0; i < this.passes.length; i++) { 138 | this.passes[i].setSize(width, height) 139 | } 140 | } 141 | 142 | }) 143 | 144 | const Pass = function () { 145 | // if set to true, the pass is processed by the composer 146 | this.enabled = true 147 | 148 | // if set to true, the pass indicates to swap read and write buffer after rendering 149 | this.needsSwap = true 150 | 151 | // if set to true, the pass clears its buffer before rendering 152 | this.clear = false 153 | 154 | // if set to true, the result of the pass is rendered to screen 155 | this.renderToScreen = false 156 | } 157 | 158 | Object.assign(Pass.prototype, { 159 | 160 | setSize: function (width, height) {}, 161 | 162 | render: function (renderer, writeBuffer, readBuffer, delta, maskActive) { 163 | console.error('Pass: .render() must be implemented in derived pass.') 164 | } 165 | 166 | }) 167 | 168 | const ShaderPass = function (shader, textureID) { 169 | Pass.call(this) 170 | 171 | this.textureID = (textureID !== undefined) ? textureID : 'tDiffuse' 172 | 173 | if (shader instanceof ShaderMaterial) { 174 | this.uniforms = shader.uniforms 175 | 176 | this.material = shader 177 | } else if (shader) { 178 | this.uniforms = UniformsUtils.clone(shader.uniforms) 179 | 180 | this.material = new ShaderMaterial({ 181 | 182 | defines: shader.defines || {}, 183 | uniforms: this.uniforms, 184 | vertexShader: shader.vertexShader, 185 | fragmentShader: shader.fragmentShader 186 | 187 | }) 188 | } 189 | 190 | this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1) 191 | this.scene = new Scene() 192 | 193 | this.quad = new Mesh(new PlaneBufferGeometry(2, 2), null) 194 | this.quad.frustumCulled = false // Avoid getting clipped 195 | this.scene.add(this.quad) 196 | } 197 | 198 | ShaderPass.prototype = Object.assign(Object.create(Pass.prototype), { 199 | 200 | constructor: ShaderPass, 201 | 202 | render: function (renderer, writeBuffer, readBuffer, delta, maskActive) { 203 | if (this.uniforms[this.textureID]) { 204 | this.uniforms[this.textureID].value = readBuffer.texture 205 | } 206 | 207 | this.quad.material = this.material 208 | 209 | if (this.renderToScreen) { 210 | renderer.render(this.scene, this.camera) 211 | } else { 212 | renderer.render(this.scene, this.camera, writeBuffer, this.clear) 213 | } 214 | } 215 | 216 | }) 217 | 218 | /** 219 | * @author alteredq / http://alteredqualia.com/ 220 | */ 221 | 222 | const RenderPass = function (scene, camera, overrideMaterial, clearColor, clearAlpha) { 223 | Pass.call(this) 224 | 225 | this.scene = scene 226 | this.camera = camera 227 | 228 | this.overrideMaterial = overrideMaterial 229 | 230 | this.clearColor = clearColor 231 | this.clearAlpha = (clearAlpha !== undefined) ? clearAlpha : 0 232 | 233 | this.clear = true 234 | this.clearDepth = false 235 | this.needsSwap = false 236 | } 237 | 238 | RenderPass.prototype = Object.assign(Object.create(Pass.prototype), { 239 | 240 | constructor: RenderPass, 241 | 242 | render: function (renderer, writeBuffer, readBuffer, delta, maskActive) { 243 | var oldAutoClear = renderer.autoClear 244 | renderer.autoClear = false 245 | 246 | this.scene.overrideMaterial = this.overrideMaterial 247 | 248 | var oldClearColor, oldClearAlpha 249 | 250 | if (this.clearColor) { 251 | oldClearColor = renderer.getClearColor().getHex() 252 | oldClearAlpha = renderer.getClearAlpha() 253 | 254 | renderer.setClearColor(this.clearColor, this.clearAlpha) 255 | } 256 | 257 | if (this.clearDepth) { 258 | renderer.clearDepth() 259 | } 260 | 261 | renderer.render(this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear) 262 | 263 | if (this.clearColor) { 264 | renderer.setClearColor(oldClearColor, oldClearAlpha) 265 | } 266 | 267 | this.scene.overrideMaterial = null 268 | renderer.autoClear = oldAutoClear 269 | } 270 | 271 | }) 272 | 273 | export { EffectComposer, ShaderPass, RenderPass } 274 | -------------------------------------------------------------------------------- /src/libs/post/Vignette.js: -------------------------------------------------------------------------------- 1 | const Vignette = { 2 | 3 | uniforms: { 4 | tDiffuse: { value: null }, 5 | offset: { value: 2.0 }, 6 | bgColor: { value: null } 7 | }, 8 | 9 | vertexShader: ` 10 | 11 | varying vec2 vUv; 12 | 13 | void main() { 14 | vUv = uv; 15 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 16 | } 17 | 18 | `, 19 | 20 | fragmentShader: ` 21 | 22 | uniform vec3 bgColor; 23 | uniform float offset; 24 | uniform sampler2D tDiffuse; 25 | 26 | varying vec2 vUv; 27 | 28 | void main() { 29 | 30 | vec4 texel = texture2D( tDiffuse, vUv ); 31 | vec2 uv = ( vUv - vec2( 0.5 ) ) * vec2( offset ); 32 | 33 | vec3 result = mix( 34 | texel.rgb, 35 | bgColor, 36 | dot( uv, uv ) 37 | ); 38 | 39 | gl_FragColor = vec4( 40 | clamp( 41 | result, 42 | bgColor, 43 | vec3( 1. ) 44 | ), 45 | texel.a 46 | ); 47 | 48 | } 49 | 50 | ` 51 | 52 | } 53 | 54 | export default Vignette 55 | -------------------------------------------------------------------------------- /src/post/BlendLighten.js: -------------------------------------------------------------------------------- 1 | import { 2 | Color 3 | } from 'three' 4 | 5 | const BlendLighten = { 6 | uniforms: { 7 | 'tDiffuse': { value: null }, 8 | 'brightness': { value: 0.18 }, 9 | 'contrast': { value: 0.35 }, 10 | 'blendColor': { value: new Color(0x000000) } 11 | }, 12 | 13 | vertexShader: ` 14 | varying vec2 vUv; 15 | void main() { 16 | 17 | vUv = uv; 18 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 19 | } 20 | `, 21 | 22 | fragmentShader: ` 23 | 24 | float blendLighten(float base, float blend) { 25 | return max(blend,base); 26 | } 27 | 28 | vec3 blendLighten(vec3 base, vec3 blend) { 29 | return vec3(blendLighten(base.r,blend.r),blendLighten(base.g,blend.g),blendLighten(base.b,blend.b)); 30 | } 31 | 32 | vec3 blendLighten(vec3 base, vec3 blend, float opacity) { 33 | return (blendLighten(base, blend) * opacity + base * (1.0 - opacity)); 34 | } 35 | 36 | uniform sampler2D tDiffuse; 37 | uniform float brightness; 38 | uniform float contrast; 39 | uniform vec3 blendColor; 40 | 41 | varying vec2 vUv; 42 | 43 | void main() { 44 | 45 | //vec3 bgColor = vec3(18. / 255., 19. / 255., 38. / 255.); 46 | vec3 bgColor = blendColor; 47 | 48 | gl_FragColor = texture2D( tDiffuse, vUv ); 49 | 50 | gl_FragColor.rgb += brightness; 51 | 52 | if (contrast > 0.0) { 53 | gl_FragColor.rgb = (gl_FragColor.rgb - 0.5) / (1.0 - contrast) + 0.5; 54 | } else { 55 | gl_FragColor.rgb = (gl_FragColor.rgb - 0.5) * (1.0 + contrast) + 0.5; 56 | } 57 | 58 | vec3 color = blendLighten(bgColor.rgb, gl_FragColor.rgb); 59 | 60 | gl_FragColor.rgb = color; 61 | 62 | } 63 | 64 | ` 65 | 66 | } 67 | 68 | export default BlendLighten 69 | -------------------------------------------------------------------------------- /src/post/BrightnessContrast.js: -------------------------------------------------------------------------------- 1 | const BrightnessContrast = { 2 | uniforms: { 3 | 'tDiffuse': { value: null }, 4 | 'brightness': { value: 0.18 }, 5 | 'contrast': { value: 0.35 } 6 | }, 7 | 8 | vertexShader: ` 9 | varying vec2 vUv; 10 | void main() { 11 | 12 | vUv = uv; 13 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 14 | } 15 | `, 16 | 17 | fragmentShader: ` 18 | 19 | uniform sampler2D tDiffuse; 20 | uniform float brightness; 21 | uniform float contrast; 22 | 23 | varying vec2 vUv; 24 | 25 | void main() { 26 | 27 | gl_FragColor = texture2D( tDiffuse, vUv ); 28 | 29 | gl_FragColor.rgb += brightness; 30 | 31 | if (contrast > 0.0) { 32 | gl_FragColor.rgb = (gl_FragColor.rgb - 0.5) / (1.0 - contrast) + 0.5; 33 | } else { 34 | gl_FragColor.rgb = (gl_FragColor.rgb - 0.5) * (1.0 + contrast) + 0.5; 35 | } 36 | 37 | } 38 | 39 | ` 40 | 41 | } 42 | 43 | export default BrightnessContrast 44 | -------------------------------------------------------------------------------- /src/post/CopyShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Full-screen textured quad shader 5 | */ 6 | 7 | const CopyShader = { 8 | 9 | uniforms: { 10 | 11 | 'tDiffuse': { value: null }, 12 | 'opacity': { value: 1.0 } 13 | 14 | }, 15 | 16 | vertexShader: [ 17 | 18 | 'varying vec2 vUv;', 19 | 20 | 'void main() {', 21 | 22 | 'vUv = uv;', 23 | 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 24 | 25 | '}' 26 | 27 | ].join('\n'), 28 | 29 | fragmentShader: [ 30 | 31 | 'uniform float opacity;', 32 | 33 | 'uniform sampler2D tDiffuse;', 34 | 35 | 'varying vec2 vUv;', 36 | 37 | 'void main() {', 38 | 39 | 'vec4 texel = texture2D( tDiffuse, vUv );', 40 | 'gl_FragColor = opacity * texel;', 41 | 42 | '}' 43 | 44 | ].join('\n') 45 | 46 | } 47 | 48 | export default CopyShader 49 | -------------------------------------------------------------------------------- /src/post/EffectComposer.js: -------------------------------------------------------------------------------- 1 | import { 2 | Vector2, 3 | LinearFilter, 4 | RGBAFormat, 5 | WebGLRenderTarget, 6 | Clock, 7 | OrthographicCamera, 8 | PlaneBufferGeometry, 9 | Mesh, 10 | ShaderMaterial, 11 | UniformsUtils, 12 | Color, 13 | Vector3, 14 | AdditiveBlending, 15 | MeshBasicMaterial 16 | } from 'three' 17 | import CopyShader from './CopyShader' 18 | 19 | /** 20 | * @author alteredq / http://alteredqualia.com/ 21 | */ 22 | 23 | const EffectComposer = function (renderer, renderTarget) { 24 | this.renderer = renderer 25 | 26 | if (renderTarget === undefined) { 27 | var parameters = { 28 | minFilter: LinearFilter, 29 | magFilter: LinearFilter, 30 | format: RGBAFormat, 31 | stencilBuffer: false 32 | } 33 | 34 | var size = renderer.getSize(new Vector2()) 35 | this._pixelRatio = renderer.getPixelRatio() 36 | this._width = size.width 37 | this._height = size.height 38 | 39 | renderTarget = new WebGLRenderTarget(this._width * this._pixelRatio, this._height * this._pixelRatio, parameters) 40 | renderTarget.texture.name = 'EffectComposer.rt1' 41 | } else { 42 | this._pixelRatio = 1 43 | this._width = renderTarget.width 44 | this._height = renderTarget.height 45 | } 46 | 47 | this.renderTarget1 = renderTarget 48 | this.renderTarget2 = renderTarget.clone() 49 | this.renderTarget2.texture.name = 'EffectComposer.rt2' 50 | 51 | this.writeBuffer = this.renderTarget1 52 | this.readBuffer = this.renderTarget2 53 | 54 | this.renderToScreen = true 55 | 56 | this.passes = [] 57 | 58 | // dependencies 59 | 60 | if (CopyShader === undefined) { 61 | console.error('EffectComposer relies on CopyShader') 62 | } 63 | 64 | if (ShaderPass === undefined) { 65 | console.error('EffectComposer relies on ShaderPass') 66 | } 67 | 68 | this.copyPass = new ShaderPass(CopyShader) 69 | 70 | this.clock = new Clock() 71 | } 72 | 73 | Object.assign(EffectComposer.prototype, { 74 | 75 | swapBuffers: function () { 76 | var tmp = this.readBuffer 77 | this.readBuffer = this.writeBuffer 78 | this.writeBuffer = tmp 79 | }, 80 | 81 | addPass: function (pass) { 82 | this.passes.push(pass) 83 | pass.setSize(this._width * this._pixelRatio, this._height * this._pixelRatio) 84 | }, 85 | 86 | insertPass: function (pass, index) { 87 | this.passes.splice(index, 0, pass) 88 | }, 89 | 90 | isLastEnabledPass: function (passIndex) { 91 | for (var i = passIndex + 1; i < this.passes.length; i++) { 92 | if (this.passes[ i ].enabled) { 93 | return false 94 | } 95 | } 96 | 97 | return true 98 | }, 99 | 100 | render: function (deltaTime) { 101 | // deltaTime value is in seconds 102 | 103 | if (deltaTime === undefined) { 104 | deltaTime = this.clock.getDelta() 105 | } 106 | 107 | var currentRenderTarget = this.renderer.getRenderTarget() 108 | 109 | var maskActive = false 110 | 111 | var pass; var i; var il = this.passes.length 112 | 113 | for (i = 0; i < il; i++) { 114 | pass = this.passes[ i ] 115 | 116 | if (pass.enabled === false) continue 117 | 118 | pass.renderToScreen = (this.renderToScreen && this.isLastEnabledPass(i)) 119 | pass.render(this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive) 120 | 121 | if (pass.needsSwap) { 122 | if (maskActive) { 123 | var context = this.renderer.getContext() 124 | var stencil = this.renderer.state.buffers.stencil 125 | 126 | // context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); 127 | stencil.setFunc(context.NOTEQUAL, 1, 0xffffffff) 128 | 129 | this.copyPass.render(this.renderer, this.writeBuffer, this.readBuffer, deltaTime) 130 | 131 | // context.stencilFunc( context.EQUAL, 1, 0xffffffff ); 132 | stencil.setFunc(context.EQUAL, 1, 0xffffffff) 133 | } 134 | 135 | this.swapBuffers() 136 | } 137 | 138 | if (MaskPass !== undefined) { 139 | if (pass instanceof MaskPass) { 140 | maskActive = true 141 | } else if (pass instanceof ClearMaskPass) { 142 | maskActive = false 143 | } 144 | } 145 | } 146 | 147 | this.renderer.setRenderTarget(currentRenderTarget) 148 | }, 149 | 150 | reset: function (renderTarget) { 151 | if (renderTarget === undefined) { 152 | var size = this.renderer.getSize(new Vector2()) 153 | this._pixelRatio = this.renderer.getPixelRatio() 154 | this._width = size.width 155 | this._height = size.height 156 | 157 | renderTarget = this.renderTarget1.clone() 158 | renderTarget.setSize(this._width * this._pixelRatio, this._height * this._pixelRatio) 159 | } 160 | 161 | this.renderTarget1.dispose() 162 | this.renderTarget2.dispose() 163 | this.renderTarget1 = renderTarget 164 | this.renderTarget2 = renderTarget.clone() 165 | 166 | this.writeBuffer = this.renderTarget1 167 | this.readBuffer = this.renderTarget2 168 | }, 169 | 170 | setSize: function (width, height) { 171 | this._width = width 172 | this._height = height 173 | 174 | var effectiveWidth = this._width * this._pixelRatio 175 | var effectiveHeight = this._height * this._pixelRatio 176 | 177 | this.renderTarget1.setSize(effectiveWidth, effectiveHeight) 178 | this.renderTarget2.setSize(effectiveWidth, effectiveHeight) 179 | 180 | for (var i = 0; i < this.passes.length; i++) { 181 | this.passes[ i ].setSize(effectiveWidth, effectiveHeight) 182 | } 183 | }, 184 | 185 | setPixelRatio: function (pixelRatio) { 186 | this._pixelRatio = pixelRatio 187 | 188 | this.setSize(this._width, this._height) 189 | } 190 | 191 | }) 192 | 193 | const Pass = function () { 194 | // if set to true, the pass is processed by the composer 195 | this.enabled = true 196 | 197 | // if set to true, the pass indicates to swap read and write buffer after rendering 198 | this.needsSwap = true 199 | 200 | // if set to true, the pass clears its buffer before rendering 201 | this.clear = false 202 | 203 | // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer. 204 | this.renderToScreen = false 205 | } 206 | 207 | Object.assign(Pass.prototype, { 208 | 209 | setSize: function (/* width, height */) {}, 210 | 211 | render: function (/* renderer, writeBuffer, readBuffer, deltaTime, maskActive */) { 212 | console.error('Pass: .render() must be implemented in derived pass.') 213 | } 214 | 215 | }) 216 | 217 | // Helper for passes that need to fill the viewport with a single quad. 218 | Pass.FullScreenQuad = (function () { 219 | var camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1) 220 | var geometry = new PlaneBufferGeometry(2, 2) 221 | 222 | var FullScreenQuad = function (material) { 223 | this._mesh = new Mesh(geometry, material) 224 | } 225 | 226 | Object.defineProperty(FullScreenQuad.prototype, 'material', { 227 | 228 | get: function () { 229 | return this._mesh.material 230 | }, 231 | 232 | set: function (value) { 233 | this._mesh.material = value 234 | } 235 | 236 | }) 237 | 238 | Object.assign(FullScreenQuad.prototype, { 239 | 240 | dispose: function () { 241 | this._mesh.geometry.dispose() 242 | }, 243 | 244 | render: function (renderer) { 245 | renderer.render(this._mesh, camera) 246 | } 247 | 248 | }) 249 | 250 | return FullScreenQuad 251 | })() 252 | 253 | /** 254 | * @author alteredq / http://alteredqualia.com/ 255 | */ 256 | 257 | const ShaderPass = function (shader, textureID) { 258 | Pass.call(this) 259 | 260 | this.textureID = (textureID !== undefined) ? textureID : 'tDiffuse' 261 | 262 | if (shader instanceof ShaderMaterial) { 263 | this.uniforms = shader.uniforms 264 | 265 | this.material = shader 266 | } else if (shader) { 267 | this.uniforms = UniformsUtils.clone(shader.uniforms) 268 | 269 | this.material = new ShaderMaterial({ 270 | 271 | defines: Object.assign({}, shader.defines), 272 | uniforms: this.uniforms, 273 | vertexShader: shader.vertexShader, 274 | fragmentShader: shader.fragmentShader 275 | 276 | }) 277 | } 278 | 279 | this.fsQuad = new Pass.FullScreenQuad(this.material) 280 | } 281 | 282 | ShaderPass.prototype = Object.assign(Object.create(Pass.prototype), { 283 | 284 | constructor: ShaderPass, 285 | 286 | render: function (renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { 287 | if (this.uniforms[ this.textureID ]) { 288 | this.uniforms[ this.textureID ].value = readBuffer.texture 289 | } 290 | 291 | this.fsQuad.material = this.material 292 | 293 | if (this.renderToScreen) { 294 | renderer.setRenderTarget(null) 295 | this.fsQuad.render(renderer) 296 | } else { 297 | renderer.setRenderTarget(writeBuffer) 298 | // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/js/pull/15571#issuecomment-465669600 299 | if (this.clear) renderer.clear(renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil) 300 | this.fsQuad.render(renderer) 301 | } 302 | } 303 | 304 | }) 305 | 306 | /** 307 | * @author alteredq / http://alteredqualia.com/ 308 | */ 309 | 310 | const MaskPass = function (scene, camera) { 311 | Pass.call(this) 312 | 313 | this.scene = scene 314 | this.camera = camera 315 | 316 | this.clear = true 317 | this.needsSwap = false 318 | 319 | this.inverse = false 320 | } 321 | 322 | MaskPass.prototype = Object.assign(Object.create(Pass.prototype), { 323 | 324 | constructor: MaskPass, 325 | 326 | render: function (renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { 327 | var context = renderer.getContext() 328 | var state = renderer.state 329 | 330 | // don't update color or depth 331 | 332 | state.buffers.color.setMask(false) 333 | state.buffers.depth.setMask(false) 334 | 335 | // lock buffers 336 | 337 | state.buffers.color.setLocked(true) 338 | state.buffers.depth.setLocked(true) 339 | 340 | // set up stencil 341 | 342 | var writeValue, clearValue 343 | 344 | if (this.inverse) { 345 | writeValue = 0 346 | clearValue = 1 347 | } else { 348 | writeValue = 1 349 | clearValue = 0 350 | } 351 | 352 | state.buffers.stencil.setTest(true) 353 | state.buffers.stencil.setOp(context.REPLACE, context.REPLACE, context.REPLACE) 354 | state.buffers.stencil.setFunc(context.ALWAYS, writeValue, 0xffffffff) 355 | state.buffers.stencil.setClear(clearValue) 356 | state.buffers.stencil.setLocked(true) 357 | 358 | // draw into the stencil buffer 359 | 360 | renderer.setRenderTarget(readBuffer) 361 | if (this.clear) renderer.clear() 362 | renderer.render(this.scene, this.camera) 363 | 364 | renderer.setRenderTarget(writeBuffer) 365 | if (this.clear) renderer.clear() 366 | renderer.render(this.scene, this.camera) 367 | 368 | // unlock color and depth buffer for subsequent rendering 369 | 370 | state.buffers.color.setLocked(false) 371 | state.buffers.depth.setLocked(false) 372 | 373 | // only render where stencil is set to 1 374 | 375 | state.buffers.stencil.setLocked(false) 376 | state.buffers.stencil.setFunc(context.EQUAL, 1, 0xffffffff) // draw if == 1 377 | state.buffers.stencil.setOp(context.KEEP, context.KEEP, context.KEEP) 378 | state.buffers.stencil.setLocked(true) 379 | } 380 | 381 | }) 382 | 383 | const ClearMaskPass = function () { 384 | Pass.call(this) 385 | 386 | this.needsSwap = false 387 | } 388 | 389 | ClearMaskPass.prototype = Object.create(Pass.prototype) 390 | 391 | Object.assign(ClearMaskPass.prototype, { 392 | 393 | render: function (renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */) { 394 | renderer.state.buffers.stencil.setLocked(false) 395 | renderer.state.buffers.stencil.setTest(false) 396 | } 397 | 398 | }) 399 | 400 | /** 401 | * @author alteredq / http://alteredqualia.com/ 402 | */ 403 | 404 | const RenderPass = function (scene, camera, overrideMaterial, clearColor, clearAlpha) { 405 | Pass.call(this) 406 | 407 | this.scene = scene 408 | this.camera = camera 409 | 410 | this.overrideMaterial = overrideMaterial 411 | 412 | this.clearColor = clearColor 413 | this.clearAlpha = (clearAlpha !== undefined) ? clearAlpha : 0 414 | 415 | this.clear = true 416 | this.clearDepth = false 417 | this.needsSwap = false 418 | } 419 | 420 | RenderPass.prototype = Object.assign(Object.create(Pass.prototype), { 421 | 422 | constructor: RenderPass, 423 | 424 | render: function (renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { 425 | var oldAutoClear = renderer.autoClear 426 | renderer.autoClear = false 427 | 428 | this.scene.overrideMaterial = this.overrideMaterial 429 | 430 | var oldClearColor, oldClearAlpha 431 | 432 | if (this.clearColor) { 433 | oldClearColor = renderer.getClearColor().getHex() 434 | oldClearAlpha = renderer.getClearAlpha() 435 | 436 | renderer.setClearColor(this.clearColor, this.clearAlpha) 437 | } 438 | 439 | if (this.clearDepth) { 440 | renderer.clearDepth() 441 | } 442 | 443 | renderer.setRenderTarget(this.renderToScreen ? null : readBuffer) 444 | 445 | // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/js/pull/15571#issuecomment-465669600 446 | if (this.clear) renderer.clear(renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil) 447 | renderer.render(this.scene, this.camera) 448 | 449 | if (this.clearColor) { 450 | renderer.setClearColor(oldClearColor, oldClearAlpha) 451 | } 452 | 453 | this.scene.overrideMaterial = null 454 | renderer.autoClear = oldAutoClear 455 | } 456 | 457 | }) 458 | 459 | /** 460 | * @author alteredq / http://alteredqualia.com/ 461 | */ 462 | 463 | const TexturePass = function (map, opacity) { 464 | Pass.call(this) 465 | 466 | if (CopyShader === undefined) { console.error('TexturePass relies on CopyShader') } 467 | 468 | var shader = CopyShader 469 | 470 | this.map = map 471 | this.opacity = (opacity !== undefined) ? opacity : 1.0 472 | 473 | this.uniforms = UniformsUtils.clone(shader.uniforms) 474 | 475 | this.material = new ShaderMaterial({ 476 | 477 | uniforms: this.uniforms, 478 | vertexShader: shader.vertexShader, 479 | fragmentShader: shader.fragmentShader, 480 | depthTest: false, 481 | depthWrite: false 482 | 483 | }) 484 | 485 | this.needsSwap = false 486 | 487 | this.fsQuad = new Pass.FullScreenQuad(null) 488 | } 489 | 490 | TexturePass.prototype = Object.assign(Object.create(Pass.prototype), { 491 | 492 | constructor: TexturePass, 493 | 494 | render: function (renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { 495 | var oldAutoClear = renderer.autoClear 496 | renderer.autoClear = false 497 | 498 | this.fsQuad.material = this.material 499 | 500 | this.uniforms[ 'opacity' ].value = this.opacity 501 | this.uniforms[ 'tDiffuse' ].value = this.map 502 | this.material.transparent = (this.opacity < 1.0) 503 | 504 | renderer.setRenderTarget(this.renderToScreen ? null : readBuffer) 505 | if (this.clear) renderer.clear() 506 | this.fsQuad.render(renderer) 507 | 508 | renderer.autoClear = oldAutoClear 509 | } 510 | 511 | }) 512 | 513 | /** 514 | * @author bhouston / http://clara.io/ 515 | * 516 | * Luminosity 517 | * http://en.wikipedia.org/wiki/Luminosity 518 | */ 519 | 520 | const LuminosityHighPassShader = { 521 | 522 | shaderID: 'luminosityHighPass', 523 | 524 | uniforms: { 525 | 526 | 'tDiffuse': { value: null }, 527 | 'luminosityThreshold': { value: 1.0 }, 528 | 'smoothWidth': { value: 1.0 }, 529 | 'defaultColor': { value: new Color(0x000000) }, 530 | 'defaultOpacity': { value: 0.0 } 531 | 532 | }, 533 | 534 | vertexShader: [ 535 | 536 | 'varying vec2 vUv;', 537 | 538 | 'void main() {', 539 | 540 | ' vUv = uv;', 541 | 542 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 543 | 544 | '}' 545 | 546 | ].join('\n'), 547 | 548 | fragmentShader: [ 549 | 550 | 'uniform sampler2D tDiffuse;', 551 | 'uniform vec3 defaultColor;', 552 | 'uniform float defaultOpacity;', 553 | 'uniform float luminosityThreshold;', 554 | 'uniform float smoothWidth;', 555 | 556 | 'varying vec2 vUv;', 557 | 558 | 'void main() {', 559 | 560 | ' vec4 texel = texture2D( tDiffuse, vUv );', 561 | 562 | ' vec3 luma = vec3( 0.299, 0.587, 0.114 );', 563 | 564 | ' float v = dot( texel.xyz, luma );', 565 | 566 | ' vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity );', 567 | 568 | ' float alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v );', 569 | 570 | ' gl_FragColor = mix( outputColor, texel, alpha );', 571 | 572 | '}' 573 | 574 | ].join('\n') 575 | 576 | } 577 | 578 | /** 579 | * @author spidersharma / http://eduperiment.com/ 580 | * 581 | * Inspired from Unreal Engine 582 | * https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ 583 | */ 584 | 585 | const UnrealBloomPass = function (resolution, strength, radius, threshold, alphaSum) { 586 | Pass.call(this) 587 | 588 | this.strength = (strength !== undefined) ? strength : 1 589 | this.radius = radius 590 | this.threshold = threshold 591 | this.resolution = (resolution !== undefined) ? new Vector2(resolution.x, resolution.y) : new Vector2(256, 256) 592 | 593 | // create color only once here, reuse it later inside the render function 594 | this.clearColor = new Color(0, 0, 0) 595 | 596 | // render targets 597 | var pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat } 598 | this.renderTargetsHorizontal = [] 599 | this.renderTargetsVertical = [] 600 | this.nMips = 5 601 | var resx = Math.round(this.resolution.x / 2) 602 | var resy = Math.round(this.resolution.y / 2) 603 | 604 | this.renderTargetBright = new WebGLRenderTarget(resx, resy, pars) 605 | this.renderTargetBright.texture.name = 'UnrealBloomPass.bright' 606 | this.renderTargetBright.texture.generateMipmaps = false 607 | 608 | for (var i = 0; i < this.nMips; i++) { 609 | var renderTargetHorizonal = new WebGLRenderTarget(resx, resy, pars) 610 | 611 | renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i 612 | renderTargetHorizonal.texture.generateMipmaps = false 613 | 614 | this.renderTargetsHorizontal.push(renderTargetHorizonal) 615 | 616 | var renderTargetVertical = new WebGLRenderTarget(resx, resy, pars) 617 | 618 | renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i 619 | renderTargetVertical.texture.generateMipmaps = false 620 | 621 | this.renderTargetsVertical.push(renderTargetVertical) 622 | 623 | resx = Math.round(resx / 2) 624 | 625 | resy = Math.round(resy / 2) 626 | } 627 | 628 | // luminosity high pass material 629 | 630 | if (LuminosityHighPassShader === undefined) { console.error('UnrealBloomPass relies on LuminosityHighPassShader') } 631 | 632 | var highPassShader = LuminosityHighPassShader 633 | this.highPassUniforms = UniformsUtils.clone(highPassShader.uniforms) 634 | 635 | this.highPassUniforms[ 'luminosityThreshold' ].value = threshold 636 | this.highPassUniforms[ 'smoothWidth' ].value = 0.01 637 | 638 | this.materialHighPassFilter = new ShaderMaterial({ 639 | uniforms: this.highPassUniforms, 640 | vertexShader: highPassShader.vertexShader, 641 | fragmentShader: highPassShader.fragmentShader, 642 | defines: {} 643 | }) 644 | 645 | // Gaussian Blur Materials 646 | this.separableBlurMaterials = [] 647 | var kernelSizeArray = [ 3, 5, 7, 9, 11 ] 648 | var resx = Math.round(this.resolution.x / 2) 649 | var resy = Math.round(this.resolution.y / 2) 650 | 651 | for (var i = 0; i < this.nMips; i++) { 652 | this.separableBlurMaterials.push(this.getSeperableBlurMaterial(kernelSizeArray[ i ], alphaSum)) 653 | 654 | this.separableBlurMaterials[ i ].uniforms[ 'texSize' ].value = new Vector2(resx, resy) 655 | 656 | resx = Math.round(resx / 2) 657 | 658 | resy = Math.round(resy / 2) 659 | } 660 | 661 | // Composite material 662 | this.compositeMaterial = this.getCompositeMaterial(this.nMips) 663 | this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture 664 | this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture 665 | this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture 666 | this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture 667 | this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture 668 | this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength 669 | this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1 670 | this.compositeMaterial.needsUpdate = true 671 | 672 | var bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ] 673 | this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors 674 | this.bloomTintColors = [ new Vector3(1, 1, 1), new Vector3(1, 1, 1), new Vector3(1, 1, 1), 675 | new Vector3(1, 1, 1), new Vector3(1, 1, 1) ] 676 | this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors 677 | 678 | // copy material 679 | if (CopyShader === undefined) { 680 | console.error('UnrealBloomPass relies on CopyShader') 681 | } 682 | 683 | var copyShader = CopyShader 684 | 685 | this.copyUniforms = UniformsUtils.clone(copyShader.uniforms) 686 | this.copyUniforms[ 'opacity' ].value = 1.0 687 | 688 | this.materialCopy = new ShaderMaterial({ 689 | uniforms: this.copyUniforms, 690 | vertexShader: copyShader.vertexShader, 691 | fragmentShader: copyShader.fragmentShader, 692 | blending: AdditiveBlending, 693 | depthTest: false, 694 | depthWrite: false, 695 | transparent: true 696 | }) 697 | 698 | this.enabled = true 699 | this.needsSwap = false 700 | 701 | this.oldClearColor = new Color() 702 | this.oldClearAlpha = 1 703 | 704 | this.basic = new MeshBasicMaterial() 705 | 706 | this.fsQuad = new Pass.FullScreenQuad(null) 707 | } 708 | 709 | UnrealBloomPass.prototype = Object.assign(Object.create(Pass.prototype), { 710 | 711 | constructor: UnrealBloomPass, 712 | 713 | dispose: function () { 714 | for (var i = 0; i < this.renderTargetsHorizontal.length; i++) { 715 | this.renderTargetsHorizontal[ i ].dispose() 716 | } 717 | 718 | for (var i = 0; i < this.renderTargetsVertical.length; i++) { 719 | this.renderTargetsVertical[ i ].dispose() 720 | } 721 | 722 | this.renderTargetBright.dispose() 723 | }, 724 | 725 | setSize: function (width, height) { 726 | var resx = Math.round(width / 2) 727 | var resy = Math.round(height / 2) 728 | 729 | this.renderTargetBright.setSize(resx, resy) 730 | 731 | for (var i = 0; i < this.nMips; i++) { 732 | this.renderTargetsHorizontal[ i ].setSize(resx, resy) 733 | this.renderTargetsVertical[ i ].setSize(resx, resy) 734 | 735 | this.separableBlurMaterials[ i ].uniforms[ 'texSize' ].value = new Vector2(resx, resy) 736 | 737 | resx = Math.round(resx / 2) 738 | resy = Math.round(resy / 2) 739 | } 740 | }, 741 | 742 | render: function (renderer, writeBuffer, readBuffer, deltaTime, maskActive) { 743 | this.oldClearColor.copy(renderer.getClearColor()) 744 | this.oldClearAlpha = renderer.getClearAlpha() 745 | var oldAutoClear = renderer.autoClear 746 | renderer.autoClear = false 747 | 748 | renderer.setClearColor(this.clearColor, 0) 749 | 750 | if (maskActive) renderer.state.buffers.stencil.setTest(false) 751 | 752 | // Render input to screen 753 | 754 | if (this.renderToScreen) { 755 | this.fsQuad.material = this.basic 756 | this.basic.map = readBuffer.texture 757 | 758 | renderer.setRenderTarget(null) 759 | renderer.clear() 760 | this.fsQuad.render(renderer) 761 | } 762 | 763 | // 1. Extract Bright Areas 764 | 765 | this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture 766 | this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold 767 | this.fsQuad.material = this.materialHighPassFilter 768 | 769 | renderer.setRenderTarget(this.renderTargetBright) 770 | renderer.clear() 771 | this.fsQuad.render(renderer) 772 | 773 | // 2. Blur All the mips progressively 774 | 775 | var inputRenderTarget = this.renderTargetBright 776 | 777 | for (var i = 0; i < this.nMips; i++) { 778 | this.fsQuad.material = this.separableBlurMaterials[ i ] 779 | 780 | this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture 781 | this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX 782 | renderer.setRenderTarget(this.renderTargetsHorizontal[ i ]) 783 | renderer.clear() 784 | this.fsQuad.render(renderer) 785 | 786 | this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture 787 | this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY 788 | renderer.setRenderTarget(this.renderTargetsVertical[ i ]) 789 | renderer.clear() 790 | this.fsQuad.render(renderer) 791 | 792 | inputRenderTarget = this.renderTargetsVertical[ i ] 793 | } 794 | 795 | // Composite All the mips 796 | 797 | this.fsQuad.material = this.compositeMaterial 798 | this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength 799 | this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius 800 | this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors 801 | 802 | renderer.setRenderTarget(this.renderTargetsHorizontal[ 0 ]) 803 | renderer.clear() 804 | this.fsQuad.render(renderer) 805 | 806 | // Blend it additively over the input texture 807 | 808 | this.fsQuad.material = this.materialCopy 809 | this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture 810 | 811 | if (maskActive) renderer.state.buffers.stencil.setTest(true) 812 | 813 | if (this.renderToScreen) { 814 | renderer.setRenderTarget(null) 815 | this.fsQuad.render(renderer) 816 | } else { 817 | renderer.setRenderTarget(readBuffer) 818 | this.fsQuad.render(renderer) 819 | } 820 | 821 | // Restore renderer settings 822 | 823 | renderer.setClearColor(this.oldClearColor, this.oldClearAlpha) 824 | renderer.autoClear = oldAutoClear 825 | }, 826 | 827 | getSeperableBlurMaterial: function (kernelRadius, alphaSum) { 828 | return new ShaderMaterial({ 829 | 830 | defines: { 831 | 'KERNEL_RADIUS': kernelRadius, 832 | 'SIGMA': kernelRadius 833 | }, 834 | 835 | uniforms: { 836 | 'colorTexture': { value: null }, 837 | 'texSize': { value: new Vector2(0.5, 0.5) }, 838 | 'direction': { value: new Vector2(0.5, 0.5) }, 839 | 'alphaSum': { value: alphaSum } 840 | }, 841 | 842 | vertexShader: 843 | 'varying vec2 vUv;\n\ 844 | void main() {\n\ 845 | vUv = uv;\n\ 846 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 847 | }', 848 | 849 | fragmentShader: 850 | '#include \ 851 | varying vec2 vUv;\n\ 852 | uniform sampler2D colorTexture;\n\ 853 | uniform vec2 texSize;\ 854 | uniform vec2 direction;\ 855 | uniform float alphaSum;\ 856 | \ 857 | float gaussianPdf(in float x, in float sigma) {\ 858 | return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;\ 859 | }\ 860 | void main() {\n\ 861 | vec2 invSize = 1.0 / texSize;\ 862 | float fSigma = float(SIGMA);\ 863 | float weightSum = gaussianPdf(0.0, fSigma);\ 864 | float alphaSum = alphaSum;\ 865 | vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;\ 866 | for( int i = 1; i < KERNEL_RADIUS; i ++ ) {\ 867 | float x = float(i);\ 868 | float w = gaussianPdf(x, fSigma);\ 869 | vec2 uvOffset = direction * invSize * x;\ 870 | vec4 sample1 = texture2D( colorTexture, vUv + uvOffset);\ 871 | vec4 sample2 = texture2D( colorTexture, vUv - uvOffset);\ 872 | diffuseSum += (sample1.rgb + sample2.rgb) * w;\ 873 | alphaSum += (sample1.a + sample2.a) * w;\ 874 | weightSum += 2.0 * w;\ 875 | }\ 876 | gl_FragColor = vec4(diffuseSum/weightSum, alphaSum/weightSum);\n\ 877 | }' 878 | }) 879 | }, 880 | 881 | getCompositeMaterial: function (nMips) { 882 | return new ShaderMaterial({ 883 | 884 | defines: { 885 | 'NUM_MIPS': nMips 886 | }, 887 | 888 | uniforms: { 889 | 'blurTexture1': { value: null }, 890 | 'blurTexture2': { value: null }, 891 | 'blurTexture3': { value: null }, 892 | 'blurTexture4': { value: null }, 893 | 'blurTexture5': { value: null }, 894 | 'dirtTexture': { value: null }, 895 | 'bloomStrength': { value: 1.0 }, 896 | 'bloomFactors': { value: null }, 897 | 'bloomTintColors': { value: null }, 898 | 'bloomRadius': { value: 0.0 } 899 | }, 900 | 901 | vertexShader: 902 | 'varying vec2 vUv;\n\ 903 | void main() {\n\ 904 | vUv = uv;\n\ 905 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 906 | }', 907 | 908 | fragmentShader: 909 | 'varying vec2 vUv;\ 910 | uniform sampler2D blurTexture1;\ 911 | uniform sampler2D blurTexture2;\ 912 | uniform sampler2D blurTexture3;\ 913 | uniform sampler2D blurTexture4;\ 914 | uniform sampler2D blurTexture5;\ 915 | uniform sampler2D dirtTexture;\ 916 | uniform float bloomStrength;\ 917 | uniform float bloomRadius;\ 918 | uniform float bloomFactors[NUM_MIPS];\ 919 | uniform vec3 bloomTintColors[NUM_MIPS];\ 920 | \ 921 | float lerpBloomFactor(const in float factor) { \ 922 | float mirrorFactor = 1.2 - factor;\ 923 | return mix(factor, mirrorFactor, bloomRadius);\ 924 | }\ 925 | \ 926 | void main() {\ 927 | gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + \ 928 | lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + \ 929 | lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + \ 930 | lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + \ 931 | lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );\ 932 | }' 933 | }) 934 | } 935 | 936 | }) 937 | 938 | UnrealBloomPass.BlurDirectionX = new Vector2(1.0, 0.0) 939 | UnrealBloomPass.BlurDirectionY = new Vector2(0.0, 1.0) 940 | 941 | export { EffectComposer, ShaderPass, RenderPass, UnrealBloomPass, TexturePass } 942 | -------------------------------------------------------------------------------- /src/post/Film.js: -------------------------------------------------------------------------------- 1 | const Film = { 2 | 3 | uniforms: { 4 | 5 | tDiffuse: { value: null }, 6 | time: { value: 0.0 }, 7 | nIntensity: { value: 0.4 }, 8 | sIntensity: { value: 0.0 }, 9 | sCount: { value: 0 }, 10 | grayscale: { value: 0 } 11 | 12 | }, 13 | 14 | vertexShader: ` 15 | 16 | varying vec2 vUv; 17 | 18 | void main() { 19 | 20 | vUv = uv; 21 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 22 | 23 | } 24 | 25 | `, 26 | 27 | fragmentShader: ` 28 | 29 | #include 30 | 31 | // control parameter 32 | uniform float time; 33 | 34 | uniform bool grayscale; 35 | 36 | // noise effect intensity value (0 = no effect, 1 = full effect) 37 | uniform float nIntensity; 38 | 39 | // scanlines effect intensity value (0 = no effect, 1 = full effect) 40 | uniform float sIntensity; 41 | 42 | // scanlines effect count value (0 = no effect, 4096 = full effect) 43 | uniform float sCount; 44 | 45 | uniform sampler2D tDiffuse; 46 | 47 | varying vec2 vUv; 48 | 49 | void main() { 50 | 51 | // sample the source 52 | vec4 cTextureScreen = texture2D( tDiffuse, vUv ); 53 | 54 | // make some noise 55 | float dx = rand( vUv + time ); 56 | 57 | // add noise 58 | vec3 cResult = cTextureScreen.rgb + cTextureScreen.rgb * clamp( 0.1 + dx, 0.0, 1.0 ); 59 | 60 | // get us a sine and cosine 61 | vec2 sc = vec2( sin( vUv.y * sCount ), cos( vUv.y * sCount ) ); 62 | 63 | // add scanlines 64 | cResult += cTextureScreen.rgb * vec3( sc.x, sc.y, sc.x ) * sIntensity; 65 | 66 | // interpolate between source and result by intensity 67 | cResult = cTextureScreen.rgb + clamp( nIntensity, 0.0,1.0 ) * ( cResult - cTextureScreen.rgb ); 68 | 69 | // convert to grayscale if desired 70 | if( grayscale ) { 71 | 72 | cResult = vec3( cResult.r * 0.3 + cResult.g * 0.59 + cResult.b * 0.11 ); 73 | 74 | } 75 | 76 | gl_FragColor = vec4( cResult, cTextureScreen.a ); 77 | 78 | } 79 | 80 | ` 81 | 82 | } 83 | 84 | export default Film 85 | -------------------------------------------------------------------------------- /src/post/Vignette.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Vignette shader 5 | * based on PaintEffect postprocess from ro.me 6 | * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js 7 | */ 8 | 9 | const Vignette = { 10 | 11 | uniforms: { 12 | 13 | tDiffuse: { value: null }, 14 | offset: { value: 1.0 }, 15 | darkness: { value: 1.3 } 16 | 17 | }, 18 | 19 | vertexShader: ` 20 | 21 | varying vec2 vUv; 22 | 23 | void main() { 24 | 25 | vUv = uv; 26 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 27 | 28 | } 29 | 30 | `, 31 | 32 | fragmentShader: ` 33 | 34 | uniform float offset; 35 | uniform float darkness; 36 | 37 | uniform sampler2D tDiffuse; 38 | 39 | varying vec2 vUv; 40 | 41 | float random(vec2 co){ 42 | return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453); 43 | } 44 | 45 | // based on https://www.shadertoy.com/view/MslGR8 46 | vec3 dithering( vec3 color ) { 47 | //Calculate grid position 48 | float grid_position = random( gl_FragCoord.xy ); 49 | //Shift the individual colors differently, thus making it even harder to see the dithering pattern 50 | vec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 ); 51 | //modify shift acording to grid position. 52 | dither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position ); 53 | //shift the color by dither_shift 54 | return color + dither_shift_RGB; 55 | } 56 | 57 | void main() { 58 | 59 | // Eskils vignette 60 | 61 | vec4 texel = texture2D( tDiffuse, vUv ); 62 | vec2 uv = ( vUv - vec2( 0.5 ) ) * vec2( offset ); 63 | gl_FragColor = vec4( dithering( mix( texel.rgb, vec3( 1.0 - darkness ), dot( uv, uv ) ) ), texel.a ); 64 | 65 | /* 66 | // alternative version from glfx.js 67 | // this one makes more "dusty" look (as opposed to "burned") 68 | 69 | "vec4 color = texture2D( tDiffuse, vUv );", 70 | "float dist = distance( vUv, vec2( 0.5 ) );", 71 | "color.rgb *= smoothstep( 0.8, offset * 0.799, dist *( darkness + offset ) );", 72 | "gl_FragColor = color;", 73 | */ 74 | 75 | } 76 | 77 | ` 78 | 79 | } 80 | 81 | export default Vignette 82 | -------------------------------------------------------------------------------- /src/shaders/applyQuaternionToVector.glsl: -------------------------------------------------------------------------------- 1 | vec3 applyQuaternionToVector( vec4 q, vec3 v ){ 2 | return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v ); 3 | } 4 | 5 | 6 | #pragma glslify: export(applyQuaternionToVector) 7 | -------------------------------------------------------------------------------- /src/shaders/blur.frag: -------------------------------------------------------------------------------- 1 | // https://github.com/Jam3/glsl-fast-gaussian-blur/blob/master/9.glsl 2 | vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 3 | vec4 color = vec4(0.0); 4 | vec2 off1 = vec2(1.3846153846) * direction; 5 | vec2 off2 = vec2(3.2307692308) * direction; 6 | color += texture2D(image, uv) * 0.2270270270; 7 | color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162; 8 | color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162; 9 | color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703; 10 | color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703; 11 | return color; 12 | } 13 | 14 | vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 15 | vec4 color = vec4(0.0); 16 | vec2 off1 = vec2(1.411764705882353) * direction; 17 | vec2 off2 = vec2(3.2941176470588234) * direction; 18 | vec2 off3 = vec2(5.176470588235294) * direction; 19 | color += texture2D(image, uv) * 0.1964825501511404; 20 | color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344; 21 | color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344; 22 | color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732; 23 | color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732; 24 | color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057; 25 | color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057; 26 | return color; 27 | } 28 | 29 | uniform sampler2D uTexture; 30 | varying vec2 vUv; 31 | 32 | void main() { 33 | 34 | vec2 res = vec2(1280.0, 720.0); 35 | 36 | vec4 blurH = blur9(uTexture, vUv, res, vec2(1.0, 0.0)); 37 | vec4 blurV = blur9(uTexture, vUv, res, vec2(0.0, 1.0)); 38 | 39 | gl_FragColor = mix(blurH, blurV, 0.5); 40 | 41 | } -------------------------------------------------------------------------------- /src/shaders/curlNoise.glsl: -------------------------------------------------------------------------------- 1 | // https://github.com/fazeaction/webgl-001/blob/master/src/shaders/curl.glsl 2 | 3 | #pragma glslify: snoise = require(glsl-noise/simplex/3d) 4 | // Using @eddietree implementation from 5 | // His brilliant demo 'Artifacts' 6 | 7 | vec3 snoiseVec3( vec3 x ){ 8 | 9 | float s = snoise(vec3( x )); 10 | float s1 = snoise(vec3( x.y - 19.1 , x.z + 33.4 , x.x + 47.2 )); 11 | float s2 = snoise(vec3( x.z + 74.2 , x.x - 124.5 , x.y + 99.4 )); 12 | vec3 c = vec3( s , s1 , s2 ); 13 | return c; 14 | 15 | } 16 | 17 | 18 | vec3 curlNoise( vec3 p ){ 19 | 20 | const float e = 1e-1; 21 | vec3 dx = vec3( e , 0.0 , 0.0 ); 22 | vec3 dy = vec3( 0.0 , e , 0.0 ); 23 | vec3 dz = vec3( 0.0 , 0.0 , e ); 24 | 25 | vec3 p_x0 = snoiseVec3( p - dx ); 26 | vec3 p_x1 = snoiseVec3( p + dx ); 27 | vec3 p_y0 = snoiseVec3( p - dy ); 28 | vec3 p_y1 = snoiseVec3( p + dy ); 29 | vec3 p_z0 = snoiseVec3( p - dz ); 30 | vec3 p_z1 = snoiseVec3( p + dz ); 31 | 32 | float x = p_y1.z - p_y0.z - p_z1.y + p_z0.y; 33 | float y = p_z1.x - p_z0.x - p_x1.z + p_x0.z; 34 | float z = p_x1.y - p_x0.y - p_y1.x + p_y0.x; 35 | 36 | const float divisor = 1.0 / ( 2.0 * e ); 37 | vec3 result = normalize( vec3( x , y , z ) * divisor ); 38 | // return vec3( 39 | // floor(result.x + 0.5), 40 | // floor(result.y + 0.5), 41 | // floor(result.z + 0.5) 42 | // ); 43 | return result; 44 | 45 | } 46 | 47 | #pragma glslify: export(curlNoise) -------------------------------------------------------------------------------- /src/shaders/edgeDetect.frag: -------------------------------------------------------------------------------- 1 | #extension GL_OES_standard_derivatives : enable 2 | 3 | // #pragma glslify: blend = require(glsl-blend/overlay) 4 | // #pragma glslify: edgeDetect = require(glsl-edge-detection) 5 | 6 | uniform sampler2D uTexture; 7 | varying vec2 vUv; 8 | 9 | void main() { 10 | 11 | // fast edge detection https://www.shadertoy.com/view/MdGGRt 12 | vec4 o = vec4(1.0); 13 | 14 | float edgeThreshold = 50.0; 15 | 16 | o -= o - length(fwidth(texture2D(uTexture, vUv))) * edgeThreshold; 17 | 18 | gl_FragColor = 1.0 - o; 19 | } -------------------------------------------------------------------------------- /src/shaders/empty.frag: -------------------------------------------------------------------------------- 1 | void main() { 2 | gl_FragColor = vec4(0.0); 3 | } -------------------------------------------------------------------------------- /src/shaders/markers.frag: -------------------------------------------------------------------------------- 1 | uniform vec3 diffuse; 2 | uniform float opacity; 3 | 4 | #ifndef FLAT_SHADED 5 | 6 | varying vec3 vNormal; 7 | 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | void main() { 26 | 27 | #include 28 | 29 | vec4 diffuseColor = vec4( diffuse, opacity ); 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); 39 | 40 | // accumulation (baked indirect lighting only) 41 | #ifdef USE_LIGHTMAP 42 | 43 | reflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity; 44 | 45 | #else 46 | 47 | reflectedLight.indirectDiffuse += vec3( 1.0 ); 48 | 49 | #endif 50 | 51 | // modulation 52 | #include 53 | 54 | reflectedLight.indirectDiffuse *= diffuseColor.rgb; 55 | 56 | vec3 outgoingLight = reflectedLight.indirectDiffuse; 57 | 58 | #include 59 | 60 | gl_FragColor = vec4( outgoingLight, diffuseColor.a ); 61 | 62 | #include 63 | #include 64 | #include 65 | #include 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/shaders/markers.vert: -------------------------------------------------------------------------------- 1 | #pragma glslify: applyQuaternionToVector = require('./applyQuaternionToVector'); 2 | #pragma glslify: rotationMatrix = require('./rotationMatrix'); 3 | 4 | uniform float uTime; 5 | 6 | attribute vec3 offset; 7 | attribute float scale; 8 | attribute vec4 quaternion; 9 | attribute float id; 10 | attribute float isSelected; 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | void main() { 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifdef USE_ENVMAP 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #endif 38 | 39 | #include 40 | 41 | // scale 42 | transformed.xyz *= scale; 43 | 44 | mat4 rotation = rotationMatrix(offset.xyz * vec3(0.0, 0.0, 1.0), (id + uTime * 1.5)); 45 | vec4 newPos = rotation * vec4( transformed, 1.0 ); 46 | 47 | transformed.xyz = newPos.xyz; 48 | transformed.xyz = applyQuaternionToVector( quaternion, transformed.xyz ); 49 | transformed.xyz += offset.xyz; 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/shaders/mousePos.frag: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | uniform sampler2D uMousePosTexture; 4 | uniform vec2 uMousePos; 5 | uniform vec2 uPrevMousePos; 6 | uniform float uAspect; 7 | 8 | float distToSegment( vec2 x1, vec2 x2, vec2 p ) { 9 | 10 | vec2 v = x2 - x1; 11 | vec2 w = p - x1; 12 | 13 | float c1 = dot(w,v); 14 | float c2 = dot(v,v); 15 | 16 | float div = mix( c2, c1, step( c2, c1 ) ); 17 | 18 | float mult = step( 0.0, c1 ); 19 | 20 | float b = c1 * mult / div; 21 | vec2 pb = x1 + b*v; 22 | 23 | return distance( p, pb ); 24 | 25 | } 26 | 27 | void main() { 28 | 29 | vec4 mousePosTexture = texture2D(uMousePosTexture, vUv); 30 | 31 | float mouseRadius = 0.35; 32 | 33 | float decay = 0.96; 34 | vec4 color = vec4(0.0, 0.0, 0.0, 1.0); 35 | 36 | // vec2 correctedMousePos = uMousePos * vec2(uAspect, 1.0); 37 | 38 | float dist = distToSegment(uPrevMousePos * vec2(uAspect, 1.0), uMousePos * vec2(uAspect, 1.0), vUv * vec2(uAspect, 1.0)); 39 | 40 | if (dist < mouseRadius) { 41 | float mouse = pow(1.0-abs(dist) * 1.0, 125.0); 42 | color.b = mouse; 43 | 44 | vec2 dir = vec2((uMousePos * vec2(uAspect, 1.0)) - (uPrevMousePos * vec2(uAspect, 1.0) ) ); 45 | color.xy = dir*0.5; 46 | } 47 | 48 | color += mousePosTexture * decay; 49 | 50 | gl_FragColor = color; 51 | } -------------------------------------------------------------------------------- /src/shaders/particles.frag: -------------------------------------------------------------------------------- 1 | 2 | #pragma glslify: snoise3 = require(glsl-noise/simplex/3d) 3 | 4 | uniform sampler2D uTexture; 5 | uniform vec2 uTextureSize; 6 | uniform float uTime; 7 | uniform float uAspect; 8 | uniform float uIsMobile; 9 | 10 | varying vec2 vPUv; 11 | varying vec2 vUv; 12 | varying vec4 vMousePosTexture; 13 | varying float vCamDist; 14 | 15 | void main() { 16 | 17 | vec2 uv = vUv * vec2(uAspect, 1.0); 18 | vec2 puv = vPUv; 19 | 20 | // pixel color 21 | vec4 color = texture2D(uTexture, puv); 22 | 23 | if (color.r + color.g + color.b < 0.01) { 24 | discard; 25 | } 26 | 27 | // greyscale 28 | float grey = color.r * 0.21 + color.g * 0.71 + color.b * 0.07; 29 | color = vec4(grey * 1.0, grey * 0.2, grey * 0.2, 1.0); 30 | 31 | //float noiseVal = snoise3(vec3(puv * 2.0, uTime * 0.2 )); 32 | 33 | // color.r *= max( 0.4, noiseVal ); 34 | // color += noiseVal * 0.02; 35 | 36 | float border = 1.0; 37 | float radius = 0.5; 38 | if (uIsMobile == 1.0) { 39 | border = 1.0; 40 | radius = 0.6; 41 | } 42 | 43 | // float radius = 0.5 + (vCamDist * 0.005); 44 | // float radius = 0.5 + (1.0-(uTextureSize.x+uTextureSize.y) * 0.001); 45 | float dist = radius - distance(uv, vec2(0.5)); 46 | float t = smoothstep(0.0, border, dist); 47 | if (uIsMobile == 1.0) { 48 | t *= 2.0; 49 | } 50 | 51 | color.a *= t; 52 | 53 | gl_FragColor = color; 54 | 55 | } -------------------------------------------------------------------------------- /src/shaders/particles.vert: -------------------------------------------------------------------------------- 1 | uniform vec2 uTextureSize; 2 | uniform sampler2D uTexture; 3 | uniform sampler2D uMousePosTexture; 4 | uniform sampler2D positionTexture; 5 | uniform sampler2D defaultPositionTexture; 6 | uniform sampler2D initialPositionTexture; 7 | uniform vec2 uMousePos; 8 | uniform vec2 uPrevMousePos; 9 | uniform float uNoiseMix; 10 | uniform float uTime; 11 | uniform float uAspect; 12 | uniform vec3 uCamPos; 13 | 14 | attribute vec2 offset; 15 | attribute vec3 tPosition; 16 | 17 | varying vec2 vUv; 18 | varying vec2 vPUv; 19 | varying vec4 vMousePosTexture; 20 | varying float vCamDist; 21 | 22 | void main() { 23 | 24 | vCamDist = dot(uCamPos, uCamPos); 25 | 26 | vUv = uv; 27 | 28 | // particle uv 29 | vec2 puv = offset.xy / uTextureSize; 30 | vPUv = puv; 31 | 32 | vec4 color = texture2D(uTexture, puv); 33 | 34 | if (color.r + color.g + color.b > 0.01) { 35 | 36 | #include 37 | 38 | vec4 noisePositionData = (texture2D(positionTexture, tPosition.xy) / (uTextureSize.x )); 39 | vec4 defaultPosition = (texture2D(defaultPositionTexture, tPosition.xy) /(uTextureSize.x )); 40 | vec4 initialPosition = (texture2D(initialPositionTexture, tPosition.xy) / (uTextureSize.x )); 41 | 42 | vec4 mousePosTexture = texture2D(uMousePosTexture, puv); 43 | vMousePosTexture = mousePosTexture; 44 | 45 | vec2 dir = mousePosTexture.xy; 46 | 47 | defaultPosition.xyz = mix(initialPosition.xyz, defaultPosition.xyz, clamp(0.5 + uTime * 0.3, 0.0, 1.0)); 48 | 49 | transformed.xyz = mix(defaultPosition.xyz, noisePositionData.xyz, clamp(mousePosTexture.b, 0.0, 1.0 ) ); 50 | transformed.z = 0.0; 51 | 52 | float puvToMouse = distance(uMousePos * vec2(uAspect, 1.0), puv * vec2(uAspect, 1.0)); 53 | if (puvToMouse < 0.4) { 54 | transformed.xy += ( clamp(dir * 2.0, -1.0, 1.0) * clamp( pow(1.0-puvToMouse * 2.0, 10.0) , 0.0, 1.0 ) ) * (uNoiseMix * 0.1); 55 | } 56 | 57 | #include 58 | 59 | vec4 newPos = vec4(position, 0.); 60 | 61 | float scale = 0.006; 62 | newPos.xy *= scale; 63 | 64 | mvPosition.xyz += newPos.xyz; 65 | mvPosition.xy -= 0.5; 66 | 67 | gl_Position = projectionMatrix * mvPosition; 68 | 69 | } else { 70 | 71 | gl_Position = vec4(99999.9); 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/shaders/passThrough.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D texture; 2 | varying vec2 vUv; 3 | 4 | void main() { 5 | gl_FragColor = texture2D(texture, vUv); 6 | } -------------------------------------------------------------------------------- /src/shaders/passThrough.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main() { 4 | vUv = uv; 5 | gl_Position = vec4(position, 1.); 6 | } -------------------------------------------------------------------------------- /src/shaders/paths.frag: -------------------------------------------------------------------------------- 1 | #define STANDARD 2 | 3 | #ifdef PHYSICAL 4 | #define REFLECTIVITY 5 | #define CLEARCOAT 6 | #define TRANSPARENCY 7 | #endif 8 | 9 | uniform vec3 diffuse; 10 | uniform vec3 emissive; 11 | uniform float roughness; 12 | uniform float metalness; 13 | uniform float opacity; 14 | 15 | #ifdef TRANSPARENCY 16 | uniform float transparency; 17 | #endif 18 | 19 | #ifdef REFLECTIVITY 20 | uniform float reflectivity; 21 | #endif 22 | 23 | #ifdef CLEARCOAT 24 | uniform float clearcoat; 25 | uniform float clearcoatRoughness; 26 | #endif 27 | 28 | #ifdef USE_SHEEN 29 | uniform vec3 sheen; 30 | #endif 31 | 32 | varying vec3 vViewPosition; 33 | 34 | #ifndef FLAT_SHADED 35 | 36 | varying vec3 vNormal; 37 | 38 | #ifdef USE_TANGENT 39 | 40 | varying vec3 vTangent; 41 | varying vec3 vBitangent; 42 | 43 | #endif 44 | 45 | #endif 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | 74 | void main() { 75 | 76 | #include 77 | 78 | vec4 diffuseColor = vec4( diffuse, opacity ); 79 | ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); 80 | vec3 totalEmissiveRadiance = emissive; 81 | 82 | #include 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | 95 | // accumulation 96 | #include 97 | #include 98 | #include 99 | #include 100 | 101 | // modulation 102 | #include 103 | 104 | vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance; 105 | 106 | // this is a stub for the transparency model 107 | #ifdef TRANSPARENCY 108 | diffuseColor.a *= saturate( 1. - transparency + linearToRelativeLuminance( reflectedLight.directSpecular + reflectedLight.indirectSpecular ) ); 109 | #endif 110 | 111 | gl_FragColor = vec4( outgoingLight, diffuseColor.a ); 112 | 113 | #include 114 | #include 115 | #include 116 | #include 117 | #include 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/shaders/paths.vert: -------------------------------------------------------------------------------- 1 | #define STANDARD 2 | 3 | vec3 applyQuaternionToVector( vec4 q, vec3 v ){ 4 | return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v ); 5 | } 6 | 7 | uniform float uTime; 8 | 9 | attribute vec3 offset; 10 | attribute float scale; 11 | attribute vec4 quaternion; 12 | 13 | varying vec3 vViewPosition; 14 | 15 | #ifndef FLAT_SHADED 16 | 17 | varying vec3 vNormal; 18 | 19 | #ifdef USE_TANGENT 20 | 21 | varying vec3 vTangent; 22 | varying vec3 vBitangent; 23 | 24 | #endif 25 | 26 | #endif 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | void main() { 41 | 42 | #include 43 | #include 44 | #include 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED 53 | 54 | vNormal = normalize( transformedNormal ); 55 | 56 | #ifdef USE_TANGENT 57 | 58 | vTangent = normalize( transformedTangent ); 59 | vBitangent = normalize( cross( vNormal, vTangent ) * tangent.w ); 60 | 61 | #endif 62 | 63 | #endif 64 | 65 | #include 66 | 67 | transformed.xyz = applyQuaternionToVector( quaternion, transformed.xyz ); 68 | transformed.xyz += offset.xyz; 69 | 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | 77 | vViewPosition = - mvPosition.xyz; 78 | 79 | #include 80 | #include 81 | #include 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/shaders/pickers.frag: -------------------------------------------------------------------------------- 1 | varying vec3 vPickerColor; 2 | 3 | void main() { 4 | gl_FragColor = vec4(vPickerColor.rgb, 1.0); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/pickers.vert: -------------------------------------------------------------------------------- 1 | #pragma glslify: applyQuaternionToVector = require('./applyQuaternionToVector'); 2 | // #pragma glslify: rotationMatrix = require('./rotationMatrix'); 3 | 4 | uniform float uTime; 5 | 6 | attribute vec3 pickerColor; 7 | attribute vec3 offset; 8 | attribute float scale; 9 | attribute vec4 quaternion; 10 | attribute float id; 11 | attribute float isSelected; 12 | 13 | varying vec3 vPickerColor; 14 | 15 | void main() { 16 | 17 | vPickerColor = pickerColor; 18 | 19 | #include 20 | 21 | // scale 22 | transformed.xyz *= scale; 23 | transformed.xyz = applyQuaternionToVector( quaternion, transformed.xyz ); 24 | transformed.xyz += offset.xyz; 25 | 26 | 27 | #include 28 | 29 | } -------------------------------------------------------------------------------- /src/shaders/position.frag: -------------------------------------------------------------------------------- 1 | #pragma glslify: curlNoise = require('./curlNoise'); 2 | 3 | varying vec2 vUv; 4 | 5 | uniform sampler2D positionTexture; 6 | uniform sampler2D defaultPositionTexture; 7 | uniform float uFrame; 8 | uniform float uNoiseMix; 9 | 10 | void main() { 11 | vec4 defaultPosition = texture2D(defaultPositionTexture, vUv); 12 | vec4 currentPosition = texture2D(positionTexture, vUv); 13 | 14 | float kernelSize = 0.03; 15 | vec3 scaledPosition = vec3(currentPosition.x, currentPosition.y, sin(uFrame * 0.001) * 100.0) * kernelSize; 16 | 17 | float noiseSpeed = 0.4; 18 | 19 | currentPosition.xyz = currentPosition.xyz + (curlNoise(scaledPosition) ) * noiseSpeed; 20 | 21 | currentPosition = mix(defaultPosition, currentPosition, uNoiseMix * 0.95); 22 | 23 | gl_FragColor = currentPosition; 24 | 25 | } -------------------------------------------------------------------------------- /src/shaders/rotationMatrix.glsl: -------------------------------------------------------------------------------- 1 | mat4 rotationMatrix(vec3 axis, float angle) { 2 | axis = normalize(axis); 3 | float s = sin(angle); 4 | float c = cos(angle); 5 | float oc = 1.0 - c; 6 | 7 | return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, 8 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, 9 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 10 | 0.0, 0.0, 0.0, 1.0); 11 | } 12 | 13 | #pragma glslify: export(rotationMatrix) 14 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: { 5 | index: path.join(__dirname, './src/index.js') 6 | }, 7 | output: { 8 | path: path.join(__dirname, './build'), 9 | filename: 'index.js', 10 | library: 'Main', 11 | libraryTarget: 'umd' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.(js|jsx|mjs)$/, 17 | loader: require.resolve('babel-loader'), 18 | options: { 19 | compact: false 20 | } 21 | }, 22 | { 23 | test: /\.(glsl|frag|vert)$/, 24 | use: [ 25 | require.resolve('raw-loader'), 26 | require.resolve('glslify-loader') 27 | ] 28 | }, 29 | { 30 | test: /\.(glb)$/, 31 | use: [ 32 | require.resolve('url-loader') 33 | ] 34 | }, 35 | { 36 | test: /\.md$/, 37 | exclude: /node_modules/, 38 | use: [ 39 | { 40 | loader: 'html-loader' 41 | }, 42 | { 43 | loader: 'markdown-loader' 44 | } 45 | ] 46 | }, 47 | // "url" loader works like "file" loader except that it embeds assets 48 | // smaller than specified limit in bytes as data URLs to avoid requests. 49 | // A missing `test` is equivalent to a match. 50 | { 51 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/], 52 | loader: require.resolve('url-loader') 53 | }, 54 | { 55 | test: /\.css$/i, 56 | use: [ 57 | { 58 | loader: 'style-loader' 59 | }, 60 | { 61 | loader: 'css-loader', 62 | options: { 63 | modules: true 64 | } 65 | }, 66 | { 67 | loader: 'sass-loader' 68 | } 69 | ] 70 | } 71 | ] 72 | }, 73 | resolve: { 74 | extensions: ['.js'] 75 | }, 76 | mode: 'production' 77 | } 78 | -------------------------------------------------------------------------------- /webpack_demo.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | 4 | module.exports = { 5 | entry: { 6 | index: path.join(__dirname, 'demo/src/index.js') 7 | }, 8 | output: { 9 | path: path.join(__dirname, 'public/build'), 10 | filename: '[name].bundle.js', 11 | publicPath: '/' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.jsx?$/, 17 | exclude: /node_modules/, 18 | use: { 19 | loader: 'babel-loader' 20 | } 21 | }, 22 | { 23 | test: /\.(glsl|frag|vert)$/, 24 | use: [ 25 | require.resolve('raw-loader'), 26 | require.resolve('glslify-loader') 27 | ] 28 | }, 29 | { 30 | test: /\.md$/, 31 | exclude: /node_modules/, 32 | use: [ 33 | { 34 | loader: 'html-loader' 35 | }, 36 | { 37 | loader: 'markdown-loader' 38 | } 39 | ] 40 | }, 41 | // "url" loader works like "file" loader except that it embeds assets 42 | // smaller than specified limit in bytes as data URLs to avoid requests. 43 | // A missing `test` is equivalent to a match. 44 | { 45 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/], 46 | loader: require.resolve('file-loader') 47 | // options: { 48 | // limit: 1000 49 | // } 50 | }, 51 | { 52 | test: /\.s?css$/, 53 | // exclude: /node_modules/, 54 | use: [ 55 | { 56 | loader: 'style-loader' 57 | }, 58 | { 59 | loader: 'css-loader', 60 | options: { 61 | modules: true 62 | } 63 | }, 64 | { 65 | loader: 'sass-loader' 66 | } 67 | ] 68 | } 69 | ] 70 | }, 71 | plugins: [ 72 | new HtmlWebpackPlugin({ 73 | template: './public/index.html' 74 | }) 75 | ], 76 | devtool: 'inline-source-map', 77 | mode: 'development' 78 | } 79 | -------------------------------------------------------------------------------- /webpack_demo.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | 4 | module.exports = { 5 | entry: { 6 | index: path.join(__dirname, 'demo/src/index.js') 7 | }, 8 | output: { 9 | path: path.join(__dirname, 'docs'), 10 | filename: '[name].bundle.js' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.(js|jsx|mjs)$/, 16 | loader: require.resolve('babel-loader'), 17 | options: { 18 | compact: false 19 | } 20 | }, 21 | { 22 | test: /\.(glsl|frag|vert)$/, 23 | use: [ 24 | require.resolve('raw-loader'), 25 | require.resolve('glslify-loader') 26 | ] 27 | }, 28 | { 29 | test: /\.md$/, 30 | exclude: /node_modules/, 31 | use: [ 32 | { 33 | loader: 'html-loader' 34 | }, 35 | { 36 | loader: 'markdown-loader' 37 | } 38 | ] 39 | }, 40 | // "url" loader works like "file" loader except that it embeds assets 41 | // smaller than specified limit in bytes as data URLs to avoid requests. 42 | // A missing `test` is equivalent to a match. 43 | { 44 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/], 45 | loader: require.resolve('file-loader'), 46 | options: { 47 | limit: 10000 48 | } 49 | }, 50 | { 51 | test: /\.s?css$/, 52 | // exclude: /node_modules/, 53 | use: [ 54 | { 55 | loader: 'style-loader' 56 | }, 57 | { 58 | loader: 'css-loader', 59 | options: { 60 | modules: true 61 | } 62 | }, 63 | { 64 | loader: 'sass-loader' 65 | } 66 | ] 67 | 68 | } 69 | ] 70 | }, 71 | plugins: [ 72 | new HtmlWebpackPlugin({ 73 | template: './public/index.html' 74 | }) 75 | ], 76 | mode: 'production' 77 | } 78 | --------------------------------------------------------------------------------