├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── package.json ├── server.js ├── src ├── Main.js ├── components │ ├── Loader │ │ ├── index.js │ │ ├── styles.scss │ │ └── template.html │ ├── Logo │ │ ├── index.js │ │ ├── styles.scss │ │ └── template.html │ ├── WebGLBase │ │ ├── core │ │ │ ├── AbstractCamera.js │ │ │ ├── AbstractRenderer.js │ │ │ └── AbstractScene.js │ │ └── postProcessing │ │ │ ├── EffectComposer.js │ │ │ ├── PostProcessing.js │ │ │ ├── passes │ │ │ └── RGBSplit │ │ │ │ ├── frag.glsl │ │ │ │ └── index.js │ │ │ └── shaders │ │ │ └── vertex │ │ │ └── basic.glsl │ └── WebGLHome │ │ ├── core │ │ └── Scene.js │ │ ├── index.js │ │ ├── meshes │ │ └── Ball │ │ │ ├── BallGeometry.js │ │ │ ├── BallMaterial.js │ │ │ ├── index.js │ │ │ └── shader │ │ │ ├── frag.glsl │ │ │ └── vert.glsl │ │ ├── styles.scss │ │ └── template.html ├── config │ ├── messages │ │ ├── global.js │ │ ├── index.js │ │ └── webGL.js │ ├── resources.js │ └── webgl │ │ ├── common │ │ ├── camera.js │ │ └── index.js │ │ ├── home │ │ ├── ball.js │ │ ├── camera.js │ │ ├── index.js │ │ ├── lights.js │ │ ├── postProcessing.js │ │ └── renderer.js │ │ └── index.js ├── containers │ ├── Application │ │ ├── index.js │ │ └── template.html │ └── Homepage │ │ ├── index.js │ │ ├── styles.scss │ │ └── template.html ├── core │ ├── Router.js │ └── States.js ├── helpers │ ├── AssetsLoader.js │ ├── Emitter.js │ ├── GUI.js │ ├── SoundManager.js │ └── VideoManager.js ├── mixins │ ├── EventManagerMixin.js │ └── FadeTransitionMixin.js ├── stylesheets │ ├── common │ │ ├── _base.scss │ │ ├── _fonts.scss │ │ ├── _keyframes.scss │ │ ├── _layout.scss │ │ ├── _mixins.scss │ │ ├── _reset.scss │ │ ├── _text.scss │ │ └── _variables.scss │ └── main.scss ├── template │ └── index.tpl.html └── utils │ ├── maths │ ├── clamp.js │ ├── diagonal.js │ ├── distance.js │ ├── index.js │ ├── lerp.js │ ├── lighten-darken-color.js │ ├── loop-index.js │ ├── map.js │ ├── normalize.js │ ├── parabola.js │ ├── random-float.js │ ├── random-hex-color.js │ ├── random-int.js │ ├── shader-parse.js │ ├── smooth-step.js │ └── to-type.js │ └── webgl │ ├── AWDLoader.js │ ├── AugmentedConsole.js │ ├── Clock.js │ ├── ConstantSpline.js │ ├── MeshLine.js │ ├── OrbitControls.js │ └── shader-parse.js ├── static ├── fonts │ └── .keep ├── images │ └── .keep └── textures │ └── stroke.png └── webpack ├── webpack.dev.config.babel.js └── webpack.prod.config.babel.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": ["add-module-exports"] 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore some folders 2 | dist 3 | doc 4 | 5 | # Ignore config stuff 6 | server.*.js 7 | *config.js 8 | serviceworker.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true 8 | }, 9 | "ecmaFeatures": { 10 | "classes": true, 11 | "forOf": true, 12 | "es6": true, 13 | "blockBindings": true 14 | }, 15 | "rules": { 16 | "indent": [2, 2, {"SwitchCase": 1}], 17 | "semi": 1, 18 | "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 0}], 19 | "no-var": 1 20 | }, 21 | "globals": { 22 | "__DEV__": true, 23 | "__PROD__": true, 24 | "THREE": true, 25 | "React": true, 26 | "Vue": true, 27 | "TimelineLite": false, 28 | "TimelineMax": false, 29 | "TweenLite": false, 30 | "TweenMax": false, 31 | "Back": false, 32 | "Bounce": false, 33 | "Circ": false, 34 | "Cubic": false, 35 | "Ease": false, 36 | "EaseLookup": false, 37 | "Elastic": false, 38 | "Expo": false, 39 | "Linear": false, 40 | "Power0": false, 41 | "Power1": false, 42 | "Power2": false, 43 | "Power3": false, 44 | "Power4": false, 45 | "Quad": false, 46 | "Quart": false, 47 | "Quint": false, 48 | "RoughEase": false, 49 | "Sine": false, 50 | "SlowMo": false, 51 | "SteppedEase": false, 52 | "Strong": false, 53 | "Draggable": false, 54 | "SplitText": false, 55 | "VelocityTracker": false, 56 | "CSSPlugin": false, 57 | "ThrowPropsPlugin": false, 58 | "BezierPlugin": false 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | 53 | # git config 54 | .gitattributes text 55 | .gitignore text 56 | .gitconfig text 57 | 58 | # code analysis config 59 | .jshintrc text 60 | .jscsrc text 61 | .jshintignore text 62 | .csslintrc text 63 | 64 | # misc config 65 | *.yaml text 66 | *.yml text 67 | .editorconfig text 68 | 69 | # build config 70 | *.npmignore text 71 | *.bowerrc text 72 | 73 | # Heroku 74 | Procfile text 75 | .slugignore text 76 | 77 | # Documentation 78 | *.md text 79 | LICENSE text 80 | AUTHORS text 81 | 82 | 83 | # 84 | ## These files are binary and should be left untouched 85 | # 86 | 87 | # (binary is a macro for -text -diff) 88 | *.png binary 89 | *.jpg binary 90 | *.jpeg binary 91 | *.gif binary 92 | *.ico binary 93 | *.mov binary 94 | *.mp4 binary 95 | *.mp3 binary 96 | *.flv binary 97 | *.fla binary 98 | *.swf binary 99 | *.gz binary 100 | *.zip binary 101 | *.7z binary 102 | *.ttf binary 103 | *.eot binary 104 | *.woff binary 105 | *.pyc binary 106 | *.pdf binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules/ 4 | dist/ 5 | doc/ 6 | *.log 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2016 Heng Patrick 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [ OUTDATED ] Vue.js + Three.js + webpack boilerplate 2 | 3 | ## Warning 4 | This boilerplate is no longer updated. 5 | 6 | ## Description 7 | 8 | Vue.js + Three.js boilerplate created with [@FabienMotte](https://github.com/FabienMotte). 9 | 10 | It's based on my [vue.js boilerplate](https://github.com/patrickheng/vuejs-webpack-boilerplate). 11 | 12 | I added to it : 13 | - Abstract classes for THREE Camera / Renderer / Scene 14 | - My WebGL helpers and utils 15 | - File organisation with external config files for scene, meshes, camera and lights 16 | - An assets loader with progression check ( handle textures, videos, and awd files ) 17 | 18 | #### TECHNOLOGIES 19 | 20 | * Three.js 21 | * Vue.js 22 | * ControlKit.js 23 | * Vue EventManagerMixin ( to handle easilly emitter and dom events ) 24 | * EventEmitter.js 25 | * GSAP 26 | * Babel | ES2015 27 | * Webpack 28 | * ESLint 29 | * SCSS 30 | 31 | ##### Install dependancies : 32 | ```shell 33 | npm i 34 | ``` 35 | 36 | ##### Launch the project : 37 | ```shell 38 | npm start 39 | ``` 40 | 41 | The project will be launched at [http://0.0.0.0:3000/](http://0.0.0.0:3000/) 42 | 43 | ##### Build for production : 44 | ```shell 45 | npm run build 46 | ``` 47 | 48 | Feel free to contact me for any questions or suggestion :) 49 | Hope you like it XOXO <3 50 | 51 | - [Twitter](http://twitter.fr/pat_hg) 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuejs-threejs-webpack-boilerplate", 3 | "version": "0.1.0", 4 | "description": "vue.js + three.js boilerplate using webpack, gsap and scss. Written in es2015", 5 | "keywords": [ 6 | "boilerplate", 7 | "webpack", 8 | "three.js", 9 | "vue.js" 10 | ], 11 | "license": "MIT", 12 | "private": false, 13 | "main": "index.js", 14 | "author": { 15 | "name": "Patrick Heng", 16 | "url": "http://hengpatrick.fr/", 17 | "mail": "hengpatrick.pro@gmail.com" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/patrickheng/vuejs-threejs-webpack-boilerplate.git" 22 | }, 23 | "scripts": { 24 | "lint": "eslint .", 25 | "start": "NODE_ENV=development PORT=3000 babel-node server", 26 | "build": "NODE_ENV=production webpack --config webpack/webpack.prod.config.babel.js --progress --profile --colors", 27 | "test": "echo \"Error: no test specified\" && exit 1" 28 | }, 29 | "dependencies": { 30 | "@superguigui/wagner": "^0.1.25", 31 | "augmented-compile-shader": "^1.0.0", 32 | "clone": "^1.0.2", 33 | "detect-browser": "^1.3.1", 34 | "domready": "^1.0.8", 35 | "glsl-noise": "0.0.0", 36 | "gsap": "^1.18.5", 37 | "howler": "^1.1.29", 38 | "lodash.debounce": "^4.0.3", 39 | "lodash.findindex": "^4.4.0", 40 | "lodash.findwhere": "^3.1.0", 41 | "mobile-detect": "^1.3.2", 42 | "raf-loop": "^1.1.3", 43 | "style-loader": "^0.13.0", 44 | "three": "^0.78.0", 45 | "three-orbit-controls": "^72.0.0", 46 | "vue": "^1.0.20", 47 | "vue-router": "^0.7.11" 48 | }, 49 | "devDependencies": { 50 | "autoprefixer-loader": "^3.2.0", 51 | "babel-cli": "^6.4.0", 52 | "babel-core": "^6.3.26", 53 | "babel-eslint": "6.0.0-beta.3", 54 | "babel-loader": "^6.2.1", 55 | "babel-plugin-add-module-exports": "^0.1.2", 56 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 57 | "babel-preset-es2015": "^6.3.13", 58 | "babel-preset-stage-0": "^6.3.13", 59 | "breakpoint-sass": "^2.7.0", 60 | "clean-webpack-plugin": "^0.1.6", 61 | "connect-history-api-fallback": "^1.1.0", 62 | "controlkit": "^0.1.9", 63 | "copy-webpack-plugin": "^1.0.0", 64 | "css-loader": "^0.23.1", 65 | "eslint": "2.4.0", 66 | "eslint-loader": "^1.2.0", 67 | "exports-loader": "^0.6.3", 68 | "express": "^4.13.4", 69 | "extract-text-webpack-plugin": "^1.0.1", 70 | "glslify": "^5.1.0", 71 | "glslify-fancy-imports": "^1.0.1", 72 | "glslify-hex": "^2.0.1", 73 | "glslify-loader": "^1.0.2", 74 | "html-loader": "^0.4.3", 75 | "html-webpack-plugin": "^2.7.2", 76 | "ify-loader": "^1.0.3", 77 | "imports-loader": "^0.6.5", 78 | "json-loader": "^0.5.4", 79 | "node-sass": "^3.4.2", 80 | "port-number": "^1.0.0", 81 | "raw-loader": "^0.5.1", 82 | "rimraf": "^2.5.1", 83 | "sass-loader": "^3.1.2", 84 | "stats-webpack-plugin": "^0.3.0", 85 | "webpack": "^1.12.12", 86 | "webpack-dev-middleware": "^1.5.1", 87 | "webpack-hot-middleware": "^2.6.2" 88 | }, 89 | "glslify": { 90 | "transform": [ 91 | "glslify-fancy-imports", 92 | "glslify-hex" 93 | ] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | import express from 'express'; 3 | import webpack from 'webpack'; 4 | import history from 'connect-history-api-fallback'; 5 | import portNumber from 'port-number'; 6 | import webpackDevMiddleware from 'webpack-dev-middleware'; 7 | import webpackHotMiddleware from 'webpack-hot-middleware'; 8 | import devConfig from './webpack/webpack.dev.config.babel.js'; 9 | 10 | const env = process.env.NODE_ENV; 11 | const isDeveloping = env !== 'production'; 12 | const port = process.env.PORT ? process.env.PORT : portNumber(); 13 | const ip = isDeveloping ? '0.0.0.0' : '0.0.0.0'; 14 | 15 | const app = express(); 16 | 17 | app.use( history() ); 18 | 19 | if( isDeveloping ) { 20 | 21 | const compiler = webpack( devConfig ); 22 | 23 | app.use( webpackDevMiddleware( compiler, { 24 | publicPath: devConfig.output.publicPath, 25 | headers: { 'Access-Control-Allow-Origin': '*' }, 26 | stats: { 27 | colors: true, 28 | hash: false, 29 | timings: true, 30 | chunks: false, 31 | chunkModules: false, 32 | modules: false 33 | } 34 | }) ); 35 | 36 | app.use( webpackHotMiddleware( compiler ) ); 37 | 38 | // Serve pure static assets 39 | app.use( express.static( './static' ) ); 40 | 41 | } else { 42 | 43 | app.use( express.static( __dirname + '/dist' ) ); 44 | } 45 | 46 | app.listen( port, ip, error => { 47 | if ( error ) throw error; 48 | 49 | /*eslint-disable no-console */ 50 | console.info( `[${env}] Listening on port ${port}. Open up http://${ip}:${port}/ in your browser.` ); 51 | /*eslint-enable no-console */ 52 | }); 53 | -------------------------------------------------------------------------------- /src/Main.js: -------------------------------------------------------------------------------- 1 | import Application from 'containers/Application'; 2 | 3 | import Router from 'core/Router'; 4 | 5 | import 'stylesheets/main.scss'; 6 | 7 | import domready from 'domready'; 8 | 9 | class Main { 10 | 11 | constructor() { 12 | 13 | this.bind(); 14 | 15 | this.addEventListeners(); 16 | 17 | this.router = Router; 18 | 19 | this.start(); 20 | } 21 | 22 | bind() {} 23 | 24 | addEventListeners() {} 25 | 26 | start() { 27 | 28 | this.router.start(Application, '#application'); 29 | 30 | } 31 | } 32 | 33 | domready(() => { 34 | 35 | new Main(); 36 | }); 37 | -------------------------------------------------------------------------------- /src/components/Loader/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import './styles.scss'; 4 | 5 | import EventManagerMixin from 'mixins/EventManagerMixin'; 6 | 7 | import { 8 | RESSOURCES_PROGRESS 9 | } from 'config/messages'; 10 | 11 | export default Vue.extend({ 12 | 13 | mixins: [ EventManagerMixin ], 14 | 15 | template: require( './template.html' ), 16 | 17 | emitterEvents: [{ 18 | message: RESSOURCES_PROGRESS, 19 | method: 'onResourceProgress' 20 | }], 21 | 22 | domEvents: [], 23 | 24 | props : { 25 | }, 26 | 27 | data() { 28 | 29 | return { 30 | progress: 0 31 | }; 32 | }, 33 | 34 | created() { 35 | this.tweenProgress = 0; 36 | this.canTween = true; 37 | }, 38 | 39 | ready() { 40 | 41 | }, 42 | 43 | methods: { 44 | 45 | onResourceProgress( p ) { 46 | this.progress = Math.ceil( p * 100 ); 47 | 48 | TweenMax.to(this.$els.bar, 0.2, { scaleX: p, ease: Expo.easeOut}); 49 | 50 | if( this.progress === 100 ) { 51 | 52 | TweenMax.to(this, 0.1, { tweenProgress: this.progress, onUpdate: () => { 53 | this.progress = Math.ceil( this.tweenProgress ); 54 | }}); 55 | } 56 | else if ( this.progress <= 100 ) { 57 | 58 | TweenMax.to(this, 0.4, { tweenProgress: this.progress, onUpdate: () => { 59 | this.progress = Math.ceil( this.tweenProgress ); 60 | }}); 61 | } else { 62 | 63 | this.progress = 0; 64 | this.tweenProgress = 0; 65 | } 66 | 67 | } 68 | }, 69 | 70 | components: {} 71 | }); 72 | -------------------------------------------------------------------------------- /src/components/Loader/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~stylesheets/common/variables'; 2 | @import '~stylesheets/common/mixins'; 3 | 4 | .loader { 5 | position: absolute; 6 | background: $color-white; 7 | transition: opacity 1s ease-out; 8 | z-index: 10000; 9 | width: 100%; 10 | height: 100%; 11 | 12 | &-leave { 13 | opacity: 0; 14 | } 15 | } 16 | 17 | .loader__text { 18 | position: absolute; 19 | right: 7rem; 20 | bottom: 7rem; 21 | text-align: right; 22 | font-size: 15rem; 23 | transition: transform 0.3s ease-out; 24 | color: $color-grey-light; 25 | } 26 | 27 | .loader__bar { 28 | position: absolute; 29 | left: 0; 30 | bottom: 0; 31 | width: 100%; 32 | height: 2rem; 33 | background: rgba( $color-black, 0.1 ); 34 | transform: scaleX(0); 35 | } 36 | 37 | @include breakpoint( $bp-tablet-medium ) { 38 | 39 | } 40 | 41 | @include breakpoint( $bp-phone-large ) { 42 | 43 | .loader__text { 44 | right: 3rem; 45 | bottom: 3rem; 46 | font-size: 10rem; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Loader/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | {{ progress }}% 6 | 7 |
8 | 9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /src/components/Logo/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import './styles.scss'; 4 | 5 | import EventManagerMixin from 'mixins/EventManagerMixin'; 6 | 7 | import { 8 | ROUTER_ROUTE_CHANGE 9 | } from 'config/messages'; 10 | 11 | export default Vue.extend({ 12 | 13 | mixins: [ EventManagerMixin ], 14 | 15 | template: require( './template.html' ), 16 | 17 | emitterEvents: [{ 18 | message: ROUTER_ROUTE_CHANGE, 19 | method: 'onRouterRouteChange' 20 | }], 21 | 22 | data() { 23 | 24 | return { 25 | }; 26 | }, 27 | 28 | ready() { 29 | this.generateTimelineMax(); 30 | }, 31 | 32 | methods: { 33 | 34 | generateTimelineMax() {}, 35 | 36 | onRouterRouteChange() {} 37 | } 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /src/components/Logo/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~stylesheets/common/variables'; 2 | @import '~stylesheets/common/mixins'; 3 | 4 | .logo { 5 | position: absolute; 6 | top: 5rem; 7 | left: 5rem; 8 | font-size: 1.3rem; 9 | color: $color-grey-medium; 10 | transition: color 0.3s ease-out; 11 | cursor: pointer; 12 | 13 | .desktop-device &:hover { 14 | color: $color-black; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Logo/template.html: -------------------------------------------------------------------------------- 1 |

2 | Patrick Heng - Vue + Three boiler 3 |

4 | -------------------------------------------------------------------------------- /src/components/WebGLBase/core/AbstractCamera.js: -------------------------------------------------------------------------------- 1 | import OrbitControls from 'utils/webgl/OrbitControls'; 2 | import GUI from 'helpers/GUI'; 3 | 4 | /** 5 | * AbstractCamera class 6 | */ 7 | class AbstractCamera extends THREE.PerspectiveCamera { 8 | 9 | /** 10 | * Constructor function 11 | */ 12 | constructor({ fov, aspect, near, far, position, orbitControls }) { 13 | 14 | super( fov, aspect, near, far ); 15 | 16 | this.basePosition = new THREE.Vector3(); 17 | 18 | this.position.copy( position ); 19 | 20 | this.basePosition.copy( position ); 21 | 22 | if( orbitControls ) { 23 | this.controls = new OrbitControls( this ); 24 | } 25 | 26 | this.position.range = [ -1000, 1000 ]; 27 | this.rotation.range = [ - Math.PI * 2, Math.PI * 2 ]; 28 | 29 | // this.addGUI(); 30 | } 31 | 32 | addGUI() { 33 | 34 | GUI.panel 35 | .addGroup({ label: 'Camera', enable: false }) 36 | .addSubGroup({ label: 'Position', enable: false }) 37 | .addSlider( this.position, 'x', 'range', { label: 'X', dp: 0 }) 38 | .addSlider( this.position, 'y', 'range', { label: 'Y', dp: 0 }) 39 | .addSlider( this.position, 'z', 'range', { label: 'Z', dp: 0 }) 40 | .addSubGroup({ label: 'Rotation', enable: false }) 41 | .addSlider( this.rotation, 'x', 'range', { label: 'X' }) 42 | .addSlider( this.rotation, 'y', 'range', { label: 'Y' }) 43 | .addSlider( this.rotation, 'z', 'range', { label: 'Z' }); 44 | } 45 | 46 | /** 47 | * handleWindowResize function 48 | * @param {Object} size Viewport size 49 | * @param {number} param.width Viewport width 50 | * @param {number} param.height Viewport height 51 | */ 52 | handleWindowResize({ width, height }) { 53 | this.aspect = width / height; 54 | this.updateProjectionMatrix(); 55 | } 56 | } 57 | 58 | export default AbstractCamera; 59 | -------------------------------------------------------------------------------- /src/components/WebGLBase/core/AbstractRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AbstractRenderer class 3 | */ 4 | class AbstractRenderer extends THREE.WebGLRenderer { 5 | 6 | /** 7 | * constructor function 8 | */ 9 | constructor({ antialias, alpha, clearColor, clearColorAlpha, pixelRatio }) { 10 | 11 | super({ antialias, alpha }); 12 | 13 | this.setSize( window.innerWidth, window.innerHeight ); 14 | this.setClearColor( clearColor, clearColorAlpha ); 15 | this.setPixelRatio( pixelRatio ); 16 | this.clear(); 17 | 18 | this.shadowMap.enabled = true; 19 | this.shadowMapSoft = true; 20 | 21 | this.shadowCameraNear = 3; 22 | this.shadowCameraFar = 2000; 23 | this.shadowCameraFov = 50; 24 | 25 | this.shadowMapBias = 0.0039; 26 | this.shadowMapDarkness = 0.5; 27 | this.shadowMapWidth = 1024; 28 | this.shadowMapHeight = 1024; 29 | } 30 | 31 | /** 32 | * handleWindowResize function 33 | * @param {Object} size Viewport size 34 | * @param {number} param.width Viewport width 35 | * @param {number} param.height Viewport height 36 | */ 37 | handleWindowResize({ width, height }) { 38 | this.setSize( width, height ); 39 | } 40 | } 41 | 42 | export default AbstractRenderer; 43 | -------------------------------------------------------------------------------- /src/components/WebGLBase/core/AbstractScene.js: -------------------------------------------------------------------------------- 1 | import raf from 'raf-loop'; 2 | import AbstractRenderer from 'components/WebGLBase/core/AbstractRenderer'; 3 | import AbstractCamera from 'components/WebGLBase/core/AbstractCamera'; 4 | import EffectComposer from 'components/WebGLBase/postProcessing/EffectComposer'; 5 | import PostProcessing from 'components/WebGLBase/postProcessing/PostProcessing'; 6 | import Clock from 'utils/webgl/Clock'; 7 | 8 | 9 | /** 10 | * AbstractScene class 11 | */ 12 | class AbstractScene extends THREE.Scene { 13 | 14 | /** 15 | * constructor function 16 | * @param {Object} Configuration 17 | */ 18 | constructor({ camera, renderer, postProcessing }) { 19 | 20 | super(); 21 | 22 | const { fov, aspect, near, far, position, orbitControls } = camera; 23 | 24 | // Abstract camera 25 | this.camera = new AbstractCamera({ fov, aspect, near, far, position, orbitControls }); 26 | this.bufferCamera = new AbstractCamera({ fov, aspect, near, far, position, orbitControls }); 27 | 28 | const { antialias, alpha, clearColor, clearColorAlpha, pixelRatio } = renderer; 29 | 30 | // Abstract renderer 31 | this.renderer = new AbstractRenderer({ antialias, alpha, clearColor, clearColorAlpha, pixelRatio }); 32 | 33 | if( alpha ) { 34 | this.renderer.setClearColor( 0xffffff, 0 ); 35 | } 36 | 37 | // Effect composer 38 | this.effectComposer = new EffectComposer( this.renderer ); 39 | this.postProcessing = new PostProcessing( this.effectComposer, this, this.camera, this.renderer, postProcessing ); 40 | 41 | this.width = window.innerWidth; 42 | this.height = window.innerHeight; 43 | 44 | // Misc 45 | this.clock = new Clock(); 46 | this.raf = raf( ::this.render ).start(); 47 | } 48 | 49 | 50 | /** 51 | * preRender function 52 | */ 53 | preRender() { 54 | this.postProcessing.update(); 55 | } 56 | 57 | handleWindowResize({ width, height }) { 58 | this.width = width; 59 | this.height = height; 60 | this.camera.handleWindowResize({ width, height }); 61 | this.renderer.handleWindowResize({ width, height }); 62 | this.effectComposer.handleWindowResize({ width, height }); 63 | } 64 | } 65 | 66 | export default AbstractScene; 67 | -------------------------------------------------------------------------------- /src/components/WebGLBase/postProcessing/EffectComposer.js: -------------------------------------------------------------------------------- 1 | import { Composer } from '@superguigui/wagner'; 2 | 3 | /** 4 | * EffectComposer class 5 | */ 6 | class EffectComposer extends Composer { 7 | 8 | /** 9 | * Constructor function 10 | * @param {object} renderer Renderer 11 | */ 12 | constructor( renderer ) { 13 | super( renderer, { useRGBA: true }); 14 | } 15 | 16 | /** 17 | * handleWindowResize function 18 | * @param {Object} size Viewport size 19 | * @param {number} param.width Viewport width 20 | * @param {number} param.height Viewport height 21 | */ 22 | handleWindowResize({ width, height }) { 23 | this.setSize( width, height ); 24 | } 25 | } 26 | 27 | export default EffectComposer; 28 | -------------------------------------------------------------------------------- /src/components/WebGLBase/postProcessing/PostProcessing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PostProcessing class 3 | */ 4 | class PostProcessing { 5 | 6 | /** 7 | * Constructor function 8 | * 9 | * @param {EffectComposer} EffectComposer EffectComposer instance 10 | * @param {Scene} Scene Scene instance 11 | * @param {Camera} Camera Camera instance 12 | * @param {Renderer} Renderer Renderer instance 13 | * @param {Configuration} Configuration Configuration instance 14 | */ 15 | constructor( EffectComposer, Scene, Camera, Renderer, Configuration ) { 16 | 17 | this.composer = EffectComposer; 18 | this.scene = Scene; 19 | this.camera = Camera; 20 | this.renderer = Renderer; 21 | this.configuration = Configuration; 22 | this.usePostProcessing = this.configuration.active; 23 | 24 | this.glitchTimeout = null; 25 | 26 | if( this.usePostProcessing ) { 27 | this.passes = this.configuration.passes.filter( pass => pass.active ); 28 | this.constructors = this.passes.map( pass => pass.constructor() ); 29 | } 30 | 31 | // this.debug(); 32 | } 33 | 34 | debug() { 35 | 36 | const onKeyUp = ( ev ) => { 37 | 38 | if( ev.keyCode === 87 ) { // w 39 | 40 | this.usePostProcessing = this.usePostProcessing ? false : true; 41 | 42 | if ( this.usePostProcessing ) { 43 | this.startGlitch(); 44 | } else { 45 | window.clearTimeout( this.glitchTimeout ); 46 | } 47 | } 48 | }; 49 | 50 | document.addEventListener( 'keyup', onKeyUp, false ); 51 | } 52 | 53 | /** 54 | * update function 55 | */ 56 | update( ) { 57 | 58 | if( this.usePostProcessing ) { 59 | 60 | this.composer.reset(); 61 | this.composer.render( this.scene, this.camera ); 62 | 63 | for ( let i = 0; i < this.constructors.length; i++ ) { 64 | this.composer.pass( this.constructors[ i ] ); 65 | } 66 | 67 | this.composer.toScreen(); 68 | 69 | } else { 70 | 71 | this.renderer.render( this.scene, this.camera ); 72 | } 73 | } 74 | } 75 | 76 | export default PostProcessing; 77 | -------------------------------------------------------------------------------- /src/components/WebGLBase/postProcessing/passes/RGBSplit/frag.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | uniform sampler2D tInput; 3 | uniform vec2 delta; 4 | uniform vec2 resolution; 5 | 6 | void main() { 7 | 8 | vec2 dir = vUv - vec2( .5 ); 9 | float d = .7 * length( dir ); 10 | normalize( dir ); 11 | vec2 value = d * dir * delta; 12 | 13 | vec4 c1 = texture2D( tInput, vUv - value / resolution.x ); 14 | vec4 c2 = texture2D( tInput, vUv ); 15 | vec4 c3 = texture2D( tInput, vUv + value / resolution.y ); 16 | 17 | gl_FragColor = vec4( c1.r, c2.g, c3.b, c1.a + c2.a + c3.b ); 18 | 19 | } -------------------------------------------------------------------------------- /src/components/WebGLBase/postProcessing/passes/RGBSplit/index.js: -------------------------------------------------------------------------------- 1 | import Pass from '@superguigui/wagner/src/Pass'; 2 | 3 | import vertexShader from '../../shaders/vertex/basic.glsl'; 4 | import fragmentShader from './frag.glsl'; 5 | 6 | /** 7 | * RGBSplit class 8 | */ 9 | class RGBSplit extends Pass { 10 | 11 | /** 12 | * Constructor function 13 | * @param {object} options Options 14 | */ 15 | constructor( options = {}) { 16 | super(); 17 | 18 | this.setShader( vertexShader, fragmentShader ); 19 | this.params.delta = options.delta; 20 | } 21 | 22 | /** 23 | * Run function 24 | * @param {object} composer Composer 25 | */ 26 | run( composer ) { 27 | this.shader.uniforms.delta.value.copy( this.params.delta ); 28 | 29 | composer.pass( this.shader ); 30 | } 31 | } 32 | 33 | export default RGBSplit; -------------------------------------------------------------------------------- /src/components/WebGLBase/postProcessing/shaders/vertex/basic.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main() { 4 | 5 | vUv = uv; 6 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 7 | 8 | } -------------------------------------------------------------------------------- /src/components/WebGLHome/core/Scene.js: -------------------------------------------------------------------------------- 1 | import States from 'core/States'; 2 | import Emitter from 'helpers/Emitter'; 3 | import AbstractScene from 'webgl/core/AbstractScene'; 4 | import { lights as lightsConfig } from 'config/webgl/home'; 5 | 6 | import Ball from '../meshes/Ball'; 7 | 8 | import { 9 | RAYCAST_TOGGLE 10 | } from 'config/messages'; 11 | 12 | /** 13 | * Scene class 14 | */ 15 | class Scene extends AbstractScene { 16 | 17 | /** 18 | * constructor function 19 | */ 20 | constructor( config, resources ) { 21 | 22 | super({ 23 | camera: config.camera, 24 | renderer: config.renderer, 25 | postProcessing: config.postProcessing 26 | }); 27 | 28 | this.config = config; 29 | this.resources = resources; 30 | 31 | this.mouseCamPos = new THREE.Vector2(); 32 | this.mouseCastPos = new THREE.Vector2(); 33 | this.raycaster = new THREE.Raycaster(); 34 | this.isIntersecting = false; 35 | this.previousIntersectIndex = false; 36 | 37 | // Camera 38 | this.camera.position.copy( config.camera.position ); 39 | this.camera.lookAt( config.camera.target ); 40 | 41 | this.renderer.setClearColor( 0xffffff, 0 ); 42 | 43 | this.addListeners(); 44 | 45 | this.initLights(); 46 | this.initMeshes(); 47 | } 48 | 49 | addListeners() { 50 | 51 | // Bind 52 | [ 'handleMouseMove' ] 53 | .forEach( ( fn ) => this[ fn ] = this[ fn ].bind( this ) ); 54 | 55 | document.addEventListener( 'mousemove', this.handleMouseMove, false ); 56 | } 57 | 58 | dispose() { 59 | document.removeEventListener( 'mousemove', this.handleMouseMove, false ); 60 | } 61 | 62 | initLights() { 63 | this.pointLights = []; 64 | 65 | this.ambientLight = new THREE.AmbientLight( lightsConfig.ambientLight.color, lightsConfig.ambientLight.intensity ); 66 | this.add( this.ambientLight ); 67 | } 68 | 69 | initMeshes() { 70 | 71 | this.ball = new Ball( this.config.ball, this.resources ); 72 | this.ball.play(); 73 | this.add( this.ball ); 74 | 75 | } 76 | 77 | raycast() { 78 | 79 | this.raycaster.setFromCamera( this.mouseCastPos, this.camera ); 80 | 81 | const intersects = this.raycaster.intersectObjects( [ this.ball ] ); 82 | 83 | if ( intersects.length > 0 ) { 84 | 85 | if( !this.isIntersecting ) { 86 | Emitter.emit( RAYCAST_TOGGLE, true ); 87 | } 88 | this.isIntersecting = true; 89 | 90 | } else if( this.isIntersecting ) { 91 | Emitter.emit( RAYCAST_TOGGLE, false ); 92 | 93 | this.isIntersecting = false; 94 | } 95 | 96 | } 97 | 98 | handleMouseMove( ev ) { 99 | 100 | if ( this.config.camera.orbitControls ) return; 101 | 102 | const mouseSensibility = 0.5; 103 | this.mouseCamPos.x = (- ( ev.pageX - this.width / 2 ) / ( this.width / 2 ) ) * mouseSensibility; 104 | this.mouseCamPos.y = ( ( ev.pageY - this.height / 2 ) / ( this.height / 2 ) ) * mouseSensibility; 105 | 106 | this.mouseCastPos.x = ( ev.clientX / this.width ) * 2 - 1; 107 | this.mouseCastPos.y = - ( ev.clientY / this.height ) * 2 + 1; 108 | } 109 | 110 | handleWindowResize({ width, height }) { 111 | this.width = width; 112 | this.height = height; 113 | 114 | this.camera.handleWindowResize({ width, height }); 115 | this.renderer.handleWindowResize({ width, height }); 116 | this.effectComposer.handleWindowResize({ width, height }); 117 | } 118 | 119 | /** 120 | * render function 121 | */ 122 | render() { 123 | 124 | if( States.isDesktop ) this.raycast(); 125 | 126 | this.preRender(); 127 | 128 | this.ball.update( this.clock.time, this.clock.delta ); 129 | } 130 | } 131 | 132 | export default Scene; 133 | -------------------------------------------------------------------------------- /src/components/WebGLHome/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import './styles.scss'; 4 | 5 | import EventManagerMixin from 'mixins/EventManagerMixin'; 6 | 7 | import Scene from './core/Scene'; 8 | 9 | import { home as homeConfig } from 'config/webgl'; 10 | 11 | import { 12 | WINDOW_RESIZE, 13 | RAYCAST_TOGGLE 14 | } from 'config/messages'; 15 | 16 | export default Vue.extend({ 17 | 18 | mixins: [ EventManagerMixin ], 19 | 20 | template: require( './template.html' ), 21 | 22 | emitterEvents: [{ 23 | message: WINDOW_RESIZE, 24 | method: 'onWindowResize' 25 | },{ 26 | message: RAYCAST_TOGGLE, 27 | method: 'onRaycastToggle' 28 | }], 29 | 30 | domEvents: [], 31 | 32 | props: { 33 | resources: Object 34 | }, 35 | 36 | data() { 37 | 38 | return { 39 | isIntersecting: false 40 | }; 41 | }, 42 | 43 | created() { 44 | this.scene = new Scene( homeConfig, this.resources ); 45 | this.sceneDomEl = this.scene.renderer.domElement; 46 | }, 47 | 48 | ready() { 49 | this.$el.appendChild( this.sceneDomEl ); 50 | }, 51 | 52 | beforeDestroy() { 53 | this.scene.raf.stop(); 54 | this.scene.remove(); 55 | }, 56 | 57 | methods: { 58 | 59 | onWindowResize({ width, height }) { 60 | this.scene.handleWindowResize({ width, height }); 61 | }, 62 | 63 | onRaycastToggle( toggle ) { 64 | this.isIntersecting = toggle; 65 | } 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /src/components/WebGLHome/meshes/Ball/BallGeometry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * BallGeometry class 3 | */ 4 | class BallGeometry extends THREE.SphereGeometry { 5 | 6 | /** 7 | * constructor function 8 | */ 9 | constructor({ radius, segments: { width: segmentsWidth, height: segmentsHeight } }) { 10 | super( radius, segmentsWidth, segmentsHeight ); 11 | 12 | } 13 | } 14 | 15 | export default BallGeometry; 16 | -------------------------------------------------------------------------------- /src/components/WebGLHome/meshes/Ball/BallMaterial.js: -------------------------------------------------------------------------------- 1 | import shaderParse from 'utils/webgl/shader-parse'; 2 | import vertexShader from './shader/vert.glsl'; 3 | import fragmentShader from './shader/frag.glsl'; 4 | import GUI from 'helpers/GUI'; 5 | 6 | 7 | class BallMaterial extends THREE.ShaderMaterial { 8 | 9 | /** 10 | * Constructor function 11 | * @param {Object} options Options 12 | */ 13 | constructor() { 14 | const defines = {}; 15 | const baseShader = THREE.ShaderLib.phong; 16 | const baseUniforms = THREE.UniformsUtils.clone( baseShader.uniforms ); 17 | 18 | const uniforms = THREE.UniformsUtils.merge( [ 19 | baseUniforms, 20 | { 21 | u_time: { type: 'f', value: 0.0 }, 22 | u_speed: { type: 'f', value: 0.0, range: [ 0, 20 ] }, 23 | u_amp: { type: 'f', value: 0.0, range: [ 0, 20 ] } 24 | } 25 | ]); 26 | 27 | const options = { 28 | fragmentShader: shaderParse( fragmentShader ), 29 | vertexShader: shaderParse( vertexShader ), 30 | defines: defines, 31 | uniforms: uniforms, 32 | lights: true, 33 | fog: false, 34 | side: THREE.FrontSide, 35 | blending: THREE.NormalBlending, 36 | transparent: true, 37 | wireframe: true 38 | }; 39 | 40 | super( options ); 41 | 42 | this.defaultUniforms = { 43 | u_speed: { type: 'f', value: 0.2, range: [ 0, 20 ] }, 44 | u_amp: { type: 'f', value: 3.0, range: [ 0, 20 ] } 45 | }; 46 | 47 | this.hoverTl = new TimelineMax(); 48 | this.isPaused = false; 49 | this.needsUpdate = true; 50 | 51 | this.addGUI(); 52 | } 53 | 54 | addGUI() { 55 | GUI.panel 56 | .addGroup({ label: 'Ball vertex', enable: false }) 57 | .addSlider( this.uniforms.u_speed, 'value', 'range', { label: 'Speed' }) 58 | .addSlider( this.uniforms.u_amp, 'value', 'range', { label: 'Amplitude' }); 59 | } 60 | 61 | play() { 62 | 63 | this.uniforms.u_speed.value = this.defaultUniforms.u_speed.value; 64 | this.uniforms.u_amp.value = this.defaultUniforms.u_amp.value; 65 | 66 | this.isPaused = false; 67 | } 68 | 69 | pause() { 70 | 71 | this.isPaused = true; 72 | 73 | clearTimeout( this.noiseGlitchInterval ); 74 | 75 | this.uniforms.u_speed.value = 0.0; 76 | this.uniforms.u_amp.value = 0.0; 77 | } 78 | 79 | hover() { 80 | 81 | this.hoverTl.progress(1); 82 | this.hoverTl.kill(); 83 | 84 | this.hoverTl 85 | .to( this.uniforms.u_speed, 1, { 86 | value: this.defaultUniforms.u_speed.value * 2, 87 | ease: Expo.easeOut 88 | }) 89 | .to( this.uniforms.u_amp, 1, { 90 | value: this.defaultUniforms.u_amp.value * 2, 91 | ease: Expo.easeOut 92 | }, 0); 93 | } 94 | 95 | unHover() { 96 | 97 | this.hoverTl.kill(); 98 | 99 | let speed, amp; 100 | 101 | if( this.isPaused ) { 102 | speed = 0; 103 | amp = 0; 104 | } else { 105 | speed = this.defaultUniforms.u_speed.value; 106 | amp = this.defaultUniforms.u_amp.value; 107 | } 108 | 109 | this.hoverTl 110 | .to( this.uniforms.u_speed, 0.5, { 111 | value: speed, 112 | ease: Expo.easeOut 113 | }) 114 | .to( this.uniforms.u_amp, 0.5, { 115 | value: amp, 116 | ease: Expo.easeOut 117 | }, 0); 118 | } 119 | 120 | /** 121 | * Update function 122 | * @param {number} time Time 123 | */ 124 | update( time ) { 125 | 126 | if( !this.isPaused ) { 127 | this.uniforms.u_time.value = time; 128 | } 129 | 130 | } 131 | } 132 | 133 | export default BallMaterial; 134 | -------------------------------------------------------------------------------- /src/components/WebGLHome/meshes/Ball/index.js: -------------------------------------------------------------------------------- 1 | import BallGeometry from './BallGeometry'; 2 | import BallMaterial from './BallMaterial'; 3 | import Emitter from 'helpers/Emitter'; 4 | import GUI from 'helpers/GUI'; 5 | import { 6 | RAYCAST_TOGGLE 7 | } from 'config/messages'; 8 | 9 | /** 10 | * Ball class 11 | */ 12 | class Ball extends THREE.Mesh { 13 | 14 | /** 15 | * Constructor function 16 | * @param {Object} configuration Configuration 17 | */ 18 | constructor({ geometry, material, position }, resources ) { 19 | 20 | super( new BallGeometry( geometry ), new BallMaterial( material, resources )); 21 | 22 | this.isHovered = false; 23 | 24 | this.castShadow = true; 25 | 26 | this.position.copy(position); 27 | 28 | this.position.range = [ -300, 300 ]; 29 | 30 | this.rotationSpeed = 0.005; 31 | 32 | this.addGUI(); 33 | this.addListeners(); 34 | this.generateTimelineMax(); 35 | 36 | this.enterTl.play(); 37 | } 38 | 39 | addListeners() { 40 | this.onRaycastToggle = ::this.onRaycastToggle; 41 | 42 | Emitter.on( RAYCAST_TOGGLE, this.onRaycastToggle ); 43 | } 44 | 45 | generateTimelineMax() { 46 | 47 | this.enterTl = new TimelineMax({ paused: true }); 48 | 49 | this.enterTl 50 | .from(this.position, 2, { z: 500, ease: Expo.easeOut }); 51 | } 52 | 53 | play() { 54 | this.material.play(); 55 | } 56 | 57 | pause() { 58 | this.material.pause(); 59 | } 60 | 61 | addGUI() { 62 | GUI.panel 63 | .addGroup({ label: 'Ball position', enable: false }) 64 | .addSlider( this.position, 'x', 'range', { label: 'X', dp: 0 }) 65 | .addSlider( this.position, 'y', 'range', { label: 'Y', dp: 0 }) 66 | .addSlider( this.position, 'z', 'range', { label: 'Z', dp: 0 }); 67 | } 68 | 69 | onRaycastToggle( toggle ) { 70 | if( toggle ) { 71 | this.material.hover(); 72 | } else { 73 | this.material.unHover(); 74 | } 75 | } 76 | 77 | update( time ) { 78 | this.rotation.y += this.rotationSpeed; 79 | this.material.update( time ); 80 | } 81 | } 82 | 83 | export default Ball; 84 | -------------------------------------------------------------------------------- /src/components/WebGLHome/meshes/Ball/shader/frag.glsl: -------------------------------------------------------------------------------- 1 | #define PHONG 2 | 3 | uniform vec3 diffuse; 4 | uniform vec3 emissive; 5 | uniform vec3 specular; 6 | uniform float shininess; 7 | uniform float opacity; 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | void main() { 32 | 33 | #include 34 | 35 | vec4 diffuseColor = vec4( diffuse, opacity ); 36 | ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); 37 | vec3 totalEmissiveRadiance = emissive; 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | // accumulation 50 | #include 51 | #include 52 | 53 | // modulation 54 | #include 55 | 56 | vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance; 57 | 58 | #include 59 | 60 | 61 | gl_FragColor = vec4( outgoingLight, diffuseColor.a ); 62 | 63 | #include 64 | #include 65 | #include 66 | #include 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/components/WebGLHome/meshes/Ball/shader/vert.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: cnoise2 = require(glsl-noise/classic/2d); 2 | // #pragma glslify: snoise2 = require(glsl-noise/simplex/2d); 3 | // #pragma glslify: pnoise2 = require(glsl-noise/periodic/2d); 4 | 5 | uniform float u_time; 6 | uniform float u_speed; 7 | uniform float u_amp; 8 | 9 | varying vec3 vViewPosition; 10 | 11 | 12 | #ifndef FLAT_SHADED 13 | 14 | varying vec3 vNormal; 15 | 16 | #endif 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #pragma glslify: cnoise2 = require(glsl-noise/classic/2d); 31 | 32 | void main() { 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | #include 53 | #include 54 | #include 55 | 56 | vNormal = normal; 57 | 58 | float displacement = u_amp * cnoise2( vec2( position * 0.05 ) + u_time * u_speed ); 59 | vec3 newPosition = position + normal * displacement; 60 | 61 | mvPosition = modelViewMatrix * vec4( position, 1.0 ); 62 | vViewPosition = - mvPosition.xyz; 63 | 64 | gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/components/WebGLHome/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~stylesheets/common/variables'; 2 | @import '~stylesheets/common/mixins'; 3 | 4 | .webgl-home { 5 | position: relative; 6 | z-index: 0; 7 | 8 | &--is-intersecting { 9 | cursor: pointer; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/WebGLHome/template.html: -------------------------------------------------------------------------------- 1 |
7 | 8 |
9 | -------------------------------------------------------------------------------- /src/config/messages/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalMessages = { 4 | 5 | /* 6 | * WINDOW 7 | */ 8 | 9 | WINDOW_RESIZE: 'WINDOW_RESIZE', 10 | WINDOW_ON_FOCUS: 'WINDOW_ON_FOCUS', 11 | WINDOW_ON_BLUR: 'WINDOW_ON_BLUR', 12 | WINDOW_ON_MOUSEMOVE: 'WINDOW_ON_MOUSEMOVE', 13 | 14 | /* 15 | * RESOURCES 16 | */ 17 | RESSOURCES_PROGRESS: 'RESSOURCES_PROGRESS', 18 | RESOURCES_READY: 'RESOURCES_READY', 19 | 20 | /* 21 | * ROUTER 22 | */ 23 | ROUTER_LEAVE_HOME: 'ROUTER_LEAVE_HOME', 24 | ROUTER_ROUTE_CHANGE: 'ROUTER_ROUTE_CHANGE' 25 | }; 26 | 27 | module.exports = globalMessages; 28 | -------------------------------------------------------------------------------- /src/config/messages/index.js: -------------------------------------------------------------------------------- 1 | import global from './global'; 2 | import webGL from './webGL'; 3 | 4 | const messages = { 5 | ...global, 6 | ...webGL 7 | }; 8 | 9 | module.exports = messages; 10 | -------------------------------------------------------------------------------- /src/config/messages/webGL.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const webGLMessage = { 4 | 5 | RAYCAST_TOGGLE: 'RAYCAST_TOGGLE' 6 | }; 7 | 8 | module.exports = webGLMessage; 9 | -------------------------------------------------------------------------------- /src/config/resources.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | // ==== Textures 3 | { 4 | type: 'texture', 5 | id: 'stroke', 6 | url: '/textures/stroke.png' 7 | } 8 | // ==== Sounds 9 | // { 10 | // type: 'audio', 11 | // id: 'sound-example', 12 | // url: './audios/sound-example.mp3' 13 | // }, 14 | // 15 | // ==== Videos 16 | // { 17 | // type: 'video', 18 | // id: 'video-example', 19 | // url: '/videos/video-example.png' 20 | // } 21 | // 22 | // ==== AWD 23 | // { 24 | // type: 'model', 25 | // id: 'model-example', 26 | // url: './models/model-example.awd' 27 | // }, 28 | ]; 29 | -------------------------------------------------------------------------------- /src/config/webgl/common/camera.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fov: 45, 3 | aspect: window.innerWidth / window.innerHeight, 4 | near: 1, 5 | far: 1000, 6 | position: new THREE.Vector3( 0, 0, 0 ), 7 | orbitControls: false 8 | }; -------------------------------------------------------------------------------- /src/config/webgl/common/index.js: -------------------------------------------------------------------------------- 1 | export camera from './camera'; -------------------------------------------------------------------------------- /src/config/webgl/home/ball.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | position: new THREE.Vector3( 0, 0, 0 ), 4 | 5 | geometry: { 6 | radius: 50, 7 | segments: { 8 | width: 50, 9 | height: 50 10 | } 11 | }, 12 | 13 | material: { 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/config/webgl/home/camera.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fov: 45, 3 | aspect: window.innerWidth / window.innerHeight, 4 | near: 1, 5 | far: 2000, 6 | position: new THREE.Vector3( 0, 0, 250 ), 7 | target: new THREE.Vector3( 0, 0, 0 ), 8 | orbitControls: false 9 | }; 10 | -------------------------------------------------------------------------------- /src/config/webgl/home/index.js: -------------------------------------------------------------------------------- 1 | export camera from './camera'; 2 | export renderer from './renderer'; 3 | export postProcessing from './postProcessing'; 4 | export lights from './lights'; 5 | export ball from './ball'; 6 | -------------------------------------------------------------------------------- /src/config/webgl/home/lights.js: -------------------------------------------------------------------------------- 1 | export default { 2 | ambientLight: { 3 | color: new THREE.Color( '#fefefe' ), 4 | intensity: 1 5 | }, 6 | directionalLight: { 7 | color: new THREE.Color( '#e7e3e3' ), 8 | intensity: 3, 9 | target: new THREE.Vector3( 0, 0, 0 ), 10 | position: new THREE.Vector3( 0, 1, 0 ) 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/config/webgl/home/postProcessing.js: -------------------------------------------------------------------------------- 1 | import { BlendMode } from '@superguigui/wagner'; 2 | import MultiPassBloomPass from '@superguigui/wagner/src/passes/bloom/MultiPassBloomPass'; 3 | import TiltShiftPass from '@superguigui/wagner/src/passes/tiltshift/tiltshiftPass'; 4 | import VignettePass from '@superguigui/wagner/src/passes/vignette/VignettePass'; 5 | import NoisePass from '@superguigui/wagner/src/passes/noise/noise'; 6 | import FXAAPass from '@superguigui/wagner/src/passes/fxaa/FXAAPass'; 7 | import RGBSplitPass from 'webgl/postProcessing/passes/RGBSplit'; 8 | 9 | export default { 10 | active: false, 11 | effectComposer: { 12 | useRGBA: true 13 | }, 14 | passes: [ 15 | { 16 | name: 'RGBSplitPass', 17 | active: false, 18 | constructor: () => new RGBSplitPass({ 19 | delta: new THREE.Vector2(10, 10) 20 | }) 21 | }, 22 | { 23 | name: 'multiPassBloomPass', 24 | active: false, 25 | constructor: () => new MultiPassBloomPass({ 26 | blurAmount: 0.05, 27 | applyZoomBlur: true, 28 | zoomBlurStrength: 2, 29 | blendMode: BlendMode.Screen 30 | }) 31 | }, 32 | { 33 | name: 'tiltShiftPass', 34 | active: false, 35 | constructor: () => new TiltShiftPass({ 36 | bluramount: 0.5, 37 | center: 1, 38 | stepSize: 0.005 39 | }) 40 | }, 41 | { 42 | name: 'noisePass', 43 | active: true, 44 | constructor: () => new NoisePass({ 45 | amount: 0.02, 46 | speed: 0.1 47 | }) 48 | }, 49 | { 50 | name: 'vignettePass', 51 | active: false, 52 | constructor: () => new VignettePass({ 53 | boost: 1, 54 | reduction: 0.5 55 | }) 56 | }, 57 | { 58 | name: 'fxaaPass', 59 | active: false, 60 | constructor: () => new FXAAPass() 61 | } 62 | ] 63 | }; 64 | -------------------------------------------------------------------------------- /src/config/webgl/home/renderer.js: -------------------------------------------------------------------------------- 1 | export default { 2 | antialias: true, //window.devicePixelRatio <= 1 3 | alpha: true, 4 | clearColor: new THREE.Color( 0xffffff ), 5 | pixelRatio: 1 // window.devicePixelRatio || 1 6 | }; 7 | -------------------------------------------------------------------------------- /src/config/webgl/index.js: -------------------------------------------------------------------------------- 1 | import * as common from './common'; 2 | import * as home from './home'; 3 | 4 | export default { common, home }; 5 | -------------------------------------------------------------------------------- /src/containers/Application/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import EventManagerMixin from 'mixins/EventManagerMixin'; 4 | 5 | import States from 'core/States'; 6 | 7 | import debounce from 'lodash.debounce'; 8 | 9 | import 'augmented-compile-shader'; 10 | import 'utils/webgl/AugmentedConsole'; 11 | import 'gsap'; 12 | 13 | import { 14 | WINDOW_RESIZE, 15 | WINDOW_ON_FOCUS, 16 | WINDOW_ON_BLUR 17 | } from 'config/messages'; 18 | 19 | import LogoComponent from 'components/Logo'; 20 | 21 | export default Vue.extend({ 22 | 23 | mixins: [EventManagerMixin], 24 | 25 | template: require('./template.html'), 26 | 27 | emitterEvents: [], 28 | 29 | domEvents: [{ 30 | target: window, 31 | event: 'resize', 32 | method: 'handleWindowResize' 33 | },{ 34 | target: window, 35 | event: 'blur', 36 | method: 'handleWindowBlur' 37 | },{ 38 | target: window, 39 | event: 'focus', 40 | method: 'handleWindowFocus' 41 | }], 42 | 43 | data() { 44 | 45 | return { 46 | }; 47 | }, 48 | 49 | ready() { 50 | this.creditsLog(); 51 | this.addDeviceClass(); 52 | this.addBrowserClass(); 53 | }, 54 | 55 | methods: { 56 | 57 | bind() { 58 | this.handleWindowResize = debounce(this.broadcastWindowSize, 200); 59 | }, 60 | 61 | creditsLog() { 62 | /*eslint-disable */ 63 | console.log('%c', 'background: #ffffff; font-size: 11px; color: #f0f0f0'); 64 | 65 | console.log('%c > Portfolio: http://hengpatrick.fr', 'background: #2c3e50; padding:5px; font-size: 11px; color: #ffffff'); 66 | console.log('%c > Lab: http://lab.hengpatrick.fr', 'background: #2c3e50; padding:5px; font-size: 11px; color: #ffffff'); 67 | console.log('%c > Twitter: http://twitter.com/Pat_Hg', 'background: #2c3e50; padding:5px; font-size: 11px; color: #ffffff'); 68 | console.log('%c', 'background: #ffffff; font-size: 11px; color: #f0f0f0'); 69 | /*eslint-enable */ 70 | }, 71 | 72 | addBrowserClass() { 73 | this.$el.classList.add(States.browserName + '-browser'); 74 | }, 75 | 76 | addDeviceClass() { 77 | this.$el.classList.add(States.deviceType + '-device'); 78 | }, 79 | 80 | handleWindowBlur() { 81 | this.emitter.emit( WINDOW_ON_BLUR ); 82 | }, 83 | 84 | handleWindowFocus() { 85 | this.emitter.emit( WINDOW_ON_FOCUS ); 86 | }, 87 | 88 | broadcastWindowSize() { 89 | this.emitter.emit(WINDOW_RESIZE, { 90 | width: window.innerWidth, 91 | height: window.innerHeight 92 | }); 93 | } 94 | }, 95 | 96 | components: { 97 | 'logo' : LogoComponent 98 | } 99 | }); 100 | -------------------------------------------------------------------------------- /src/containers/Application/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 |
8 | -------------------------------------------------------------------------------- /src/containers/Homepage/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import './styles.scss'; 4 | 5 | import EventManagerMixin from 'mixins/EventManagerMixin'; 6 | import LoaderComponent from 'components/Loader'; 7 | import WebGLHomeComponent from 'components/WebGLHome'; 8 | import AssetsLoader from 'helpers/AssetsLoader'; 9 | 10 | 11 | import { 12 | WINDOW_RESIZE, 13 | WINDOW_ON_MOUSEMOVE, 14 | RESOURCES_READY, 15 | ROUTER_LEAVE_HOME 16 | } from 'config/messages'; 17 | 18 | export default Vue.extend({ 19 | 20 | mixins: [ EventManagerMixin ], 21 | 22 | template: require( './template.html' ), 23 | 24 | route: { 25 | 26 | activate: function( { next } ) { 27 | 28 | TweenMax.fromTo(this.$el, 0.7, { 29 | opacity: 0 30 | }, { 31 | opacity: 1, 32 | ease: Expo.easeOut 33 | }); 34 | 35 | next(); 36 | }, 37 | 38 | deactivate: function( { next } ) { 39 | 40 | this.emitter.emit( ROUTER_LEAVE_HOME ); 41 | TweenMax.to(this.$el, 0.7, { 42 | opacity: 0, 43 | onComplete: next, 44 | ease: Expo.easeOut, 45 | delay: 0.5 46 | }); 47 | } 48 | 49 | }, 50 | 51 | emitterEvents: [{ 52 | message: WINDOW_RESIZE, 53 | method: 'onWindowResize' 54 | }], 55 | 56 | domEvents: [{ 57 | target: window, 58 | event: 'mousemove', 59 | method: 'handleMouseMove' 60 | }], 61 | 62 | data() { 63 | 64 | return { 65 | assetsIsLoaded: false, 66 | resources: {} 67 | }; 68 | }, 69 | 70 | created() { 71 | 72 | this.loader = AssetsLoader; 73 | 74 | this.loader 75 | .load() 76 | .then( resources => { 77 | 78 | resources.forEach( ({ id, resource }) => this.resources[ id ] = resource ); 79 | this.emitter.emit( RESOURCES_READY, this.resources ); 80 | 81 | this.assetsIsLoaded = true; 82 | } ); 83 | }, 84 | 85 | beforeDestroy() { 86 | }, 87 | 88 | methods: { 89 | 90 | onWindowResize() {}, 91 | 92 | handleMouseMove( ev ) { 93 | this.emitter.emit( WINDOW_ON_MOUSEMOVE, ev ); 94 | } 95 | }, 96 | 97 | components: { 98 | 'loader': LoaderComponent, 99 | 'webgl-home': WebGLHomeComponent 100 | } 101 | }); 102 | -------------------------------------------------------------------------------- /src/containers/Homepage/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~stylesheets/common/variables'; 2 | @import '~stylesheets/common/mixins'; 3 | 4 | .page--home { 5 | width: 100%; 6 | height: 100%; 7 | overflow: hidden; 8 | } 9 | 10 | .page__container { 11 | position: relative; 12 | width: 100%; 13 | height: 100%; 14 | overflow: hidden; 15 | } 16 | -------------------------------------------------------------------------------- /src/containers/Homepage/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
8 | 9 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/core/Router.js: -------------------------------------------------------------------------------- 1 | import VueRouter from 'vue-router'; 2 | import Emitter from 'helpers/Emitter'; 3 | import HomePageComponent from 'containers/Homepage'; 4 | 5 | import { 6 | ROUTER_ROUTE_CHANGE 7 | } from 'config/messages'; 8 | 9 | Vue.use( VueRouter ); 10 | 11 | class Router extends VueRouter { 12 | 13 | constructor() { 14 | 15 | super({ 16 | hashbang: false, 17 | pushState: true, 18 | history: true, 19 | abstract: false, 20 | saveScrollPosition: false, 21 | transitionOnLoad: false 22 | }); 23 | 24 | this.map({ 25 | 26 | '*': { 27 | name: "home", 28 | component: HomePageComponent 29 | } 30 | }); 31 | 32 | this.afterEach( ({ to }) => { 33 | Emitter.emit( ROUTER_ROUTE_CHANGE, to.name ); 34 | }); 35 | } 36 | } 37 | 38 | export default new Router; 39 | -------------------------------------------------------------------------------- /src/core/States.js: -------------------------------------------------------------------------------- 1 | import MobileDetect from 'mobile-detect'; 2 | import browser from 'detect-browser'; 3 | 4 | class States { 5 | 6 | constructor() { 7 | 8 | this.userAgent = window.navigator.userAgent; 9 | this.mobileDetect = new MobileDetect( this.userAgent ); 10 | this.deviceType = this.getDeviceType(); 11 | this.browserName = browser.name; 12 | 13 | this.isDesktop = ( this.deviceType === 'desktop' ); 14 | this.isTablet = ( this.deviceType === 'tablet' ); 15 | this.isMobile = ( this.deviceType === 'mobile' ); 16 | } 17 | 18 | getDeviceType() { 19 | if( this.mobileDetect.tablet() ) { 20 | return "tablet"; 21 | } else if ( this.mobileDetect.mobile() ) { 22 | return "mobile"; 23 | } else { 24 | return "desktop"; 25 | } 26 | } 27 | 28 | isIE() { 29 | return ( this.userAgent.indexOf( 'MSIE ' ) > 0 || this.userAgent.indexOf( 'Trident/' ) > 0 || this.userAgent.indexOf( 'Edge/' ) > 0 ); 30 | } 31 | } 32 | 33 | 34 | export default new States(); 35 | -------------------------------------------------------------------------------- /src/helpers/AssetsLoader.js: -------------------------------------------------------------------------------- 1 | import AWDLoader from 'utils/webgl/AWDLoader'; 2 | import SoundManager from 'helpers/SoundManager'; 3 | import VideoManager from 'helpers/VideoManager'; 4 | import files from 'config/resources'; 5 | 6 | import Emitter from 'helpers/Emitter'; 7 | 8 | import { RESSOURCES_PROGRESS } from 'config/messages'; 9 | 10 | /** 11 | * AssetsLoader class 12 | */ 13 | class AssetsLoader { 14 | 15 | /** 16 | * constructor function 17 | * @param {array} files Files to load 18 | * @param {function} onResourceLoaded Called everytime a resource is loaded 19 | */ 20 | constructor() { 21 | 22 | this.promises = []; 23 | this.totalProgress = files.length; 24 | this.currentProgress = 0; 25 | 26 | const getLoader = type => { 27 | switch( type ) { 28 | case 'model': return new AWDLoader(); 29 | case 'texture': return new THREE.TextureLoader(); 30 | case 'audio': return SoundManager; 31 | case 'video': return VideoManager; 32 | } 33 | }; 34 | 35 | files.map( file => { 36 | 37 | const { type, id, url } = file; 38 | 39 | const promise = new Promise( ( resolve, reject ) => { 40 | getLoader( type ).load( 41 | url, 42 | resource => { 43 | 44 | resolve({ id, resource }); 45 | 46 | this.currentProgress++; 47 | 48 | Emitter.emit( RESSOURCES_PROGRESS, this.currentProgress / this.totalProgress ); 49 | 50 | if(this.currentProgress >= this.totalProgress) { 51 | this.load(); 52 | } 53 | }, 54 | () => null, 55 | () => reject, 56 | id 57 | ); 58 | }); 59 | 60 | this.promises.push( promise ); 61 | 62 | }); 63 | } 64 | 65 | /** 66 | * load function 67 | * @return {promise} Promise 68 | */ 69 | load() { 70 | 71 | return Promise.all( this.promises ); 72 | } 73 | 74 | } 75 | 76 | export default new AssetsLoader(); 77 | -------------------------------------------------------------------------------- /src/helpers/Emitter.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'event-emitter'; 2 | 3 | const Emitter = new EventEmitter; 4 | 5 | window.Emitter = Emitter; 6 | 7 | export default Emitter; 8 | -------------------------------------------------------------------------------- /src/helpers/GUI.js: -------------------------------------------------------------------------------- 1 | import ControlKit from 'controlkit'; 2 | 3 | class GUI extends ControlKit { 4 | 5 | constructor( options ) { 6 | super( options ); 7 | 8 | this.panel = this.addPanel(); 9 | } 10 | 11 | removeGroup( index ) { 12 | this.panel._groups[ index ]._node._element.remove(); 13 | } 14 | 15 | removeLastGroup() { 16 | this.removeGroup( this.panel._groups.length - 1 ); 17 | } 18 | 19 | addPanel( options = {}) { 20 | return super.addPanel({ 21 | align: 'right', 22 | position: [ 10, 10 ], 23 | opacity: 0.8, 24 | width: 275, 25 | ratio: 10, 26 | fixed: false, 27 | ...options 28 | }); 29 | } 30 | } 31 | 32 | export default new GUI(); 33 | -------------------------------------------------------------------------------- /src/helpers/SoundManager.js: -------------------------------------------------------------------------------- 1 | import { Howl, Howler } from 'howler'; 2 | import Emitter from 'helpers/Emitter'; 3 | 4 | import { 5 | WINDOW_ON_FOCUS, 6 | WINDOW_ON_BLUR 7 | } from 'config/messages'; 8 | 9 | /** 10 | * SoundManager class 11 | */ 12 | class SoundManager { 13 | 14 | constructor() { 15 | 16 | this.sounds = []; 17 | 18 | this.blockMute = false; 19 | 20 | this.bind(); 21 | 22 | this.addListeners(); 23 | } 24 | 25 | bind() { 26 | 27 | [ 'onWindowBlur', 'onWindowFocus' ] 28 | .forEach( ( fn ) => this[ fn ] = this[ fn ].bind( this ) ); 29 | 30 | } 31 | 32 | addListeners() { 33 | 34 | Emitter.on( WINDOW_ON_FOCUS, this.onWindowFocus ); 35 | Emitter.on( WINDOW_ON_BLUR, this.onWindowBlur ); 36 | 37 | } 38 | 39 | get( id ) { 40 | 41 | if( typeof this.sounds[ id ] === 'undefined' ) return false; 42 | 43 | return this.sounds[ id ]; 44 | } 45 | 46 | play( id ) { 47 | 48 | if( typeof this.sounds[ id ] === 'undefined' ) return; 49 | 50 | this.sounds[ id ].play(); 51 | 52 | } 53 | 54 | onWindowFocus() { 55 | if(this.blockMute) return; 56 | 57 | Howler.unmute(); 58 | } 59 | 60 | onWindowBlur() { 61 | this.mute(); 62 | } 63 | 64 | mute() { 65 | Howler.mute(); 66 | } 67 | 68 | lockMute() { 69 | this.blockMute = true; 70 | this.mute(); 71 | } 72 | 73 | unmute() { 74 | Howler.unmute(); 75 | this.blockMute = false; 76 | } 77 | 78 | load( url, onLoad, onSucess, onReject, id ) { 79 | 80 | const audio = new Howl({ 81 | urls: [ url ], 82 | volume: 1, 83 | onload: () => { 84 | 85 | this.sounds[ id ] = audio; 86 | 87 | onLoad( audio ); 88 | } 89 | }); 90 | } 91 | 92 | } 93 | 94 | export default new SoundManager(); 95 | -------------------------------------------------------------------------------- /src/helpers/VideoManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * VideoManager class 3 | */ 4 | 5 | class VideoManager { 6 | 7 | constructor() { 8 | this.videos = []; 9 | } 10 | 11 | load(url, onLoad, onSucess, onReject, id) { 12 | const request = document.createElement('video'); 13 | request.addEventListener('canplaythrough', ()=> { 14 | onLoad(request); 15 | }, false); 16 | request.addEventListener('error', onReject, false); 17 | request.preload = 'auto'; 18 | request.src = url; 19 | request.load(); 20 | this.videos[id] = request; 21 | } 22 | } 23 | 24 | export default new VideoManager(); 25 | -------------------------------------------------------------------------------- /src/mixins/EventManagerMixin.js: -------------------------------------------------------------------------------- 1 | import Emitter from 'helpers/Emitter'; 2 | 3 | /* 4 | * ==== EventManagerMixin ==== 5 | */ 6 | 7 | const EventManagerMixin = { 8 | 9 | emitterEvents: [], 10 | 11 | domEvents: [], 12 | 13 | created() { 14 | 15 | this.emitter = Emitter; 16 | this.emitterEvents = this.$options.emitterEvents; 17 | this.domEvents = this.$options.domEvents; 18 | 19 | this.bind(); 20 | }, 21 | 22 | ready() { 23 | this.addEventListeners(); 24 | }, 25 | 26 | beforeDestroy() { 27 | 28 | this.removeEventListeners(); 29 | }, 30 | 31 | methods: { 32 | 33 | bind() { 34 | 35 | this.emitterEvents.forEach((event) => { 36 | if(typeof this[event.method] === 'undefined') { 37 | /*eslint-disable */ 38 | console.error('Missing method attribute for ', this); 39 | /*eslint-enable */ 40 | } else { 41 | this[event.method] = ::this[event.method]; 42 | } 43 | }); 44 | }, 45 | 46 | addEventListeners() { 47 | 48 | // Add EventEmitter events 49 | this.emitterEvents.forEach((emitterEvent) => { 50 | 51 | if(typeof emitterEvent.once !== 'undefined') { 52 | if(emitterEvent.once) { 53 | this.emitter.once(emitterEvent.message, this[emitterEvent.method]); 54 | } 55 | } else { 56 | 57 | /*eslint-disable */ 58 | if(typeof emitterEvent.message === 'undefined') { 59 | console.error('Missing message attribute for ', emitterEvent); 60 | } else if (typeof this[emitterEvent.method] === 'undefined') { 61 | console.error('Missing method attribute for ', emitterEvent); 62 | } else { 63 | this.emitter.on(emitterEvent.message, this[emitterEvent.method]); 64 | } 65 | /*eslint-enable */ 66 | 67 | } 68 | }); 69 | 70 | // Add DOM events 71 | this.domEvents.forEach((domEvent) => { 72 | 73 | if( typeof domEvent.target === 'undefined') { 74 | domEvent.target = document; 75 | } 76 | 77 | domEvent.target.addEventListener(domEvent.event, this[domEvent.method], false); 78 | }); 79 | }, 80 | 81 | removeEventListeners() { 82 | 83 | // Remove EventEmitter events 84 | this.emitterEvents.forEach((emitterEvent) => { 85 | 86 | this.emitter.off(emitterEvent.message, this[emitterEvent.method]); 87 | }); 88 | 89 | // Remove DOM events 90 | this.domEvents.forEach((domEvent) => { 91 | 92 | domEvent.target.removeEventListener(domEvent.event, this[domEvent.method], false); 93 | }); 94 | } 95 | } 96 | }; 97 | 98 | module.exports = EventManagerMixin; 99 | -------------------------------------------------------------------------------- /src/mixins/FadeTransitionMixin.js: -------------------------------------------------------------------------------- 1 | import { TweenMax, Expo } from 'gsap'; 2 | 3 | /* 4 | * ==== FadeTransitionMixin ==== 5 | */ 6 | 7 | const FadeTransitionMixin = { 8 | 9 | route: { 10 | 11 | activate: function( { next } ) { 12 | 13 | TweenMax.fromTo(this.$el, 0.7, { 14 | opacity: 0 15 | }, { 16 | opacity: 1, 17 | ease: Expo.easeOut 18 | }); 19 | 20 | next(); 21 | }, 22 | 23 | deactivate: function( { next } ) { 24 | 25 | TweenMax.to(this.$el, 0.7, { 26 | opacity: 0, 27 | onComplete: next, 28 | ease: Expo.easeOut 29 | }); 30 | } 31 | 32 | } 33 | 34 | }; 35 | 36 | module.exports = FadeTransitionMixin; 37 | -------------------------------------------------------------------------------- /src/stylesheets/common/_base.scss: -------------------------------------------------------------------------------- 1 | html { 2 | width: 100%; 3 | height: 100%; 4 | overflow: hidden; 5 | font-size: 62.5%; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | body, #application { 11 | width: 100%; 12 | height: 100%; 13 | background: $color-white; 14 | } 15 | 16 | * { 17 | outline: none; 18 | box-sizing: border-box; 19 | } 20 | 21 | button { 22 | border: 0; 23 | padding: 0; 24 | margin: 0; 25 | background: transparent; 26 | } 27 | 28 | a { 29 | text-decoration: none; 30 | } 31 | 32 | canvas { 33 | vertical-align: middle; 34 | } 35 | 36 | .link { 37 | &:after { 38 | left: 0; 39 | position: absolute; 40 | content: ''; 41 | width: 100%; 42 | height: 0.4rem; 43 | background: rgba( $color-black, 0.2 ); 44 | bottom: 0; 45 | margin-top: 0.4rem; 46 | opacity: 0; 47 | transform: scaleY( 0 ); 48 | transform-origin: center; 49 | transition: opacity 0.3s ease-out, transform 0.3s ease-out; 50 | z-index: -1; 51 | transform-origin: bottom; 52 | } 53 | 54 | .desktop-device &:hover { 55 | &:after { 56 | opacity: 1; 57 | transform: none; 58 | } 59 | } 60 | } 61 | 62 | .is-grabbing { 63 | cursor: -webkit-grabbing !important; 64 | } 65 | -------------------------------------------------------------------------------- /src/stylesheets/common/_fonts.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-threejs-webpack-boilerplate/c4fe5079823471a182808bc3d241edd54ae8a9c6/src/stylesheets/common/_fonts.scss -------------------------------------------------------------------------------- /src/stylesheets/common/_keyframes.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-threejs-webpack-boilerplate/c4fe5079823471a182808bc3d241edd54ae8a9c6/src/stylesheets/common/_keyframes.scss -------------------------------------------------------------------------------- /src/stylesheets/common/_layout.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100vh; 4 | } -------------------------------------------------------------------------------- /src/stylesheets/common/_mixins.scss: -------------------------------------------------------------------------------- 1 | @import '../../../node_modules/breakpoint-sass/stylesheets/breakpoint'; 2 | 3 | @mixin full-absolute { 4 | position: absolute; 5 | z-index: 0; 6 | top: 0; 7 | right: 0; 8 | bottom: 0; 9 | left: 0; 10 | } 11 | 12 | @mixin center-x { 13 | position: absolute; 14 | left: 50%; 15 | transform: translate3d(-50%, 0%, 0); 16 | } 17 | 18 | @mixin center-y { 19 | position: absolute; 20 | top: 50%; 21 | transform: translate3d(0%, -50%, 0); 22 | } 23 | 24 | @mixin center-xy { 25 | position: absolute; 26 | top: 50%; 27 | left: 50%; 28 | transform: translate3d(-50%, -50%, 0); 29 | } 30 | 31 | @mixin clearfix { 32 | &:after { 33 | display: table; 34 | clear: both; 35 | width: 100%; 36 | height: 1px; 37 | content: ''; 38 | } 39 | } 40 | 41 | @mixin sharp-translate { 42 | -webkit-filter: blur(0); 43 | filter:blur(radius); 44 | -webkit-font-smoothing: subpixel-antialiased; 45 | -webkit-perspective: 1000; 46 | } 47 | 48 | @mixin aspect-ratio($width, $height) { 49 | position: relative; 50 | &:before { 51 | display: block; 52 | width: 100%; 53 | padding-top: ($height / $width) * 100%; 54 | content: ''; 55 | } 56 | > .aspect-ratio { 57 | position: absolute; 58 | top: 0; 59 | right: 0; 60 | bottom: 0; 61 | left: 0; 62 | } 63 | } 64 | 65 | @mixin no-select { 66 | -webkit-touch-callout: none; 67 | -webkit-user-select: none; 68 | -khtml-user-select: none; 69 | -moz-user-select: none; 70 | -ms-user-select: none; 71 | user-select: none; 72 | } 73 | 74 | @function ease($key) { 75 | @if map-has-key($ease, $key) { 76 | @return map-get($ease, $key); 77 | } 78 | 79 | @warn "Unkown '#{$key}' in $ease."; 80 | @return null; 81 | } 82 | -------------------------------------------------------------------------------- /src/stylesheets/common/_reset.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | canvas { 45 | padding: 0; 46 | margin: 0; 47 | } 48 | -------------------------------------------------------------------------------- /src/stylesheets/common/_text.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-threejs-webpack-boilerplate/c4fe5079823471a182808bc3d241edd54ae8a9c6/src/stylesheets/common/_text.scss -------------------------------------------------------------------------------- /src/stylesheets/common/_variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Breakpoints 3 | */ 4 | 5 | $bp-mobile: (min-device-width 320px) (max-device-width 736px); 6 | $bp-mobile-portrait: (min-device-width 320px) (max-device-width 736px) (orientation portrait); 7 | $bp-mobile-landscape: (min-device-width 320px) (max-device-width 736px) (orientation landscape); 8 | 9 | $bp-tablet: (min-device-width 768px) (max-device-width 1024px); 10 | $bp-tablet-portrait: (min-device-width 768px) (max-device-width 1024px) (orientation portrait); 11 | $bp-tablet-landscape: (min-device-width 768px) (max-device-width 1024px) (orientation landscape); 12 | 13 | $bp-tablet-portrait-mobile: (min-device-width 320px) (max-device-width 736px), (min-device-width 768px) (max-device-width 1024px) (orientation portrait); 14 | 15 | $bp-desktop-small: (max-width 1280px); 16 | $bp-tablet-large: (max-width 1024px); 17 | $bp-tablet-medium: (max-width 800px); 18 | $bp-phone-large: (max-width 600px); 19 | $bp-phone-small: (max-width 350px); 20 | 21 | /* 22 | * Ease 23 | */ 24 | 25 | $ease: ( 26 | in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530), 27 | in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190), 28 | in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220), 29 | in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060), 30 | in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715), 31 | in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035), 32 | in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335), 33 | in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045), 34 | out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940), 35 | out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000), 36 | out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000), 37 | out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000), 38 | out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000), 39 | out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000), 40 | out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000), 41 | out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275), 42 | in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955), 43 | in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000), 44 | in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000), 45 | in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000), 46 | in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950), 47 | in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000), 48 | in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860), 49 | in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550) 50 | ); 51 | 52 | /* 53 | * Colors 54 | */ 55 | 56 | $color-black: #000000; 57 | $color-white: #FFFFFF; 58 | $color-grey-dark: #474747; 59 | $color-grey-medium: #9b9b9b; 60 | $color-grey-light: #e9e9e9; 61 | 62 | 63 | /* 64 | * Fonts 65 | */ 66 | 67 | /* 68 | * Size 69 | */ 70 | -------------------------------------------------------------------------------- /src/stylesheets/main.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | @import '../../node_modules/breakpoint-sass/stylesheets/breakpoint'; 4 | 5 | @import 'common/reset'; 6 | 7 | @import 'common/fonts'; 8 | 9 | @import 'common/mixins'; 10 | 11 | @import 'common/variables'; 12 | 13 | @import 'common/base'; 14 | 15 | @import 'common/text'; 16 | 17 | @import 'common/layout'; 18 | 19 | @import 'common/keyframes'; 20 | -------------------------------------------------------------------------------- /src/template/index.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue.js + Three.js Boiler - Patrick Heng 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 |
52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/utils/maths/clamp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clamp a value between two bounds 3 | * 4 | * @param {number} min Minimum boundary 5 | * @param {number} max Maximum boundary 6 | * @param {number} v Value to clamp 7 | * @return {number} Clamped value 8 | */ 9 | export default function clamp(min, max, v) { 10 | if (v < min) return min; 11 | if (v > max) return max; 12 | return v; 13 | } -------------------------------------------------------------------------------- /src/utils/maths/diagonal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Diagonal of a rectangle 3 | * 4 | * @param {number} w Width 5 | * @param {number} h Height 6 | * @return {number} Diagonal length 7 | */ 8 | export default function diagonal(w, h) { 9 | return Math.sqrt(w * w + h * h); 10 | } -------------------------------------------------------------------------------- /src/utils/maths/distance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Distance between two points 3 | * 4 | * @param {number} x1 X coord of the first point 5 | * @param {number} y1 Y coord of the first point 6 | * @param {number} x2 X coord of the second point 7 | * @param {number} y2 Y coord of the second point 8 | * @return {number} Computed distance 9 | */ 10 | export default function distance(x1, y1, x2, y2) { 11 | const dx = x1 - x2; 12 | const dy = y1 - y2; 13 | return Math.sqrt(dx * dx + dy * dy); 14 | } -------------------------------------------------------------------------------- /src/utils/maths/index.js: -------------------------------------------------------------------------------- 1 | export clamp from './clamp'; 2 | export diagonal from './diagonal'; 3 | export distance from './distance'; 4 | export lerp from './lerp'; 5 | export lightenDarkenColor from './lighten-darken-color'; 6 | export loopIndex from './loop-index'; 7 | export map from './map'; 8 | export normalize from './normalize'; 9 | export parabola from './parabola'; 10 | export randomFloat from './random-float'; 11 | export randomHexColor from './random-hex-color'; 12 | export randomInt from './random-int'; 13 | export smoothStep from './smooth-step'; 14 | export toType from './to-type'; 15 | -------------------------------------------------------------------------------- /src/utils/maths/lerp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Linear interpolation between two values (lerping) 3 | * 4 | * @param {number} x First point 5 | * @param {number} y Second point 6 | * @param {number} r Value to interpolate 7 | * @return {number} Lerped value 8 | */ 9 | export default function lerp(x, y, r) { 10 | return x + ((y - x) * r); 11 | } -------------------------------------------------------------------------------- /src/utils/maths/lighten-darken-color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Lighten or darken a color 3 | * 4 | * @param {string} col Color 5 | * @param {number} amt Amount 6 | * @return {string} Computed hexadecimal 7 | */ 8 | export default function lightenDarkenColor(col, amt) { 9 | 10 | let usePound = false; 11 | 12 | if (col[0] == "#") { 13 | col = col.slice(1); 14 | usePound = true; 15 | } 16 | 17 | const num = parseInt(col,16); 18 | 19 | let r = (num >> 16) + amt; 20 | 21 | if (r > 255) r = 255; 22 | else if (r < 0) r = 0; 23 | 24 | let b = ((num >> 8) & 0x00FF) + amt; 25 | 26 | if (b > 255) b = 255; 27 | else if (b < 0) b = 0; 28 | 29 | let g = (num & 0x0000FF) + amt; 30 | 31 | if (g > 255) g = 255; 32 | else if (g < 0) g = 0; 33 | 34 | return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16); 35 | } -------------------------------------------------------------------------------- /src/utils/maths/loop-index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loop on an index value 3 | * 4 | * @param {number} index Index 5 | * @param {number} length Length 6 | * @return {number} Looped index 7 | */ 8 | export default function loopIndex(index, length) { 9 | if (index < 0) { 10 | index = length + index % length; 11 | } 12 | if (index >= length) { 13 | return index % length; 14 | } 15 | return index; 16 | } -------------------------------------------------------------------------------- /src/utils/maths/map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Re-maps a number from one range to another 3 | * 4 | * @param {number} value The incoming value to be converted 5 | * @param {number} start1 Lower bound of the value's current range 6 | * @param {number} stop1 Upper bound of the value's current range 7 | * @param {number} start2 Lower bound of the value's target range 8 | * @param {number} stop2 Upper bound of the value's target range 9 | * @return {number} Remapped number 10 | */ 11 | export default function map(value, start1, stop1, start2, stop2) { 12 | return ((value - start1) / (stop1 - start1)) * (stop2 - start2) + start2; 13 | } -------------------------------------------------------------------------------- /src/utils/maths/normalize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Normalize a value between two bounds 3 | * 4 | * @param {number} min Minimum boundary 5 | * @param {number} max Maximum boundary 6 | * @param {number} x Value to normalize 7 | * @return {number} Normalized value 8 | */ 9 | export default function normalize(min, max, x) { 10 | return (x - min) / (max - min); 11 | } -------------------------------------------------------------------------------- /src/utils/maths/parabola.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remap the 0..1 interval into 0..1 parabola, such that the corners are remaped to 0 and the center to 1. 3 | * In other words, parabola(0) = parabola(1) = 0, and parabola(1/2) = 1. 4 | * 5 | * @param {number} x Coordinate on X axis 6 | * @param {number} k Value to map 7 | * @return {number} Mapped value 8 | */ 9 | export default function parabola(x, k) { 10 | return Math.pow( 4 * x * ( 1 - x ), k ); 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/maths/random-float.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random float 3 | * 4 | * @param {number} minValue Minimum boundary 5 | * @param {number} maxValue Maximum boundary 6 | * @param {number} precision Precision 7 | * @return {number} Generated float 8 | */ 9 | export default function randomFloat( minValue, maxValue, precision = 2 ) { 10 | return parseFloat( Math.min( minValue + ( Math.random() * ( maxValue - minValue ) ), maxValue ).toFixed( precision ) ); 11 | } -------------------------------------------------------------------------------- /src/utils/maths/random-hex-color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random hexadecimal color 3 | * 4 | * @return {string} Hexadecimal color 5 | */ 6 | export default function randomHexColor() { 7 | return '0x' + Math.floor( Math.random() * 16777215 ).toString( 16 ); 8 | } -------------------------------------------------------------------------------- /src/utils/maths/random-int.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random integer 3 | * 4 | * @param {number} min Minimum boundary 5 | * @param {number} max Maximum boundary 6 | * @return {number} Generated integer 7 | */ 8 | export default function randomInt(min, max) { 9 | return Math.floor( Math.random() * ( max - min + 1 ) + min ); 10 | } -------------------------------------------------------------------------------- /src/utils/maths/shader-parse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replace THREE chunk 3 | * 4 | * @param {string} a Match 5 | * @param {string} b First match 6 | * @return {string} Parsed GLSL 7 | */ 8 | function replaceThreeChunkFn(a, b) { 9 | return THREE.ShaderChunk[b] + '\n'; 10 | } 11 | 12 | /** 13 | * Parse shader and replace THREE chunk 14 | * 15 | * @param {string} glsl GLSL 16 | * @return {string} Parsed GLSL 17 | */ 18 | export default function shaderParse(glsl) { 19 | return glsl.replace(/\/\/\s?chunk\(\s?(\w+)\s?\);/g, replaceThreeChunkFn); 20 | } -------------------------------------------------------------------------------- /src/utils/maths/smooth-step.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Smooth a value 3 | * 4 | * @param {number} min Minimum boundary 5 | * @param {number} max Maximum boundary 6 | * @param {number} v Value 7 | * @return {number} Smoothed value 8 | */ 9 | export default function smoothStep(min, max, v) { 10 | const x = Math.max( 0, Math.min( 1, ( v - min ) / ( max - min ) ) ); 11 | return x * x * ( 3 - 2 * x ); 12 | } -------------------------------------------------------------------------------- /src/utils/maths/to-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the type of an object 3 | * 4 | * @param {object} obj Object 5 | * @return {string} Type of the object 6 | */ 7 | export default function toType(obj) { 8 | return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase(); 9 | } -------------------------------------------------------------------------------- /src/utils/webgl/AWDLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var UNCOMPRESSED = 0, 3 | DEFLATE = 1, 4 | LZMA = 2, 5 | 6 | AWD_FIELD_INT8 = 1, 7 | AWD_FIELD_INT16 = 2, 8 | AWD_FIELD_INT32 = 3, 9 | AWD_FIELD_UINT8 = 4, 10 | AWD_FIELD_UINT16 = 5, 11 | AWD_FIELD_UINT32 = 6, 12 | AWD_FIELD_FLOAT32 = 7, 13 | AWD_FIELD_FLOAT64 = 8, 14 | AWD_FIELD_BOOL = 21, 15 | AWD_FIELD_COLOR = 22, 16 | AWD_FIELD_BADDR = 23, 17 | AWD_FIELD_STRING = 31, 18 | AWD_FIELD_BYTEARRAY = 32, 19 | AWD_FIELD_VECTOR2x1 = 41, 20 | AWD_FIELD_VECTOR3x1 = 42, 21 | AWD_FIELD_VECTOR4x1 = 43, 22 | AWD_FIELD_MTX3x2 = 44, 23 | AWD_FIELD_MTX3x3 = 45, 24 | AWD_FIELD_MTX4x3 = 46, 25 | AWD_FIELD_MTX4x4 = 47, 26 | 27 | BOOL = 21, 28 | COLOR = 22, 29 | BADDR = 23, 30 | 31 | INT8 = 1, 32 | INT16 = 2, 33 | INT32 = 3, 34 | UINT8 = 4, 35 | UINT16 = 5, 36 | UINT32 = 6, 37 | FLOAT32 = 7, 38 | FLOAT64 = 8; 39 | 40 | var littleEndian = true; 41 | 42 | function Block() { 43 | 44 | this.id = 0; 45 | this.data = null; 46 | 47 | } 48 | 49 | const AWDProperties = function() {} 50 | 51 | AWDProperties.prototype = { 52 | set : function( key, value ) { 53 | 54 | this[ key ] = value; 55 | 56 | }, 57 | 58 | get : function( key, fallback ) { 59 | 60 | if ( this.hasOwnProperty( key ) ) 61 | return this[ key ]; 62 | else return fallback; 63 | 64 | } 65 | } 66 | 67 | const AWDLoader = function ( manager ) { 68 | 69 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 70 | 71 | this.trunk = new THREE.Object3D(); 72 | 73 | this.materialFactory = undefined; 74 | 75 | this._url = ''; 76 | this._baseDir = ''; 77 | 78 | this._data; 79 | this._ptr = 0; 80 | 81 | this._version = []; 82 | this._streaming = false; 83 | this._optimized_for_accuracy = false; 84 | this._compression = 0; 85 | this._bodylen = 0xFFFFFFFF; 86 | 87 | this._blocks = [ new Block() ]; 88 | 89 | this._accuracyMatrix = false; 90 | this._accuracyGeo = false; 91 | this._accuracyProps = false; 92 | 93 | }; 94 | 95 | AWDLoader.prototype = { 96 | 97 | constructor: AWDLoader, 98 | 99 | load: function ( url, onLoad, onProgress, onError ) { 100 | 101 | var scope = this; 102 | 103 | this._url = url; 104 | this._baseDir = url.substr( 0, url.lastIndexOf( '/' ) + 1 ); 105 | 106 | var loader = new THREE.XHRLoader( this.manager ); 107 | loader.setResponseType( 'arraybuffer' ); 108 | loader.load( url, function ( text ) { 109 | 110 | onLoad( scope.parse( text ) ); 111 | 112 | }, onProgress, onError ); 113 | 114 | }, 115 | 116 | parse: function ( data ) { 117 | 118 | var blen = data.byteLength; 119 | 120 | this._ptr = 0; 121 | this._data = new DataView( data ); 122 | 123 | this._parseHeader( ); 124 | 125 | if ( this._compression != 0 ) { 126 | 127 | console.error( 'compressed AWD not supported' ); 128 | 129 | } 130 | 131 | if ( ! this._streaming && this._bodylen != data.byteLength - this._ptr ) { 132 | 133 | console.error( 'AWDLoader: body len does not match file length', this._bodylen, blen - this._ptr ); 134 | 135 | } 136 | 137 | while ( this._ptr < blen ) { 138 | 139 | this.parseNextBlock(); 140 | 141 | } 142 | 143 | return this.trunk; 144 | 145 | }, 146 | 147 | parseNextBlock: function() { 148 | 149 | var assetData, 150 | ns, type, len, block, 151 | blockId = this.readU32(), 152 | ns = this.readU8(), 153 | type = this.readU8(), 154 | flags = this.readU8(), 155 | len = this.readU32(); 156 | 157 | 158 | switch ( type ) { 159 | case 1: 160 | assetData = this.parseMeshData( len ); 161 | break; 162 | case 22: 163 | assetData = this.parseContainer( len ); 164 | break; 165 | case 23: 166 | assetData = this.parseMeshInstance( len ); 167 | break; 168 | case 81: 169 | assetData = this.parseMaterial( len ); 170 | break; 171 | case 82: 172 | assetData = this.parseTexture( len ); 173 | break; 174 | case 101: 175 | assetData = this.parseSkeleton( len ); 176 | break; 177 | 178 | // case 111: 179 | // assetData = this.parseMeshPoseAnimation(len, true); 180 | // break; 181 | case 112: 182 | assetData = this.parseMeshPoseAnimation( len, false ); 183 | break; 184 | case 113: 185 | assetData = this.parseVertexAnimationSet( len ); 186 | break; 187 | case 102: 188 | assetData = this.parseSkeletonPose( len ); 189 | break; 190 | case 103: 191 | assetData = this.parseSkeletonAnimation( len ); 192 | break; 193 | case 122: 194 | assetData = this.parseAnimatorSet( len ); 195 | break; 196 | // case 121: 197 | // assetData = parseUVAnimation(len); 198 | // break; 199 | default: 200 | //debug('Ignoring block!',type, len); 201 | this._ptr += len; 202 | break; 203 | } 204 | 205 | 206 | // Store block reference for later use 207 | this._blocks[ blockId ] = block = new Block(); 208 | block.data = assetData; 209 | block.id = blockId; 210 | 211 | 212 | }, 213 | 214 | _parseHeader: function () { 215 | 216 | var version = this._version, 217 | awdmagic = 218 | ( this.readU8() << 16 ) 219 | | ( this.readU8() << 8 ) 220 | | this.readU8(); 221 | 222 | if ( awdmagic != 4282180 ) 223 | throw new Error( "AWDLoader - bad magic" ); 224 | 225 | version[ 0 ] = this.readU8(); 226 | version[ 1 ] = this.readU8(); 227 | 228 | var flags = this.readU16(); 229 | 230 | this._streaming = ( flags & 0x1 ) == 0x1; 231 | 232 | if ( ( version[ 0 ] === 2 ) && ( version[ 1 ] === 1 ) ) { 233 | 234 | this._accuracyMatrix = ( flags & 0x2 ) === 0x2; 235 | this._accuracyGeo = ( flags & 0x4 ) === 0x4; 236 | this._accuracyProps = ( flags & 0x8 ) === 0x8; 237 | 238 | } 239 | 240 | this._geoNrType = this._accuracyGeo ? FLOAT64 : FLOAT32; 241 | this._matrixNrType = this._accuracyMatrix ? FLOAT64 : FLOAT32; 242 | this._propsNrType = this._accuracyProps ? FLOAT64 : FLOAT32; 243 | 244 | this._optimized_for_accuracy = ( flags & 0x2 ) === 0x2; 245 | 246 | this._compression = this.readU8(); 247 | this._bodylen = this.readU32(); 248 | 249 | }, 250 | 251 | parseContainer: function ( len ) { 252 | 253 | var parent, 254 | ctr = new THREE.Object3D(), 255 | par_id = this.readU32(), 256 | mtx = this.parseMatrix4(); 257 | 258 | ctr.name = this.readUTF(); 259 | ctr.applyMatrix( mtx ); 260 | 261 | parent = this._blocks[ par_id ].data || this.trunk; 262 | parent.add( ctr ); 263 | 264 | this.parseProperties( { 265 | 1: this._matrixNrType, 266 | 2: this._matrixNrType, 267 | 3: this._matrixNrType, 268 | 4: UINT8 269 | } ); 270 | 271 | ctr.extra = this.parseUserAttributes(); 272 | 273 | return ctr; 274 | 275 | }, 276 | 277 | parseMeshInstance: function ( len ) { 278 | 279 | var name, 280 | mesh, geometries, meshLen, meshes, 281 | par_id, data_id, 282 | mtx, 283 | materials, mat, mat_id, 284 | num_materials, 285 | materials_parsed, 286 | parent, 287 | i; 288 | 289 | par_id = this.readU32(); 290 | mtx = this.parseMatrix4(); 291 | name = this.readUTF(); 292 | data_id = this.readU32(); 293 | num_materials = this.readU16(); 294 | 295 | geometries = this.getBlock( data_id ); 296 | 297 | materials = []; 298 | materials_parsed = 0; 299 | 300 | for ( i = 0; i < num_materials; i ++ ) { 301 | 302 | mat_id = this.readU32(); 303 | mat = this.getBlock( mat_id ); 304 | materials.push( mat ); 305 | 306 | } 307 | 308 | meshLen = geometries.length; 309 | meshes = []; 310 | 311 | // TODO : BufferGeometry don't support "geometryGroups" for now. 312 | // so we create sub meshes for each groups 313 | if ( meshLen > 1 ) { 314 | 315 | mesh = new THREE.Object3D(); 316 | for ( i = 0; i < meshLen; i ++ ) { 317 | 318 | var sm = new THREE.Mesh( geometries[ i ] ); 319 | meshes.push( sm ); 320 | mesh.add( sm ); 321 | 322 | } 323 | 324 | } else { 325 | 326 | mesh = new THREE.Mesh( geometries[ 0 ] ); 327 | meshes.push( mesh ); 328 | 329 | } 330 | 331 | mesh.applyMatrix( mtx ); 332 | mesh.name = name; 333 | 334 | 335 | parent = this.getBlock( par_id ) || this.trunk; 336 | parent.add( mesh ); 337 | 338 | 339 | var matLen = materials.length; 340 | var maxLen = Math.max( meshLen, matLen ); 341 | for ( i = 0; i < maxLen; i ++ ) 342 | meshes[ i % meshLen ].material = materials[ i % matLen ]; 343 | 344 | 345 | // Ignore for now 346 | this.parseProperties( null ); 347 | mesh.extra = this.parseUserAttributes(); 348 | 349 | return mesh; 350 | 351 | }, 352 | 353 | parseMaterial: function ( len ) { 354 | 355 | var name, 356 | type, 357 | props, 358 | mat, 359 | attributes, 360 | finalize, 361 | num_methods, 362 | methods_parsed; 363 | 364 | name = this.readUTF(); 365 | type = this.readU8(); 366 | num_methods = this.readU8(); 367 | 368 | //log( "AWDLoader parseMaterial ",name ) 369 | 370 | // Read material numerical properties 371 | // (1=color, 2=bitmap url, 11=alpha_blending, 12=alpha_threshold, 13=repeat) 372 | props = this.parseProperties( { 373 | 1: AWD_FIELD_INT32, 374 | 2: AWD_FIELD_BADDR, 375 | 11: AWD_FIELD_BOOL, 376 | 12: AWD_FIELD_FLOAT32, 377 | 13: AWD_FIELD_BOOL 378 | } ); 379 | 380 | methods_parsed = 0; 381 | 382 | while ( methods_parsed < num_methods ) { 383 | 384 | var method_type = this.readU16(); 385 | this.parseProperties( null ); 386 | this.parseUserAttributes(); 387 | 388 | } 389 | 390 | attributes = this.parseUserAttributes(); 391 | 392 | if ( this.materialFactory !== undefined ) { 393 | 394 | mat = this.materialFactory( name ); 395 | if ( mat ) return mat; 396 | 397 | } 398 | 399 | mat = new THREE.MeshPhongMaterial(); 400 | 401 | if ( type === 1 ) { 402 | 403 | // Color material 404 | mat.color.setHex( props.get( 1, 0xcccccc ) ); 405 | 406 | } else if ( type === 2 ) { 407 | 408 | // Bitmap material 409 | var tex_addr = props.get( 2, 0 ); 410 | mat.map = this.getBlock( tex_addr ); 411 | 412 | } 413 | 414 | mat.extra = attributes; 415 | mat.alphaThreshold = props.get( 12, 0.0 ); 416 | mat.repeat = props.get( 13, false ); 417 | 418 | 419 | return mat; 420 | 421 | }, 422 | 423 | parseTexture: function( len ) { 424 | 425 | var name = this.readUTF(), 426 | type = this.readU8(), 427 | asset, 428 | data_len; 429 | 430 | // External 431 | if ( type === 0 ) { 432 | 433 | data_len = this.readU32(); 434 | var url = this.readUTFBytes( data_len ); 435 | console.log( url ); 436 | 437 | asset = this.loadTexture( url ); 438 | 439 | } else { 440 | // embed texture not supported 441 | } 442 | // Ignore for now 443 | this.parseProperties( null ); 444 | 445 | this.parseUserAttributes(); 446 | return asset; 447 | 448 | }, 449 | 450 | loadTexture: function( url ) { 451 | 452 | var tex = new THREE.Texture(); 453 | 454 | var loader = new THREE.ImageLoader( this.manager ); 455 | 456 | loader.load( this._baseDir + url, function( image ) { 457 | 458 | tex.image = image; 459 | tex.needsUpdate = true; 460 | 461 | } ); 462 | 463 | return tex; 464 | 465 | }, 466 | 467 | parseSkeleton: function( len ) { 468 | 469 | // Array 470 | var name = this.readUTF(), 471 | num_joints = this.readU16(), 472 | skeleton = [], 473 | joints_parsed = 0; 474 | 475 | this.parseProperties( null ); 476 | 477 | while ( joints_parsed < num_joints ) { 478 | 479 | var joint, ibp; 480 | 481 | // Ignore joint id 482 | this.readU16(); 483 | 484 | joint = new THREE.Bone(); 485 | joint.parent = this.readU16() - 1; // 0=null in AWD 486 | joint.name = this.readUTF(); 487 | 488 | ibp = this.parseMatrix4(); 489 | joint.skinMatrix = ibp; 490 | 491 | // Ignore joint props/attributes for now 492 | this.parseProperties( null ); 493 | this.parseUserAttributes(); 494 | 495 | skeleton.push( joint ); 496 | joints_parsed ++; 497 | 498 | } 499 | 500 | // Discard attributes for now 501 | this.parseUserAttributes(); 502 | 503 | 504 | return skeleton; 505 | 506 | }, 507 | 508 | parseSkeletonPose: function( blockID ) { 509 | 510 | var name = this.readUTF(); 511 | 512 | var num_joints = this.readU16(); 513 | this.parseProperties( null ); 514 | 515 | // debug( 'parse Skeleton Pose. joints : ' + num_joints); 516 | 517 | var pose = []; 518 | 519 | var joints_parsed = 0; 520 | 521 | while ( joints_parsed < num_joints ) { 522 | 523 | var joint_pose; 524 | 525 | var has_transform; //:uint; 526 | var mtx_data; 527 | 528 | has_transform = this.readU8(); 529 | 530 | if ( has_transform === 1 ) { 531 | 532 | mtx_data = this.parseMatrix4(); 533 | 534 | } else { 535 | 536 | mtx_data = new THREE.Matrix4(); 537 | 538 | } 539 | pose[ joints_parsed ] = mtx_data; 540 | joints_parsed ++; 541 | 542 | } 543 | 544 | // Skip attributes for now 545 | this.parseUserAttributes(); 546 | 547 | return pose; 548 | 549 | }, 550 | 551 | parseSkeletonAnimation: function( blockID ) { 552 | 553 | var frame_dur; 554 | var pose_addr; 555 | var pose; 556 | 557 | var name = this.readUTF(); 558 | 559 | var clip = []; 560 | 561 | var num_frames = this.readU16(); 562 | this.parseProperties( null ); 563 | 564 | var frames_parsed = 0; 565 | var returnedArray; 566 | 567 | // debug( 'parse Skeleton Animation. frames : ' + num_frames); 568 | 569 | while ( frames_parsed < num_frames ) { 570 | 571 | pose_addr = this.readU32(); 572 | frame_dur = this.readU16(); 573 | 574 | pose = this._blocks[ pose_addr ].data; 575 | // debug( 'pose address ',pose[2].elements[12],pose[2].elements[13],pose[2].elements[14] ); 576 | clip.push( { 577 | pose : pose, 578 | duration : frame_dur 579 | } ); 580 | 581 | frames_parsed ++; 582 | 583 | } 584 | 585 | if ( clip.length === 0 ) { 586 | 587 | // debug("Could not this SkeletonClipNode, because no Frames where set."); 588 | return; 589 | 590 | } 591 | // Ignore attributes for now 592 | this.parseUserAttributes(); 593 | return clip; 594 | 595 | }, 596 | 597 | parseVertexAnimationSet: function( len ) { 598 | 599 | var poseBlockAdress, 600 | name = this.readUTF(), 601 | num_frames = this.readU16(), 602 | props = this.parseProperties( { 1: UINT16 } ), 603 | frames_parsed = 0, 604 | skeletonFrames = []; 605 | 606 | while ( frames_parsed < num_frames ) { 607 | 608 | poseBlockAdress = this.readU32(); 609 | skeletonFrames.push( this._blocks[ poseBlockAdress ].data ); 610 | frames_parsed ++; 611 | 612 | } 613 | 614 | this.parseUserAttributes(); 615 | 616 | 617 | return skeletonFrames; 618 | 619 | }, 620 | 621 | parseAnimatorSet: function( len ) { 622 | 623 | var targetMesh; 624 | 625 | var animSetBlockAdress; //:int 626 | 627 | var targetAnimationSet; //:AnimationSetBase; 628 | var outputString = ""; //:String = ""; 629 | var name = this.readUTF(); 630 | var type = this.readU16(); 631 | 632 | var props = this.parseProperties( { 1: BADDR } ); 633 | 634 | animSetBlockAdress = this.readU32(); 635 | var targetMeshLength = this.readU16(); 636 | 637 | var meshAdresses = []; //:Vector. = new Vector.; 638 | 639 | for ( var i = 0; i < targetMeshLength; i ++ ) 640 | meshAdresses.push( this.readU32() ); 641 | 642 | var activeState = this.readU16(); 643 | var autoplay = Boolean( this.readU8() ); 644 | this.parseUserAttributes(); 645 | this.parseUserAttributes(); 646 | 647 | var returnedArray; 648 | var targetMeshes = []; //:Vector. = new Vector.; 649 | 650 | for ( i = 0; i < meshAdresses.length; i ++ ) { 651 | 652 | // returnedArray = getAssetByID(meshAdresses[i], [AssetType.MESH]); 653 | // if (returnedArray[0]) 654 | targetMeshes.push( this._blocks[ meshAdresses[ i ]].data ); 655 | 656 | } 657 | 658 | targetAnimationSet = this._blocks[ animSetBlockAdress ].data; 659 | var thisAnimator; 660 | 661 | if ( type == 1 ) { 662 | 663 | 664 | thisAnimator = { 665 | animationSet : targetAnimationSet, 666 | skeleton : this._blocks[ props.get( 1, 0 ) ].data 667 | }; 668 | 669 | } else if ( type == 2 ) { 670 | // debug( "vertex Anim???"); 671 | } 672 | 673 | 674 | for ( i = 0; i < targetMeshes.length; i ++ ) { 675 | 676 | targetMeshes[ i ].animator = thisAnimator; 677 | 678 | } 679 | // debug("Parsed a Animator: Name = " + name); 680 | 681 | return thisAnimator; 682 | 683 | }, 684 | 685 | parseMeshData: function ( len ) { 686 | 687 | var name = this.readUTF(), 688 | num_subs = this.readU16(), 689 | geom, 690 | subs_parsed = 0, 691 | props, 692 | buffer, 693 | skinW, skinI, 694 | geometries = []; 695 | 696 | props = this.parseProperties( { 697 | 1: this._geoNrType, 698 | 2: this._geoNrType 699 | } ); 700 | 701 | // Loop through sub meshes 702 | while ( subs_parsed < num_subs ) { 703 | 704 | var sm_len, sm_end, attrib; 705 | 706 | geom = new THREE.BufferGeometry(); 707 | geom.name = name; 708 | geometries.push( geom ); 709 | 710 | 711 | sm_len = this.readU32(); 712 | sm_end = this._ptr + sm_len; 713 | 714 | 715 | // Ignore for now 716 | this.parseProperties( { 1: this._geoNrType, 2: this._geoNrType } ); 717 | 718 | // Loop through data streams 719 | while ( this._ptr < sm_end ) { 720 | 721 | var idx = 0, 722 | str_type = this.readU8(), 723 | str_ftype = this.readU8(), 724 | str_len = this.readU32(), 725 | str_end = str_len + this._ptr; 726 | 727 | // VERTICES 728 | // ------------------ 729 | if ( str_type === 1 ) { 730 | 731 | buffer = new Float32Array( ( str_len / 12 ) * 3 ); 732 | attrib = new THREE.BufferAttribute( buffer, 3 ); 733 | 734 | geom.addAttribute( 'position', attrib ); 735 | idx = 0; 736 | 737 | while ( this._ptr < str_end ) { 738 | 739 | buffer[ idx ] = - this.readF32(); 740 | buffer[ idx + 1 ] = this.readF32(); 741 | buffer[ idx + 2 ] = this.readF32(); 742 | idx += 3; 743 | 744 | } 745 | 746 | } 747 | 748 | // INDICES 749 | // ----------------- 750 | else if ( str_type === 2 ) { 751 | 752 | buffer = new Uint16Array( str_len / 2 ); 753 | attrib = new THREE.BufferAttribute( buffer, 1 ); 754 | geom.setIndex( attrib ); 755 | 756 | idx = 0; 757 | 758 | while ( this._ptr < str_end ) { 759 | 760 | buffer[ idx + 1 ] = this.readU16(); 761 | buffer[ idx ] = this.readU16(); 762 | buffer[ idx + 2 ] = this.readU16(); 763 | idx += 3; 764 | 765 | } 766 | 767 | } 768 | 769 | // UVS 770 | // ------------------- 771 | else if ( str_type === 3 ) { 772 | 773 | buffer = new Float32Array( ( str_len / 8 ) * 2 ); 774 | attrib = new THREE.BufferAttribute( buffer, 2 ); 775 | 776 | geom.addAttribute( 'uv', attrib ); 777 | idx = 0; 778 | 779 | while ( this._ptr < str_end ) { 780 | 781 | buffer[ idx ] = this.readF32(); 782 | buffer[ idx + 1 ] = 1.0 - this.readF32(); 783 | idx += 2; 784 | 785 | } 786 | 787 | } 788 | 789 | // NORMALS 790 | else if ( str_type === 4 ) { 791 | 792 | buffer = new Float32Array( ( str_len / 12 ) * 3 ); 793 | attrib = new THREE.BufferAttribute( buffer, 3 ); 794 | geom.addAttribute( 'normal', attrib ); 795 | idx = 0; 796 | 797 | while ( this._ptr < str_end ) { 798 | 799 | buffer[ idx ] = - this.readF32(); 800 | buffer[ idx + 1 ] = this.readF32(); 801 | buffer[ idx + 2 ] = this.readF32(); 802 | idx += 3; 803 | 804 | } 805 | 806 | } 807 | 808 | // else if (str_type == 6) { 809 | // skinI = new Float32Array( str_len>>1 ); 810 | // idx = 0 811 | 812 | // while (this._ptr < str_end) { 813 | // skinI[idx] = this.readU16(); 814 | // idx++; 815 | // } 816 | 817 | // } 818 | // else if (str_type == 7) { 819 | // skinW = new Float32Array( str_len>>2 ); 820 | // idx = 0; 821 | 822 | // while (this._ptr < str_end) { 823 | // skinW[idx] = this.readF32(); 824 | // idx++; 825 | // } 826 | // } 827 | else { 828 | 829 | this._ptr = str_end; 830 | 831 | } 832 | 833 | } 834 | 835 | this.parseUserAttributes(); 836 | 837 | geom.computeBoundingSphere(); 838 | subs_parsed ++; 839 | 840 | } 841 | 842 | //geom.computeFaceNormals(); 843 | 844 | this.parseUserAttributes(); 845 | //finalizeAsset(geom, name); 846 | 847 | return geometries; 848 | 849 | }, 850 | 851 | parseMeshPoseAnimation: function( len, poseOnly ) { 852 | 853 | var num_frames = 1, 854 | num_submeshes, 855 | frames_parsed, 856 | subMeshParsed, 857 | frame_dur, 858 | x, y, z, 859 | 860 | str_len, 861 | str_end, 862 | geom, 863 | subGeom, 864 | idx = 0, 865 | clip = {}, 866 | indices, 867 | verts, 868 | num_Streams, 869 | streamsParsed, 870 | streamtypes = [], 871 | 872 | props, 873 | thisGeo, 874 | name = this.readUTF(), 875 | geoAdress = this.readU32(); 876 | 877 | var mesh = this.getBlock( geoAdress ); 878 | 879 | if ( mesh === null ) { 880 | 881 | console.log( "parseMeshPoseAnimation target mesh not found at:", geoAdress ); 882 | return; 883 | 884 | } 885 | 886 | geom = mesh.geometry; 887 | geom.morphTargets = []; 888 | 889 | if ( ! poseOnly ) 890 | num_frames = this.readU16(); 891 | 892 | num_submeshes = this.readU16(); 893 | num_Streams = this.readU16(); 894 | 895 | // debug("VA num_frames : ", num_frames ); 896 | // debug("VA num_submeshes : ", num_submeshes ); 897 | // debug("VA numstreams : ", num_Streams ); 898 | 899 | streamsParsed = 0; 900 | while ( streamsParsed < num_Streams ) { 901 | 902 | streamtypes.push( this.readU16() ); 903 | streamsParsed ++; 904 | 905 | } 906 | props = this.parseProperties( { 1: BOOL, 2: BOOL } ); 907 | 908 | clip.looping = props.get( 1, true ); 909 | clip.stitchFinalFrame = props.get( 2, false ); 910 | 911 | frames_parsed = 0; 912 | 913 | while ( frames_parsed < num_frames ) { 914 | 915 | frame_dur = this.readU16(); 916 | subMeshParsed = 0; 917 | 918 | while ( subMeshParsed < num_submeshes ) { 919 | 920 | streamsParsed = 0; 921 | str_len = this.readU32(); 922 | str_end = this._ptr + str_len; 923 | 924 | while ( streamsParsed < num_Streams ) { 925 | 926 | if ( streamtypes[ streamsParsed ] === 1 ) { 927 | 928 | //geom.addAttribute( 'morphTarget'+frames_parsed, Float32Array, str_len/12, 3 ); 929 | var buffer = new Float32Array( str_len / 4 ); 930 | geom.morphTargets.push( { 931 | array : buffer 932 | } ); 933 | 934 | //buffer = geom.attributes['morphTarget'+frames_parsed].array 935 | idx = 0; 936 | 937 | while ( this._ptr < str_end ) { 938 | 939 | buffer[ idx ] = this.readF32(); 940 | buffer[ idx + 1 ] = this.readF32(); 941 | buffer[ idx + 2 ] = this.readF32(); 942 | idx += 3; 943 | 944 | } 945 | 946 | 947 | subMeshParsed ++; 948 | 949 | } else 950 | this._ptr = str_end; 951 | streamsParsed ++; 952 | 953 | } 954 | 955 | } 956 | 957 | 958 | frames_parsed ++; 959 | 960 | } 961 | 962 | this.parseUserAttributes(); 963 | 964 | return null; 965 | 966 | }, 967 | 968 | getBlock: function ( id ) { 969 | 970 | return this._blocks[ id ].data; 971 | 972 | }, 973 | 974 | parseMatrix4: function () { 975 | 976 | var mtx = new THREE.Matrix4(); 977 | var e = mtx.elements; 978 | 979 | e[ 0 ] = this.readF32(); 980 | e[ 1 ] = this.readF32(); 981 | e[ 2 ] = this.readF32(); 982 | e[ 3 ] = 0.0; 983 | //e[3] = 0.0; 984 | 985 | e[ 4 ] = this.readF32(); 986 | e[ 5 ] = this.readF32(); 987 | e[ 6 ] = this.readF32(); 988 | //e[7] = this.readF32(); 989 | e[ 7 ] = 0.0; 990 | 991 | e[ 8 ] = this.readF32(); 992 | e[ 9 ] = this.readF32(); 993 | e[ 10 ] = this.readF32(); 994 | //e[11] = this.readF32(); 995 | e[ 11 ] = 0.0; 996 | 997 | e[ 12 ] = - this.readF32(); 998 | e[ 13 ] = this.readF32(); 999 | e[ 14 ] = this.readF32(); 1000 | //e[15] = this.readF32(); 1001 | e[ 15 ] = 1.0; 1002 | return mtx; 1003 | 1004 | }, 1005 | 1006 | parseProperties: function ( expected ) { 1007 | 1008 | var list_len = this.readU32(); 1009 | var list_end = this._ptr + list_len; 1010 | 1011 | var props = new AWDProperties(); 1012 | 1013 | if ( expected ) { 1014 | 1015 | while ( this._ptr < list_end ) { 1016 | 1017 | var key = this.readU16(); 1018 | var len = this.readU32(); 1019 | var type; 1020 | 1021 | if ( expected.hasOwnProperty( key ) ) { 1022 | 1023 | type = expected[ key ]; 1024 | props.set( key, this.parseAttrValue( type, len ) ); 1025 | 1026 | } else { 1027 | 1028 | this._ptr += len; 1029 | 1030 | } 1031 | 1032 | } 1033 | 1034 | } 1035 | 1036 | return props; 1037 | 1038 | }, 1039 | 1040 | parseUserAttributes: function () { 1041 | 1042 | // skip for now 1043 | this._ptr = this.readU32() + this._ptr; 1044 | return null; 1045 | 1046 | }, 1047 | 1048 | parseAttrValue: function ( type, len ) { 1049 | 1050 | var elem_len; 1051 | var read_func; 1052 | 1053 | switch ( type ) { 1054 | case AWD_FIELD_INT8: 1055 | elem_len = 1; 1056 | read_func = this.readI8; 1057 | break; 1058 | case AWD_FIELD_INT16: 1059 | elem_len = 2; 1060 | read_func = this.readI16; 1061 | break; 1062 | case AWD_FIELD_INT32: 1063 | elem_len = 4; 1064 | read_func = this.readI32; 1065 | break; 1066 | case AWD_FIELD_BOOL: 1067 | case AWD_FIELD_UINT8: 1068 | elem_len = 1; 1069 | read_func = this.readU8; 1070 | break; 1071 | case AWD_FIELD_UINT16: 1072 | elem_len = 2; 1073 | read_func = this.readU16; 1074 | break; 1075 | case AWD_FIELD_UINT32: 1076 | case AWD_FIELD_BADDR: 1077 | elem_len = 4; 1078 | read_func = this.readU32; 1079 | break; 1080 | case AWD_FIELD_FLOAT32: 1081 | elem_len = 4; 1082 | read_func = this.readF32; 1083 | break; 1084 | case AWD_FIELD_FLOAT64: 1085 | elem_len = 8; 1086 | read_func = this.readF64; 1087 | break; 1088 | case AWD_FIELD_VECTOR2x1: 1089 | case AWD_FIELD_VECTOR3x1: 1090 | case AWD_FIELD_VECTOR4x1: 1091 | case AWD_FIELD_MTX3x2: 1092 | case AWD_FIELD_MTX3x3: 1093 | case AWD_FIELD_MTX4x3: 1094 | case AWD_FIELD_MTX4x4: 1095 | elem_len = 8; 1096 | read_func = this.readF64; 1097 | break; 1098 | } 1099 | 1100 | if ( elem_len < len ) { 1101 | 1102 | var list; 1103 | var num_read; 1104 | var num_elems; 1105 | 1106 | list = []; 1107 | num_read = 0; 1108 | num_elems = len / elem_len; 1109 | 1110 | while ( num_read < num_elems ) { 1111 | 1112 | list.push( read_func.call( this ) ); 1113 | num_read ++; 1114 | 1115 | } 1116 | 1117 | return list; 1118 | 1119 | } else { 1120 | 1121 | return read_func.call( this ); 1122 | 1123 | } 1124 | 1125 | }, 1126 | 1127 | readU8: function () { 1128 | 1129 | return this._data.getUint8( this._ptr ++ ); 1130 | 1131 | }, 1132 | readI8: function () { 1133 | 1134 | return this._data.getInt8( this._ptr ++ ); 1135 | 1136 | }, 1137 | readU16: function () { 1138 | 1139 | var a = this._data.getUint16( this._ptr, littleEndian ); 1140 | this._ptr += 2; 1141 | return a; 1142 | 1143 | }, 1144 | readI16: function () { 1145 | 1146 | var a = this._data.getInt16( this._ptr, littleEndian ); 1147 | this._ptr += 2; 1148 | return a; 1149 | 1150 | }, 1151 | readU32: function () { 1152 | 1153 | var a = this._data.getUint32( this._ptr, littleEndian ); 1154 | this._ptr += 4; 1155 | return a; 1156 | 1157 | }, 1158 | readI32: function () { 1159 | 1160 | var a = this._data.getInt32( this._ptr, littleEndian ); 1161 | this._ptr += 4; 1162 | return a; 1163 | 1164 | }, 1165 | readF32: function () { 1166 | 1167 | var a = this._data.getFloat32( this._ptr, littleEndian ); 1168 | this._ptr += 4; 1169 | return a; 1170 | 1171 | }, 1172 | readF64: function () { 1173 | 1174 | var a = this._data.getFloat64( this._ptr, littleEndian ); 1175 | this._ptr += 8; 1176 | return a; 1177 | 1178 | }, 1179 | 1180 | /** 1181 | * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode. 1182 | * @param {Array.} bytes UTF-8 byte array. 1183 | * @return {string} 16-bit Unicode string. 1184 | */ 1185 | readUTF: function () { 1186 | 1187 | var len = this.readU16(); 1188 | return this.readUTFBytes( len ); 1189 | 1190 | }, 1191 | 1192 | /** 1193 | * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode. 1194 | * @param {Array.} bytes UTF-8 byte array. 1195 | * @return {string} 16-bit Unicode string. 1196 | */ 1197 | readUTFBytes: function ( len ) { 1198 | 1199 | // TODO(user): Use native implementations if/when available 1200 | var out = [], c = 0; 1201 | 1202 | while ( out.length < len ) { 1203 | 1204 | var c1 = this._data.getUint8( this._ptr ++, littleEndian ); 1205 | if ( c1 < 128 ) { 1206 | 1207 | out[ c ++ ] = String.fromCharCode( c1 ); 1208 | 1209 | } else if ( c1 > 191 && c1 < 224 ) { 1210 | 1211 | var c2 = this._data.getUint8( this._ptr ++, littleEndian ); 1212 | out[ c ++ ] = String.fromCharCode( ( c1 & 31 ) << 6 | c2 & 63 ); 1213 | 1214 | } else { 1215 | 1216 | var c2 = this._data.getUint8( this._ptr ++, littleEndian ); 1217 | var c3 = this._data.getUint8( this._ptr ++, littleEndian ); 1218 | out[ c ++ ] = String.fromCharCode( 1219 | ( c1 & 15 ) << 12 | ( c2 & 63 ) << 6 | c3 & 63 1220 | ); 1221 | 1222 | } 1223 | 1224 | } 1225 | return out.join( '' ); 1226 | 1227 | } 1228 | 1229 | }; 1230 | /* eslint-enable */ 1231 | 1232 | export default AWDLoader; 1233 | -------------------------------------------------------------------------------- /src/utils/webgl/AugmentedConsole.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-threejs-webpack-boilerplate/c4fe5079823471a182808bc3d241edd54ae8a9c6/src/utils/webgl/AugmentedConsole.js -------------------------------------------------------------------------------- /src/utils/webgl/Clock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clock class 3 | */ 4 | class Clock extends THREE.Clock { 5 | 6 | /** 7 | * Constructor function 8 | */ 9 | constructor() { 10 | super( true ); // Autostart 11 | } 12 | 13 | /** 14 | * Delta getter 15 | * @return {integer} Delta 16 | */ 17 | get delta() { 18 | return this.getDelta(); 19 | } 20 | 21 | /** 22 | * Time getter 23 | * @return {integer} Elapsed time 24 | */ 25 | get time() { 26 | return this.getElapsedTime(); 27 | } 28 | } 29 | 30 | export default Clock; 31 | -------------------------------------------------------------------------------- /src/utils/webgl/ConstantSpline.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | THREE.ConstantSpline = function() { 3 | 4 | this.p0 = new THREE.Vector3(); 5 | this.p1 = new THREE.Vector3(); 6 | this.p2 = new THREE.Vector3(); 7 | this.p3 = new THREE.Vector3(); 8 | 9 | this.tmp = new THREE.Vector3(); 10 | this.res = new THREE.Vector3(); 11 | this.o = new THREE.Vector3(); 12 | 13 | this.points = []; 14 | this.lPoints = []; 15 | this.steps = []; 16 | 17 | this.inc = .01; 18 | this.d = 0; 19 | 20 | this.distancesNeedUpdate = false; 21 | 22 | }; 23 | 24 | THREE.ConstantSpline.prototype.calculate = function() { 25 | 26 | this.d = 0; 27 | this.points = []; 28 | 29 | this.o.copy( this.p0 ); 30 | 31 | for( var j = 0; j <= 1; j += this.inc ) { 32 | 33 | var i = ( 1 - j ); 34 | var ii = i * i; 35 | var iii = ii * i; 36 | var jj = j * j; 37 | var jjj = jj * j; 38 | 39 | this.res.set( 0, 0, 0 ); 40 | 41 | this.tmp.copy( this.p0 ); 42 | this.tmp.multiplyScalar( iii ); 43 | this.res.add( this.tmp ); 44 | 45 | this.tmp.copy( this.p1 ); 46 | this.tmp.multiplyScalar( 3 * j * ii ); 47 | this.res.add( this.tmp ); 48 | 49 | this.tmp.copy( this.p2 ); 50 | this.tmp.multiplyScalar( 3 * jj * i ); 51 | this.res.add( this.tmp ); 52 | 53 | this.tmp.copy( this.p3 ); 54 | this.tmp.multiplyScalar( jjj ); 55 | this.res.add( this.tmp ); 56 | 57 | this.points.push( this.res.clone() ); 58 | 59 | } 60 | 61 | this.points.push( this.p3.clone() ); 62 | 63 | this.distancesNeedUpdate = true; 64 | 65 | }; 66 | 67 | THREE.ConstantSpline.prototype.calculateDistances = function() { 68 | 69 | this.steps = []; 70 | this.d = 0; 71 | 72 | var from, to, td = 0; 73 | 74 | for( var j = 0; j < this.points.length - 1; j++ ) { 75 | 76 | this.points[ j ].distance = td; 77 | this.points[ j ].ac = this.d; 78 | 79 | from = this.points[ j ], 80 | to = this.points[ j + 1 ], 81 | td = to.distanceTo( from ); 82 | 83 | this.d += td; 84 | 85 | } 86 | 87 | this.points[ this.points.length - 1 ].distance = 0; 88 | this.points[ this.points.length - 1 ].ac = this.d; 89 | 90 | } 91 | 92 | THREE.ConstantSpline.prototype.reticulate = function( settings ) { 93 | 94 | if( this.distancesNeedUpdate ) { 95 | this.calculateDistances(); 96 | this.distancesNeedUpdate = false; 97 | } 98 | 99 | this.lPoints = []; 100 | 101 | var l = []; 102 | 103 | var steps, distancePerStep; 104 | 105 | if( settings.steps) { 106 | steps = settings.steps; 107 | distancePerStep = this.d / steps; 108 | } 109 | 110 | if( settings.distancePerStep ) { 111 | distancePerStep = settings.distancePerStep; 112 | steps = this.d / distancePerStep; 113 | } 114 | 115 | var d = 0, 116 | p = 0; 117 | 118 | this.lPoints = []; 119 | 120 | var current = new THREE.Vector3(); 121 | current.copy( this.points[ 0 ].clone() ); 122 | this.lPoints.push( current.clone() ); 123 | 124 | function splitSegment( a, b, l ) { 125 | 126 | var t = b.clone(); 127 | var d = 0; 128 | t.sub( a ); 129 | var rd = t.length(); 130 | t.normalize(); 131 | t.multiplyScalar( distancePerStep ); 132 | var s = Math.floor( rd / distancePerStep ); 133 | for( var j = 0; j < s; j++ ) { 134 | a.add( t ); 135 | l.push( a.clone() ); 136 | d += distancePerStep; 137 | } 138 | return d; 139 | } 140 | 141 | for( var j = 0; j < this.points.length; j++ ) { 142 | 143 | if( this.points[ j ].ac - d > distancePerStep ) { 144 | 145 | d += splitSegment( current, this.points[ j ], this.lPoints ); 146 | 147 | } 148 | 149 | } 150 | this.lPoints.push( this.points[ this.points.length - 1 ].clone() ); 151 | 152 | 153 | }; 154 | /* eslint-enable */ 155 | -------------------------------------------------------------------------------- /src/utils/webgl/MeshLine.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | THREE.MeshLine = function() { 3 | 4 | this.positions = []; 5 | 6 | this.previous = []; 7 | this.next = []; 8 | this.side = []; 9 | this.width = []; 10 | this.indices_array = []; 11 | this.uvs = []; 12 | 13 | this.geometry = new THREE.BufferGeometry(); 14 | 15 | this.widthCallback = null; 16 | 17 | } 18 | 19 | THREE.MeshLine.prototype.setGeometry = function( g, c ) { 20 | 21 | this.widthCallback = c; 22 | 23 | this.positions = []; 24 | 25 | if( g instanceof THREE.Geometry ) { 26 | for( var j = 0; j < g.vertices.length; j++ ) { 27 | var v = g.vertices[ j ]; 28 | this.positions.push( v.x, v.y, v.z ); 29 | this.positions.push( v.x, v.y, v.z ); 30 | } 31 | } 32 | 33 | if( g instanceof THREE.BufferGeometry ) { 34 | // read attribute positions ? 35 | } 36 | 37 | if( g instanceof Float32Array || g instanceof Array ) { 38 | for( var j = 0; j < g.length; j += 3 ) { 39 | this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] ); 40 | this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] ); 41 | } 42 | } 43 | 44 | this.process(); 45 | 46 | } 47 | 48 | THREE.MeshLine.prototype.compareV3 = function( a, b ) { 49 | 50 | var aa = a * 6; 51 | var ab = b * 6; 52 | return ( this.positions[ aa ] === this.positions[ ab ] ) && ( this.positions[ aa + 1 ] === this.positions[ ab + 1 ] ) && ( this.positions[ aa + 2 ] === this.positions[ ab + 2 ] ); 53 | 54 | } 55 | 56 | THREE.MeshLine.prototype.copyV3 = function( a ) { 57 | 58 | var aa = a * 6; 59 | return [ this.positions[ aa ], this.positions[ aa + 1 ], this.positions[ aa + 2 ] ]; 60 | 61 | } 62 | 63 | THREE.MeshLine.prototype.process = function() { 64 | 65 | var l = this.positions.length / 6; 66 | 67 | this.previous = []; 68 | this.next = []; 69 | this.side = []; 70 | this.width = []; 71 | this.indices_array = []; 72 | this.uvs = []; 73 | 74 | for( var j = 0; j < l; j++ ) { 75 | this.side.push( 1 ); 76 | this.side.push( -1 ); 77 | } 78 | 79 | var w; 80 | for( var j = 0; j < l; j++ ) { 81 | if( this.widthCallback ) w = this.widthCallback( j / ( l -1 ) ); 82 | else w = 1; 83 | this.width.push( w ); 84 | this.width.push( w ); 85 | } 86 | 87 | for( var j = 0; j < l; j++ ) { 88 | this.uvs.push( j / ( l - 1 ), 0 ); 89 | this.uvs.push( j / ( l - 1 ), 1 ); 90 | } 91 | 92 | var v; 93 | 94 | if( this.compareV3( 0, l - 1 ) ){ 95 | v = this.copyV3( l - 2 ); 96 | } else { 97 | v = this.copyV3( 0 ); 98 | } 99 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 100 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 101 | for( var j = 0; j < l - 1; j++ ) { 102 | v = this.copyV3( j ); 103 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 104 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 105 | } 106 | 107 | for( var j = 1; j < l; j++ ) { 108 | v = this.copyV3( j ); 109 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 110 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 111 | } 112 | 113 | if( this.compareV3( l - 1, 0 ) ){ 114 | v = this.copyV3( 1 ); 115 | } else { 116 | v = this.copyV3( l - 1 ); 117 | } 118 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 119 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 120 | 121 | for( var j = 0; j < l - 1; j++ ) { 122 | var n = j * 2; 123 | this.indices_array.push( n, n + 1, n + 2 ); 124 | this.indices_array.push( n + 2, n + 1, n + 3 ); 125 | } 126 | 127 | if (!this.attributes) { 128 | this.attributes = { 129 | position: new THREE.BufferAttribute( new Float32Array( this.positions ), 3 ), 130 | previous: new THREE.BufferAttribute( new Float32Array( this.previous ), 3 ), 131 | next: new THREE.BufferAttribute( new Float32Array( this.next ), 3 ), 132 | side: new THREE.BufferAttribute( new Float32Array( this.side ), 1 ), 133 | width: new THREE.BufferAttribute( new Float32Array( this.width ), 1 ), 134 | uv: new THREE.BufferAttribute( new Float32Array( this.uvs ), 2 ), 135 | index: new THREE.BufferAttribute( new Uint16Array( this.indices_array ), 1 ) 136 | } 137 | } else { 138 | this.attributes.position.copyArray(new Float32Array(this.positions)); 139 | this.attributes.position.needsUpdate = true; 140 | this.attributes.previous.copyArray(new Float32Array(this.previous)); 141 | this.attributes.previous.needsUpdate = true; 142 | this.attributes.next.copyArray(new Float32Array(this.next)); 143 | this.attributes.next.needsUpdate = true; 144 | this.attributes.side.copyArray(new Float32Array(this.side)); 145 | this.attributes.side.needsUpdate = true; 146 | this.attributes.width.copyArray(new Float32Array(this.width)); 147 | this.attributes.width.needsUpdate = true; 148 | this.attributes.uv.copyArray(new Float32Array(this.uvs)); 149 | this.attributes.uv.needsUpdate = true; 150 | this.attributes.index.copyArray(new Uint16Array(this.index)); 151 | this.attributes.index.needsUpdate = true; 152 | } 153 | 154 | this.geometry.addAttribute( 'position', this.attributes.position ); 155 | this.geometry.addAttribute( 'previous', this.attributes.previous ); 156 | this.geometry.addAttribute( 'next', this.attributes.next ); 157 | this.geometry.addAttribute( 'side', this.attributes.side ); 158 | this.geometry.addAttribute( 'width', this.attributes.width ); 159 | this.geometry.addAttribute( 'uv', this.attributes.uv ); 160 | 161 | this.geometry.setIndex( this.attributes.index ); 162 | 163 | } 164 | 165 | THREE.MeshLineMaterial = function ( parameters ) { 166 | 167 | var vertexShaderSource = [ 168 | 'precision highp float;', 169 | '', 170 | 'attribute vec3 position;', 171 | 'attribute vec3 previous;', 172 | 'attribute vec3 next;', 173 | 'attribute float side;', 174 | 'attribute float width;', 175 | 'attribute vec2 uv;', 176 | '', 177 | 'uniform mat4 projectionMatrix;', 178 | 'uniform mat4 modelViewMatrix;', 179 | 'uniform vec2 resolution;', 180 | 'uniform float lineWidth;', 181 | 'uniform vec3 color;', 182 | 'uniform float opacity;', 183 | 'uniform float near;', 184 | 'uniform float far;', 185 | 'uniform float sizeAttenuation;', 186 | '', 187 | 'varying vec2 vUV;', 188 | 'varying vec4 vColor;', 189 | 'varying vec3 vPosition;', 190 | '', 191 | 'vec2 fix( vec4 i, float aspect ) {', 192 | '', 193 | ' vec2 res = i.xy / i.w;', 194 | ' res.x *= aspect;', 195 | ' return res;', 196 | '', 197 | '}', 198 | '', 199 | 'void main() {', 200 | '', 201 | ' float aspect = resolution.x / resolution.y;', 202 | ' float pixelWidthRatio = 1. / (resolution.x * projectionMatrix[0][0]);', 203 | '', 204 | ' vColor = vec4( color, opacity );', 205 | ' vUV = uv;', 206 | '', 207 | ' mat4 m = projectionMatrix * modelViewMatrix;', 208 | ' vec4 finalPosition = m * vec4( position, 1.0 );', 209 | ' vec4 prevPos = m * vec4( previous, 1.0 );', 210 | ' vec4 nextPos = m * vec4( next, 1.0 );', 211 | '', 212 | ' vec2 currentP = fix( finalPosition, aspect );', 213 | ' vec2 prevP = fix( prevPos, aspect );', 214 | ' vec2 nextP = fix( nextPos, aspect );', 215 | '', 216 | ' float pixelWidth = finalPosition.w * pixelWidthRatio;', 217 | ' float w = 1.8 * pixelWidth * lineWidth * width;', 218 | '', 219 | ' if( sizeAttenuation == 1. ) {', 220 | ' w = 1.8 * lineWidth * width;', 221 | ' }', 222 | '', 223 | ' vec2 dir;', 224 | ' if( nextP == currentP ) dir = normalize( currentP - prevP );', 225 | ' else if( prevP == currentP ) dir = normalize( nextP - currentP );', 226 | ' else {', 227 | ' vec2 dir1 = normalize( currentP - prevP );', 228 | ' vec2 dir2 = normalize( nextP - currentP );', 229 | ' dir = normalize( dir1 + dir2 );', 230 | '', 231 | ' vec2 perp = vec2( -dir1.y, dir1.x );', 232 | ' vec2 miter = vec2( -dir.y, dir.x );', 233 | ' //w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );', 234 | '', 235 | ' }', 236 | '', 237 | ' //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;', 238 | ' vec2 normal = vec2( -dir.y, dir.x );', 239 | ' normal.x /= aspect;', 240 | ' normal *= .5 * w;', 241 | '', 242 | ' vec4 offset = vec4( normal * side, 0.0, 1.0 );', 243 | ' finalPosition.xy += offset.xy;', 244 | '', 245 | ' vPosition = ( modelViewMatrix * vec4( position, 1. ) ).xyz;', 246 | ' gl_Position = finalPosition;', 247 | '', 248 | '}' ]; 249 | 250 | var fragmentShaderSource = [ 251 | '#extension GL_OES_standard_derivatives : enable', 252 | 'precision mediump float;', 253 | '', 254 | 'uniform sampler2D map;', 255 | 'uniform float useMap;', 256 | 'uniform float useDash;', 257 | 'uniform vec2 dashArray;', 258 | '', 259 | 'varying vec2 vUV;', 260 | 'varying vec4 vColor;', 261 | 'varying vec3 vPosition;', 262 | '', 263 | 'void main() {', 264 | '', 265 | ' vec4 c = vColor;', 266 | ' if( useMap == 1. ) c *= texture2D( map, vUV );', 267 | ' if( useDash == 1. ){', 268 | ' ', 269 | ' }', 270 | ' gl_FragColor = c;', 271 | '', 272 | '}' ]; 273 | 274 | function check( v, d ) { 275 | if( v === undefined ) return d; 276 | return v; 277 | } 278 | 279 | THREE.Material.call( this ); 280 | 281 | parameters = parameters || {}; 282 | 283 | this.lineWidth = check( parameters.lineWidth, 1 ); 284 | this.map = check( parameters.map, null ); 285 | this.useMap = check( parameters.useMap, 0 ); 286 | this.color = check( parameters.color, new THREE.Color( 0xffffff ) ); 287 | this.opacity = check( parameters.opacity, 1 ); 288 | this.resolution = check( parameters.resolution, new THREE.Vector2( 1, 1 ) ); 289 | this.sizeAttenuation = check( parameters.sizeAttenuation, 1 ); 290 | this.near = check( parameters.near, 1 ); 291 | this.far = check( parameters.far, 1 ); 292 | this.dashArray = check( parameters.dashArray, [] ); 293 | this.useDash = ( this.dashArray !== [] ) ? 1 : 0; 294 | 295 | var material = new THREE.RawShaderMaterial( { 296 | uniforms:{ 297 | lineWidth: { type: 'f', value: this.lineWidth }, 298 | map: { type: 't', value: this.map }, 299 | useMap: { type: 'f', value: this.useMap }, 300 | color: { type: 'c', value: this.color }, 301 | opacity: { type: 'f', value: this.opacity }, 302 | resolution: { type: 'v2', value: this.resolution }, 303 | sizeAttenuation: { type: 'f', value: this.sizeAttenuation }, 304 | near: { type: 'f', value: this.near }, 305 | far: { type: 'f', value: this.far }, 306 | dashArray: { type: 'v2', value: new THREE.Vector2( this.dashArray[ 0 ], this.dashArray[ 1 ] ) }, 307 | useDash: { type: 'f', value: this.useDash } 308 | }, 309 | vertexShader: vertexShaderSource.join( '\r\n' ), 310 | fragmentShader: fragmentShaderSource.join( '\r\n' ) 311 | }); 312 | 313 | delete parameters.lineWidth; 314 | delete parameters.map; 315 | delete parameters.useMap; 316 | delete parameters.color; 317 | delete parameters.opacity; 318 | delete parameters.resolution; 319 | delete parameters.sizeAttenuation; 320 | delete parameters.near; 321 | delete parameters.far; 322 | delete parameters.dashArray; 323 | 324 | material.type = 'MeshLineMaterial'; 325 | 326 | material.setValues( parameters ); 327 | 328 | return material; 329 | 330 | }; 331 | 332 | THREE.MeshLineMaterial.prototype = Object.create( THREE.Material.prototype ); 333 | THREE.MeshLineMaterial.prototype.constructor = THREE.MeshLineMaterial; 334 | 335 | THREE.MeshLineMaterial.prototype.copy = function ( source ) { 336 | 337 | THREE.Material.prototype.copy.call( this, source ); 338 | 339 | this.lineWidth = source.lineWidth; 340 | this.map = source.map; 341 | this.useMap = source.useMap; 342 | this.color.copy( source.color ); 343 | this.opacity = source.opacity; 344 | this.resolution.copy( source.resolution ); 345 | this.sizeAttenuation = source.sizeAttenuation; 346 | this.near = source.near; 347 | this.far = source.far; 348 | 349 | return this; 350 | 351 | }; 352 | /* eslint-enable */ 353 | -------------------------------------------------------------------------------- /src/utils/webgl/OrbitControls.js: -------------------------------------------------------------------------------- 1 | import OrbitControls from 'three-orbit-controls'; 2 | export default OrbitControls( THREE ); 3 | -------------------------------------------------------------------------------- /src/utils/webgl/shader-parse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replace THREE chunk 3 | * 4 | * @param {string} a Match 5 | * @param {string} b First match 6 | * @return {string} Parsed GLSL 7 | */ 8 | function replaceThreeChunkFn( a, b ) { 9 | return THREE.ShaderChunk[ b ] + '\n'; 10 | } 11 | 12 | /** 13 | * Parse shader and replace THREE chunk 14 | * 15 | * @param {string} glsl GLSL 16 | * @return {string} Parsed GLSL 17 | */ 18 | export default function shaderParse( glsl ) { 19 | return glsl.replace( /\/\/\s?chunk\(\s?(\w+)\s?\);/g, replaceThreeChunkFn ); 20 | } 21 | -------------------------------------------------------------------------------- /static/fonts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-threejs-webpack-boilerplate/c4fe5079823471a182808bc3d241edd54ae8a9c6/static/fonts/.keep -------------------------------------------------------------------------------- /static/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-threejs-webpack-boilerplate/c4fe5079823471a182808bc3d241edd54ae8a9c6/static/images/.keep -------------------------------------------------------------------------------- /static/textures/stroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-threejs-webpack-boilerplate/c4fe5079823471a182808bc3d241edd54ae8a9c6/static/textures/stroke.png -------------------------------------------------------------------------------- /webpack/webpack.dev.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 5 | 6 | export default { 7 | context: path.resolve(__dirname, '..'), 8 | devtool: 'inline-source-map', 9 | entry: [ 10 | 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true', 11 | './src/Main.js' 12 | ], 13 | output: { 14 | path: __dirname, 15 | publicPath: '/', 16 | filename: 'bundle.js' 17 | }, 18 | resolve: { 19 | root: path.resolve( __dirname, '..', 'src' ), 20 | alias: { 21 | 'webgl': 'components/WebGLBase' 22 | }, 23 | extensions: [ 24 | '', 25 | '.js', 26 | '.jsx', 27 | '.json' 28 | ] 29 | }, 30 | module: { 31 | preLoaders: [ 32 | { 33 | test: /\.js?$/, 34 | exclude: /node_modules/, 35 | loader: 'eslint' 36 | } 37 | ], 38 | loaders: [ 39 | { 40 | test: /\.html?$/, 41 | exclude: /node_modules/, 42 | loader: 'html' 43 | }, 44 | { 45 | test: /\.js$/, 46 | exclude: /node_modules/, 47 | loader: 'babel' 48 | }, 49 | { 50 | test: /node_modules/, 51 | loader: 'ify' 52 | }, 53 | { 54 | test: /\.json$/, 55 | loader: 'json' 56 | }, 57 | { 58 | test: /\.css$/, 59 | exclude: /node_modules/, 60 | loaders: ['style', 'css'] 61 | }, 62 | { 63 | test: /\.scss$/, 64 | exclude: /node_modules/, 65 | loaders: ['style', 'css', 'sass'] 66 | }, 67 | { 68 | test: /\.(glsl|frag|vert)$/, 69 | exclude: /node_modules/, 70 | loader: 'raw!glslify' 71 | }, 72 | { 73 | test: /SplitText\.js$/, 74 | loader: 'imports?define=>false!exports?SplitText' 75 | } 76 | ] 77 | }, 78 | plugins: [ 79 | new HtmlWebpackPlugin({ 80 | template: 'src/template/index.tpl.html', 81 | inject: 'body', 82 | filename: 'index.html' 83 | }), 84 | new webpack.optimize.OccurenceOrderPlugin(), 85 | new webpack.HotModuleReplacementPlugin(), 86 | new webpack.DefinePlugin({ 87 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 88 | '__DEV__': JSON.stringify(true), 89 | '__PROD__': JSON.stringify(false) 90 | }), 91 | new webpack.ProvidePlugin({ 92 | 'Vue': 'vue', 93 | 'THREE': 'three' 94 | }), 95 | new CopyWebpackPlugin([ 96 | { from: 'static' } 97 | ], 98 | { ignore: ['.DS_Store', '.keep'] }) 99 | ] 100 | }; 101 | -------------------------------------------------------------------------------- /webpack/webpack.prod.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 5 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 6 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 7 | import StatsWebpackPlugin from 'stats-webpack-plugin'; 8 | 9 | export default { 10 | context: path.resolve(__dirname, '..'), 11 | entry: './src/Main.js', 12 | output: { 13 | path: path.join(__dirname, '..', 'dist'), 14 | filename: '[name]-[hash].min.js' 15 | }, 16 | resolve: { 17 | root: path.resolve( __dirname, '..', 'src' ), 18 | alias: { 19 | 'webgl': 'components/WebGLBase' 20 | }, 21 | extensions: [ 22 | '', 23 | '.js', 24 | '.jsx', 25 | '.json' 26 | ] 27 | }, 28 | module: { 29 | loaders: [ 30 | { 31 | test: /\.html?$/, 32 | exclude: /node_modules/, 33 | loader: 'html' 34 | }, 35 | { 36 | test: /\.js$/, 37 | exclude: /node_modules/, 38 | loader: 'babel' 39 | }, 40 | { 41 | test: /node_modules/, 42 | loader: 'ify' 43 | }, 44 | { 45 | test: /\.json$/, 46 | loader: 'json' 47 | }, 48 | { 49 | test: /\.css$/, 50 | loader: ExtractTextPlugin.extract('style', 'css!autoprefixer?browsers=last 2 version') 51 | }, 52 | { 53 | test: /\.scss$/, 54 | loader: ExtractTextPlugin.extract('style', 'css!autoprefixer?browsers=last 2 version!sass') 55 | }, 56 | { 57 | test: /\.(glsl|frag|vert)$/, 58 | exclude: /node_modules/, 59 | loader: 'raw!glslify' 60 | }, 61 | { 62 | test: /SplitText\.js$/, 63 | loader: 'imports?define=>false!exports?SplitText' 64 | } 65 | ] 66 | }, 67 | plugins: [ 68 | new HtmlWebpackPlugin({ 69 | template: 'src/template/index.tpl.html', 70 | inject: 'body', 71 | filename: 'index.html' 72 | }), 73 | new webpack.optimize.OccurenceOrderPlugin(), 74 | new webpack.NoErrorsPlugin(), 75 | new webpack.DefinePlugin({ 76 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 77 | '__DEV__': JSON.stringify(false), 78 | '__PROD__': JSON.stringify(true) 79 | }), 80 | new webpack.ProvidePlugin({ 81 | 'Vue': 'vue', 82 | 'THREE': 'three' 83 | }), 84 | new CopyWebpackPlugin([ 85 | { from: 'static' } 86 | ], 87 | { ignore: ['.DS_Store', '.keep'] }), 88 | new webpack.optimize.UglifyJsPlugin({ 89 | compress: { 90 | warnings: false, 91 | drop_console: true, 92 | pure_funcs: ['console.log'] 93 | } 94 | }), 95 | new ExtractTextPlugin('[name]-[hash].min.css', { allChunks: true }), 96 | new CleanWebpackPlugin(['dist'], { root: path.join(__dirname, '..') }), 97 | new StatsWebpackPlugin('webpack.stats.json') 98 | ] 99 | }; 100 | --------------------------------------------------------------------------------