├── .eslintignore ├── .babelrc ├── src ├── index.js ├── scripts │ ├── helpers.js │ ├── app.js │ └── vendor │ │ ├── OrbitControls.js │ │ └── gsap.3.0.2.min.js ├── styles │ └── index.css └── index.html ├── .github ├── dependabot.yml └── workflows │ └── node.yml ├── .editorconfig ├── README.md ├── .eslintrc ├── webpack.prod.js ├── webpack.dev.js ├── public ├── app.79c5664ed9fe95b746fc.css ├── index.html ├── app.79c5664ed9fe95b746fc.js ├── OrbitControls.js └── gsap.3.0.2.min.js ├── LICENSE ├── package.json ├── webpack.common.js └── .gitignore /.eslintignore: -------------------------------------------------------------------------------- 1 | public/ 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import App from './scripts/app'; 2 | 3 | new App().init(); 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "09:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.{diff,md}] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🧲 Magnetism 2 | 3 | ![Node.js CI](https://github.com/iondrimba/magnetism/workflows/Node.js%20CI/badge.svg) 4 | 5 | ![Image Title](https://raw.githubusercontent.com/iondrimba/images/master/magnetism.gif) 6 | 7 | ## [Demo](https://iondrimba.github.io/magnetism/public/index.html) 8 | 9 | ## Credits 10 | 11 | * [Threejs](https://threejs.org/) 12 | * [TweenMax](https://greensock.com/tweenmax) 13 | 14 | ## Find me @ 15 | 16 | [Twitter](https://twitter.com/code__music), [Codepen](https://codepen.io/iondrimba/) 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "THREE": true, 4 | "gsap": true, 5 | "dat": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended" 9 | ], 10 | "env": { 11 | "browser": true, 12 | "es6": true, 13 | "node": true 14 | }, 15 | "parserOptions": { 16 | "ecmaVersion": 8, 17 | "sourceType": "module" 18 | }, 19 | "rules": { 20 | "eqeqeq": 1, 21 | "curly": 1, 22 | "no-console": 1, 23 | "quotes": [ 24 | 1, "single" 25 | ] 26 | }, 27 | "overrides": [ 28 | { 29 | "files": [ "./src/**/*.js" ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = merge(common, { 6 | mode: 'production', 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.css$/, 11 | use: [ 12 | MiniCssExtractPlugin.loader, 13 | 'css-loader', 14 | ] 15 | } 16 | ] 17 | }, 18 | plugins: [ 19 | new MiniCssExtractPlugin({ 20 | filename: '[name].[hash].css', 21 | chunkFilename: '[id].[hash].css' 22 | }) 23 | ] 24 | }); 25 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | const common = require('./webpack.common.js'); 4 | const webpack = require('webpack'); 5 | 6 | module.exports = merge(common, { 7 | mode: 'development', 8 | devServer: { 9 | contentBase: path.join(__dirname, 'public'), 10 | hot: true, 11 | open: true, 12 | port: 9000 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.css$/, 18 | use: [ 19 | 'style-loader', 20 | 'css-loader', 21 | ] 22 | }, 23 | ] 24 | }, 25 | plugins: [ 26 | new webpack.HotModuleReplacementPlugin() 27 | ] 28 | }); 29 | -------------------------------------------------------------------------------- /src/scripts/helpers.js: -------------------------------------------------------------------------------- 1 | const radians = (degrees) => { 2 | return degrees * Math.PI / 180; 3 | } 4 | 5 | const distance = (x1, y1, x2, y2) => { 6 | return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)); 7 | } 8 | 9 | const map = (value, start1, stop1, start2, stop2) => { 10 | return (value - start1) / (stop1 - start1) * (stop2 - start2) + start2 11 | } 12 | 13 | const hexToRgbTreeJs = (hex) => { 14 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 15 | 16 | return result ? { 17 | r: parseInt(result[1], 16) / 255, 18 | g: parseInt(result[2], 16) / 255, 19 | b: parseInt(result[3], 16) / 255 20 | } : null; 21 | } 22 | 23 | export { 24 | radians, 25 | distance, 26 | map, 27 | hexToRgbTreeJs 28 | }; -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | audio, 2 | canvas, 3 | video { 4 | display: inline-block; 5 | } 6 | 7 | html { 8 | font-family: sans-serif; 9 | -ms-text-size-adjust: 100%; 10 | -webkit-text-size-adjust: 100%; 11 | } 12 | 13 | *, 14 | *::after, 15 | *::before { 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | margin: 0; 21 | color: #fff; 22 | background-color: #801336; 23 | font-family: 'Ropa Sans', Arial, sans-serif; 24 | overflow: hidden; 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } 28 | 29 | canvas { 30 | width: 100%; 31 | height: 100%; 32 | position: absolute; 33 | z-index: 0; 34 | top: 0; 35 | left: 0; 36 | } 37 | 38 | .dg.ac { 39 | z-index: 2 !important; 40 | } -------------------------------------------------------------------------------- /public/app.79c5664ed9fe95b746fc.css: -------------------------------------------------------------------------------- 1 | audio, 2 | canvas, 3 | video { 4 | display: inline-block; 5 | } 6 | 7 | html { 8 | font-family: sans-serif; 9 | -ms-text-size-adjust: 100%; 10 | -webkit-text-size-adjust: 100%; 11 | } 12 | 13 | *, 14 | *::after, 15 | *::before { 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | margin: 0; 21 | color: #fff; 22 | background-color: #801336; 23 | font-family: 'Ropa Sans', Arial, sans-serif; 24 | overflow: hidden; 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } 28 | 29 | canvas { 30 | width: 100%; 31 | height: 100%; 32 | position: absolute; 33 | z-index: 0; 34 | top: 0; 35 | left: 0; 36 | } 37 | 38 | .dg.ac { 39 | z-index: 2 !important; 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run build 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ion Drimba F. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magnetism", 3 | "version": "1.0.0", 4 | "description": "magnetism", 5 | "main": "src/index.js", 6 | "keywords": [ 7 | "Javascript", 8 | "Threejs", 9 | "Interactive", 10 | "Magnetism", 11 | "ES6" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/iondrimba/magnetism.git" 16 | }, 17 | "scripts": { 18 | "start": "webpack-dev-server --open --config webpack.dev.js", 19 | "build": "webpack --config webpack.prod.js" 20 | }, 21 | "author": "Ion D. Filho (https://iondrimbafilho.me)", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@babel/core": "^7.19.6", 25 | "@babel/preset-env": "^7.19.4", 26 | "babel-core": "^6.7.0", 27 | "babel-loader": "^8.1.0", 28 | "babel-polyfill": "^6.6.1", 29 | "babel-preset-env": "^1.7.0", 30 | "clean-webpack-plugin": "^3.0.0", 31 | "copy-webpack-plugin": "^6.3.2", 32 | "css-loader": "^5.2.2", 33 | "html-webpack-plugin": "^4.5.2", 34 | "mini-css-extract-plugin": "^0.9.0", 35 | "optimize-css-assets-webpack-plugin": "^5.0.4", 36 | "style-loader": "^1.2.1", 37 | "uglifyjs-webpack-plugin": "^2.2.0", 38 | "webpack": "^4.46.0", 39 | "webpack-cli": "^3.3.11", 40 | "webpack-dev-server": "^3.11.2", 41 | "webpack-merge": "^4.2.2" 42 | }, 43 | "dependencies": { 44 | "stats.js": "^0.17.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const CopyPlugin = require("copy-webpack-plugin"); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 6 | 7 | module.exports = { 8 | entry: { 9 | app: './src/index.js' 10 | }, 11 | output: { 12 | filename: '[name].[hash].js', 13 | path: path.resolve(__dirname, 'public') 14 | }, 15 | resolve: { 16 | alias: { 17 | styles: path.resolve(__dirname, './src/styles/'), 18 | } 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.js$/, 24 | exclude: /node_modules/, 25 | use: { 26 | loader: 'babel-loader' 27 | } 28 | } 29 | ] 30 | }, 31 | plugins: [ 32 | new webpack.DefinePlugin({ 33 | 'process.env': { 34 | NODE_ENV: JSON.stringify(process.env.NODE_ENV) 35 | } 36 | }), 37 | new CleanWebpackPlugin({ 38 | cleanOnceBeforeBuildPatterns: ['app.*'], 39 | }), 40 | new CopyPlugin({ 41 | patterns: [ 42 | { from: "./src/scripts/vendor/three.r123.min.js", to: "three.r123.min.js" }, 43 | { from: "./src/scripts/vendor/OrbitControls.js", to: "OrbitControls.js" }, 44 | { from: "./src/scripts/vendor/dat.0.7.2.gui.js", to: "dat.0.7.2.gui.js" }, 45 | { from: "./src/scripts/vendor/gsap.3.0.2.min.js", to: "gsap.3.0.2.min.js" }, 46 | ], 47 | }), 48 | new HtmlWebpackPlugin({ 49 | filename: 'index.html', 50 | template: 'src/index.html' 51 | }), 52 | ] 53 | }; 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 🧲 Magnetism
-------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 🧲 Magnetism 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 |
63 |
64 |
65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /public/app.79c5664ed9fe95b746fc.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function i(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=t,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=2)}([function(e,t,i){var n;e.exports=((n=function(){function e(e){return r.appendChild(e.dom),e}function t(e){for(var t=0;ts+1e3&&(l.update(1e3*o/(e-s),100),s=e,o=0,d)){var t=performance.memory;d.update(t.usedJSHeapSize/1048576,t.jsHeapSizeLimit/1048576)}return e},update:function(){a=this.end()},domElement:r,setMode:t}}).Panel=function(e,t,i){var n=1/0,r=0,a=Math.round,s=a(window.devicePixelRatio||1),o=80*s,l=48*s,h=3*s,d=2*s,c=3*s,u=15*s,f=74*s,p=30*s,m=document.createElement("canvas");m.width=o,m.height=l,m.style.cssText="width:80px;height:48px";var g=m.getContext("2d");return g.font="bold "+9*s+"px Helvetica,Arial,sans-serif",g.textBaseline="top",g.fillStyle=i,g.fillRect(0,0,o,l),g.fillStyle=t,g.fillText(e,h,d),g.fillRect(c,u,f,p),g.fillStyle=i,g.globalAlpha=.9,g.fillRect(c,u,f,p),{dom:m,update:function(l,w){n=Math.min(n,l),r=Math.max(r,l),g.fillStyle=i,g.globalAlpha=1,g.fillRect(0,0,o,u),g.fillStyle=t,g.fillText(a(l)+" "+e+" ("+a(n)+"-"+a(r)+")",h,d),g.drawImage(m,c+s,u,f-s,p,c,u,f-s,p),g.fillRect(c+f-s,u,s,p),g.fillStyle=i,g.globalAlpha=.9,g.fillRect(c+f-s,u,s,a((1-l/w)*p))}}},n)},function(e,t,i){},function(e,t,i){"use strict";i.r(t);i(1);var n=i(0),r=i.n(n),a=function(e){var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16)/255,g:parseInt(t[2],16)/255,b:parseInt(t[3],16)/255}:null};function s(e,t){for(var i=0;i0&&void 0!==arguments[0]?arguments[0]:0;this.stats.begin(),this.controls.update();var t=e-this.lastFrameTime;t>=this.frameInterval&&(this.lastFrameTime=e-t%this.frameInterval,this.draw()),this.renderer.render(this.scene,this.camera),this.stats.end(),requestAnimationFrame(this.animate.bind(this))}}])&&s(t.prototype,i),n&&s(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}())).init()}]); -------------------------------------------------------------------------------- /src/scripts/app.js: -------------------------------------------------------------------------------- 1 | import 'styles/index.css'; 2 | import Stats from 'stats.js'; 3 | import { 4 | radians, 5 | map, 6 | distance, 7 | hexToRgbTreeJs 8 | } from './helpers'; 9 | 10 | export default class App { 11 | setup() { 12 | this.targetFPS = 60; 13 | this.frameInterval = 1000 / this.targetFPS; // milliseconds per frame 14 | this.lastFrameTime = 0; 15 | this.stats = new Stats(); 16 | this.stats.showPanel(0); 17 | document.body.appendChild(this.stats.dom); 18 | 19 | this.gui = new dat.GUI(); 20 | this.backgroundColor = '#801336'; 21 | this.gutter = { 22 | size: -.9 23 | }; 24 | 25 | this.pendulum = { 26 | length: 4, 27 | angle: 180, 28 | angleVelocity: 0, 29 | angleAcceleration: 0, 30 | origin: { 31 | x: 0, 32 | y: 0, 33 | }, 34 | current: { 35 | x: 0, 36 | y: 0, 37 | } 38 | }; 39 | 40 | this.meshes = []; 41 | this.grid = { 42 | cols: 40, 43 | rows: 20, 44 | }; 45 | 46 | this.width = window.innerWidth; 47 | this.height = window.innerHeight; 48 | 49 | const gui = this.gui.addFolder('Background'); 50 | gui.addColor(this, 'backgroundColor').onChange((color) => { 51 | document.body.style.backgroundColor = color; 52 | }); 53 | 54 | window.addEventListener('resize', this.onResize.bind(this), { 55 | passive: true 56 | }); 57 | } 58 | 59 | createScene() { 60 | this.scene = new THREE.Scene(); 61 | this.renderer = new THREE.WebGLRenderer({ 62 | antialias: true, 63 | alpha: true 64 | }); 65 | 66 | this.renderer.setSize(window.innerWidth, window.innerHeight); 67 | 68 | document.body.appendChild(this.renderer.domElement); 69 | } 70 | 71 | addSphere() { 72 | const meshParams = { 73 | color: '#fff400', 74 | }; 75 | 76 | const geometry = new THREE.SphereGeometry(.3, 32, 32); 77 | const material = new THREE.MeshPhysicalMaterial(meshParams); 78 | 79 | this.sphere = new THREE.Mesh(geometry, material); 80 | this.sphere.position.set(0, 0, 0); 81 | 82 | const gui = this.gui.addFolder('Sphere Material'); 83 | gui.addColor(meshParams, 'color').onChange((color) => { 84 | material.color = hexToRgbTreeJs(color); 85 | }); 86 | 87 | this.scene.add(this.sphere); 88 | } 89 | 90 | createCamera() { 91 | this.camera = new THREE.PerspectiveCamera(20, window.innerWidth / window.innerHeight, 1, 1000); 92 | this.camera.position.set(10, 10, 10); 93 | 94 | this.scene.add(this.camera); 95 | } 96 | 97 | addAmbientLight() { 98 | const obj = { color: '#fff' }; 99 | const light = new THREE.AmbientLight(obj.color, 1); 100 | 101 | this.scene.add(light); 102 | } 103 | 104 | getMesh(geometry, material, count) { 105 | const mesh = new THREE.InstancedMesh(geometry, material, count); 106 | mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); 107 | mesh.castShadow = true; 108 | mesh.receiveShadow = true; 109 | 110 | return mesh; 111 | } 112 | 113 | addCameraControls() { 114 | this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); 115 | this.controls.maxPolarAngle = radians(50); 116 | } 117 | 118 | onResize() { 119 | this.width = window.innerWidth; 120 | this.height = window.innerHeight; 121 | 122 | this.camera.aspect = this.width / this.height; 123 | this.camera.updateProjectionMatrix(); 124 | this.renderer.setSize(this.width, this.height); 125 | } 126 | 127 | init() { 128 | this.setup(); 129 | 130 | this.createScene(); 131 | 132 | this.createCamera(); 133 | 134 | this.addSphere(); 135 | 136 | this.addAmbientLight(); 137 | 138 | this.createGrid(); 139 | 140 | this.addCameraControls(); 141 | 142 | requestAnimationFrame(this.animate.bind(this)); 143 | } 144 | 145 | createGrid() { 146 | this.topMaterialProps = { 147 | color: '#ff214a', 148 | }; 149 | 150 | this.insideMaterialProps = { 151 | color: '#190d6d', 152 | }; 153 | 154 | this.leftMaterialProps = { 155 | color: '#fff400', 156 | }; 157 | 158 | this.topMaterial = new THREE.MeshPhysicalMaterial(this.topMaterialProps); 159 | this.insideMaterial = new THREE.MeshPhysicalMaterial(this.insideMaterialProps); 160 | this.leftMaterial = new THREE.MeshPhysicalMaterial(this.leftMaterialProps); 161 | const materials = [this.leftMaterial, this.leftMaterial, this.topMaterial, this.insideMaterial, this.insideMaterial, this.insideMaterial]; 162 | 163 | const gui = this.gui.addFolder('Mesh Material Top'); 164 | gui.addColor(this.topMaterialProps, 'color').onChange((color) => { 165 | this.topMaterial.color = hexToRgbTreeJs(color); 166 | }); 167 | 168 | const guiinside = this.gui.addFolder('Mesh Material Inside'); 169 | guiinside.addColor(this.insideMaterialProps, 'color').onChange((color) => { 170 | this.insideMaterial.color = hexToRgbTreeJs(color); 171 | }); 172 | 173 | const guileft = this.gui.addFolder('Mesh Material Left'); 174 | guileft.addColor(this.leftMaterialProps, 'color').onChange((color) => { 175 | this.leftMaterial.color = hexToRgbTreeJs(color); 176 | }); 177 | 178 | const geometry = new THREE.BoxBufferGeometry(.1, .1, .1); 179 | geometry.translate( 0, .025, 0 ); 180 | this.mesh = this.getMesh(geometry, materials, this.grid.rows * this.grid.cols); 181 | this.scene.add(this.mesh); 182 | 183 | let ii = 0; 184 | const centerX = ((this.grid.cols) + ((this.grid.cols) * this.gutter.size)) * .46; 185 | const centerZ = ((this.grid.rows) + ((this.grid.rows) * this.gutter.size)) * .46; 186 | 187 | for (let row = 0; row < this.grid.rows; row++) { 188 | this.meshes[row] = []; 189 | 190 | for (let col = 0; col < this.grid.cols; col++) { 191 | const pivot = new THREE.Object3D(); 192 | 193 | pivot.scale.set(1, 0.001, 1); 194 | pivot.position.set(col + (col * this.gutter.size)-centerX, 0, row + (row * this.gutter.size)-centerZ); 195 | this.meshes[row][col] = pivot; 196 | 197 | pivot.updateMatrix(); 198 | 199 | this.mesh.setMatrixAt(ii++, pivot.matrix); 200 | } 201 | } 202 | 203 | this.mesh.instanceMatrix.needsUpdate = true; 204 | } 205 | 206 | draw() { 207 | this.pendulum.current.x = this.pendulum.origin.x + this.pendulum.length * Math.sin(this.pendulum.angle); 208 | this.pendulum.current.y = this.pendulum.origin.y + this.pendulum.length * Math.cos(this.pendulum.angle); 209 | this.pendulum.angleAcceleration = .58 * .0019 * Math.sin(this.pendulum.angle); 210 | this.pendulum.angleVelocity += this.pendulum.angleAcceleration; 211 | this.pendulum.angle += this.pendulum.angleVelocity; 212 | this.sphere.position.set(this.pendulum.current.x, this.pendulum.current.y + 3.6, 0); 213 | 214 | let ii = 0; 215 | for (let row = 0; row < this.grid.rows; row++) { 216 | for (let col = 0; col < this.grid.cols; col++) { 217 | 218 | const pivot = this.meshes[row][col]; 219 | const dist = distance(this.sphere.position.x, this.sphere.position.z, pivot.position.x, pivot.position.z ); 220 | const y = map(dist, .6, 0.001, 0, 140 * dist); 221 | gsap.to(pivot.scale, .2, { y: y < 0.001 ? 0.001 : y }); 222 | 223 | pivot.updateMatrix(); 224 | 225 | this.mesh.setMatrixAt(ii++, pivot.matrix); 226 | } 227 | } 228 | 229 | this.mesh.instanceMatrix.needsUpdate = true; 230 | } 231 | 232 | animate(currentTime = 0) { 233 | this.stats.begin(); 234 | this.controls.update(); 235 | 236 | const timeElapsed = currentTime - this.lastFrameTime; 237 | 238 | // Only run animation logic if enough time has passed 239 | if (timeElapsed >= this.frameInterval) { 240 | // Update lastFrameTime, accounting for the actual time passed 241 | // This prevents timing drift 242 | this.lastFrameTime = currentTime - (timeElapsed % this.frameInterval); 243 | 244 | this.draw(); 245 | } 246 | 247 | this.renderer.render(this.scene, this.camera); 248 | this.stats.end(); 249 | requestAnimationFrame(this.animate.bind(this)); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /public/OrbitControls.js: -------------------------------------------------------------------------------- 1 | // This set of controls performs orbiting, dollying (zooming), and panning. 2 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 3 | // 4 | // Orbit - left mouse / touch: one-finger move 5 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 6 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 7 | 8 | THREE.OrbitControls = function ( object, domElement ) { 9 | 10 | if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); 11 | if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); 12 | 13 | this.object = object; 14 | this.domElement = domElement; 15 | 16 | // Set to false to disable this control 17 | this.enabled = true; 18 | 19 | // "target" sets the location of focus, where the object orbits around 20 | this.target = new THREE.Vector3(); 21 | 22 | // How far you can dolly in and out ( PerspectiveCamera only ) 23 | this.minDistance = 0; 24 | this.maxDistance = Infinity; 25 | 26 | // How far you can zoom in and out ( OrthographicCamera only ) 27 | this.minZoom = 0; 28 | this.maxZoom = Infinity; 29 | 30 | // How far you can orbit vertically, upper and lower limits. 31 | // Range is 0 to Math.PI radians. 32 | this.minPolarAngle = 0; // radians 33 | this.maxPolarAngle = Math.PI; // radians 34 | 35 | // How far you can orbit horizontally, upper and lower limits. 36 | // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) 37 | this.minAzimuthAngle = - Infinity; // radians 38 | this.maxAzimuthAngle = Infinity; // radians 39 | 40 | // Set to true to enable damping (inertia) 41 | // If damping is enabled, you must call controls.update() in your animation loop 42 | this.enableDamping = false; 43 | this.dampingFactor = 0.05; 44 | 45 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 46 | // Set to false to disable zooming 47 | this.enableZoom = true; 48 | this.zoomSpeed = 1.0; 49 | 50 | // Set to false to disable rotating 51 | this.enableRotate = true; 52 | this.rotateSpeed = 1.0; 53 | 54 | // Set to false to disable panning 55 | this.enablePan = true; 56 | this.panSpeed = 1.0; 57 | this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up 58 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 59 | 60 | // Set to true to automatically rotate around the target 61 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 62 | this.autoRotate = false; 63 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 64 | 65 | // Set to false to disable use of the keys 66 | this.enableKeys = true; 67 | 68 | // The four arrow keys 69 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 70 | 71 | // Mouse buttons 72 | this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; 73 | 74 | // Touch fingers 75 | this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; 76 | 77 | // for reset 78 | this.target0 = this.target.clone(); 79 | this.position0 = this.object.position.clone(); 80 | this.zoom0 = this.object.zoom; 81 | 82 | // 83 | // public methods 84 | // 85 | 86 | this.getPolarAngle = function () { 87 | 88 | return spherical.phi; 89 | 90 | }; 91 | 92 | this.getAzimuthalAngle = function () { 93 | 94 | return spherical.theta; 95 | 96 | }; 97 | 98 | this.saveState = function () { 99 | 100 | scope.target0.copy( scope.target ); 101 | scope.position0.copy( scope.object.position ); 102 | scope.zoom0 = scope.object.zoom; 103 | 104 | }; 105 | 106 | this.reset = function () { 107 | 108 | scope.target.copy( scope.target0 ); 109 | scope.object.position.copy( scope.position0 ); 110 | scope.object.zoom = scope.zoom0; 111 | 112 | scope.object.updateProjectionMatrix(); 113 | scope.dispatchEvent( changeEvent ); 114 | 115 | scope.update(); 116 | 117 | state = STATE.NONE; 118 | 119 | }; 120 | 121 | // this method is exposed, but perhaps it would be better if we can make it private... 122 | this.update = function () { 123 | 124 | var offset = new THREE.Vector3(); 125 | 126 | // so camera.up is the orbit axis 127 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 128 | var quatInverse = quat.clone().invert(); 129 | 130 | var lastPosition = new THREE.Vector3(); 131 | var lastQuaternion = new THREE.Quaternion(); 132 | 133 | var twoPI = 2 * Math.PI; 134 | 135 | return function update() { 136 | 137 | var position = scope.object.position; 138 | 139 | offset.copy( position ).sub( scope.target ); 140 | 141 | // rotate offset to "y-axis-is-up" space 142 | offset.applyQuaternion( quat ); 143 | 144 | // angle from z-axis around y-axis 145 | spherical.setFromVector3( offset ); 146 | 147 | if ( scope.autoRotate && state === STATE.NONE ) { 148 | 149 | rotateLeft( getAutoRotationAngle() ); 150 | 151 | } 152 | 153 | if ( scope.enableDamping ) { 154 | 155 | spherical.theta += sphericalDelta.theta * scope.dampingFactor; 156 | spherical.phi += sphericalDelta.phi * scope.dampingFactor; 157 | 158 | } else { 159 | 160 | spherical.theta += sphericalDelta.theta; 161 | spherical.phi += sphericalDelta.phi; 162 | 163 | } 164 | 165 | // restrict theta to be between desired limits 166 | 167 | var min = scope.minAzimuthAngle; 168 | var max = scope.maxAzimuthAngle; 169 | 170 | if ( isFinite( min ) && isFinite( max ) ) { 171 | 172 | if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; 173 | 174 | if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; 175 | 176 | if ( min <= max ) { 177 | 178 | spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); 179 | 180 | } else { 181 | 182 | spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? 183 | Math.max( min, spherical.theta ) : 184 | Math.min( max, spherical.theta ); 185 | 186 | } 187 | 188 | } 189 | 190 | // restrict phi to be between desired limits 191 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 192 | 193 | spherical.makeSafe(); 194 | 195 | 196 | spherical.radius *= scale; 197 | 198 | // restrict radius to be between desired limits 199 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 200 | 201 | // move target to panned location 202 | 203 | if ( scope.enableDamping === true ) { 204 | 205 | scope.target.addScaledVector( panOffset, scope.dampingFactor ); 206 | 207 | } else { 208 | 209 | scope.target.add( panOffset ); 210 | 211 | } 212 | 213 | offset.setFromSpherical( spherical ); 214 | 215 | // rotate offset back to "camera-up-vector-is-up" space 216 | offset.applyQuaternion( quatInverse ); 217 | 218 | position.copy( scope.target ).add( offset ); 219 | 220 | scope.object.lookAt( scope.target ); 221 | 222 | if ( scope.enableDamping === true ) { 223 | 224 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 225 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 226 | 227 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 228 | 229 | } else { 230 | 231 | sphericalDelta.set( 0, 0, 0 ); 232 | 233 | panOffset.set( 0, 0, 0 ); 234 | 235 | } 236 | 237 | scale = 1; 238 | 239 | // update condition is: 240 | // min(camera displacement, camera rotation in radians)^2 > EPS 241 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 242 | 243 | if ( zoomChanged || 244 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 245 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 246 | 247 | scope.dispatchEvent( changeEvent ); 248 | 249 | lastPosition.copy( scope.object.position ); 250 | lastQuaternion.copy( scope.object.quaternion ); 251 | zoomChanged = false; 252 | 253 | return true; 254 | 255 | } 256 | 257 | return false; 258 | 259 | }; 260 | 261 | }(); 262 | 263 | this.dispose = function () { 264 | 265 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 266 | 267 | scope.domElement.removeEventListener( 'pointerdown', onPointerDown, false ); 268 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 269 | 270 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 271 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 272 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 273 | 274 | scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove, false ); 275 | scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp, false ); 276 | 277 | scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); 278 | 279 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 280 | 281 | }; 282 | 283 | // 284 | // internals 285 | // 286 | 287 | var scope = this; 288 | 289 | var changeEvent = { type: 'change' }; 290 | var startEvent = { type: 'start' }; 291 | var endEvent = { type: 'end' }; 292 | 293 | var STATE = { 294 | NONE: - 1, 295 | ROTATE: 0, 296 | DOLLY: 1, 297 | PAN: 2, 298 | TOUCH_ROTATE: 3, 299 | TOUCH_PAN: 4, 300 | TOUCH_DOLLY_PAN: 5, 301 | TOUCH_DOLLY_ROTATE: 6 302 | }; 303 | 304 | var state = STATE.NONE; 305 | 306 | var EPS = 0.000001; 307 | 308 | // current position in spherical coordinates 309 | var spherical = new THREE.Spherical(); 310 | var sphericalDelta = new THREE.Spherical(); 311 | 312 | var scale = 1; 313 | var panOffset = new THREE.Vector3(); 314 | var zoomChanged = false; 315 | 316 | var rotateStart = new THREE.Vector2(); 317 | var rotateEnd = new THREE.Vector2(); 318 | var rotateDelta = new THREE.Vector2(); 319 | 320 | var panStart = new THREE.Vector2(); 321 | var panEnd = new THREE.Vector2(); 322 | var panDelta = new THREE.Vector2(); 323 | 324 | var dollyStart = new THREE.Vector2(); 325 | var dollyEnd = new THREE.Vector2(); 326 | var dollyDelta = new THREE.Vector2(); 327 | 328 | function getAutoRotationAngle() { 329 | 330 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 331 | 332 | } 333 | 334 | function getZoomScale() { 335 | 336 | return Math.pow( 0.95, scope.zoomSpeed ); 337 | 338 | } 339 | 340 | function rotateLeft( angle ) { 341 | 342 | sphericalDelta.theta -= angle; 343 | 344 | } 345 | 346 | function rotateUp( angle ) { 347 | 348 | sphericalDelta.phi -= angle; 349 | 350 | } 351 | 352 | var panLeft = function () { 353 | 354 | var v = new THREE.Vector3(); 355 | 356 | return function panLeft( distance, objectMatrix ) { 357 | 358 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 359 | v.multiplyScalar( - distance ); 360 | 361 | panOffset.add( v ); 362 | 363 | }; 364 | 365 | }(); 366 | 367 | var panUp = function () { 368 | 369 | var v = new THREE.Vector3(); 370 | 371 | return function panUp( distance, objectMatrix ) { 372 | 373 | if ( scope.screenSpacePanning === true ) { 374 | 375 | v.setFromMatrixColumn( objectMatrix, 1 ); 376 | 377 | } else { 378 | 379 | v.setFromMatrixColumn( objectMatrix, 0 ); 380 | v.crossVectors( scope.object.up, v ); 381 | 382 | } 383 | 384 | v.multiplyScalar( distance ); 385 | 386 | panOffset.add( v ); 387 | 388 | }; 389 | 390 | }(); 391 | 392 | // deltaX and deltaY are in pixels; right and down are positive 393 | var pan = function () { 394 | 395 | var offset = new THREE.Vector3(); 396 | 397 | return function pan( deltaX, deltaY ) { 398 | 399 | var element = scope.domElement; 400 | 401 | if ( scope.object.isPerspectiveCamera ) { 402 | 403 | // perspective 404 | var position = scope.object.position; 405 | offset.copy( position ).sub( scope.target ); 406 | var targetDistance = offset.length(); 407 | 408 | // half of the fov is center to top of screen 409 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 410 | 411 | // we use only clientHeight here so aspect ratio does not distort speed 412 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 413 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 414 | 415 | } else if ( scope.object.isOrthographicCamera ) { 416 | 417 | // orthographic 418 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 419 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 420 | 421 | } else { 422 | 423 | // camera neither orthographic nor perspective 424 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 425 | scope.enablePan = false; 426 | 427 | } 428 | 429 | }; 430 | 431 | }(); 432 | 433 | function dollyOut( dollyScale ) { 434 | 435 | if ( scope.object.isPerspectiveCamera ) { 436 | 437 | scale /= dollyScale; 438 | 439 | } else if ( scope.object.isOrthographicCamera ) { 440 | 441 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 442 | scope.object.updateProjectionMatrix(); 443 | zoomChanged = true; 444 | 445 | } else { 446 | 447 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 448 | scope.enableZoom = false; 449 | 450 | } 451 | 452 | } 453 | 454 | function dollyIn( dollyScale ) { 455 | 456 | if ( scope.object.isPerspectiveCamera ) { 457 | 458 | scale *= dollyScale; 459 | 460 | } else if ( scope.object.isOrthographicCamera ) { 461 | 462 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 463 | scope.object.updateProjectionMatrix(); 464 | zoomChanged = true; 465 | 466 | } else { 467 | 468 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 469 | scope.enableZoom = false; 470 | 471 | } 472 | 473 | } 474 | 475 | // 476 | // event callbacks - update the object state 477 | // 478 | 479 | function handleMouseDownRotate( event ) { 480 | 481 | rotateStart.set( event.clientX, event.clientY ); 482 | 483 | } 484 | 485 | function handleMouseDownDolly( event ) { 486 | 487 | dollyStart.set( event.clientX, event.clientY ); 488 | 489 | } 490 | 491 | function handleMouseDownPan( event ) { 492 | 493 | panStart.set( event.clientX, event.clientY ); 494 | 495 | } 496 | 497 | function handleMouseMoveRotate( event ) { 498 | 499 | rotateEnd.set( event.clientX, event.clientY ); 500 | 501 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 502 | 503 | var element = scope.domElement; 504 | 505 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 506 | 507 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 508 | 509 | rotateStart.copy( rotateEnd ); 510 | 511 | scope.update(); 512 | 513 | } 514 | 515 | function handleMouseMoveDolly( event ) { 516 | 517 | dollyEnd.set( event.clientX, event.clientY ); 518 | 519 | dollyDelta.subVectors( dollyEnd, dollyStart ); 520 | 521 | if ( dollyDelta.y > 0 ) { 522 | 523 | dollyOut( getZoomScale() ); 524 | 525 | } else if ( dollyDelta.y < 0 ) { 526 | 527 | dollyIn( getZoomScale() ); 528 | 529 | } 530 | 531 | dollyStart.copy( dollyEnd ); 532 | 533 | scope.update(); 534 | 535 | } 536 | 537 | function handleMouseMovePan( event ) { 538 | 539 | panEnd.set( event.clientX, event.clientY ); 540 | 541 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 542 | 543 | pan( panDelta.x, panDelta.y ); 544 | 545 | panStart.copy( panEnd ); 546 | 547 | scope.update(); 548 | 549 | } 550 | 551 | function handleMouseUp( /*event*/ ) { 552 | 553 | // no-op 554 | 555 | } 556 | 557 | function handleMouseWheel( event ) { 558 | 559 | if ( event.deltaY < 0 ) { 560 | 561 | dollyIn( getZoomScale() ); 562 | 563 | } else if ( event.deltaY > 0 ) { 564 | 565 | dollyOut( getZoomScale() ); 566 | 567 | } 568 | 569 | scope.update(); 570 | 571 | } 572 | 573 | function handleKeyDown( event ) { 574 | 575 | var needsUpdate = false; 576 | 577 | switch ( event.keyCode ) { 578 | 579 | case scope.keys.UP: 580 | pan( 0, scope.keyPanSpeed ); 581 | needsUpdate = true; 582 | break; 583 | 584 | case scope.keys.BOTTOM: 585 | pan( 0, - scope.keyPanSpeed ); 586 | needsUpdate = true; 587 | break; 588 | 589 | case scope.keys.LEFT: 590 | pan( scope.keyPanSpeed, 0 ); 591 | needsUpdate = true; 592 | break; 593 | 594 | case scope.keys.RIGHT: 595 | pan( - scope.keyPanSpeed, 0 ); 596 | needsUpdate = true; 597 | break; 598 | 599 | } 600 | 601 | if ( needsUpdate ) { 602 | 603 | // prevent the browser from scrolling on cursor keys 604 | event.preventDefault(); 605 | 606 | scope.update(); 607 | 608 | } 609 | 610 | 611 | } 612 | 613 | function handleTouchStartRotate( event ) { 614 | 615 | if ( event.touches.length == 1 ) { 616 | 617 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 618 | 619 | } else { 620 | 621 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 622 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 623 | 624 | rotateStart.set( x, y ); 625 | 626 | } 627 | 628 | } 629 | 630 | function handleTouchStartPan( event ) { 631 | 632 | if ( event.touches.length == 1 ) { 633 | 634 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 635 | 636 | } else { 637 | 638 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 639 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 640 | 641 | panStart.set( x, y ); 642 | 643 | } 644 | 645 | } 646 | 647 | function handleTouchStartDolly( event ) { 648 | 649 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 650 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 651 | 652 | var distance = Math.sqrt( dx * dx + dy * dy ); 653 | 654 | dollyStart.set( 0, distance ); 655 | 656 | } 657 | 658 | function handleTouchStartDollyPan( event ) { 659 | 660 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 661 | 662 | if ( scope.enablePan ) handleTouchStartPan( event ); 663 | 664 | } 665 | 666 | function handleTouchStartDollyRotate( event ) { 667 | 668 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 669 | 670 | if ( scope.enableRotate ) handleTouchStartRotate( event ); 671 | 672 | } 673 | 674 | function handleTouchMoveRotate( event ) { 675 | 676 | if ( event.touches.length == 1 ) { 677 | 678 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 679 | 680 | } else { 681 | 682 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 683 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 684 | 685 | rotateEnd.set( x, y ); 686 | 687 | } 688 | 689 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 690 | 691 | var element = scope.domElement; 692 | 693 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 694 | 695 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 696 | 697 | rotateStart.copy( rotateEnd ); 698 | 699 | } 700 | 701 | function handleTouchMovePan( event ) { 702 | 703 | if ( event.touches.length == 1 ) { 704 | 705 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 706 | 707 | } else { 708 | 709 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 710 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 711 | 712 | panEnd.set( x, y ); 713 | 714 | } 715 | 716 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 717 | 718 | pan( panDelta.x, panDelta.y ); 719 | 720 | panStart.copy( panEnd ); 721 | 722 | } 723 | 724 | function handleTouchMoveDolly( event ) { 725 | 726 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 727 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 728 | 729 | var distance = Math.sqrt( dx * dx + dy * dy ); 730 | 731 | dollyEnd.set( 0, distance ); 732 | 733 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 734 | 735 | dollyOut( dollyDelta.y ); 736 | 737 | dollyStart.copy( dollyEnd ); 738 | 739 | } 740 | 741 | function handleTouchMoveDollyPan( event ) { 742 | 743 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 744 | 745 | if ( scope.enablePan ) handleTouchMovePan( event ); 746 | 747 | } 748 | 749 | function handleTouchMoveDollyRotate( event ) { 750 | 751 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 752 | 753 | if ( scope.enableRotate ) handleTouchMoveRotate( event ); 754 | 755 | } 756 | 757 | function handleTouchEnd( /*event*/ ) { 758 | 759 | // no-op 760 | 761 | } 762 | 763 | // 764 | // event handlers - FSM: listen for events and reset state 765 | // 766 | 767 | function onPointerDown( event ) { 768 | 769 | if ( scope.enabled === false ) return; 770 | 771 | switch ( event.pointerType ) { 772 | 773 | case 'mouse': 774 | case 'pen': 775 | onMouseDown( event ); 776 | break; 777 | 778 | // TODO touch 779 | 780 | } 781 | 782 | } 783 | 784 | function onPointerMove( event ) { 785 | 786 | if ( scope.enabled === false ) return; 787 | 788 | switch ( event.pointerType ) { 789 | 790 | case 'mouse': 791 | case 'pen': 792 | onMouseMove( event ); 793 | break; 794 | 795 | // TODO touch 796 | 797 | } 798 | 799 | } 800 | 801 | function onPointerUp( event ) { 802 | 803 | switch ( event.pointerType ) { 804 | 805 | case 'mouse': 806 | case 'pen': 807 | onMouseUp( event ); 808 | break; 809 | 810 | // TODO touch 811 | 812 | } 813 | 814 | } 815 | 816 | function onMouseDown( event ) { 817 | 818 | // Prevent the browser from scrolling. 819 | event.preventDefault(); 820 | 821 | // Manually set the focus since calling preventDefault above 822 | // prevents the browser from setting it automatically. 823 | 824 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 825 | 826 | var mouseAction; 827 | 828 | switch ( event.button ) { 829 | 830 | case 0: 831 | 832 | mouseAction = scope.mouseButtons.LEFT; 833 | break; 834 | 835 | case 1: 836 | 837 | mouseAction = scope.mouseButtons.MIDDLE; 838 | break; 839 | 840 | case 2: 841 | 842 | mouseAction = scope.mouseButtons.RIGHT; 843 | break; 844 | 845 | default: 846 | 847 | mouseAction = - 1; 848 | 849 | } 850 | 851 | switch ( mouseAction ) { 852 | 853 | case THREE.MOUSE.DOLLY: 854 | 855 | if ( scope.enableZoom === false ) return; 856 | 857 | handleMouseDownDolly( event ); 858 | 859 | state = STATE.DOLLY; 860 | 861 | break; 862 | 863 | case THREE.MOUSE.ROTATE: 864 | 865 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 866 | 867 | if ( scope.enablePan === false ) return; 868 | 869 | handleMouseDownPan( event ); 870 | 871 | state = STATE.PAN; 872 | 873 | } else { 874 | 875 | if ( scope.enableRotate === false ) return; 876 | 877 | handleMouseDownRotate( event ); 878 | 879 | state = STATE.ROTATE; 880 | 881 | } 882 | 883 | break; 884 | 885 | case THREE.MOUSE.PAN: 886 | 887 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 888 | 889 | if ( scope.enableRotate === false ) return; 890 | 891 | handleMouseDownRotate( event ); 892 | 893 | state = STATE.ROTATE; 894 | 895 | } else { 896 | 897 | if ( scope.enablePan === false ) return; 898 | 899 | handleMouseDownPan( event ); 900 | 901 | state = STATE.PAN; 902 | 903 | } 904 | 905 | break; 906 | 907 | default: 908 | 909 | state = STATE.NONE; 910 | 911 | } 912 | 913 | if ( state !== STATE.NONE ) { 914 | 915 | scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove, false ); 916 | scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp, false ); 917 | 918 | scope.dispatchEvent( startEvent ); 919 | 920 | } 921 | 922 | } 923 | 924 | function onMouseMove( event ) { 925 | 926 | if ( scope.enabled === false ) return; 927 | 928 | event.preventDefault(); 929 | 930 | switch ( state ) { 931 | 932 | case STATE.ROTATE: 933 | 934 | if ( scope.enableRotate === false ) return; 935 | 936 | handleMouseMoveRotate( event ); 937 | 938 | break; 939 | 940 | case STATE.DOLLY: 941 | 942 | if ( scope.enableZoom === false ) return; 943 | 944 | handleMouseMoveDolly( event ); 945 | 946 | break; 947 | 948 | case STATE.PAN: 949 | 950 | if ( scope.enablePan === false ) return; 951 | 952 | handleMouseMovePan( event ); 953 | 954 | break; 955 | 956 | } 957 | 958 | } 959 | 960 | function onMouseUp( event ) { 961 | 962 | scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove, false ); 963 | scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp, false ); 964 | 965 | if ( scope.enabled === false ) return; 966 | 967 | handleMouseUp( event ); 968 | 969 | scope.dispatchEvent( endEvent ); 970 | 971 | state = STATE.NONE; 972 | 973 | } 974 | 975 | function onMouseWheel( event ) { 976 | 977 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 978 | 979 | event.preventDefault(); 980 | event.stopPropagation(); 981 | 982 | scope.dispatchEvent( startEvent ); 983 | 984 | handleMouseWheel( event ); 985 | 986 | scope.dispatchEvent( endEvent ); 987 | 988 | } 989 | 990 | function onKeyDown( event ) { 991 | 992 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 993 | 994 | handleKeyDown( event ); 995 | 996 | } 997 | 998 | function onTouchStart( event ) { 999 | 1000 | if ( scope.enabled === false ) return; 1001 | 1002 | event.preventDefault(); // prevent scrolling 1003 | 1004 | switch ( event.touches.length ) { 1005 | 1006 | case 1: 1007 | 1008 | switch ( scope.touches.ONE ) { 1009 | 1010 | case THREE.TOUCH.ROTATE: 1011 | 1012 | if ( scope.enableRotate === false ) return; 1013 | 1014 | handleTouchStartRotate( event ); 1015 | 1016 | state = STATE.TOUCH_ROTATE; 1017 | 1018 | break; 1019 | 1020 | case THREE.TOUCH.PAN: 1021 | 1022 | if ( scope.enablePan === false ) return; 1023 | 1024 | handleTouchStartPan( event ); 1025 | 1026 | state = STATE.TOUCH_PAN; 1027 | 1028 | break; 1029 | 1030 | default: 1031 | 1032 | state = STATE.NONE; 1033 | 1034 | } 1035 | 1036 | break; 1037 | 1038 | case 2: 1039 | 1040 | switch ( scope.touches.TWO ) { 1041 | 1042 | case THREE.TOUCH.DOLLY_PAN: 1043 | 1044 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1045 | 1046 | handleTouchStartDollyPan( event ); 1047 | 1048 | state = STATE.TOUCH_DOLLY_PAN; 1049 | 1050 | break; 1051 | 1052 | case THREE.TOUCH.DOLLY_ROTATE: 1053 | 1054 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1055 | 1056 | handleTouchStartDollyRotate( event ); 1057 | 1058 | state = STATE.TOUCH_DOLLY_ROTATE; 1059 | 1060 | break; 1061 | 1062 | default: 1063 | 1064 | state = STATE.NONE; 1065 | 1066 | } 1067 | 1068 | break; 1069 | 1070 | default: 1071 | 1072 | state = STATE.NONE; 1073 | 1074 | } 1075 | 1076 | if ( state !== STATE.NONE ) { 1077 | 1078 | scope.dispatchEvent( startEvent ); 1079 | 1080 | } 1081 | 1082 | } 1083 | 1084 | function onTouchMove( event ) { 1085 | 1086 | if ( scope.enabled === false ) return; 1087 | 1088 | event.preventDefault(); // prevent scrolling 1089 | event.stopPropagation(); 1090 | 1091 | switch ( state ) { 1092 | 1093 | case STATE.TOUCH_ROTATE: 1094 | 1095 | if ( scope.enableRotate === false ) return; 1096 | 1097 | handleTouchMoveRotate( event ); 1098 | 1099 | scope.update(); 1100 | 1101 | break; 1102 | 1103 | case STATE.TOUCH_PAN: 1104 | 1105 | if ( scope.enablePan === false ) return; 1106 | 1107 | handleTouchMovePan( event ); 1108 | 1109 | scope.update(); 1110 | 1111 | break; 1112 | 1113 | case STATE.TOUCH_DOLLY_PAN: 1114 | 1115 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1116 | 1117 | handleTouchMoveDollyPan( event ); 1118 | 1119 | scope.update(); 1120 | 1121 | break; 1122 | 1123 | case STATE.TOUCH_DOLLY_ROTATE: 1124 | 1125 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1126 | 1127 | handleTouchMoveDollyRotate( event ); 1128 | 1129 | scope.update(); 1130 | 1131 | break; 1132 | 1133 | default: 1134 | 1135 | state = STATE.NONE; 1136 | 1137 | } 1138 | 1139 | } 1140 | 1141 | function onTouchEnd( event ) { 1142 | 1143 | if ( scope.enabled === false ) return; 1144 | 1145 | handleTouchEnd( event ); 1146 | 1147 | scope.dispatchEvent( endEvent ); 1148 | 1149 | state = STATE.NONE; 1150 | 1151 | } 1152 | 1153 | function onContextMenu( event ) { 1154 | 1155 | if ( scope.enabled === false ) return; 1156 | 1157 | event.preventDefault(); 1158 | 1159 | } 1160 | 1161 | // 1162 | 1163 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1164 | 1165 | scope.domElement.addEventListener( 'pointerdown', onPointerDown, false ); 1166 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1167 | 1168 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1169 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1170 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1171 | 1172 | scope.domElement.addEventListener( 'keydown', onKeyDown, false ); 1173 | 1174 | // force an update at start 1175 | 1176 | this.update(); 1177 | 1178 | }; 1179 | 1180 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1181 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 1182 | 1183 | 1184 | // This set of controls performs orbiting, dollying (zooming), and panning. 1185 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 1186 | // This is very similar to OrbitControls, another set of touch behavior 1187 | // 1188 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate 1189 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 1190 | // Pan - left mouse, or arrow keys / touch: one-finger move 1191 | 1192 | THREE.MapControls = function ( object, domElement ) { 1193 | 1194 | THREE.OrbitControls.call( this, object, domElement ); 1195 | 1196 | this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up 1197 | 1198 | this.mouseButtons.LEFT = THREE.MOUSE.PAN; 1199 | this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; 1200 | 1201 | this.touches.ONE = THREE.TOUCH.PAN; 1202 | this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; 1203 | 1204 | }; 1205 | 1206 | THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1207 | THREE.MapControls.prototype.constructor = THREE.MapControls; 1208 | -------------------------------------------------------------------------------- /src/scripts/vendor/OrbitControls.js: -------------------------------------------------------------------------------- 1 | // This set of controls performs orbiting, dollying (zooming), and panning. 2 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 3 | // 4 | // Orbit - left mouse / touch: one-finger move 5 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 6 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 7 | 8 | THREE.OrbitControls = function ( object, domElement ) { 9 | 10 | if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); 11 | if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); 12 | 13 | this.object = object; 14 | this.domElement = domElement; 15 | 16 | // Set to false to disable this control 17 | this.enabled = true; 18 | 19 | // "target" sets the location of focus, where the object orbits around 20 | this.target = new THREE.Vector3(); 21 | 22 | // How far you can dolly in and out ( PerspectiveCamera only ) 23 | this.minDistance = 0; 24 | this.maxDistance = Infinity; 25 | 26 | // How far you can zoom in and out ( OrthographicCamera only ) 27 | this.minZoom = 0; 28 | this.maxZoom = Infinity; 29 | 30 | // How far you can orbit vertically, upper and lower limits. 31 | // Range is 0 to Math.PI radians. 32 | this.minPolarAngle = 0; // radians 33 | this.maxPolarAngle = Math.PI; // radians 34 | 35 | // How far you can orbit horizontally, upper and lower limits. 36 | // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) 37 | this.minAzimuthAngle = - Infinity; // radians 38 | this.maxAzimuthAngle = Infinity; // radians 39 | 40 | // Set to true to enable damping (inertia) 41 | // If damping is enabled, you must call controls.update() in your animation loop 42 | this.enableDamping = false; 43 | this.dampingFactor = 0.05; 44 | 45 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 46 | // Set to false to disable zooming 47 | this.enableZoom = true; 48 | this.zoomSpeed = 1.0; 49 | 50 | // Set to false to disable rotating 51 | this.enableRotate = true; 52 | this.rotateSpeed = 1.0; 53 | 54 | // Set to false to disable panning 55 | this.enablePan = true; 56 | this.panSpeed = 1.0; 57 | this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up 58 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 59 | 60 | // Set to true to automatically rotate around the target 61 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 62 | this.autoRotate = false; 63 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 64 | 65 | // Set to false to disable use of the keys 66 | this.enableKeys = true; 67 | 68 | // The four arrow keys 69 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 70 | 71 | // Mouse buttons 72 | this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; 73 | 74 | // Touch fingers 75 | this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; 76 | 77 | // for reset 78 | this.target0 = this.target.clone(); 79 | this.position0 = this.object.position.clone(); 80 | this.zoom0 = this.object.zoom; 81 | 82 | // 83 | // public methods 84 | // 85 | 86 | this.getPolarAngle = function () { 87 | 88 | return spherical.phi; 89 | 90 | }; 91 | 92 | this.getAzimuthalAngle = function () { 93 | 94 | return spherical.theta; 95 | 96 | }; 97 | 98 | this.saveState = function () { 99 | 100 | scope.target0.copy( scope.target ); 101 | scope.position0.copy( scope.object.position ); 102 | scope.zoom0 = scope.object.zoom; 103 | 104 | }; 105 | 106 | this.reset = function () { 107 | 108 | scope.target.copy( scope.target0 ); 109 | scope.object.position.copy( scope.position0 ); 110 | scope.object.zoom = scope.zoom0; 111 | 112 | scope.object.updateProjectionMatrix(); 113 | scope.dispatchEvent( changeEvent ); 114 | 115 | scope.update(); 116 | 117 | state = STATE.NONE; 118 | 119 | }; 120 | 121 | // this method is exposed, but perhaps it would be better if we can make it private... 122 | this.update = function () { 123 | 124 | var offset = new THREE.Vector3(); 125 | 126 | // so camera.up is the orbit axis 127 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 128 | var quatInverse = quat.clone().invert(); 129 | 130 | var lastPosition = new THREE.Vector3(); 131 | var lastQuaternion = new THREE.Quaternion(); 132 | 133 | var twoPI = 2 * Math.PI; 134 | 135 | return function update() { 136 | 137 | var position = scope.object.position; 138 | 139 | offset.copy( position ).sub( scope.target ); 140 | 141 | // rotate offset to "y-axis-is-up" space 142 | offset.applyQuaternion( quat ); 143 | 144 | // angle from z-axis around y-axis 145 | spherical.setFromVector3( offset ); 146 | 147 | if ( scope.autoRotate && state === STATE.NONE ) { 148 | 149 | rotateLeft( getAutoRotationAngle() ); 150 | 151 | } 152 | 153 | if ( scope.enableDamping ) { 154 | 155 | spherical.theta += sphericalDelta.theta * scope.dampingFactor; 156 | spherical.phi += sphericalDelta.phi * scope.dampingFactor; 157 | 158 | } else { 159 | 160 | spherical.theta += sphericalDelta.theta; 161 | spherical.phi += sphericalDelta.phi; 162 | 163 | } 164 | 165 | // restrict theta to be between desired limits 166 | 167 | var min = scope.minAzimuthAngle; 168 | var max = scope.maxAzimuthAngle; 169 | 170 | if ( isFinite( min ) && isFinite( max ) ) { 171 | 172 | if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; 173 | 174 | if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; 175 | 176 | if ( min <= max ) { 177 | 178 | spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); 179 | 180 | } else { 181 | 182 | spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? 183 | Math.max( min, spherical.theta ) : 184 | Math.min( max, spherical.theta ); 185 | 186 | } 187 | 188 | } 189 | 190 | // restrict phi to be between desired limits 191 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 192 | 193 | spherical.makeSafe(); 194 | 195 | 196 | spherical.radius *= scale; 197 | 198 | // restrict radius to be between desired limits 199 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 200 | 201 | // move target to panned location 202 | 203 | if ( scope.enableDamping === true ) { 204 | 205 | scope.target.addScaledVector( panOffset, scope.dampingFactor ); 206 | 207 | } else { 208 | 209 | scope.target.add( panOffset ); 210 | 211 | } 212 | 213 | offset.setFromSpherical( spherical ); 214 | 215 | // rotate offset back to "camera-up-vector-is-up" space 216 | offset.applyQuaternion( quatInverse ); 217 | 218 | position.copy( scope.target ).add( offset ); 219 | 220 | scope.object.lookAt( scope.target ); 221 | 222 | if ( scope.enableDamping === true ) { 223 | 224 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 225 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 226 | 227 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 228 | 229 | } else { 230 | 231 | sphericalDelta.set( 0, 0, 0 ); 232 | 233 | panOffset.set( 0, 0, 0 ); 234 | 235 | } 236 | 237 | scale = 1; 238 | 239 | // update condition is: 240 | // min(camera displacement, camera rotation in radians)^2 > EPS 241 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 242 | 243 | if ( zoomChanged || 244 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 245 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 246 | 247 | scope.dispatchEvent( changeEvent ); 248 | 249 | lastPosition.copy( scope.object.position ); 250 | lastQuaternion.copy( scope.object.quaternion ); 251 | zoomChanged = false; 252 | 253 | return true; 254 | 255 | } 256 | 257 | return false; 258 | 259 | }; 260 | 261 | }(); 262 | 263 | this.dispose = function () { 264 | 265 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 266 | 267 | scope.domElement.removeEventListener( 'pointerdown', onPointerDown, false ); 268 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 269 | 270 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 271 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 272 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 273 | 274 | scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove, false ); 275 | scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp, false ); 276 | 277 | scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); 278 | 279 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 280 | 281 | }; 282 | 283 | // 284 | // internals 285 | // 286 | 287 | var scope = this; 288 | 289 | var changeEvent = { type: 'change' }; 290 | var startEvent = { type: 'start' }; 291 | var endEvent = { type: 'end' }; 292 | 293 | var STATE = { 294 | NONE: - 1, 295 | ROTATE: 0, 296 | DOLLY: 1, 297 | PAN: 2, 298 | TOUCH_ROTATE: 3, 299 | TOUCH_PAN: 4, 300 | TOUCH_DOLLY_PAN: 5, 301 | TOUCH_DOLLY_ROTATE: 6 302 | }; 303 | 304 | var state = STATE.NONE; 305 | 306 | var EPS = 0.000001; 307 | 308 | // current position in spherical coordinates 309 | var spherical = new THREE.Spherical(); 310 | var sphericalDelta = new THREE.Spherical(); 311 | 312 | var scale = 1; 313 | var panOffset = new THREE.Vector3(); 314 | var zoomChanged = false; 315 | 316 | var rotateStart = new THREE.Vector2(); 317 | var rotateEnd = new THREE.Vector2(); 318 | var rotateDelta = new THREE.Vector2(); 319 | 320 | var panStart = new THREE.Vector2(); 321 | var panEnd = new THREE.Vector2(); 322 | var panDelta = new THREE.Vector2(); 323 | 324 | var dollyStart = new THREE.Vector2(); 325 | var dollyEnd = new THREE.Vector2(); 326 | var dollyDelta = new THREE.Vector2(); 327 | 328 | function getAutoRotationAngle() { 329 | 330 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 331 | 332 | } 333 | 334 | function getZoomScale() { 335 | 336 | return Math.pow( 0.95, scope.zoomSpeed ); 337 | 338 | } 339 | 340 | function rotateLeft( angle ) { 341 | 342 | sphericalDelta.theta -= angle; 343 | 344 | } 345 | 346 | function rotateUp( angle ) { 347 | 348 | sphericalDelta.phi -= angle; 349 | 350 | } 351 | 352 | var panLeft = function () { 353 | 354 | var v = new THREE.Vector3(); 355 | 356 | return function panLeft( distance, objectMatrix ) { 357 | 358 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 359 | v.multiplyScalar( - distance ); 360 | 361 | panOffset.add( v ); 362 | 363 | }; 364 | 365 | }(); 366 | 367 | var panUp = function () { 368 | 369 | var v = new THREE.Vector3(); 370 | 371 | return function panUp( distance, objectMatrix ) { 372 | 373 | if ( scope.screenSpacePanning === true ) { 374 | 375 | v.setFromMatrixColumn( objectMatrix, 1 ); 376 | 377 | } else { 378 | 379 | v.setFromMatrixColumn( objectMatrix, 0 ); 380 | v.crossVectors( scope.object.up, v ); 381 | 382 | } 383 | 384 | v.multiplyScalar( distance ); 385 | 386 | panOffset.add( v ); 387 | 388 | }; 389 | 390 | }(); 391 | 392 | // deltaX and deltaY are in pixels; right and down are positive 393 | var pan = function () { 394 | 395 | var offset = new THREE.Vector3(); 396 | 397 | return function pan( deltaX, deltaY ) { 398 | 399 | var element = scope.domElement; 400 | 401 | if ( scope.object.isPerspectiveCamera ) { 402 | 403 | // perspective 404 | var position = scope.object.position; 405 | offset.copy( position ).sub( scope.target ); 406 | var targetDistance = offset.length(); 407 | 408 | // half of the fov is center to top of screen 409 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 410 | 411 | // we use only clientHeight here so aspect ratio does not distort speed 412 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 413 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 414 | 415 | } else if ( scope.object.isOrthographicCamera ) { 416 | 417 | // orthographic 418 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 419 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 420 | 421 | } else { 422 | 423 | // camera neither orthographic nor perspective 424 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 425 | scope.enablePan = false; 426 | 427 | } 428 | 429 | }; 430 | 431 | }(); 432 | 433 | function dollyOut( dollyScale ) { 434 | 435 | if ( scope.object.isPerspectiveCamera ) { 436 | 437 | scale /= dollyScale; 438 | 439 | } else if ( scope.object.isOrthographicCamera ) { 440 | 441 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 442 | scope.object.updateProjectionMatrix(); 443 | zoomChanged = true; 444 | 445 | } else { 446 | 447 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 448 | scope.enableZoom = false; 449 | 450 | } 451 | 452 | } 453 | 454 | function dollyIn( dollyScale ) { 455 | 456 | if ( scope.object.isPerspectiveCamera ) { 457 | 458 | scale *= dollyScale; 459 | 460 | } else if ( scope.object.isOrthographicCamera ) { 461 | 462 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 463 | scope.object.updateProjectionMatrix(); 464 | zoomChanged = true; 465 | 466 | } else { 467 | 468 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 469 | scope.enableZoom = false; 470 | 471 | } 472 | 473 | } 474 | 475 | // 476 | // event callbacks - update the object state 477 | // 478 | 479 | function handleMouseDownRotate( event ) { 480 | 481 | rotateStart.set( event.clientX, event.clientY ); 482 | 483 | } 484 | 485 | function handleMouseDownDolly( event ) { 486 | 487 | dollyStart.set( event.clientX, event.clientY ); 488 | 489 | } 490 | 491 | function handleMouseDownPan( event ) { 492 | 493 | panStart.set( event.clientX, event.clientY ); 494 | 495 | } 496 | 497 | function handleMouseMoveRotate( event ) { 498 | 499 | rotateEnd.set( event.clientX, event.clientY ); 500 | 501 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 502 | 503 | var element = scope.domElement; 504 | 505 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 506 | 507 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 508 | 509 | rotateStart.copy( rotateEnd ); 510 | 511 | scope.update(); 512 | 513 | } 514 | 515 | function handleMouseMoveDolly( event ) { 516 | 517 | dollyEnd.set( event.clientX, event.clientY ); 518 | 519 | dollyDelta.subVectors( dollyEnd, dollyStart ); 520 | 521 | if ( dollyDelta.y > 0 ) { 522 | 523 | dollyOut( getZoomScale() ); 524 | 525 | } else if ( dollyDelta.y < 0 ) { 526 | 527 | dollyIn( getZoomScale() ); 528 | 529 | } 530 | 531 | dollyStart.copy( dollyEnd ); 532 | 533 | scope.update(); 534 | 535 | } 536 | 537 | function handleMouseMovePan( event ) { 538 | 539 | panEnd.set( event.clientX, event.clientY ); 540 | 541 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 542 | 543 | pan( panDelta.x, panDelta.y ); 544 | 545 | panStart.copy( panEnd ); 546 | 547 | scope.update(); 548 | 549 | } 550 | 551 | function handleMouseUp( /*event*/ ) { 552 | 553 | // no-op 554 | 555 | } 556 | 557 | function handleMouseWheel( event ) { 558 | 559 | if ( event.deltaY < 0 ) { 560 | 561 | dollyIn( getZoomScale() ); 562 | 563 | } else if ( event.deltaY > 0 ) { 564 | 565 | dollyOut( getZoomScale() ); 566 | 567 | } 568 | 569 | scope.update(); 570 | 571 | } 572 | 573 | function handleKeyDown( event ) { 574 | 575 | var needsUpdate = false; 576 | 577 | switch ( event.keyCode ) { 578 | 579 | case scope.keys.UP: 580 | pan( 0, scope.keyPanSpeed ); 581 | needsUpdate = true; 582 | break; 583 | 584 | case scope.keys.BOTTOM: 585 | pan( 0, - scope.keyPanSpeed ); 586 | needsUpdate = true; 587 | break; 588 | 589 | case scope.keys.LEFT: 590 | pan( scope.keyPanSpeed, 0 ); 591 | needsUpdate = true; 592 | break; 593 | 594 | case scope.keys.RIGHT: 595 | pan( - scope.keyPanSpeed, 0 ); 596 | needsUpdate = true; 597 | break; 598 | 599 | } 600 | 601 | if ( needsUpdate ) { 602 | 603 | // prevent the browser from scrolling on cursor keys 604 | event.preventDefault(); 605 | 606 | scope.update(); 607 | 608 | } 609 | 610 | 611 | } 612 | 613 | function handleTouchStartRotate( event ) { 614 | 615 | if ( event.touches.length == 1 ) { 616 | 617 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 618 | 619 | } else { 620 | 621 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 622 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 623 | 624 | rotateStart.set( x, y ); 625 | 626 | } 627 | 628 | } 629 | 630 | function handleTouchStartPan( event ) { 631 | 632 | if ( event.touches.length == 1 ) { 633 | 634 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 635 | 636 | } else { 637 | 638 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 639 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 640 | 641 | panStart.set( x, y ); 642 | 643 | } 644 | 645 | } 646 | 647 | function handleTouchStartDolly( event ) { 648 | 649 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 650 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 651 | 652 | var distance = Math.sqrt( dx * dx + dy * dy ); 653 | 654 | dollyStart.set( 0, distance ); 655 | 656 | } 657 | 658 | function handleTouchStartDollyPan( event ) { 659 | 660 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 661 | 662 | if ( scope.enablePan ) handleTouchStartPan( event ); 663 | 664 | } 665 | 666 | function handleTouchStartDollyRotate( event ) { 667 | 668 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 669 | 670 | if ( scope.enableRotate ) handleTouchStartRotate( event ); 671 | 672 | } 673 | 674 | function handleTouchMoveRotate( event ) { 675 | 676 | if ( event.touches.length == 1 ) { 677 | 678 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 679 | 680 | } else { 681 | 682 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 683 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 684 | 685 | rotateEnd.set( x, y ); 686 | 687 | } 688 | 689 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 690 | 691 | var element = scope.domElement; 692 | 693 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 694 | 695 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 696 | 697 | rotateStart.copy( rotateEnd ); 698 | 699 | } 700 | 701 | function handleTouchMovePan( event ) { 702 | 703 | if ( event.touches.length == 1 ) { 704 | 705 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 706 | 707 | } else { 708 | 709 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 710 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 711 | 712 | panEnd.set( x, y ); 713 | 714 | } 715 | 716 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 717 | 718 | pan( panDelta.x, panDelta.y ); 719 | 720 | panStart.copy( panEnd ); 721 | 722 | } 723 | 724 | function handleTouchMoveDolly( event ) { 725 | 726 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 727 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 728 | 729 | var distance = Math.sqrt( dx * dx + dy * dy ); 730 | 731 | dollyEnd.set( 0, distance ); 732 | 733 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 734 | 735 | dollyOut( dollyDelta.y ); 736 | 737 | dollyStart.copy( dollyEnd ); 738 | 739 | } 740 | 741 | function handleTouchMoveDollyPan( event ) { 742 | 743 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 744 | 745 | if ( scope.enablePan ) handleTouchMovePan( event ); 746 | 747 | } 748 | 749 | function handleTouchMoveDollyRotate( event ) { 750 | 751 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 752 | 753 | if ( scope.enableRotate ) handleTouchMoveRotate( event ); 754 | 755 | } 756 | 757 | function handleTouchEnd( /*event*/ ) { 758 | 759 | // no-op 760 | 761 | } 762 | 763 | // 764 | // event handlers - FSM: listen for events and reset state 765 | // 766 | 767 | function onPointerDown( event ) { 768 | 769 | if ( scope.enabled === false ) return; 770 | 771 | switch ( event.pointerType ) { 772 | 773 | case 'mouse': 774 | case 'pen': 775 | onMouseDown( event ); 776 | break; 777 | 778 | // TODO touch 779 | 780 | } 781 | 782 | } 783 | 784 | function onPointerMove( event ) { 785 | 786 | if ( scope.enabled === false ) return; 787 | 788 | switch ( event.pointerType ) { 789 | 790 | case 'mouse': 791 | case 'pen': 792 | onMouseMove( event ); 793 | break; 794 | 795 | // TODO touch 796 | 797 | } 798 | 799 | } 800 | 801 | function onPointerUp( event ) { 802 | 803 | switch ( event.pointerType ) { 804 | 805 | case 'mouse': 806 | case 'pen': 807 | onMouseUp( event ); 808 | break; 809 | 810 | // TODO touch 811 | 812 | } 813 | 814 | } 815 | 816 | function onMouseDown( event ) { 817 | 818 | // Prevent the browser from scrolling. 819 | event.preventDefault(); 820 | 821 | // Manually set the focus since calling preventDefault above 822 | // prevents the browser from setting it automatically. 823 | 824 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 825 | 826 | var mouseAction; 827 | 828 | switch ( event.button ) { 829 | 830 | case 0: 831 | 832 | mouseAction = scope.mouseButtons.LEFT; 833 | break; 834 | 835 | case 1: 836 | 837 | mouseAction = scope.mouseButtons.MIDDLE; 838 | break; 839 | 840 | case 2: 841 | 842 | mouseAction = scope.mouseButtons.RIGHT; 843 | break; 844 | 845 | default: 846 | 847 | mouseAction = - 1; 848 | 849 | } 850 | 851 | switch ( mouseAction ) { 852 | 853 | case THREE.MOUSE.DOLLY: 854 | 855 | if ( scope.enableZoom === false ) return; 856 | 857 | handleMouseDownDolly( event ); 858 | 859 | state = STATE.DOLLY; 860 | 861 | break; 862 | 863 | case THREE.MOUSE.ROTATE: 864 | 865 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 866 | 867 | if ( scope.enablePan === false ) return; 868 | 869 | handleMouseDownPan( event ); 870 | 871 | state = STATE.PAN; 872 | 873 | } else { 874 | 875 | if ( scope.enableRotate === false ) return; 876 | 877 | handleMouseDownRotate( event ); 878 | 879 | state = STATE.ROTATE; 880 | 881 | } 882 | 883 | break; 884 | 885 | case THREE.MOUSE.PAN: 886 | 887 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 888 | 889 | if ( scope.enableRotate === false ) return; 890 | 891 | handleMouseDownRotate( event ); 892 | 893 | state = STATE.ROTATE; 894 | 895 | } else { 896 | 897 | if ( scope.enablePan === false ) return; 898 | 899 | handleMouseDownPan( event ); 900 | 901 | state = STATE.PAN; 902 | 903 | } 904 | 905 | break; 906 | 907 | default: 908 | 909 | state = STATE.NONE; 910 | 911 | } 912 | 913 | if ( state !== STATE.NONE ) { 914 | 915 | scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove, false ); 916 | scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp, false ); 917 | 918 | scope.dispatchEvent( startEvent ); 919 | 920 | } 921 | 922 | } 923 | 924 | function onMouseMove( event ) { 925 | 926 | if ( scope.enabled === false ) return; 927 | 928 | event.preventDefault(); 929 | 930 | switch ( state ) { 931 | 932 | case STATE.ROTATE: 933 | 934 | if ( scope.enableRotate === false ) return; 935 | 936 | handleMouseMoveRotate( event ); 937 | 938 | break; 939 | 940 | case STATE.DOLLY: 941 | 942 | if ( scope.enableZoom === false ) return; 943 | 944 | handleMouseMoveDolly( event ); 945 | 946 | break; 947 | 948 | case STATE.PAN: 949 | 950 | if ( scope.enablePan === false ) return; 951 | 952 | handleMouseMovePan( event ); 953 | 954 | break; 955 | 956 | } 957 | 958 | } 959 | 960 | function onMouseUp( event ) { 961 | 962 | scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove, false ); 963 | scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp, false ); 964 | 965 | if ( scope.enabled === false ) return; 966 | 967 | handleMouseUp( event ); 968 | 969 | scope.dispatchEvent( endEvent ); 970 | 971 | state = STATE.NONE; 972 | 973 | } 974 | 975 | function onMouseWheel( event ) { 976 | 977 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 978 | 979 | event.preventDefault(); 980 | event.stopPropagation(); 981 | 982 | scope.dispatchEvent( startEvent ); 983 | 984 | handleMouseWheel( event ); 985 | 986 | scope.dispatchEvent( endEvent ); 987 | 988 | } 989 | 990 | function onKeyDown( event ) { 991 | 992 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 993 | 994 | handleKeyDown( event ); 995 | 996 | } 997 | 998 | function onTouchStart( event ) { 999 | 1000 | if ( scope.enabled === false ) return; 1001 | 1002 | event.preventDefault(); // prevent scrolling 1003 | 1004 | switch ( event.touches.length ) { 1005 | 1006 | case 1: 1007 | 1008 | switch ( scope.touches.ONE ) { 1009 | 1010 | case THREE.TOUCH.ROTATE: 1011 | 1012 | if ( scope.enableRotate === false ) return; 1013 | 1014 | handleTouchStartRotate( event ); 1015 | 1016 | state = STATE.TOUCH_ROTATE; 1017 | 1018 | break; 1019 | 1020 | case THREE.TOUCH.PAN: 1021 | 1022 | if ( scope.enablePan === false ) return; 1023 | 1024 | handleTouchStartPan( event ); 1025 | 1026 | state = STATE.TOUCH_PAN; 1027 | 1028 | break; 1029 | 1030 | default: 1031 | 1032 | state = STATE.NONE; 1033 | 1034 | } 1035 | 1036 | break; 1037 | 1038 | case 2: 1039 | 1040 | switch ( scope.touches.TWO ) { 1041 | 1042 | case THREE.TOUCH.DOLLY_PAN: 1043 | 1044 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1045 | 1046 | handleTouchStartDollyPan( event ); 1047 | 1048 | state = STATE.TOUCH_DOLLY_PAN; 1049 | 1050 | break; 1051 | 1052 | case THREE.TOUCH.DOLLY_ROTATE: 1053 | 1054 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1055 | 1056 | handleTouchStartDollyRotate( event ); 1057 | 1058 | state = STATE.TOUCH_DOLLY_ROTATE; 1059 | 1060 | break; 1061 | 1062 | default: 1063 | 1064 | state = STATE.NONE; 1065 | 1066 | } 1067 | 1068 | break; 1069 | 1070 | default: 1071 | 1072 | state = STATE.NONE; 1073 | 1074 | } 1075 | 1076 | if ( state !== STATE.NONE ) { 1077 | 1078 | scope.dispatchEvent( startEvent ); 1079 | 1080 | } 1081 | 1082 | } 1083 | 1084 | function onTouchMove( event ) { 1085 | 1086 | if ( scope.enabled === false ) return; 1087 | 1088 | event.preventDefault(); // prevent scrolling 1089 | event.stopPropagation(); 1090 | 1091 | switch ( state ) { 1092 | 1093 | case STATE.TOUCH_ROTATE: 1094 | 1095 | if ( scope.enableRotate === false ) return; 1096 | 1097 | handleTouchMoveRotate( event ); 1098 | 1099 | scope.update(); 1100 | 1101 | break; 1102 | 1103 | case STATE.TOUCH_PAN: 1104 | 1105 | if ( scope.enablePan === false ) return; 1106 | 1107 | handleTouchMovePan( event ); 1108 | 1109 | scope.update(); 1110 | 1111 | break; 1112 | 1113 | case STATE.TOUCH_DOLLY_PAN: 1114 | 1115 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1116 | 1117 | handleTouchMoveDollyPan( event ); 1118 | 1119 | scope.update(); 1120 | 1121 | break; 1122 | 1123 | case STATE.TOUCH_DOLLY_ROTATE: 1124 | 1125 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1126 | 1127 | handleTouchMoveDollyRotate( event ); 1128 | 1129 | scope.update(); 1130 | 1131 | break; 1132 | 1133 | default: 1134 | 1135 | state = STATE.NONE; 1136 | 1137 | } 1138 | 1139 | } 1140 | 1141 | function onTouchEnd( event ) { 1142 | 1143 | if ( scope.enabled === false ) return; 1144 | 1145 | handleTouchEnd( event ); 1146 | 1147 | scope.dispatchEvent( endEvent ); 1148 | 1149 | state = STATE.NONE; 1150 | 1151 | } 1152 | 1153 | function onContextMenu( event ) { 1154 | 1155 | if ( scope.enabled === false ) return; 1156 | 1157 | event.preventDefault(); 1158 | 1159 | } 1160 | 1161 | // 1162 | 1163 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1164 | 1165 | scope.domElement.addEventListener( 'pointerdown', onPointerDown, false ); 1166 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1167 | 1168 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1169 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1170 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1171 | 1172 | scope.domElement.addEventListener( 'keydown', onKeyDown, false ); 1173 | 1174 | // force an update at start 1175 | 1176 | this.update(); 1177 | 1178 | }; 1179 | 1180 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1181 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 1182 | 1183 | 1184 | // This set of controls performs orbiting, dollying (zooming), and panning. 1185 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 1186 | // This is very similar to OrbitControls, another set of touch behavior 1187 | // 1188 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate 1189 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 1190 | // Pan - left mouse, or arrow keys / touch: one-finger move 1191 | 1192 | THREE.MapControls = function ( object, domElement ) { 1193 | 1194 | THREE.OrbitControls.call( this, object, domElement ); 1195 | 1196 | this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up 1197 | 1198 | this.mouseButtons.LEFT = THREE.MOUSE.PAN; 1199 | this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; 1200 | 1201 | this.touches.ONE = THREE.TOUCH.PAN; 1202 | this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; 1203 | 1204 | }; 1205 | 1206 | THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1207 | THREE.MapControls.prototype.constructor = THREE.MapControls; 1208 | -------------------------------------------------------------------------------- /public/gsap.3.0.2.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * GSAP 3.0.2 3 | * https://greensock.com 4 | * 5 | * @license Copyright 2019, GreenSock. All rights reserved. 6 | * Subject to the terms at https://greensock.com/standard-license or for Club GreenSock members, the agreement issued with that membership. 7 | * @author: Jack Doyle, jack@greensock.com 8 | */ 9 | 10 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function n(t){return"string"==typeof t}function o(t){return"function"==typeof t}function p(t){return"number"==typeof t}function q(t){return void 0===t}function r(t){return"object"==typeof t}function s(t){return!1!==t}function t(){return"undefined"!=typeof window}function u(t){return o(t)||n(t)}function J(t){return(l=dt(t,at))&&re}function K(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function L(t,e){return!e&&console.warn(t)}function M(t,e){return t&&(at[t]=e)&&l&&(l[t]=e)||at}function N(){return 0}function W(t){var e,n,i=t[0];if(r(i)||o(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(n=pt.length;n--&&!pt[n].targetTest(i););e=pt[n]}for(n=t.length;n--;)t[n]&&(t[n]._gsap||(t[n]._gsap=new Bt(t[n],e)))||t.splice(n,1);return t}function X(t){return t._gsap||W(vt(t))[0]._gsap}function Y(t,e){var r=t[e];return o(r)?t[e]():q(r)&&t.getAttribute(e)||r}function Z(t,e){return(t=t.split(",")).forEach(e)||t}function $(t){return Math.round(1e4*t)/1e4}function _(t,e){for(var r=e.length,n=0;t.indexOf(e[n])<0&&++na;)s=s._prev;s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[n]=e,e._prev=s,e.parent=t}(t,e,"_first","_last",t._sort?"_start":0),(t._recent=e)._time||!e._dur&&e._initted){var n=(t.rawTime()-e._start)*e._ts;(!e._dur||mt(0,e.totalDuration(),n)-e._tTime>E)&&e.render(n,!0)}if(pa(t),t._dp&&t._time>=t._dur&&t._ts&&t._dur=D?s.endTime(!1):t._dur;return n(e)&&(isNaN(e)||e in a)?"<"===(r=e.charAt(0))||">"===r?("<"===r?s._start:s.endTime(0<=s._repeat))+(parseFloat(e.substr(1))||0):(r=e.indexOf("="))<0?(e in a||(a[e]=o),a[e]):(i=+(e.charAt(r-1)+e.substr(r+1)),1(i=Math.abs(i))&&(a=n,o=i);return a}function Ya(t){return oa(t),t.progress()<1&&bt(t,"onInterrupt"),t}function bb(t,e,r){return(6*(t=t<0?t+1:1>16,t>>8&Tt,t&Tt]:0:wt.black;if(!c){if(","===t.substr(-1)&&(t=t.substr(0,t.length-1)),wt[t])c=wt[t];else if("#"===t.charAt(0))4===t.length&&(t="#"+(r=t.charAt(1))+r+(n=t.charAt(2))+n+(i=t.charAt(3))+i),c=[(t=parseInt(t.substr(1),16))>>16,t>>8&Tt,t&Tt];else if("hsl"===t.substr(0,3))if(c=f=t.match(H),e){if(~t.indexOf("="))return t.match(tt)}else a=+c[0]%360/360,s=c[1]/100,r=2*(o=c[2]/100)-(n=o<=.5?o*(s+1):o+s-o*s),3=n&&ee)return n;n=n._next}else for(n=t._last;n&&n._start>=r;){if(!n._dur&&"isPause"===n.data&&n._start=i._start)&&i._ts&&h!==i){if(i.parent!==this)return this.render(t,e,r);if(i.render(0=this.totalDuration())&&(!t&&m||oa(this,1),e||t<0&&!d||(bt(this,g===_?"onComplete":"onReverseComplete",!0),this._prom&&this._prom())))}return this},t.add=function add(t,e){var r=this;if(p(e)||(e=Aa(this,e)),!(t instanceof zt)){if(Q(t))return t.forEach(function(t){return r.add(t,e)}),pa(this);if(n(t))return this.addLabel(t,e);if(!o(t))return this;t=Xt.delayedCall(0,t)}return this!==t?ua(this,t,e):this},t.getChildren=function getChildren(t,e,r,n){void 0===t&&(t=!0),void 0===e&&(e=!0),void 0===r&&(r=!0),void 0===n&&(n=-D);for(var i=[],a=this._first;a;)a._start>=n&&(a instanceof Xt?e&&i.push(a):(r&&i.push(a),t&&i.push.apply(i,a.getChildren(!0,e,r)))),a=a._next;return i},t.getById=function getById(t){for(var e=this.getChildren(1,1,1),r=e.length;r--;)if(e[r].vars.id===t)return e[r]},t.remove=function remove(t){return n(t)?this.removeLabel(t):o(t)?this.killTweensOf(t):(na(this,t),t===this._recent&&(this._recent=this._last),pa(this))},t.totalTime=function totalTime(t,e){return arguments.length?(this._forcing=1,this.parent||this._dp||!this._ts||(this._start=At.time-(0=r&&(i._start+=t),i=i._next;if(e)for(n in a)a[n]>=r&&(a[n]+=t);return pa(this)},t.invalidate=function invalidate(){var t=this._first;for(this._lock=0;t;)t.invalidate(),t=t._next;return i.prototype.invalidate.call(this)},t.clear=function clear(t){void 0===t&&(t=!0);for(var e,r=this._first;r;)e=r._next,this.remove(r),r=e;return this._time=this._tTime=0,t&&(this.labels={}),pa(this)},t.totalDuration=function totalDuration(t){var e,r,n=0,i=this,a=i._last,s=D,o=i._repeat,u=o*i._rDelay||0,h=o<0;if(arguments.length)return h?i:i.timeScale(i.totalDuration()/t);if(i._dirty){for(;a;)e=a._prev,a._dirty&&a.totalDuration(),a._start>s&&i._sort&&a._ts&&!i._lock?(i._lock=1,ua(i,a,a._start-a._delay),i._lock=0):s=a._start,a._start<0&&a._ts&&(n-=a._start,(!i.parent&&!i._dp||i.parent&&i.parent.smoothChildTiming)&&(i._start+=a._start/i._ts,i._time-=a._start,i._tTime-=a._start),i.shiftChildren(-a._start,!1,-D),s=0),n<(r=a._end=a._start+a._tDur/Math.abs(a._ts||a._pauseTS))&&a._ts&&(n=$(r)),a=e;i._dur=i===z&&i._time>n?i._time:Math.min(D,n),i._tDur=h&&(i._dur||u)?1e20:Math.min(D,n*(o+1)+u),i._end=i._start+(i._tDur/Math.abs(i._ts||i._pauseTS)||0),i._dirty=0}return i._tDur},Timeline.updateRoot=function updateRoot(t){if(z._ts&&ca(z,ta(t,z)),At.frame>=ft){ft+=j.autoSleep||120;var e=z._first;if((!e||!e._ts)&&j.autoSleep&&At._listeners.length<2){for(;e&&!e._ts;)e=e._next;e||At.sleep()}}},Timeline}(zt);fa(Rt.prototype,{_lock:0,_hasPause:0,_forcing:0});function Bb(t,e,i,a,s,u){var h,l,f,p;if(ht[t]&&!1!==(h=new ht[t]).init(s,h.rawVars?e[t]:function _processVars(t,e,i,a,s){if(o(t)&&(t=It(t,s,e,i,a)),!r(t)||t.style&&t.nodeType||Q(t))return n(t)?It(t,s,e,i,a):t;var u,h={};for(u in t)h[u]=It(t[u],s,e,i,a);return h}(e[t],a,s,u,i),i,a,u)&&(i._pt=l=new te(i._pt,s,t,0,1,h.render,h,0,h.priority),i!==c))for(f=i._ptLookup[i._targets.indexOf(s)],p=h._props.length;p--;)f[h._props[p]]=l;return h}var Et,Lt=function _addPropTween(t,e,r,i,a,s,u,h,l){o(i)&&(i=i(a||0,t,s));var f,p=t[e],c="get"!==r?r:o(p)?l?t[e.indexOf("set")||!o(t["get"+e.substr(3)])?e:"get"+e.substr(3)](l):t[e]():p,d=o(p)?l?Zt:Ut:jt;if(n(i)&&(~i.indexOf("random(")&&(i=Ta(i)),"="===i.charAt(1)&&(i=parseFloat(c)+parseFloat(i.substr(2))*("-"===i.charAt(0)?-1:1)+(Da(c)||0))),c!==i)return isNaN(c+i)?(p||e in t||K(e,i),function _addComplexStringPropTween(t,e,r,n,i,a,s){var o,u,h,l,f,p,c,d,_=new te(this._pt,t,e,0,1,Qt,null,i),m=0,g=0;for(_.b=r,_.e=n,r+="",(c=~(n+="").indexOf("random("))&&(n=Ta(n)),a&&(a(d=[r,n],t,e),r=d[0],n=d[1]),u=r.match(et)||[];o=et.exec(n);)l=o[0],f=n.substring(m,o.index),h?h=(h+1)%5:"rgba("===f.substr(-5)&&(h=1),l!==u[g++]&&(p=parseFloat(u[g-1]),_._pt={_next:_._pt,p:f||1===g?f:",",s:p,c:"="===l.charAt(1)?parseFloat(l.substr(2))*("-"===l.charAt(0)?-1:1):parseFloat(l)-p,m:h&&h<4?Math.round:0},m=et.lastIndex);return _.c=m")});else{if(l=k.length,d=b?Ja(b):N,r(b))for(f in b)~Yt.indexOf(f)&&((_=_||{})[f]=b[f]);for(o=0;o=t._tDur||e<0)&&t.ratio===u&&(t.ratio&&oa(t,1),r||(bt(t,t.ratio?"onComplete":"onReverseComplete",!0),t._prom&&t._prom()))}}(this,t,e,r);return this},t.targets=function targets(){return this._targets},t.invalidate=function invalidate(){return this._pt=this._op=this._startAt=this._onUpdate=this._act=this._lazy=0,this._ptLookup=[],this.timeline&&this.timeline.invalidate(),A.prototype.invalidate.call(this)},t.kill=function kill(t,e){if(void 0===e&&(e="all"),!(t||e&&"all"!==e)&&(this._lazy=0,this.parent))return Ya(this);if(this.timeline)return this.timeline.killTweensOf(t,e,!!Et),this;var r,i,a,s,o,u,h,l=this._targets,f=t?vt(t):l,p=this._ptLookup,c=this._pt;if((!e||"all"===e)&&function _arraysMatch(t,e){for(var r=t.length,n=r===e.length;n&&r--&&t[r]===e[r];);return r<0}(l,f))return Ya(this);for(r=this._op=this._op||[],"all"!==e&&(n(e)&&(o={},Z(e,function(t){return o[t]=1}),e=o),e=function _addAliasesToVars(t,e){var r,n,i,a,s=t[0]?X(t[0]).harness:0,o=s&&s.aliases;if(!o)return e;for(n in r=dt({},e),o)if(n in r)for(i=(a=o[n].split(",")).length;i--;)r[a[i]]=r[n];return r}(l,e)),h=l.length;h--;)if(~f.indexOf(l[h]))for(o in i=p[h],"all"===e?(r[h]=e,s=i,a={}):(a=r[h]=r[h]||{},s=e),s)(u=i&&i[o])&&("kill"in u.d&&!0!==u.d.kill(o)||(na(this,u,"_pt"),delete i[o])),"all"!==a&&(a[o]=1);return this._initted&&!this._pt&&c&&Ya(this),this},Tween.to=function to(t,e,r){return new Tween(t,e,r)},Tween.from=function from(t,e){return new Tween(t,aa(arguments,1))},Tween.delayedCall=function delayedCall(t,e,r,n){return new Tween(e,0,{immediateRender:!1,lazy:!1,overwrite:!1,delay:t,onComplete:e,onReverseComplete:e,onCompleteParams:r,onReverseCompleteParams:r,callbackScope:n})},Tween.fromTo=function fromTo(t,e,r){return new Tween(t,aa(arguments,2))},Tween.set=function set(t,e){return e.duration=0,e.repeatDelay||(e.repeat=0),new Tween(t,e)},Tween.killTweensOf=function killTweensOf(t,e,r){return z.killTweensOf(t,e,r)},Tween}(zt);fa(Xt.prototype,{_targets:[],_lazy:0,_startAt:0,_op:0,_onInit:0}),Z("staggerTo,staggerFrom,staggerFromTo",function(r){Xt[r]=function(){var t=new Rt,e=vt(arguments);return e.splice("staggerFromTo"===r?5:4,0,0),t[r].apply(t,e)}});function Mb(t,e,r){return t.setAttribute(e,r)}function Ub(t,e,r,n){n.mSet(t,e,n.m.call(n.tween,r,n.mt),n)}var jt=function _setterPlain(t,e,r){return t[e]=r},Ut=function _setterFunc(t,e,r){return t[e](r)},Zt=function _setterFuncWithParam(t,e,r,n){return t[e](n.fp,r)},qt=function _getSetter(t,e){return o(t[e])?Ut:q(t[e])&&t.setAttribute?Mb:jt},Vt=function _renderPlain(t,e){return e.set(e.t,e.p,Math.round(1e4*(e.s+e.c*t))/1e4,e)},Gt=function _renderBoolean(t,e){return e.set(e.t,e.p,!!(e.s+e.c*t),e)},Qt=function _renderComplexString(t,e){var r=e._pt,n="";if(!t&&e.b)n=e.b;else if(1===t&&e.e)n=e.e;else{for(;r;)n=r.p+(r.m?r.m(r.s+r.c*t):Math.round(1e4*(r.s+r.c*t))/1e4)+n,r=r._next;n+=e.c}e.set(e.t,e.p,n,e)},Wt=function _renderPropTweens(t,e){for(var r=e._pt;r;)r.r(t,r.d),r=r._next},Kt=function _addPluginModifier(t,e,r,n){for(var i,a=this._pt;a;)i=a._next,a.p===n&&a.modifier(t,e,r),a=i},Jt=function _killPropTweensOf(t){for(var e,r,n=this._pt;n;)r=n._next,n.p===t&&!n.op||n.op===t?na(this,n,"_pt"):n.dep||(e=1),n=r;return!e},Ht=function _sortPropTweensByPriority(t){for(var e,r,n,i,a=t._pt;a;){for(e=a._next,r=n;r&&r.pr>a.pr;)r=r._next;(a._prev=r?r._prev:i)?a._prev._next=a:n=a,(a._next=r)?r._prev=a:i=a,a=e}t._pt=n},te=(PropTween.prototype.modifier=function modifier(t,e,r){this.mSet=this.mSet||this.set,this.set=Ub,this.m=t,this.mt=r,this.tween=e},PropTween);function PropTween(t,e,r,n,i,a,s,o,u){this.t=e,this.s=n,this.c=i,this.p=r,this.r=a||Vt,this.d=s||this,this.set=o||jt,this.pr=u||0,(this._next=t)&&(t._prev=this)}Z(ct+",parent,duration,ease,delay,overwrite,runBackwards,startAt,yoyo,immediateRender,repeat,repeatDelay,data,paused,reversed,lazy,callbackScope,stringFilter,id,yoyoEase,stagger,inherit,repeatRefresh,keyframes,autoRevert",function(t){st[t]=1,"on"===t.substr(0,2)&&(st[t+"Params"]=1)}),at.TweenMax=at.TweenLite=Xt,at.TimelineLite=at.TimelineMax=Rt,z=new Rt({sortChildren:!1,defaults:R,autoRemoveChildren:!0,id:"root"}),j.stringFilter=gb;var ee={registerPlugin:function registerPlugin(){for(var t=arguments.length,e=new Array(t),r=0;ra;)s=s._prev;s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[n]=e,e._prev=s,e.parent=t}(t,e,"_first","_last",t._sort?"_start":0),(t._recent=e)._time||!e._dur&&e._initted){var n=(t.rawTime()-e._start)*e._ts;(!e._dur||mt(0,e.totalDuration(),n)-e._tTime>E)&&e.render(n,!0)}if(pa(t),t._dp&&t._time>=t._dur&&t._ts&&t._dur=D?s.endTime(!1):t._dur;return n(e)&&(isNaN(e)||e in a)?"<"===(r=e.charAt(0))||">"===r?("<"===r?s._start:s.endTime(0<=s._repeat))+(parseFloat(e.substr(1))||0):(r=e.indexOf("="))<0?(e in a||(a[e]=o),a[e]):(i=+(e.charAt(r-1)+e.substr(r+1)),1(i=Math.abs(i))&&(a=n,o=i);return a}function Ya(t){return oa(t),t.progress()<1&&bt(t,"onInterrupt"),t}function bb(t,e,r){return(6*(t=t<0?t+1:1>16,t>>8&Tt,t&Tt]:0:wt.black;if(!c){if(","===t.substr(-1)&&(t=t.substr(0,t.length-1)),wt[t])c=wt[t];else if("#"===t.charAt(0))4===t.length&&(t="#"+(r=t.charAt(1))+r+(n=t.charAt(2))+n+(i=t.charAt(3))+i),c=[(t=parseInt(t.substr(1),16))>>16,t>>8&Tt,t&Tt];else if("hsl"===t.substr(0,3))if(c=f=t.match(H),e){if(~t.indexOf("="))return t.match(tt)}else a=+c[0]%360/360,s=c[1]/100,r=2*(o=c[2]/100)-(n=o<=.5?o*(s+1):o+s-o*s),3=n&&ee)return n;n=n._next}else for(n=t._last;n&&n._start>=r;){if(!n._dur&&"isPause"===n.data&&n._start=i._start)&&i._ts&&h!==i){if(i.parent!==this)return this.render(t,e,r);if(i.render(0=this.totalDuration())&&(!t&&m||oa(this,1),e||t<0&&!d||(bt(this,g===_?"onComplete":"onReverseComplete",!0),this._prom&&this._prom())))}return this},t.add=function add(t,e){var r=this;if(p(e)||(e=Aa(this,e)),!(t instanceof zt)){if(Q(t))return t.forEach(function(t){return r.add(t,e)}),pa(this);if(n(t))return this.addLabel(t,e);if(!o(t))return this;t=Xt.delayedCall(0,t)}return this!==t?ua(this,t,e):this},t.getChildren=function getChildren(t,e,r,n){void 0===t&&(t=!0),void 0===e&&(e=!0),void 0===r&&(r=!0),void 0===n&&(n=-D);for(var i=[],a=this._first;a;)a._start>=n&&(a instanceof Xt?e&&i.push(a):(r&&i.push(a),t&&i.push.apply(i,a.getChildren(!0,e,r)))),a=a._next;return i},t.getById=function getById(t){for(var e=this.getChildren(1,1,1),r=e.length;r--;)if(e[r].vars.id===t)return e[r]},t.remove=function remove(t){return n(t)?this.removeLabel(t):o(t)?this.killTweensOf(t):(na(this,t),t===this._recent&&(this._recent=this._last),pa(this))},t.totalTime=function totalTime(t,e){return arguments.length?(this._forcing=1,this.parent||this._dp||!this._ts||(this._start=At.time-(0=r&&(i._start+=t),i=i._next;if(e)for(n in a)a[n]>=r&&(a[n]+=t);return pa(this)},t.invalidate=function invalidate(){var t=this._first;for(this._lock=0;t;)t.invalidate(),t=t._next;return i.prototype.invalidate.call(this)},t.clear=function clear(t){void 0===t&&(t=!0);for(var e,r=this._first;r;)e=r._next,this.remove(r),r=e;return this._time=this._tTime=0,t&&(this.labels={}),pa(this)},t.totalDuration=function totalDuration(t){var e,r,n=0,i=this,a=i._last,s=D,o=i._repeat,u=o*i._rDelay||0,h=o<0;if(arguments.length)return h?i:i.timeScale(i.totalDuration()/t);if(i._dirty){for(;a;)e=a._prev,a._dirty&&a.totalDuration(),a._start>s&&i._sort&&a._ts&&!i._lock?(i._lock=1,ua(i,a,a._start-a._delay),i._lock=0):s=a._start,a._start<0&&a._ts&&(n-=a._start,(!i.parent&&!i._dp||i.parent&&i.parent.smoothChildTiming)&&(i._start+=a._start/i._ts,i._time-=a._start,i._tTime-=a._start),i.shiftChildren(-a._start,!1,-D),s=0),n<(r=a._end=a._start+a._tDur/Math.abs(a._ts||a._pauseTS))&&a._ts&&(n=$(r)),a=e;i._dur=i===z&&i._time>n?i._time:Math.min(D,n),i._tDur=h&&(i._dur||u)?1e20:Math.min(D,n*(o+1)+u),i._end=i._start+(i._tDur/Math.abs(i._ts||i._pauseTS)||0),i._dirty=0}return i._tDur},Timeline.updateRoot=function updateRoot(t){if(z._ts&&ca(z,ta(t,z)),At.frame>=ft){ft+=j.autoSleep||120;var e=z._first;if((!e||!e._ts)&&j.autoSleep&&At._listeners.length<2){for(;e&&!e._ts;)e=e._next;e||At.sleep()}}},Timeline}(zt);fa(Rt.prototype,{_lock:0,_hasPause:0,_forcing:0});function Bb(t,e,i,a,s,u){var h,l,f,p;if(ht[t]&&!1!==(h=new ht[t]).init(s,h.rawVars?e[t]:function _processVars(t,e,i,a,s){if(o(t)&&(t=It(t,s,e,i,a)),!r(t)||t.style&&t.nodeType||Q(t))return n(t)?It(t,s,e,i,a):t;var u,h={};for(u in t)h[u]=It(t[u],s,e,i,a);return h}(e[t],a,s,u,i),i,a,u)&&(i._pt=l=new te(i._pt,s,t,0,1,h.render,h,0,h.priority),i!==c))for(f=i._ptLookup[i._targets.indexOf(s)],p=h._props.length;p--;)f[h._props[p]]=l;return h}var Et,Lt=function _addPropTween(t,e,r,i,a,s,u,h,l){o(i)&&(i=i(a||0,t,s));var f,p=t[e],c="get"!==r?r:o(p)?l?t[e.indexOf("set")||!o(t["get"+e.substr(3)])?e:"get"+e.substr(3)](l):t[e]():p,d=o(p)?l?Zt:Ut:jt;if(n(i)&&(~i.indexOf("random(")&&(i=Ta(i)),"="===i.charAt(1)&&(i=parseFloat(c)+parseFloat(i.substr(2))*("-"===i.charAt(0)?-1:1)+(Da(c)||0))),c!==i)return isNaN(c+i)?(p||e in t||K(e,i),function _addComplexStringPropTween(t,e,r,n,i,a,s){var o,u,h,l,f,p,c,d,_=new te(this._pt,t,e,0,1,Qt,null,i),m=0,g=0;for(_.b=r,_.e=n,r+="",(c=~(n+="").indexOf("random("))&&(n=Ta(n)),a&&(a(d=[r,n],t,e),r=d[0],n=d[1]),u=r.match(et)||[];o=et.exec(n);)l=o[0],f=n.substring(m,o.index),h?h=(h+1)%5:"rgba("===f.substr(-5)&&(h=1),l!==u[g++]&&(p=parseFloat(u[g-1]),_._pt={_next:_._pt,p:f||1===g?f:",",s:p,c:"="===l.charAt(1)?parseFloat(l.substr(2))*("-"===l.charAt(0)?-1:1):parseFloat(l)-p,m:h&&h<4?Math.round:0},m=et.lastIndex);return _.c=m")});else{if(l=k.length,d=b?Ja(b):N,r(b))for(f in b)~Yt.indexOf(f)&&((_=_||{})[f]=b[f]);for(o=0;o=t._tDur||e<0)&&t.ratio===u&&(t.ratio&&oa(t,1),r||(bt(t,t.ratio?"onComplete":"onReverseComplete",!0),t._prom&&t._prom()))}}(this,t,e,r);return this},t.targets=function targets(){return this._targets},t.invalidate=function invalidate(){return this._pt=this._op=this._startAt=this._onUpdate=this._act=this._lazy=0,this._ptLookup=[],this.timeline&&this.timeline.invalidate(),A.prototype.invalidate.call(this)},t.kill=function kill(t,e){if(void 0===e&&(e="all"),!(t||e&&"all"!==e)&&(this._lazy=0,this.parent))return Ya(this);if(this.timeline)return this.timeline.killTweensOf(t,e,!!Et),this;var r,i,a,s,o,u,h,l=this._targets,f=t?vt(t):l,p=this._ptLookup,c=this._pt;if((!e||"all"===e)&&function _arraysMatch(t,e){for(var r=t.length,n=r===e.length;n&&r--&&t[r]===e[r];);return r<0}(l,f))return Ya(this);for(r=this._op=this._op||[],"all"!==e&&(n(e)&&(o={},Z(e,function(t){return o[t]=1}),e=o),e=function _addAliasesToVars(t,e){var r,n,i,a,s=t[0]?X(t[0]).harness:0,o=s&&s.aliases;if(!o)return e;for(n in r=dt({},e),o)if(n in r)for(i=(a=o[n].split(",")).length;i--;)r[a[i]]=r[n];return r}(l,e)),h=l.length;h--;)if(~f.indexOf(l[h]))for(o in i=p[h],"all"===e?(r[h]=e,s=i,a={}):(a=r[h]=r[h]||{},s=e),s)(u=i&&i[o])&&("kill"in u.d&&!0!==u.d.kill(o)||(na(this,u,"_pt"),delete i[o])),"all"!==a&&(a[o]=1);return this._initted&&!this._pt&&c&&Ya(this),this},Tween.to=function to(t,e,r){return new Tween(t,e,r)},Tween.from=function from(t,e){return new Tween(t,aa(arguments,1))},Tween.delayedCall=function delayedCall(t,e,r,n){return new Tween(e,0,{immediateRender:!1,lazy:!1,overwrite:!1,delay:t,onComplete:e,onReverseComplete:e,onCompleteParams:r,onReverseCompleteParams:r,callbackScope:n})},Tween.fromTo=function fromTo(t,e,r){return new Tween(t,aa(arguments,2))},Tween.set=function set(t,e){return e.duration=0,e.repeatDelay||(e.repeat=0),new Tween(t,e)},Tween.killTweensOf=function killTweensOf(t,e,r){return z.killTweensOf(t,e,r)},Tween}(zt);fa(Xt.prototype,{_targets:[],_lazy:0,_startAt:0,_op:0,_onInit:0}),Z("staggerTo,staggerFrom,staggerFromTo",function(r){Xt[r]=function(){var t=new Rt,e=vt(arguments);return e.splice("staggerFromTo"===r?5:4,0,0),t[r].apply(t,e)}});function Mb(t,e,r){return t.setAttribute(e,r)}function Ub(t,e,r,n){n.mSet(t,e,n.m.call(n.tween,r,n.mt),n)}var jt=function _setterPlain(t,e,r){return t[e]=r},Ut=function _setterFunc(t,e,r){return t[e](r)},Zt=function _setterFuncWithParam(t,e,r,n){return t[e](n.fp,r)},qt=function _getSetter(t,e){return o(t[e])?Ut:q(t[e])&&t.setAttribute?Mb:jt},Vt=function _renderPlain(t,e){return e.set(e.t,e.p,Math.round(1e4*(e.s+e.c*t))/1e4,e)},Gt=function _renderBoolean(t,e){return e.set(e.t,e.p,!!(e.s+e.c*t),e)},Qt=function _renderComplexString(t,e){var r=e._pt,n="";if(!t&&e.b)n=e.b;else if(1===t&&e.e)n=e.e;else{for(;r;)n=r.p+(r.m?r.m(r.s+r.c*t):Math.round(1e4*(r.s+r.c*t))/1e4)+n,r=r._next;n+=e.c}e.set(e.t,e.p,n,e)},Wt=function _renderPropTweens(t,e){for(var r=e._pt;r;)r.r(t,r.d),r=r._next},Kt=function _addPluginModifier(t,e,r,n){for(var i,a=this._pt;a;)i=a._next,a.p===n&&a.modifier(t,e,r),a=i},Jt=function _killPropTweensOf(t){for(var e,r,n=this._pt;n;)r=n._next,n.p===t&&!n.op||n.op===t?na(this,n,"_pt"):n.dep||(e=1),n=r;return!e},Ht=function _sortPropTweensByPriority(t){for(var e,r,n,i,a=t._pt;a;){for(e=a._next,r=n;r&&r.pr>a.pr;)r=r._next;(a._prev=r?r._prev:i)?a._prev._next=a:n=a,(a._next=r)?r._prev=a:i=a,a=e}t._pt=n},te=(PropTween.prototype.modifier=function modifier(t,e,r){this.mSet=this.mSet||this.set,this.set=Ub,this.m=t,this.mt=r,this.tween=e},PropTween);function PropTween(t,e,r,n,i,a,s,o,u){this.t=e,this.s=n,this.c=i,this.p=r,this.r=a||Vt,this.d=s||this,this.set=o||jt,this.pr=u||0,(this._next=t)&&(t._prev=this)}Z(ct+",parent,duration,ease,delay,overwrite,runBackwards,startAt,yoyo,immediateRender,repeat,repeatDelay,data,paused,reversed,lazy,callbackScope,stringFilter,id,yoyoEase,stagger,inherit,repeatRefresh,keyframes,autoRevert",function(t){st[t]=1,"on"===t.substr(0,2)&&(st[t+"Params"]=1)}),at.TweenMax=at.TweenLite=Xt,at.TimelineLite=at.TimelineMax=Rt,z=new Rt({sortChildren:!1,defaults:R,autoRemoveChildren:!0,id:"root"}),j.stringFilter=gb;var ee={registerPlugin:function registerPlugin(){for(var t=arguments.length,e=new Array(t),r=0;r