├── .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 |
--------------------------------------------------------------------------------