├── .editorconfig ├── .gitignore ├── LICENSE.md ├── README.md ├── package.json ├── src ├── scripts │ ├── App.js │ ├── index.js │ ├── objects │ │ └── Sphere.js │ └── utils │ │ └── Scene.js ├── styles │ ├── base │ │ ├── base.styl │ │ ├── common.styl │ │ └── reset.styl │ └── main.styl └── templates │ └── index.tpl.html ├── static └── assets │ └── .gitkeep ├── webpack.config.js └── webpack.prod.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true; 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space; 11 | indent_size = 4; 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf; 15 | charset = utf-8; 16 | trim_trailing_whitespace = true; 17 | insert_final_newline = true; 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | *.log* 4 | *.DS_Store 5 | 6 | /build 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 [Arnaud Rocca](http://arnaudrocca.fr). 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Three.js - Starter kit 2 | 3 | Experiment starter kit using Three.js, Wagner, TweenMax & Webpack. 4 | 5 | #### Technologies : 6 | 7 | * [ES6 - Babel](https://github.com/babel/babel) 8 | * [Three.js](https://github.com/mrdoob/three.js) 9 | * [Wagner](https://github.com/superguigui/Wagner) 10 | * [GSAP - TweenMax](http://greensock.com/tweenmax) 11 | * [Webpack](https://github.com/webpack/webpack) 12 | * [Stylus](https://github.com/stylus/stylus) 13 | 14 | #### Install : 15 | 16 | Clone this repository and install dependencies : 17 | ```shell 18 | git clone https://github.com/ArnaudRocca/three-js-starter-kit.git 19 | ``` 20 | ```shell 21 | npm install 22 | ``` 23 | 24 | #### Development : 25 | 26 | Start the webpack-dev-server at http://localhost:8080/ 27 | ```shell 28 | npm start 29 | ``` 30 | 31 | #### Production : 32 | 33 | Build a bundled file outputted here : static/bundle.js 34 | ```shell 35 | npm run build 36 | ``` 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-js-starter-kit", 3 | "version": "1.0.0", 4 | "description": "Experiment starter kit using Three.js, Wagner, TweenMax & Webpack.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --progress --colors --hot --inline --config webpack.config.js", 8 | "build": "webpack --progress --colors --config webpack.prod.config.js" 9 | }, 10 | "author": { 11 | "name": "Arnaud Rocca", 12 | "url": "http://arnaudrocca.fr", 13 | "mail": "arnaud.rocca@gmail.com" 14 | }, 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/ArnaudRocca/three-js-starter-kit.git" 19 | }, 20 | "homepage": "https://github.com/ArnaudRocca/three-js-starter-kit", 21 | "dependencies": { 22 | "@superguigui/wagner": "^0.1.21", 23 | "glslify": "^5.0.2", 24 | "glslify-fancy-imports": "^1.0.1", 25 | "glslify-hex": "^2.0.1", 26 | "gsap": "^1.18.4", 27 | "lodash.bindall": "^4.4.0", 28 | "orbit-controls-es6": "^1.0.8", 29 | "stats.js": "^0.17.0", 30 | "three": "^0.76.1" 31 | }, 32 | "devDependencies": { 33 | "autoprefixer": "^6.5.4", 34 | "babel-cli": "^6.18.0", 35 | "babel-core": "^6.7.7", 36 | "babel-loader": "^6.2.4", 37 | "babel-plugin-add-module-exports": "^0.1.4", 38 | "babel-preset-es2015": "^6.6.0", 39 | "clean-webpack-plugin": "^0.1.9", 40 | "copy-webpack-plugin": "^2.1.3", 41 | "css-loader": "^0.26.1", 42 | "extract-text-webpack-plugin": "^1.0.1", 43 | "file-loader": "^0.9.0", 44 | "html-webpack-plugin": "^2.16.1", 45 | "ify-loader": "^1.0.3", 46 | "json-loader": "^0.5.4", 47 | "postcss-loader": "^1.2.1", 48 | "precss": "^1.4.0", 49 | "raw-loader": "^0.5.1", 50 | "style-loader": "^0.13.1", 51 | "stylus": "^0.54.5", 52 | "stylus-loader": "^2.4.0", 53 | "webpack": "^1.12.14", 54 | "webpack-dev-server": "^1.14.1" 55 | }, 56 | "glslify": { 57 | "transform": [ 58 | "glslify-fancy-imports", 59 | "glslify-hex" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/scripts/App.js: -------------------------------------------------------------------------------- 1 | import 'TweenMax'; 2 | import * as THREE from 'three'; 3 | import Stats from 'stats.js'; 4 | import bindAll from 'lodash.bindall'; 5 | import Scene from './utils/Scene'; 6 | import Sphere from './objects/Sphere'; 7 | 8 | export default class App { 9 | 10 | /** 11 | * @constructor 12 | */ 13 | constructor() { 14 | 15 | const $root = document.body.querySelector('.app'); 16 | 17 | this.width = window.innerWidth; 18 | this.height = window.innerHeight; 19 | 20 | this.DELTA_TIME = 0; 21 | this.CURRENT_TIME = 0; 22 | this.clock = new THREE.Clock(); 23 | 24 | this.stats = new Stats(); 25 | $root.appendChild(this.stats.dom); 26 | 27 | this.scene = new Scene({ usePostProcessing: true }, this.width, this.height); 28 | 29 | this.sphere = new Sphere(); 30 | this.scene.add(this.sphere.mesh); 31 | 32 | $root.appendChild(this.scene.renderer.domElement); 33 | 34 | bindAll(this, ['resizeHandler', 'update']); 35 | 36 | this.addListeners(); 37 | 38 | requestAnimationFrame(this.update); 39 | 40 | } 41 | 42 | /** 43 | * @method 44 | * @name addListeners 45 | */ 46 | addListeners() { 47 | 48 | window.addEventListener('resize', this.resizeHandler); 49 | 50 | } 51 | 52 | /** 53 | * @method 54 | * @name resizeHandler 55 | * @description Triggered when window is resized 56 | */ 57 | resizeHandler() { 58 | 59 | this.width = window.innerWidth; 60 | this.height = window.innerHeight; 61 | 62 | this.scene.resize(this.width, this.height); 63 | 64 | } 65 | 66 | /** 67 | * @method 68 | * @name update 69 | * @description Triggered on every frame 70 | */ 71 | update() { 72 | 73 | this.stats.begin(); 74 | 75 | this.DELTA_TIME = this.clock.getDelta(); 76 | this.CURRENT_TIME = this.clock.getElapsedTime(); 77 | 78 | this.sphere.update(this.CURRENT_TIME, this.DELTA_TIME); 79 | this.scene.render(); 80 | 81 | this.stats.end(); 82 | 83 | requestAnimationFrame(this.update); 84 | 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/scripts/index.js: -------------------------------------------------------------------------------- 1 | import App from './App'; 2 | 3 | new App(); 4 | -------------------------------------------------------------------------------- /src/scripts/objects/Sphere.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | export default class Sphere { 4 | 5 | /** 6 | * @constructor 7 | * @param {Object} options 8 | */ 9 | constructor(options = {}) { 10 | 11 | this.radius = 10; 12 | this.segments = 10; 13 | this.rotationSpeed = 1; 14 | 15 | this.geometry = new THREE.SphereGeometry(this.radius, this.segments, this.segments); 16 | this.material = new THREE.MeshPhongMaterial({ 17 | color: 0x00007F, 18 | shading: THREE.FlatShading 19 | }); 20 | 21 | this.mesh = new THREE.Mesh(this.geometry, this.material); 22 | this.mesh.name = 'sphere'; 23 | 24 | } 25 | 26 | /** 27 | * @method 28 | * @name update 29 | * @description Triggered on every frame 30 | * @param {number} time - CURRENT_TIME 31 | * @param {number} delta - DELTA_TIME 32 | */ 33 | update(time, delta) { 34 | 35 | this.mesh.rotation.x += this.rotationSpeed * delta; 36 | this.mesh.rotation.y += this.rotationSpeed * delta; 37 | this.mesh.rotation.z += this.rotationSpeed * delta; 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/scripts/utils/Scene.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import OrbitControls from 'orbit-controls-es6'; 3 | import Wagner from '@superguigui/wagner'; 4 | import VignettePass from '@superguigui/wagner/src/passes/vignette/VignettePass'; 5 | 6 | export default class Scene extends THREE.Scene { 7 | 8 | /** 9 | * @constructor 10 | * @param {Object} options 11 | * @param {number} width 12 | * @param {number} height 13 | */ 14 | constructor(options = {}, width, height) { 15 | 16 | super(); 17 | 18 | this.options = options; 19 | 20 | this.renderer = new THREE.WebGLRenderer({ antialias: true }); 21 | this.renderer.setSize(width, height); 22 | this.renderer.setPixelRatio(window.devicePixelRatio); 23 | 24 | this.renderer.setClearColor(0xFFFFFF, 1); 25 | this.renderer.autoClear = false; 26 | this.renderer.gammaInput = true; 27 | this.renderer.gammaOutput = true; 28 | 29 | this.camera = new THREE.PerspectiveCamera(50, width / height, 1, 1000); 30 | this.camera.position.set(0, 0, 50); 31 | this.camera.lookAt(new THREE.Vector3(0, 0, 0)); 32 | 33 | this.controls = new OrbitControls(this.camera, this.renderer.domElement); 34 | this.controls.enabled = true; 35 | 36 | this.initLights(); 37 | this.initPostProcessing(); 38 | 39 | } 40 | 41 | /** 42 | * @method 43 | * @name initLights 44 | */ 45 | initLights() { 46 | 47 | this.ambientLight = new THREE.AmbientLight(0xFFFFFF, 1); 48 | this.add(this.ambientLight); 49 | 50 | this.pointLight = new THREE.PointLight(0xFFFFFF, 1); 51 | this.pointLight.position.set(100, 100, 100); 52 | this.add(this.pointLight); 53 | 54 | } 55 | 56 | /** 57 | * @method 58 | * @name initPostProcessing 59 | */ 60 | initPostProcessing() { 61 | 62 | this.composer = new Wagner.Composer(this.renderer); 63 | 64 | this.vignettePass = new VignettePass({ 65 | boost: 1.0, 66 | reduction: 1.5 67 | }); 68 | 69 | } 70 | 71 | /** 72 | * @method 73 | * @name resize 74 | * @description Resize the scene according to screen size 75 | * @param {number} newWidth 76 | * @param {number} newHeight 77 | */ 78 | resize(newWidth, newHeight) { 79 | 80 | this.camera.aspect = newWidth / newHeight; 81 | this.camera.updateProjectionMatrix(); 82 | 83 | this.renderer.setSize(newWidth, newHeight); 84 | this.composer.setSize(newWidth, newHeight); 85 | 86 | } 87 | 88 | /** 89 | * @method 90 | * @name render 91 | * @description Renders/Draw the scene 92 | */ 93 | render() { 94 | 95 | this.composer.reset(); 96 | this.composer.render(this, this.camera); 97 | if (this.options.usePostProcessing === true) { 98 | this.composer.pass(this.vignettePass); 99 | } 100 | this.composer.toScreen(); 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/styles/base/base.styl: -------------------------------------------------------------------------------- 1 | @import './reset.styl' 2 | @import './common.styl' 3 | -------------------------------------------------------------------------------- /src/styles/base/common.styl: -------------------------------------------------------------------------------- 1 | body 2 | width 100% 3 | height 100% 4 | overflow hidden 5 | 6 | .app 7 | position absolute 8 | top 0 9 | left 0 10 | width 100% 11 | height 100% 12 | -------------------------------------------------------------------------------- /src/styles/base/reset.styl: -------------------------------------------------------------------------------- 1 | /* 2 | html5doctor.com Reset Stylesheet 3 | v1.6.1 4 | Last Updated: 2010-09-17 5 | Author: Richard Clark - http://richclarkdesign.com 6 | Twitter: @rich_clark 7 | Stylus-ized by 8 | dale tan 9 | http://www.whatthedale.com 10 | @HellaTan 11 | */ 12 | 13 | html, body, div, span, object, iframe, 14 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 15 | abbr, address, cite, code, 16 | del, dfn, em, img, ins, kbd, q, samp, 17 | small, strong, sub, sup, var, 18 | b, i, 19 | dl, dt, dd, ol, ul, li, 20 | fieldset, form, label, legend, 21 | table, caption, tbody, tfoot, thead, tr, th, td, 22 | article, aside, canvas, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section, summary, 24 | time, mark, audio, video 25 | background transparent 26 | border 0 27 | font-size 100% 28 | margin 0 29 | outline 0 30 | padding 0 31 | vertical-align baseline 32 | 33 | body 34 | line-height 1 35 | 36 | button 37 | cursor pointer 38 | margin 0 39 | padding 0 40 | border none 41 | appearance none 42 | outline none 43 | background none 44 | 45 | article, aside, details, figcaption, figure, 46 | footer, header, hgroup, menu, nav, section 47 | display block 48 | 49 | nav ul 50 | list-style none 51 | 52 | blockquote, q 53 | quotes none 54 | 55 | blockquote:before, blockquote:after, 56 | q:before, q:after 57 | content '' 58 | content none 59 | 60 | a 61 | background transparent 62 | font-size 100% 63 | margin 0 64 | padding 0 65 | vertical-align baseline 66 | 67 | /* change colours to suit your needs */ 68 | ins 69 | background-color #FF9 70 | color #000 71 | text-decoration none 72 | 73 | /* change colours to suit your needs */ 74 | mark 75 | background-color #FF9 76 | color #000 77 | font-style italic 78 | font-weight bold 79 | 80 | del 81 | text-decoration line-through 82 | 83 | abbr[title], dfn[title] 84 | border-bottom 1px dotted 85 | cursor help 86 | 87 | table 88 | border-collapse collapse 89 | border-spacing 0 90 | 91 | /* change border colour to suit your needs */ 92 | hr 93 | border 0 94 | border-top 1px solid #CCC 95 | display block 96 | height 1px 97 | margin 1em 0 98 | padding 0 99 | 100 | input, select 101 | vertical-align middle 102 | -------------------------------------------------------------------------------- /src/styles/main.styl: -------------------------------------------------------------------------------- 1 | @import './base/base.styl' 2 | -------------------------------------------------------------------------------- /src/templates/index.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |