├── .gitignore
├── README.md
├── gulpfile.js
├── package.json
├── project.json
├── project.sublime-project
└── src
├── assets
└── .gitkeep
├── gulp
├── config.js
├── tasks
│ ├── assets.js
│ ├── build.js
│ ├── clean.js
│ ├── html.js
│ ├── scripts.js
│ ├── server.js
│ ├── styles.js
│ └── watch.js
└── utils
│ └── on-error.js
├── index.html
├── scripts
├── .gitkeep
└── app.js
├── styles
├── .gitkeep
└── styles.scss
└── vendors
└── TrackballControls.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .sass-cache/
3 | /build/
4 | node_modules/
5 | npm-debug.log
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Playground-starter
2 |
3 | A quick starter for lab projects.
4 | Including
5 | * [Gulp](http://gulpjs.com/)
6 | * [Sass](http://sass-lang.com/)
7 | * [GSAP](http://greensock.com/gsap)
8 | * [Browserify](http://browserify.org/)
9 | * [Browsersync](https://www.browsersync.io/)
10 | * [Babel - ES6](https://babeljs.io/)
11 |
12 | ## Before everything
13 | - You'll need [Node](https://nodejs.org/) (which includes NPM).
14 | - Install Gulp using `npm install -g gulp`. This installs Gulp globally and is needed later.
15 | - Clone this repo to your local computer
16 | - Edit project.json with your datas
17 | - Install the nodes modules
18 | ```shell
19 | $ npm install
20 | ```
21 | ## Run the project
22 |
23 | There is three kind of environments available : `dev`, `staging`, `live`. To change the config of each environement you need to edit the __project.json__ file.
24 |
25 | - Build and watch the app with __development configs__
26 | ```shell
27 | $ npm start
28 | ```
29 | * Build app with __staging config__
30 | ```shell
31 | $ npm run staging
32 | ```
33 | * Build app with __live configs__
34 | ```shell
35 | $ npm run live
36 | ```
37 |
38 | ### License
39 |
40 | MIT
41 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var project = require('./project.json');
4 | var path = require('path');
5 | var fs = require('fs');
6 |
7 | // Dynamically start all tasks
8 | fs.readdirSync('./' + project.foldersName.entry + '/gulp/tasks/')
9 | .forEach(function(task) {
10 | if (path.extname(task) !== '.js') return;
11 | require('./' + project.foldersName.entry + '/gulp/tasks/' + task);
12 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-starter",
3 | "version": "1.0.0",
4 | "description": "simple project starter",
5 | "main": "gulpfile.js",
6 | "author": "Corentin Fardeau - fardeaucorentin@gmail.com",
7 | "license": "MIT",
8 | "scripts": {
9 | "start": "npm run server",
10 | "server": "gulp server --watch",
11 | "staging": "gulp server --staging",
12 | "live": "gulp server --live"
13 | },
14 | "dependencies": {
15 | "events": "^1.1.1",
16 | "fastclick": "^1.0.6",
17 | "geo-arc": "^1.1.2",
18 | "glsl-noise": "0.0.0",
19 | "gsap": "^1.18.5",
20 | "jquery": "^2.1.4",
21 | "nk-css-reset": "^1.0.1",
22 | "nk-css-utils": "^2.0.0",
23 | "size": "watsondg/size",
24 | "sniffer": "watsondg/sniffer",
25 | "three": "^0.80.1",
26 | "underscore": "^1.8.3"
27 | },
28 | "devDependencies": {
29 | "babel-preset-es2015": "^6.9.0",
30 | "babelify": "^7.3.0",
31 | "browser-sync": "2.6.5",
32 | "browserify": "^11.0.1",
33 | "browserify-shim": "^3.8.10",
34 | "colors": "^1.1.2",
35 | "del": "^2.0.0",
36 | "gulp": "^3.9.1",
37 | "gulp-autoprefixer": "3.1.0",
38 | "gulp-clean-css": "2.0.11",
39 | "gulp-concat": "2.5.2",
40 | "gulp-filter": "^3.0.1",
41 | "gulp-imagemin": "^2.3.0",
42 | "gulp-notify": "^2.2.0",
43 | "gulp-plumber": "1.0.0",
44 | "gulp-rename": "1.2.2",
45 | "gulp-sass": "2.3.2",
46 | "gulp-sourcemaps": "^1.6.0",
47 | "gulp-template": "^4.0.0",
48 | "gulp-uglify": "^1.5.4",
49 | "gulp-util": "^3.0.6",
50 | "lodash.assign": "^3.2.0",
51 | "minimist": "^1.1.2",
52 | "rimraf": "^2.4.2",
53 | "vinyl-buffer": "^1.0.0",
54 | "vinyl-source-stream": "^1.1.0",
55 | "watchify": "^3.3.1"
56 | },
57 | "browserify": {
58 | "transform": [
59 | "browserify-shim"
60 | ]
61 | },
62 | "browserify-shim": {
63 | "timelinemax": "global:TimelineMax",
64 | "tweenmax": "global:TweenMax",
65 | "jquery": "$",
66 | "underscore": "_"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "foldersName": {
3 | "entry" : "src",
4 | "build" : "build"
5 | },
6 | "env": {
7 | "dev": {
8 | "siteURL": "",
9 | "baseURL": "/",
10 | "scriptURL": "/",
11 | "assetsURL": "/assets/",
12 | "env" : "dev"
13 | },
14 | "staging": {
15 | "siteURL": "",
16 | "baseURL": "/",
17 | "scriptURL": "/",
18 | "assetsURL": "/assets/",
19 | "env" : "staging"
20 | },
21 | "live": {
22 | "siteURL": "",
23 | "baseURL": "/three-js-audio/",
24 | "scriptURL": "/three-js-flag/",
25 | "assetsURL": "/three-js-flag/assets/",
26 | "env" : "live"
27 | }
28 | },
29 | "port": 3000
30 | }
31 |
--------------------------------------------------------------------------------
/project.sublime-project:
--------------------------------------------------------------------------------
1 | {
2 | "folders":
3 | [
4 | {
5 | "path": "."
6 | }
7 | ],
8 | "settings":
9 | {
10 | "tab_size": 4
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Corentinfardeau/three-js-flag/90ddf7649fa342d031ee47c98a8f807c29bd82d1/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/gulp/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var project = require('../../project.json'),
4 | flags = require('minimist')(process.argv.slice(2));
5 |
6 | var envOpts = {};
7 |
8 | if (flags.watch) {
9 | envOpts.watch = true;
10 | envOpts.debug = true;
11 | envOpts.minify = false;
12 | envOpts.env = 'dev';
13 | }
14 |
15 | if (flags.staging) {
16 | envOpts.watch = false;
17 | envOpts.debug = false;
18 | envOpts.minify = true;
19 | envOpts.env = 'staging';
20 | }
21 |
22 | if (flags.live) {
23 | envOpts.watch = false;
24 | envOpts.debug = false;
25 | envOpts.minify = true;
26 | envOpts.env = 'live';
27 | }
28 |
29 | var options = {
30 |
31 | cssEntry : project.foldersName.entry + '/styles/',
32 | cssDest : project.foldersName.build + '/styles/',
33 | jsEntry : project.foldersName.entry + '/scripts/',
34 | jsDest : project.foldersName.build + '/scripts/',
35 | jsVendorsEntry : project.foldersName.entry + '/vendors/',
36 | mainJS : 'app.js',
37 | imageEntry : project.foldersName.entry + '/assets/images/',
38 | imageDest : project.foldersName.build + '/assets/images/',
39 | copyBase : project.foldersName.entry + '/assets/',
40 | copyDest : project.foldersName.build + '/assets/',
41 | jsVendorsDest : project.foldersName.build + '/vendors/',
42 | envOpts : envOpts
43 |
44 | }
45 |
46 | module.exports = options;
--------------------------------------------------------------------------------
/src/gulp/tasks/assets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | browserSync = require('browser-sync'),
5 | config = require('../config');
6 |
7 | gulp.task('assets', function(){
8 | return gulp.src(config.copyBase + '**/*')
9 | .pipe(gulp.dest(config.copyDest))
10 | .pipe(browserSync.reload({stream:true}));
11 | });
12 |
13 | gulp.task('vendors', function(){
14 | return gulp.src(config.jsVendorsEntry + '**/*')
15 | .pipe(gulp.dest(config.jsVendorsDest))
16 | .pipe(browserSync.reload({stream:true}));
17 | });
18 |
--------------------------------------------------------------------------------
/src/gulp/tasks/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | uglify = require('gulp-uglify'),
5 | cleanCSS = require('gulp-clean-css'),
6 | sourcemaps = require('gulp-sourcemaps'),
7 | config = require('../config');
8 |
9 | gulp.task('build', ['clean', 'assets', 'vendors', 'html', 'scripts', 'styles'], function(cb) {
10 |
11 | if (config.envOpts.minify) {
12 |
13 | gulp.src(config.jsDest + '/' + config.mainJS)
14 | .pipe(uglify({
15 | sequences: true,
16 | dead_code: true,
17 | conditionals: true,
18 | booleans: true,
19 | unused: true,
20 | if_return: true,
21 | join_vars: true
22 | }))
23 | .pipe(gulp.dest(config.jsDest));
24 |
25 | gulp.src(config.cssDest + 'styles.css')
26 | .pipe(sourcemaps.init())
27 | .pipe(cleanCSS({
28 | rebase: false,
29 | advanced: false,
30 | aggressiveMerging: false,
31 | mediaMerging: true, // pack media queries
32 | processImport: true, // automatically embed @import rules
33 | }))
34 | .pipe(sourcemaps.write('./'))
35 | .pipe(gulp.dest(config.cssDest + 'styles.css'));
36 |
37 | }
38 |
39 | cb();
40 |
41 | });
42 |
--------------------------------------------------------------------------------
/src/gulp/tasks/clean.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var rm = require('rimraf'),
4 | project = require('../../../project.json');
5 |
6 | require('gulp').task('clean', function(cb) {
7 | rm.sync(project.foldersName.build);
8 | cb();
9 | });
--------------------------------------------------------------------------------
/src/gulp/tasks/html.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | browserSync = require('browser-sync'),
5 | template = require('gulp-template'),
6 | project = require('../../../project.json'),
7 | config = require('../config');
8 |
9 | var paths = project.env[config.envOpts.env];
10 |
11 | gulp.task('html', function(){
12 |
13 | gulp.src(project.foldersName.entry + '/**/*.html')
14 | .pipe(template(paths))
15 | .pipe(gulp.dest('./' + project.foldersName.build + '/'))
16 | .pipe(browserSync.reload({stream:true}));
17 | });
18 |
--------------------------------------------------------------------------------
/src/gulp/tasks/scripts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | buffer = require('vinyl-buffer'),
5 | uglify = require('gulp-uglify'),
6 | watchify = require('watchify'),
7 | source = require('vinyl-source-stream'),
8 | sourcemaps = require('gulp-sourcemaps'),
9 | assign = require('lodash.assign'),
10 | browserify = require('browserify'),
11 | rename = require('gulp-rename'),
12 | browserSync = require('browser-sync'),
13 | babel = require('babelify'),
14 | config = require('../config.js'),
15 | onError = require('../utils/on-error');
16 |
17 | var customOpts = {
18 | entries: [config.jsEntry + '/' + config.mainJS],
19 | debug: config.envOpts.debug
20 | };
21 |
22 | var opts = assign({}, watchify.args, customOpts);
23 | var b = watchify(browserify(opts).transform("babelify", {presets: ["es2015"]}));
24 |
25 | gulp.task('scripts', bundle);
26 | b.on('update', bundle);
27 |
28 | function bundle() {
29 | return b.bundle()
30 | .on('error', onError)
31 | .pipe(source(config.mainJS))
32 | .pipe(buffer())
33 | .pipe(gulp.dest(config.jsDest))
34 | .pipe(sourcemaps.init({loadMaps: true}))
35 | .pipe(sourcemaps.write('./'))
36 | .pipe(gulp.dest(config.jsDest))
37 | .pipe(browserSync.reload({stream:true}))
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/gulp/tasks/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | browserSync = require('browser-sync'),
5 | project = require('../../../project.json'),
6 | config = require('../config'),
7 | colors = require('colors'),
8 | notify = require('gulp-notify');
9 |
10 | var serverConfig = {
11 | server: "./" + project.foldersName.build,
12 | port: project.port || 3000,
13 | browser: ['google chrome'],
14 | notify: false,
15 | minify: false,
16 | ghostMode: false,
17 | }
18 |
19 | gulp.task('server', ['build'], function(cb) {
20 |
21 | if(config.envOpts.watch){
22 | browserSync.init(serverConfig);
23 | gulp.start('watch');
24 | }else{
25 | cb()
26 | }
27 |
28 | switch(config.envOpts.env){
29 | case 'dev' :
30 | console.log('PROJECT BUILT IN DEV ENVIRONEMENT'.green);
31 | break;
32 | case 'staging' :
33 | console.log('PROJECT BUILT IN STAGING ENVIRONEMENT'.green);
34 | process.exit();
35 | break;
36 | case 'live' :
37 | console.log('PROJECT BUILT IN LIVE ENVIRONEMENT'.green);
38 | process.exit();
39 | break;
40 | }
41 |
42 |
43 |
44 | });
45 |
--------------------------------------------------------------------------------
/src/gulp/tasks/styles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | browserSync = require('browser-sync'),
5 | autoprefixer = require('gulp-autoprefixer'),
6 | sass = require('gulp-sass'),
7 | config = require('../config');
8 |
9 | gulp.task('styles', function(){
10 | gulp.src([config.cssEntry + '**/*.scss'])
11 | .pipe(sass())
12 | .pipe(autoprefixer('last 2 versions'))
13 | .pipe(gulp.dest(config.cssDest))
14 | .pipe(browserSync.reload({stream:true}))
15 | });
--------------------------------------------------------------------------------
/src/gulp/tasks/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp'),
4 | watch = require('watchify'),
5 | project = require('../../../project.json'),
6 | config = require('../config');
7 |
8 | gulp.task('watch', function() {
9 |
10 | gulp.watch(config.cssEntry +"**/*.scss", ['styles']);
11 | gulp.watch(project.foldersName.entry + "/**/*.html", ['html']);
12 | gulp.watch(project.foldersName.entry + "/assets/**/*", ['assets']);
13 |
14 | });
--------------------------------------------------------------------------------
/src/gulp/utils/on-error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var notify = require('gulp-notify');
4 |
5 | module.exports = function() {
6 | var args = Array.prototype.slice.call(arguments);
7 |
8 | // Send error to notification center with gulp-notify
9 | notify.onError({
10 | title: 'Compile Error',
11 | message: '<%= error.message %>'
12 | }).apply(this, args);
13 |
14 | // Keep gulp from hanging on this task
15 | this.emit('end');
16 | };
17 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Three.js
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ← Go back to lab
36 |
76 |
77 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/scripts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Corentinfardeau/three-js-flag/90ddf7649fa342d031ee47c98a8f807c29bd82d1/src/scripts/.gitkeep
--------------------------------------------------------------------------------
/src/scripts/app.js:
--------------------------------------------------------------------------------
1 | /**********************************************************************
2 |
3 | APP
4 |
5 | **********************************************************************/
6 |
7 | window.THREE = require('three');
8 |
9 | require('../vendors/TrackballControls.js');
10 | require('gsap');
11 | const _ = require('underscore');
12 |
13 | class App {
14 |
15 | constructor(){
16 |
17 | _.bindAll(this, 'animate', 'onKeyDown', 'onKeyUp', 'restart', 'onResize');
18 |
19 | this.isKeydown = false;
20 |
21 | this.time = 0;
22 |
23 | this.params = {
24 | text : '9',
25 | speed: 4.0,
26 | offset: 20.0,
27 | fontSize: 1000
28 | };
29 |
30 | let width = window.innerWidth;
31 | let height = window.innerHeight;
32 |
33 | this.cameraOpts = {
34 | near : 0.1,
35 | aspect: width/height,
36 | far : 10000,
37 | z: 100
38 | }
39 |
40 | this.planeHeight = 400;
41 | this.ratio = width/height;
42 | this.planeWidth = this.planeHeight*this.ratio;
43 |
44 | let fov = 2 * Math.atan( this.planeHeight / ( 2 * this.cameraOpts.z ) ) * ( 180 / Math.PI );
45 | let container = document.getElementsByClassName('container')[0];
46 |
47 | this.clock = new THREE.Clock();
48 | this.renderer = new THREE.WebGLRenderer({ antialias: true});
49 |
50 | this.camera = new THREE.PerspectiveCamera(fov, this.cameraOpts.aspect, this.cameraOpts.near, this.cameraOpts.far);
51 |
52 |
53 | this.scene = new THREE.Scene();
54 | this.scene.add(this.camera);
55 |
56 | this.camera.position.z = this.cameraOpts.z;
57 | //this.camera.position.y = 10;
58 |
59 | this.renderer.setSize(width, height);
60 |
61 | container.appendChild(this.renderer.domElement);
62 | this.renderer.render(this.scene, this.camera);
63 | this.renderer.setClearColor( 0x000000, 1);
64 |
65 | this.cameraControls = new THREE.TrackballControls(this.camera, this.renderer.domElement);
66 | this.cameraControls.target.set(0, 0, 0);
67 |
68 | this.gui = new dat.GUI();
69 | this.initDat();
70 |
71 | this.start();
72 |
73 | }
74 |
75 | restart(){
76 | let texture = new THREE.Texture(this.createText(this.params.text));
77 | texture.needsUpdate = true;
78 | this.uniforms.texture1.value = texture;
79 | this.uniforms.speed.value = this.params.speed;
80 | this.uniforms.offset.value = this.params.offset;
81 | }
82 |
83 | start(){
84 |
85 | let texture = new THREE.Texture(this.createText(this.params.text));
86 | texture.needsUpdate = true;
87 |
88 | var vertShader = document.getElementById('vertexShader').innerHTML;
89 | var fragShader = document.getElementById('fragmentShader').innerHTML;
90 |
91 | let planeGeometry = new THREE.PlaneGeometry(this.planeWidth, this.planeHeight, 200, 200);
92 |
93 | this.uniforms = {
94 | texture1: {
95 | type: 't',
96 | value: texture
97 | },
98 | time: {
99 | type: "f",
100 | value: this.time
101 | },
102 | speed: {
103 | type: "f",
104 | value: this.params.speed
105 | },
106 | offset: {
107 | type: "f",
108 | value: this.params.offset
109 | }
110 | }
111 |
112 | let planeMaterial = new THREE.ShaderMaterial({
113 | uniforms: this.uniforms,
114 | vertexShader: vertShader,
115 | fragmentShader: fragShader,
116 | wireframe : false,
117 | wireframeLinewidth : 2,
118 | transparent : true
119 | });
120 |
121 | this.plane = new THREE.Mesh(planeGeometry, planeMaterial);
122 | this.scene.add(this.plane);
123 |
124 | TweenMax.ticker.addEventListener('tick', this.animate);
125 | document.addEventListener('keydown', this.onKeyDown);
126 | document.addEventListener('keyup', this.onKeyUp);
127 | window.addEventListener('resize', this.onResize);
128 | }
129 |
130 | initDat(){
131 | this.gui.add(this.params, 'text').onChange(this.restart);
132 | this.gui.add(this.params, 'fontSize').min(100).max(1000).step(10).onChange(this.restart);
133 | this.gui.add(this.params, 'speed').min(0).max(10.0).step(1).onChange(this.restart);
134 | this.gui.add(this.params, 'offset').min(0.0).max(100.0).step(1).onChange(this.restart);
135 | }
136 |
137 | onKeyDown(e){
138 | if(e.keyCode == 32){
139 | this.isKeydown = true;
140 | }
141 | }
142 |
143 | onKeyUp(e){
144 | if(e.keyCode == 32){
145 | this.isKeydown = false;
146 | }
147 | }
148 |
149 | onResize(e){
150 | this.camera.aspect = window.innerWidth / window.innerHeight;
151 | this.camera.updateProjectionMatrix();
152 | this.renderer.setSize(window.innerWidth, window.innerHeight);
153 | this.restart();
154 | }
155 |
156 | createText(text){
157 | this.canvas = document.createElement( 'canvas' );
158 |
159 | this.canvas.height = 2048;
160 | this.canvas.width = this.canvas.height*this.ratio;
161 | let context = this.canvas.getContext( '2d' );
162 |
163 | context.font = 'Bold '+ this.params.fontSize +'px Helvetica';
164 | context.fillStyle = 'white';
165 | let width = context.measureText(text).width;
166 | context.fillText(text, this.canvas.width/2 - width/2, this.canvas.height/2 + this.params.fontSize/(2*1.5));
167 | context.fill();
168 |
169 | return this.canvas;
170 | }
171 |
172 | animate(){
173 | let delta = this.clock.getDelta();
174 | this.cameraControls.update(delta);
175 | if(!this.isKeydown){
176 | this.uniforms.time.value += delta;
177 | }
178 | this.renderer.render(this.scene, this.camera);
179 | }
180 |
181 | }
182 |
183 | new App();
--------------------------------------------------------------------------------
/src/styles/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Corentinfardeau/three-js-flag/90ddf7649fa342d031ee47c98a8f807c29bd82d1/src/styles/.gitkeep
--------------------------------------------------------------------------------
/src/styles/styles.scss:
--------------------------------------------------------------------------------
1 | /* ------------------------------------------------------------------ */
2 | /* SCSS IMPORT
3 | /* ------------------------------------------------------------------ */
4 |
5 | *{
6 | margin: 0;
7 | padding: 0;
8 |
9 | font-family: sans-serif;
10 | font-size: 12px;
11 | }
12 |
13 | .back{
14 | color: white;
15 | position: absolute;
16 | bottom : 20px;
17 | left : 20px;
18 | }
--------------------------------------------------------------------------------
/src/vendors/TrackballControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Eberhard Graether / http://egraether.com/
3 | * @author Mark Lundin / http://mark-lundin.com
4 | * @author Simone Manini / http://daron1337.github.io
5 | * @author Luca Antiga / http://lantiga.github.io
6 | */
7 |
8 | THREE.TrackballControls = function ( object, domElement ) {
9 |
10 | var _this = this;
11 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
12 |
13 | this.object = object;
14 | this.domElement = ( domElement !== undefined ) ? domElement : document;
15 |
16 | // API
17 |
18 | this.enabled = true;
19 |
20 | this.screen = { left: 0, top: 0, width: 0, height: 0 };
21 |
22 | this.rotateSpeed = 1.0;
23 | this.zoomSpeed = 1.2;
24 | this.panSpeed = 0.3;
25 |
26 | this.noRotate = false;
27 | this.noZoom = false;
28 | this.noPan = false;
29 |
30 | this.staticMoving = false;
31 | this.dynamicDampingFactor = 0.2;
32 |
33 | this.minDistance = 0;
34 | this.maxDistance = Infinity;
35 |
36 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
37 |
38 | // internals
39 |
40 | this.target = new THREE.Vector3();
41 |
42 | var EPS = 0.000001;
43 |
44 | var lastPosition = new THREE.Vector3();
45 |
46 | var _state = STATE.NONE,
47 | _prevState = STATE.NONE,
48 |
49 | _eye = new THREE.Vector3(),
50 |
51 | _movePrev = new THREE.Vector2(),
52 | _moveCurr = new THREE.Vector2(),
53 |
54 | _lastAxis = new THREE.Vector3(),
55 | _lastAngle = 0,
56 |
57 | _zoomStart = new THREE.Vector2(),
58 | _zoomEnd = new THREE.Vector2(),
59 |
60 | _touchZoomDistanceStart = 0,
61 | _touchZoomDistanceEnd = 0,
62 |
63 | _panStart = new THREE.Vector2(),
64 | _panEnd = new THREE.Vector2();
65 |
66 | // for reset
67 |
68 | this.target0 = this.target.clone();
69 | this.position0 = this.object.position.clone();
70 | this.up0 = this.object.up.clone();
71 |
72 | // events
73 |
74 | var changeEvent = { type: 'change' };
75 | var startEvent = { type: 'start' };
76 | var endEvent = { type: 'end' };
77 |
78 |
79 | // methods
80 |
81 | this.handleResize = function () {
82 |
83 | if ( this.domElement === document ) {
84 |
85 | this.screen.left = 0;
86 | this.screen.top = 0;
87 | this.screen.width = window.innerWidth;
88 | this.screen.height = window.innerHeight;
89 |
90 | } else {
91 |
92 | var box = this.domElement.getBoundingClientRect();
93 | // adjustments come from similar code in the jquery offset() function
94 | var d = this.domElement.ownerDocument.documentElement;
95 | this.screen.left = box.left + window.pageXOffset - d.clientLeft;
96 | this.screen.top = box.top + window.pageYOffset - d.clientTop;
97 | this.screen.width = box.width;
98 | this.screen.height = box.height;
99 |
100 | }
101 |
102 | };
103 |
104 | this.handleEvent = function ( event ) {
105 |
106 | if ( typeof this[ event.type ] == 'function' ) {
107 |
108 | this[ event.type ]( event );
109 |
110 | }
111 |
112 | };
113 |
114 | var getMouseOnScreen = ( function () {
115 |
116 | var vector = new THREE.Vector2();
117 |
118 | return function getMouseOnScreen( pageX, pageY ) {
119 |
120 | vector.set(
121 | ( pageX - _this.screen.left ) / _this.screen.width,
122 | ( pageY - _this.screen.top ) / _this.screen.height
123 | );
124 |
125 | return vector;
126 |
127 | };
128 |
129 | }() );
130 |
131 | var getMouseOnCircle = ( function () {
132 |
133 | var vector = new THREE.Vector2();
134 |
135 | return function getMouseOnCircle( pageX, pageY ) {
136 |
137 | vector.set(
138 | ( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
139 | ( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
140 | );
141 |
142 | return vector;
143 |
144 | };
145 |
146 | }() );
147 |
148 | this.rotateCamera = ( function() {
149 |
150 | var axis = new THREE.Vector3(),
151 | quaternion = new THREE.Quaternion(),
152 | eyeDirection = new THREE.Vector3(),
153 | objectUpDirection = new THREE.Vector3(),
154 | objectSidewaysDirection = new THREE.Vector3(),
155 | moveDirection = new THREE.Vector3(),
156 | angle;
157 |
158 | return function rotateCamera() {
159 |
160 | moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
161 | angle = moveDirection.length();
162 |
163 | if ( angle ) {
164 |
165 | _eye.copy( _this.object.position ).sub( _this.target );
166 |
167 | eyeDirection.copy( _eye ).normalize();
168 | objectUpDirection.copy( _this.object.up ).normalize();
169 | objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
170 |
171 | objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
172 | objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
173 |
174 | moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
175 |
176 | axis.crossVectors( moveDirection, _eye ).normalize();
177 |
178 | angle *= _this.rotateSpeed;
179 | quaternion.setFromAxisAngle( axis, angle );
180 |
181 | _eye.applyQuaternion( quaternion );
182 | _this.object.up.applyQuaternion( quaternion );
183 |
184 | _lastAxis.copy( axis );
185 | _lastAngle = angle;
186 |
187 | } else if ( ! _this.staticMoving && _lastAngle ) {
188 |
189 | _lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor );
190 | _eye.copy( _this.object.position ).sub( _this.target );
191 | quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
192 | _eye.applyQuaternion( quaternion );
193 | _this.object.up.applyQuaternion( quaternion );
194 |
195 | }
196 |
197 | _movePrev.copy( _moveCurr );
198 |
199 | };
200 |
201 | }() );
202 |
203 |
204 | this.zoomCamera = function () {
205 |
206 | var factor;
207 |
208 | if ( _state === STATE.TOUCH_ZOOM_PAN ) {
209 |
210 | factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
211 | _touchZoomDistanceStart = _touchZoomDistanceEnd;
212 | _eye.multiplyScalar( factor );
213 |
214 | } else {
215 |
216 | factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
217 |
218 | if ( factor !== 1.0 && factor > 0.0 ) {
219 |
220 | _eye.multiplyScalar( factor );
221 |
222 | if ( _this.staticMoving ) {
223 |
224 | _zoomStart.copy( _zoomEnd );
225 |
226 | } else {
227 |
228 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
229 |
230 | }
231 |
232 | }
233 |
234 | }
235 |
236 | };
237 |
238 | this.panCamera = ( function() {
239 |
240 | var mouseChange = new THREE.Vector2(),
241 | objectUp = new THREE.Vector3(),
242 | pan = new THREE.Vector3();
243 |
244 | return function panCamera() {
245 |
246 | mouseChange.copy( _panEnd ).sub( _panStart );
247 |
248 | if ( mouseChange.lengthSq() ) {
249 |
250 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
251 |
252 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
253 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
254 |
255 | _this.object.position.add( pan );
256 | _this.target.add( pan );
257 |
258 | if ( _this.staticMoving ) {
259 |
260 | _panStart.copy( _panEnd );
261 |
262 | } else {
263 |
264 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
265 |
266 | }
267 |
268 | }
269 |
270 | };
271 |
272 | }() );
273 |
274 | this.checkDistances = function () {
275 |
276 | if ( ! _this.noZoom || ! _this.noPan ) {
277 |
278 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
279 |
280 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
281 | _zoomStart.copy( _zoomEnd );
282 |
283 | }
284 |
285 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
286 |
287 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
288 | _zoomStart.copy( _zoomEnd );
289 |
290 | }
291 |
292 | }
293 |
294 | };
295 |
296 | this.update = function () {
297 |
298 | _eye.subVectors( _this.object.position, _this.target );
299 |
300 | if ( ! _this.noRotate ) {
301 |
302 | _this.rotateCamera();
303 |
304 | }
305 |
306 | if ( ! _this.noZoom ) {
307 |
308 | _this.zoomCamera();
309 |
310 | }
311 |
312 | if ( ! _this.noPan ) {
313 |
314 | _this.panCamera();
315 |
316 | }
317 |
318 | _this.object.position.addVectors( _this.target, _eye );
319 |
320 | _this.checkDistances();
321 |
322 | _this.object.lookAt( _this.target );
323 |
324 | if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) {
325 |
326 | _this.dispatchEvent( changeEvent );
327 |
328 | lastPosition.copy( _this.object.position );
329 |
330 | }
331 |
332 | };
333 |
334 | this.reset = function () {
335 |
336 | _state = STATE.NONE;
337 | _prevState = STATE.NONE;
338 |
339 | _this.target.copy( _this.target0 );
340 | _this.object.position.copy( _this.position0 );
341 | _this.object.up.copy( _this.up0 );
342 |
343 | _eye.subVectors( _this.object.position, _this.target );
344 |
345 | _this.object.lookAt( _this.target );
346 |
347 | _this.dispatchEvent( changeEvent );
348 |
349 | lastPosition.copy( _this.object.position );
350 |
351 | };
352 |
353 | // listeners
354 |
355 | function keydown( event ) {
356 |
357 | if ( _this.enabled === false ) return;
358 |
359 | window.removeEventListener( 'keydown', keydown );
360 |
361 | _prevState = _state;
362 |
363 | if ( _state !== STATE.NONE ) {
364 |
365 | return;
366 |
367 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) {
368 |
369 | _state = STATE.ROTATE;
370 |
371 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) {
372 |
373 | _state = STATE.ZOOM;
374 |
375 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) {
376 |
377 | _state = STATE.PAN;
378 |
379 | }
380 |
381 | }
382 |
383 | function keyup( event ) {
384 |
385 | if ( _this.enabled === false ) return;
386 |
387 | _state = _prevState;
388 |
389 | window.addEventListener( 'keydown', keydown, false );
390 |
391 | }
392 |
393 | function mousedown( event ) {
394 |
395 | if ( _this.enabled === false ) return;
396 |
397 | event.preventDefault();
398 | event.stopPropagation();
399 |
400 | if ( _state === STATE.NONE ) {
401 |
402 | _state = event.button;
403 |
404 | }
405 |
406 | if ( _state === STATE.ROTATE && ! _this.noRotate ) {
407 |
408 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
409 | _movePrev.copy( _moveCurr );
410 |
411 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
412 |
413 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
414 | _zoomEnd.copy( _zoomStart );
415 |
416 | } else if ( _state === STATE.PAN && ! _this.noPan ) {
417 |
418 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
419 | _panEnd.copy( _panStart );
420 |
421 | }
422 |
423 | document.addEventListener( 'mousemove', mousemove, false );
424 | document.addEventListener( 'mouseup', mouseup, false );
425 |
426 | _this.dispatchEvent( startEvent );
427 |
428 | }
429 |
430 | function mousemove( event ) {
431 |
432 | if ( _this.enabled === false ) return;
433 |
434 | event.preventDefault();
435 | event.stopPropagation();
436 |
437 | if ( _state === STATE.ROTATE && ! _this.noRotate ) {
438 |
439 | _movePrev.copy( _moveCurr );
440 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
441 |
442 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
443 |
444 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
445 |
446 | } else if ( _state === STATE.PAN && ! _this.noPan ) {
447 |
448 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
449 |
450 | }
451 |
452 | }
453 |
454 | function mouseup( event ) {
455 |
456 | if ( _this.enabled === false ) return;
457 |
458 | event.preventDefault();
459 | event.stopPropagation();
460 |
461 | _state = STATE.NONE;
462 |
463 | document.removeEventListener( 'mousemove', mousemove );
464 | document.removeEventListener( 'mouseup', mouseup );
465 | _this.dispatchEvent( endEvent );
466 |
467 | }
468 |
469 | function mousewheel( event ) {
470 |
471 | if ( _this.enabled === false ) return;
472 |
473 | event.preventDefault();
474 | event.stopPropagation();
475 |
476 | var delta = 0;
477 |
478 | if ( event.wheelDelta ) {
479 |
480 | // WebKit / Opera / Explorer 9
481 |
482 | delta = event.wheelDelta / 40;
483 |
484 | } else if ( event.detail ) {
485 |
486 | // Firefox
487 |
488 | delta = - event.detail / 3;
489 |
490 | }
491 |
492 | _zoomStart.y += delta * 0.01;
493 | _this.dispatchEvent( startEvent );
494 | _this.dispatchEvent( endEvent );
495 |
496 | }
497 |
498 | function touchstart( event ) {
499 |
500 | if ( _this.enabled === false ) return;
501 |
502 | switch ( event.touches.length ) {
503 |
504 | case 1:
505 | _state = STATE.TOUCH_ROTATE;
506 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
507 | _movePrev.copy( _moveCurr );
508 | break;
509 |
510 | case 2:
511 | _state = STATE.TOUCH_ZOOM_PAN;
512 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
513 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
514 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
515 |
516 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
517 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
518 | _panStart.copy( getMouseOnScreen( x, y ) );
519 | _panEnd.copy( _panStart );
520 | break;
521 |
522 | default:
523 | _state = STATE.NONE;
524 |
525 | }
526 | _this.dispatchEvent( startEvent );
527 |
528 |
529 | }
530 |
531 | function touchmove( event ) {
532 |
533 | if ( _this.enabled === false ) return;
534 |
535 | event.preventDefault();
536 | event.stopPropagation();
537 |
538 | switch ( event.touches.length ) {
539 |
540 | case 1:
541 | _movePrev.copy( _moveCurr );
542 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
543 | break;
544 |
545 | case 2:
546 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
547 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
548 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
549 |
550 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
551 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
552 | _panEnd.copy( getMouseOnScreen( x, y ) );
553 | break;
554 |
555 | default:
556 | _state = STATE.NONE;
557 |
558 | }
559 |
560 | }
561 |
562 | function touchend( event ) {
563 |
564 | if ( _this.enabled === false ) return;
565 |
566 | switch ( event.touches.length ) {
567 |
568 | case 1:
569 | _movePrev.copy( _moveCurr );
570 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
571 | break;
572 |
573 | case 2:
574 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
575 |
576 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
577 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
578 | _panEnd.copy( getMouseOnScreen( x, y ) );
579 | _panStart.copy( _panEnd );
580 | break;
581 |
582 | }
583 |
584 | _state = STATE.NONE;
585 | _this.dispatchEvent( endEvent );
586 |
587 | }
588 |
589 | function contextmenu( event ) {
590 |
591 | event.preventDefault();
592 |
593 | }
594 |
595 | this.dispose = function() {
596 |
597 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
598 | this.domElement.removeEventListener( 'mousedown', mousedown, false );
599 | this.domElement.removeEventListener( 'mousewheel', mousewheel, false );
600 | this.domElement.removeEventListener( 'MozMousePixelScroll', mousewheel, false ); // firefox
601 |
602 | this.domElement.removeEventListener( 'touchstart', touchstart, false );
603 | this.domElement.removeEventListener( 'touchend', touchend, false );
604 | this.domElement.removeEventListener( 'touchmove', touchmove, false );
605 |
606 | document.removeEventListener( 'mousemove', mousemove, false );
607 | document.removeEventListener( 'mouseup', mouseup, false );
608 |
609 | window.removeEventListener( 'keydown', keydown, false );
610 | window.removeEventListener( 'keyup', keyup, false );
611 |
612 | }
613 |
614 | this.domElement.addEventListener( 'contextmenu', contextmenu, false );
615 | this.domElement.addEventListener( 'mousedown', mousedown, false );
616 | this.domElement.addEventListener( 'mousewheel', mousewheel, false );
617 | this.domElement.addEventListener( 'MozMousePixelScroll', mousewheel, false ); // firefox
618 |
619 | this.domElement.addEventListener( 'touchstart', touchstart, false );
620 | this.domElement.addEventListener( 'touchend', touchend, false );
621 | this.domElement.addEventListener( 'touchmove', touchmove, false );
622 |
623 | window.addEventListener( 'keydown', keydown, false );
624 | window.addEventListener( 'keyup', keyup, false );
625 |
626 | this.handleResize();
627 |
628 | // force an update at start
629 | this.update();
630 |
631 | };
632 |
633 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
634 | THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;
--------------------------------------------------------------------------------