Initial commit

This commit is contained in:
Paul Graffam 2016-09-12 15:54:07 -04:00
commit af6d73cca1
41 changed files with 5192 additions and 0 deletions

22
.eslintrc Normal file
View File

@ -0,0 +1,22 @@
{
"rules": {
"quotes": [
2,
"single"
],
"semi": [
2,
"always"
],
"no-unused-vars" : 2,
"no-undef" : 2
},
"env": {
"es6": true,
"browser": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
}
}

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules
*.log
.DS_Store
.idea
Iconr

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# Three.js Webpack ES6 Boilerplate
A basic boilerplate for a Three.js project including the use of Webpack and ES6 syntax via Babel.
## Getting started
```
npm install
```
Then
```
npm run dev
```
Spins up a webpack dev server at localhost:8080 and keeps track of all js and sass changes to files and reloads upon save.
## Build
```
npm run build
```
Cleans existing build folder while linting js and copies over the public folder from src. Then sets environment to production and compiles js and css into build.

View File

@ -0,0 +1 @@
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.main{width:100%;height:100vh}

View File

@ -0,0 +1,73 @@
.rs-base{
position: absolute;
z-index: 10000;
padding: 10px;
background-color: #222;
font-size: 10px;
line-height: 1.2em;
width: 350px;
font-family: 'Roboto Condensed', tahoma, sans-serif;
left: 0;
top: 0;
overflow: hidden;
}
.rs-base h1{
margin: 0;
padding: 0;
font-size: 1.4em;
color: #fff;
margin-bottom: 5px;
cursor: pointer;
}
.rs-base div.rs-group{
margin-bottom: 10px;
}
.rs-base div.rs-group.hidden{
display: none;
}
.rs-base div.rs-fraction{
position: relative;
margin-bottom: 5px;
}
.rs-base div.rs-fraction p{
width: 120px;
text-align: right;
margin: 0;
padding: 0;
}
.rs-base div.rs-legend{
position: absolute;
line-height: 1em;
}
.rs-base div.rs-counter-base{
position: relative;
margin: 2px 0;
height: 1em;
}
.rs-base span.rs-counter-id{
position: absolute;
left: 0;
top: 0;
}
.rs-base div.rs-counter-value{
position: absolute;
left: 90px;
width: 30px;
height: 1em;
top: 0;
text-align: right;
}
.rs-base canvas.rs-canvas{
position: absolute;
right: 0;
}

File diff suppressed because one or more lines are too long

95
build/public/assets/js/dat.gui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,719 @@
// performance.now() polyfill from https://gist.github.com/paulirish/5438650
'use strict';
( function () {
// prepare base perf object
if ( typeof window.performance === 'undefined' ) {
window.performance = {};
}
if ( !window.performance.now ) {
var nowOffset = Date.now();
if ( performance.timing && performance.timing.navigationStart ) {
nowOffset = performance.timing.navigationStart;
}
window.performance.now = function now () {
return Date.now() - nowOffset;
};
}
if( !window.performance.mark ) {
window.performance.mark = function(){}
}
if( !window.performance.measure ) {
window.performance.measure = function(){}
}
} )();
window.rStats = function rStats ( settings ) {
function iterateKeys ( array, callback ) {
var keys = Object.keys( array );
for ( var j = 0, l = keys.length; j < l; j++ ) {
callback( keys[ j ] );
}
}
function importCSS ( url ) {
var element = document.createElement( 'link' );
element.href = url;
element.rel = 'stylesheet';
element.type = 'text/css';
document.getElementsByTagName( 'head' )[ 0 ].appendChild( element );
}
var _settings = settings || {};
var _colours = _settings.colours || [ '#850700', '#c74900', '#fcb300', '#284280', '#4c7c0c' ];
var _cssFont = 'https://fonts.googleapis.com/css?family=Roboto+Condensed:400,700,300';
var _cssRStats = ( _settings.CSSPath ? _settings.CSSPath : '' ) + 'rStats.css';
var _css = _settings.css || [ _cssFont, _cssRStats ];
_css.forEach(function (uri) {
importCSS( uri );
});
if ( !_settings.values ) _settings.values = {};
var _base, _div, _elHeight = 10, _elWidth = 200;
var _perfCounters = {};
function Graph ( _dom, _id, _defArg ) {
var _def = _defArg || {};
var _canvas = document.createElement( 'canvas' ),
_ctx = _canvas.getContext( '2d' ),
_max = 0,
_current = 0;
var c = _def.color ? _def.color : '#666666';
var _dotCanvas = document.createElement( 'canvas' ),
_dotCtx = _dotCanvas.getContext( '2d' );
_dotCanvas.width = 1;
_dotCanvas.height = 2 * _elHeight;
_dotCtx.fillStyle = '#444444';
_dotCtx.fillRect( 0, 0, 1, 2 * _elHeight );
_dotCtx.fillStyle = c;
_dotCtx.fillRect( 0, _elHeight, 1, _elHeight );
_dotCtx.fillStyle = '#ffffff';
_dotCtx.globalAlpha = 0.5;
_dotCtx.fillRect( 0, _elHeight, 1, 1 );
_dotCtx.globalAlpha = 1;
var _alarmCanvas = document.createElement( 'canvas' ),
_alarmCtx = _alarmCanvas.getContext( '2d' );
_alarmCanvas.width = 1;
_alarmCanvas.height = 2 * _elHeight;
_alarmCtx.fillStyle = '#444444';
_alarmCtx.fillRect( 0, 0, 1, 2 * _elHeight );
_alarmCtx.fillStyle = '#b70000';
_alarmCtx.fillRect( 0, _elHeight, 1, _elHeight );
_alarmCtx.globalAlpha = 0.5;
_alarmCtx.fillStyle = '#ffffff';
_alarmCtx.fillRect( 0, _elHeight, 1, 1 );
_alarmCtx.globalAlpha = 1;
function _init () {
_canvas.width = _elWidth;
_canvas.height = _elHeight;
_canvas.style.width = _canvas.width + 'px';
_canvas.style.height = _canvas.height + 'px';
_canvas.className = 'rs-canvas';
_dom.appendChild( _canvas );
_ctx.fillStyle = '#444444';
_ctx.fillRect( 0, 0, _canvas.width, _canvas.height );
}
function _draw ( v, alarm ) {
_current += ( v - _current ) * 0.1;
_max *= 0.99;
if ( _current > _max ) _max = _current;
_ctx.drawImage( _canvas, 1, 0, _canvas.width - 1, _canvas.height, 0, 0, _canvas.width - 1, _canvas.height );
if ( alarm ) {
_ctx.drawImage( _alarmCanvas, _canvas.width - 1, _canvas.height - _current * _canvas.height / _max - _elHeight );
} else {
_ctx.drawImage( _dotCanvas, _canvas.width - 1, _canvas.height - _current * _canvas.height / _max - _elHeight );
}
}
_init();
return {
draw: _draw
};
}
function StackGraph ( _dom, _num ) {
var _canvas = document.createElement( 'canvas' ),
_ctx = _canvas.getContext( '2d' );
function _init () {
_canvas.width = _elWidth;
_canvas.height = _elHeight * _num;
_canvas.style.width = _canvas.width + 'px';
_canvas.style.height = _canvas.height + 'px';
_canvas.className = 'rs-canvas';
_dom.appendChild( _canvas );
_ctx.fillStyle = '#444444';
_ctx.fillRect( 0, 0, _canvas.width, _canvas.height );
}
function _draw ( v ) {
_ctx.drawImage( _canvas, 1, 0, _canvas.width - 1, _canvas.height, 0, 0, _canvas.width - 1, _canvas.height );
var th = 0;
iterateKeys( v, function ( j ) {
var h = v[ j ] * _canvas.height;
_ctx.fillStyle = _colours[ j ];
_ctx.fillRect( _canvas.width - 1, th, 1, h );
th += h;
} );
}
_init();
return {
draw: _draw
};
}
function PerfCounter ( id, group ) {
var _id = id,
_time,
_value = 0,
_total = 0,
_averageValue = 0,
_accumValue = 0,
_accumStart = performance.now(),
_accumSamples = 0,
_dom = document.createElement( 'div' ),
_spanId = document.createElement( 'span' ),
_spanValue = document.createElement( 'div' ),
_spanValueText = document.createTextNode( '' ),
_def = _settings ? _settings.values[ _id.toLowerCase() ] : null,
_graph = new Graph( _dom, _id, _def ),
_started = false;
_dom.className = 'rs-counter-base';
_spanId.className = 'rs-counter-id';
_spanId.textContent = ( _def && _def.caption ) ? _def.caption : _id;
_spanValue.className = 'rs-counter-value';
_spanValue.appendChild( _spanValueText );
_dom.appendChild( _spanId );
_dom.appendChild( _spanValue );
if ( group ) group.div.appendChild( _dom );
else _div.appendChild( _dom );
_time = performance.now();
function _average ( v ) {
if ( _def && _def.average ) {
_accumValue += v;
_accumSamples++;
var t = performance.now();
if ( t - _accumStart >= ( _def.avgMs || 1000 ) ) {
_averageValue = _accumValue / _accumSamples;
_accumValue = 0;
_accumStart = t;
_accumSamples = 0;
}
}
}
function _start () {
_time = performance.now();
if( _settings.userTimingAPI ) performance.mark( _id + '-start' );
_started = true;
}
function _end () {
_value = performance.now() - _time;
if( _settings.userTimingAPI ) {
performance.mark( _id + '-end' );
if( _started ) {
performance.measure( _id, _id + '-start', _id + '-end' );
}
}
_average( _value );
}
function _tick () {
_end();
_start();
}
function _draw () {
var v = ( _def && _def.average ) ? _averageValue : _value;
_spanValueText.nodeValue = Math.round( v * 100 ) / 100;
var a = ( _def && ( ( _def.below && _value < _def.below ) || ( _def.over && _value > _def.over ) ) );
_graph.draw( _value, a );
_dom.style.color = a ? '#b70000' : '#ffffff';
}
function _frame () {
var t = performance.now();
var e = t - _time;
_total++;
if ( e > 1000 ) {
if ( _def && _def.interpolate === false ) {
_value = _total;
} else {
_value = _total * 1000 / e;
}
_total = 0;
_time = t;
_average( _value );
}
}
function _set ( v ) {
_value = v;
_average( _value );
}
return {
set: _set,
start: _start,
tick: _tick,
end: _end,
frame: _frame,
value: function () {
return _value;
},
draw: _draw
};
}
function sample () {
var _value = 0;
function _set ( v ) {
_value = v;
}
return {
set: _set,
value: function () {
return _value;
}
};
}
function _perf ( idArg ) {
var id = idArg.toLowerCase();
if ( id === undefined ) id = 'default';
if ( _perfCounters[ id ] ) return _perfCounters[ id ];
var group = null;
if ( _settings && _settings.groups ) {
iterateKeys( _settings.groups, function ( j ) {
var g = _settings.groups[ parseInt( j, 10 ) ];
if ( !group && g.values.indexOf( id.toLowerCase() ) !== -1 ) {
group = g;
}
} );
}
var p = new PerfCounter( id, group );
_perfCounters[ id ] = p;
return p;
}
function _init () {
if ( _settings.plugins ) {
if ( !_settings.values ) _settings.values = {};
if ( !_settings.groups ) _settings.groups = [];
if ( !_settings.fractions ) _settings.fractions = [];
for ( var j = 0; j < _settings.plugins.length; j++ ) {
_settings.plugins[ j ].attach( _perf );
iterateKeys( _settings.plugins[ j ].values, function ( k ) {
_settings.values[ k ] = _settings.plugins[ j ].values[ k ];
} );
_settings.groups = _settings.groups.concat( _settings.plugins[ j ].groups );
_settings.fractions = _settings.fractions.concat( _settings.plugins[ j ].fractions );
}
} else {
_settings.plugins = {};
}
_base = document.createElement( 'div' );
_base.className = 'rs-base';
_div = document.createElement( 'div' );
_div.className = 'rs-container';
_div.style.height = 'auto';
_base.appendChild( _div );
document.body.appendChild( _base );
if ( !_settings ) return;
if ( _settings.groups ) {
iterateKeys( _settings.groups, function ( j ) {
var g = _settings.groups[ parseInt( j, 10 ) ];
var div = document.createElement( 'div' );
div.className = 'rs-group';
g.div = div;
var h1 = document.createElement( 'h1' );
h1.textContent = g.caption;
h1.addEventListener( 'click', function ( e ) {
this.classList.toggle( 'hidden' );
e.preventDefault();
}.bind( div ) );
_div.appendChild( h1 );
_div.appendChild( div );
} );
}
if ( _settings.fractions ) {
iterateKeys( _settings.fractions, function ( j ) {
var f = _settings.fractions[ parseInt( j, 10 ) ];
var div = document.createElement( 'div' );
div.className = 'rs-fraction';
var legend = document.createElement( 'div' );
legend.className = 'rs-legend';
var h = 0;
iterateKeys( _settings.fractions[ j ].steps, function ( k ) {
var p = document.createElement( 'p' );
p.textContent = _settings.fractions[ j ].steps[ k ];
p.style.color = _colours[ h ];
legend.appendChild( p );
h++;
} );
div.appendChild( legend );
div.style.height = h * _elHeight + 'px';
f.div = div;
var graph = new StackGraph( div, h );
f.graph = graph;
_div.appendChild( div );
} );
}
}
function _update () {
iterateKeys( _settings.plugins, function ( j ) {
_settings.plugins[ j ].update();
} );
iterateKeys( _perfCounters, function ( j ) {
_perfCounters[ j ].draw();
} );
if ( _settings && _settings.fractions ) {
iterateKeys( _settings.fractions, function ( j ) {
var f = _settings.fractions[ parseInt( j, 10 ) ];
var v = [];
var base = _perfCounters[ f.base.toLowerCase() ];
if ( base ) {
base = base.value();
iterateKeys( _settings.fractions[ j ].steps, function ( k ) {
var s = _settings.fractions[ j ].steps[ parseInt( k, 10 ) ].toLowerCase();
var val = _perfCounters[ s ];
if ( val ) {
v.push( val.value() / base );
}
} );
}
f.graph.draw( v );
} );
}
/*if( _height != _div.clientHeight ) {
_height = _div.clientHeight;
_base.style.height = _height + 2 * _elHeight + 'px';
console.log( _base.clientHeight );
}*/
}
_init();
return function ( id ) {
if ( id ) return _perf( id );
return {
element: _base,
update: _update
};
};
}
if (typeof module === 'object') {
module.exports = window.rStats;
}
window.glStats = function () {
var _rS = null;
var _totalDrawArraysCalls = 0,
_totalDrawElementsCalls = 0,
_totalUseProgramCalls = 0,
_totalFaces = 0,
_totalVertices = 0,
_totalPoints = 0,
_totalBindTexures = 0;
function _h ( f, c ) {
return function () {
c.apply( this, arguments );
f.apply( this, arguments );
};
}
WebGLRenderingContext.prototype.drawArrays = _h( WebGLRenderingContext.prototype.drawArrays, function () {
_totalDrawArraysCalls++;
if ( arguments[ 0 ] == this.POINTS ) _totalPoints += arguments[ 2 ];
else _totalVertices += arguments[ 2 ];
} );
WebGLRenderingContext.prototype.drawElements = _h( WebGLRenderingContext.prototype.drawElements, function () {
_totalDrawElementsCalls++;
_totalFaces += arguments[ 1 ] / 3;
_totalVertices += arguments[ 1 ];
} );
WebGLRenderingContext.prototype.useProgram = _h( WebGLRenderingContext.prototype.useProgram, function () {
_totalUseProgramCalls++;
} );
WebGLRenderingContext.prototype.bindTexture = _h( WebGLRenderingContext.prototype.bindTexture, function () {
_totalBindTexures++;
} );
var _values = {
allcalls: {
over: 3000,
caption: 'Calls (hook)'
},
drawelements: {
caption: 'drawElements (hook)'
},
drawarrays: {
caption: 'drawArrays (hook)'
}
};
var _groups = [ {
caption: 'WebGL',
values: [ 'allcalls', 'drawelements', 'drawarrays', 'useprogram', 'bindtexture', 'glfaces', 'glvertices', 'glpoints' ]
} ];
var _fractions = [ {
base: 'allcalls',
steps: [ 'drawelements', 'drawarrays' ]
} ];
function _update () {
_rS( 'allcalls' ).set( _totalDrawArraysCalls + _totalDrawElementsCalls );
_rS( 'drawElements' ).set( _totalDrawElementsCalls );
_rS( 'drawArrays' ).set( _totalDrawArraysCalls );
_rS( 'bindTexture' ).set( _totalBindTexures );
_rS( 'useProgram' ).set( _totalUseProgramCalls );
_rS( 'glfaces' ).set( _totalFaces );
_rS( 'glvertices' ).set( _totalVertices );
_rS( 'glpoints' ).set( _totalPoints );
}
function _start () {
_totalDrawArraysCalls = 0;
_totalDrawElementsCalls = 0;
_totalUseProgramCalls = 0;
_totalFaces = 0;
_totalVertices = 0;
_totalPoints = 0;
_totalBindTexures = 0;
}
function _end () {}
function _attach ( r ) {
_rS = r;
}
return {
update: _update,
start: _start,
end: _end,
attach: _attach,
values: _values,
groups: _groups,
fractions: _fractions
};
};
window.threeStats = function ( renderer ) {
var _rS = null;
var _values = {
'renderer.info.memory.geometries': {
caption: 'Geometries'
},
'renderer.info.memory.textures': {
caption: 'Textures'
},
'renderer.info.programs': {
caption: 'Programs'
},
'renderer.info.render.calls': {
caption: 'Calls'
},
'renderer.info.render.faces': {
caption: 'Faces',
over: 1000
},
'renderer.info.render.points': {
caption: 'Points'
},
'renderer.info.render.vertices': {
caption: 'Vertices'
}
};
var _groups = [ {
caption: 'Three.js - Memory',
values: [ 'renderer.info.memory.geometries', 'renderer.info.programs', 'renderer.info.memory.textures' ]
}, {
caption: 'Three.js - Render',
values: [ 'renderer.info.render.calls', 'renderer.info.render.faces', 'renderer.info.render.points', 'renderer.info.render.vertices' ]
} ];
var _fractions = [];
function _update () {
_rS( 'renderer.info.memory.geometries' ).set( renderer.info.memory.geometries );
//_rS( 'renderer.info.programs' ).set( renderer.info.programs.length );
_rS( 'renderer.info.memory.textures' ).set( renderer.info.memory.textures );
_rS( 'renderer.info.render.calls' ).set( renderer.info.render.calls );
_rS( 'renderer.info.render.faces' ).set( renderer.info.render.faces );
_rS( 'renderer.info.render.points' ).set( renderer.info.render.points );
_rS( 'renderer.info.render.vertices' ).set( renderer.info.render.vertices );
}
function _start () {}
function _end () {}
function _attach ( r ) {
_rS = r;
}
return {
update: _update,
start: _start,
end: _end,
attach: _attach,
values: _values,
groups: _groups,
fractions: _fractions
};
};
/*
* From https://github.com/paulirish/memory-stats.js
*/
window.BrowserStats = function () {
var _rS = null;
var _usedJSHeapSize = 0,
_totalJSHeapSize = 0;
var memory = {
usedJSHeapSize: 0,
totalJSHeapSize: 0
};
if ( window.performance && performance.memory )
memory = performance.memory;
if ( memory.totalJSHeapSize === 0 ) {
console.warn( 'totalJSHeapSize === 0... performance.memory is only available in Chrome .' );
}
var _values = {
memory: {
caption: 'Used Memory',
average: true,
avgMs: 1000,
over: 22
},
total: {
caption: 'Total Memory'
}
};
var _groups = [ {
caption: 'Browser',
values: [ 'memory', 'total' ]
} ];
var _fractions = [ {
base: 'total',
steps: [ 'memory' ]
} ];
var log1024 = Math.log( 1024 );
function _size ( v ) {
var precision = 100; //Math.pow(10, 2);
var i = Math.floor( Math.log( v ) / log1024 );
if( v === 0 ) i = 1;
return Math.round( v * precision / Math.pow( 1024, i ) ) / precision; // + ' ' + sizes[i];
}
function _update () {
_usedJSHeapSize = _size( memory.usedJSHeapSize );
_totalJSHeapSize = _size( memory.totalJSHeapSize );
_rS( 'memory' ).set( _usedJSHeapSize );
_rS( 'total' ).set( _totalJSHeapSize );
}
function _start () {
_usedJSHeapSize = 0;
}
function _end () {}
function _attach ( r ) {
_rS = r;
}
return {
update: _update,
start: _start,
end: _end,
attach: _attach,
values: _values,
groups: _groups,
fractions: _fractions
};
};
if (typeof module === 'object') {
module.exports = {
glStats: window.glStats,
threeStats: window.threeStats,
BrowserStats: window.BrowserStats
};
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

19
build/public/index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Three.js Webpack ES6 Boilerplate</title>
<link rel="stylesheet" type="text/css" href="/assets/css/app.css">
</head>
<body>
<div class="content">
<div id="appContainer" class="main">
</div>
</div>
<script src="/assets/js/rStats.js"></script>
<script src="/assets/js/dat.gui.min.js"></script>
<script src="/assets/js/app.js"></script>
</body>
</html>

44
package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "threejs-es6-webpack-boilerplate",
"version": "1.0.0",
"description": "Boilerplate for Three.js projects set up with Babel for ES6 and compiled with webpack",
"author": "Paul Graffam",
"main": "app.js",
"scripts": {
"dev": "run-p dev:sass webpack-server webpack-watch",
"build": "run-s prebuild build:dir build:js build:sass",
"prebuild": "run-p clean lint",
"clean": "rimraf build",
"lint": "eslint src/js/; exit 0",
"webpack-server": "set NODE_ENV=0&& webpack-dev-server --hot --inline --open",
"webpack-watch": "set NODE_ENV=0&& webpack --progress --colors --watch --cache",
"dev:sass": "node-sass -w src/css/app.scss -o src/public/assets/css/",
"dev:js": "set NODE_ENV=0&& webpack",
"build:dir": "copyfiles -u 1 src/public/**/* build/",
"build:sass": "node-sass --output-style compressed src/css/ -o build/public/assets/css/",
"build:js": "set NODE_ENV=1&& webpack"
},
"dependencies": {
"es6-promise": "^3.2.1",
"normalize.css": "^4.2.0",
"three": "^0.79.0",
"tween.js": "16.2.0"
},
"devDependencies": {
"babel-core": "^6.13.2",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.13.2",
"copyfiles": "^1.0.0",
"eslint": "^3.4.0",
"file-loader": "^0.9.0",
"node-sass": "^3.8.0",
"npm-run-all": "^3.0.0",
"rimraf": "^2.5.4",
"webpack": "^1.13.2",
"webpack-dev-middleware": "^1.6.1",
"webpack-dev-server": "^1.15.0"
},
"engines": {
"node": "5.0.0"
}
}

585
src/css/app.scss Normal file
View File

@ -0,0 +1,585 @@
/* ==========================================================================
Normalize.scss settings
========================================================================== */
/**
* Includes legacy browser support IE6/7
*
* Set to false if you want to drop support for IE6 and IE7
*/
$legacy_browser_support: false !default;
/* Base
========================================================================== */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS and IE text size adjust after device orientation change,
* without disabling user zoom.
* 3. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using
* `em` units.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
@if $legacy_browser_support {
*font-size: 100%; /* 3 */
}
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
@if $legacy_browser_support {
*display: inline;
*zoom: 1;
}
}
/**
* Prevents modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability of focused elements when they are also in an
* active/hover state.
*/
a {
&:active, &:hover {
outline: 0;
};
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
@if $legacy_browser_support {
blockquote {
margin: 1em 40px;
}
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
@if $legacy_browser_support {
h2 {
font-size: 1.5em;
margin: 0.83em 0;
}
h3 {
font-size: 1.17em;
margin: 1em 0;
}
h4 {
font-size: 1em;
margin: 1.33em 0;
}
h5 {
font-size: 0.83em;
margin: 1.67em 0;
}
h6 {
font-size: 0.67em;
margin: 2.33em 0;
}
}
/**
* Addresses styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
@if $legacy_browser_support {
/**
* Addresses margins set differently in IE 6/7.
*/
p,
pre {
*margin: 1em 0;
}
/*
* Addresses CSS quotes not supported in IE 6/7.
*/
q {
*quotes: none;
}
/*
* Addresses `quotes` property not supported in Safari 4.
*/
q:before,
q:after {
content: '';
content: none;
}
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
@if $legacy_browser_support {
/* ==========================================================================
Lists
========================================================================== */
/*
* Addresses margins set differently in IE 6/7.
*/
dl,
menu,
ol,
ul {
*margin: 1em 0;
}
dd {
*margin: 0 0 0 40px;
}
/*
* Addresses paddings set differently in IE 6/7.
*/
menu,
ol,
ul {
*padding: 0 0 0 40px;
}
/*
* Corrects list images handled incorrectly in IE 7.
*/
nav ul,
nav ol {
*list-style: none;
*list-style-image: none;
}
}
/* Embedded content
========================================================================== */
/**
* 1. Remove border when inside `a` element in IE 8/9/10.
* 2. Improves image quality when scaled in IE 7.
*/
img {
border: 0;
@if $legacy_browser_support {
*-ms-interpolation-mode: bicubic; /* 2 */
}
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
* Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
@if $legacy_browser_support {
_font-family: 'courier new', monospace;
}
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
* 4. Improves appearance and consistency in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
@if $legacy_browser_support {
vertical-align: baseline; /* 3 */
*vertical-align: middle; /* 3 */
}
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
* 4. Removes inner spacing in IE 7 without affecting normal text inputs.
* Known issue: inner spacing remains in IE 6.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
@if $legacy_browser_support {
*overflow: visible; /* 4 */
}
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
* Known issue: excess padding remains in IE 6.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
@if $legacy_browser_support {
*height: 13px; /* 3 */
*width: 13px; /* 3 */
}
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
box-sizing: content-box; /* 2 */
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
* 3. Corrects text not wrapping in Firefox 3.
* 4. Corrects alignment displayed oddly in IE 6/7.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
@if $legacy_browser_support {
white-space: normal; /* 3 */
*margin-left: -7px; /* 4 */
}
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
.main {
width: 100%;
height: 100vh;
}

21
src/js/app.js Normal file
View File

@ -0,0 +1,21 @@
import Config from './data/config';
import Detector from './utils/detector';
import Main from './app/main';
// verify environment.
if(__ENV__ == 'dev') {
console.log('----- RUNNING IN DEV ENVIRONMENT! -----');
Config.isDev = true;
}
function init() {
if(!Detector.webgl) {
Detector.addGetWebGLMessage();
} else {
const container = document.getElementById('appContainer');
new Main(container);
}
}
window.onload = init;

23
src/js/app/animation.js Normal file
View File

@ -0,0 +1,23 @@
import THREE from 'three';
export default class Animation {
constructor(obj, clip) {
this.obj = obj;
this.mixer = new THREE.AnimationMixer(this.obj);
this.playClip(clip);
}
playClip(clip) {
this.action = this.mixer.clipAction(clip);
this.action.play();
}
update(delta) {
if(this.mixer) {
this.mixer.update(delta);
}
}
}

23
src/js/app/camera.js Normal file
View File

@ -0,0 +1,23 @@
import THREE from 'three';
import Config from './../data/config';
export default class Camera {
constructor(renderer) {
const width = renderer.domElement.width;
const height = renderer.domElement.height;
this.threeCamera = new THREE.PerspectiveCamera(Config.camera.fov, width / height, Config.camera.near, Config.camera.far);
this.threeCamera.position.set(Config.camera.posX, Config.camera.posY, Config.camera.posZ);
this.updateSize(renderer);
// listeners
window.addEventListener('resize', () => this.updateSize(renderer), false);
}
updateSize(renderer) {
this.threeCamera.aspect = (renderer.domElement.width * Config.dpr) / (renderer.domElement.height * Config.dpr);
this.threeCamera.updateProjectionMatrix();
}
}

28
src/js/app/controls.js vendored Normal file
View File

@ -0,0 +1,28 @@
import THREE from 'three';
import OrbitControls from '../utils/orbitControls';
import Config from './../data/config';
export default class Controls {
constructor(camera, container) {
const orbitControls = new OrbitControls(THREE);
this.threeControls = new orbitControls(camera, container);
this.init();
}
init() {
this.threeControls.target.set(Config.controls.target.x, Config.controls.target.y, Config.controls.target.z);
this.threeControls.autoRotate = Config.controls.autoRotate;
this.threeControls.autoRotateSpeed = Config.controls.autoRotateSpeed;
this.threeControls.rotateSpeed = Config.controls.rotateSpeed;
this.threeControls.zoomSpeed = Config.controls.zoomSpeed;
this.threeControls.minDistance = Config.controls.minDistance;
this.threeControls.maxDistance = Config.controls.maxDistance;
this.threeControls.minPolarAngle = Config.controls.minPolarAngle;
this.threeControls.maxPolarAngle = Config.controls.maxPolarAngle;
this.threeControls.enableDamping = Config.controls.enableDamping;
this.threeControls.enableZoom = Config.controls.enableZoom;
this.threeControls.dampingFactor = Config.controls.dampingFactor;
}
}

33
src/js/app/geometry.js Normal file
View File

@ -0,0 +1,33 @@
import THREE from 'three';
import Config from '../data/config';
export default class Geometry {
constructor(scene) {
this.scene = scene;
this.geo = null;
}
make(type) {
if(type == 'plane') {
return (width, height, widthSegments = 1, heightSegments = 1) => {
this.geo = new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);
}
}
}
place(position, rotation) {
const material = new THREE.MeshStandardMaterial({ color: 0xCCCCCC, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(this.geo, material);
mesh.position.set(...position);
mesh.rotation.set(...rotation);
if(Config.shadow.enabled) {
mesh.receiveShadow = true;
mesh.castShadow = true;
}
this.scene.add(mesh);
}
}

369
src/js/app/gui.js Normal file
View File

@ -0,0 +1,369 @@
import Config from './../data/config';
export default class GUI {
constructor(main, mesh) {
let gui = new dat.GUI();
this.camera = main.camera.threeCamera;
this.controls = main.controls.threeControls;
this.light = main.light;
/* Global */
//gui.close();
/* Camera */
let cameraFolder = gui.addFolder('Camera');
let cameraFOVGui = cameraFolder.add(Config.camera, 'fov', 0, 180).name('Camera FOV');
cameraFOVGui.onChange((value) => {
this.controls.enableRotate = false;
this.camera.fov = value;
});
cameraFOVGui.onFinishChange((value) => {
this.camera.updateProjectionMatrix();
this.controls.enableRotate = true;
});
let cameraAspectGui = cameraFolder.add(Config.camera, 'aspect', 0, 4).name('Camera Aspect');
cameraAspectGui.onChange((value) => {
this.controls.enableRotate = false;
this.camera.aspect = value;
});
cameraAspectGui.onFinishChange((value) => {
this.camera.updateProjectionMatrix();
this.controls.enableRotate = true;
});
let cameraFogColorGui = cameraFolder.addColor(Config.fog, 'color').name('Fog Color');
cameraFogColorGui.onChange((value) => {
main.scene.fog.color.setHex(value);
});
let cameraFogNearGui = cameraFolder.add(Config.fog, 'near', 0.000, 0.010).name('Fog Near');
cameraFogNearGui.onChange((value) => {
this.controls.enableRotate = false;
main.scene.fog.density = value;
});
cameraFogNearGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
/* Controls */
let controlsFolder = gui.addFolder('Controls');
controlsFolder.add(Config.controls, 'autoRotate').name('Auto Rotate').onChange((value) => {
this.controls.autoRotate = value;
});
let controlsAutoRotateSpeedGui = controlsFolder.add(Config.controls, 'autoRotateSpeed', -1, 1).name('Rotation Speed');
controlsAutoRotateSpeedGui.onChange((value) => {
this.controls.enableRotate = false;
this.controls.autoRotateSpeed = value;
});
controlsAutoRotateSpeedGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
/* Mesh */
let meshFolder = gui.addFolder('Mesh');
meshFolder.add(Config.mesh, 'translucent', true).name('Translucent').onChange((value) => {
if(value) {
mesh.material.transparent = true;
mesh.material.opacity = 0.5;
} else {
mesh.material.opacity = 1.0;
}
});
meshFolder.add(Config.mesh, 'wireframe', true).name('Wireframe').onChange((value) => {
mesh.material.wireframe = value;
});
/* Lights */
// Ambient Light
let ambientLightFolder = gui.addFolder('Ambient Light');
ambientLightFolder.add(Config.ambientLight, 'enabled').name('Enabled').onChange((value) => {
this.light.ambientLight.visible = value;
});
ambientLightFolder.addColor(Config.ambientLight, 'color').name('Color').onChange((value) => {
this.light.ambientLight.color.setHex(value);
});
// Directional Light
let directionalLightFolder = gui.addFolder('Directional Light');
directionalLightFolder.add(Config.directionalLight, 'enabled').name('Enabled').onChange((value) => {
this.light.directionalLight.visible = value;
});
directionalLightFolder.addColor(Config.directionalLight, 'color').name('Color').onChange((value) => {
this.light.directionalLight.color.setHex(value);
});
let directionalLightIntensityGui = directionalLightFolder.add(Config.directionalLight, 'intensity', 0, 2).name('Intensity');
directionalLightIntensityGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.intensity = value;
});
directionalLightIntensityGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let directionalLightPositionXGui = directionalLightFolder.add(Config.directionalLight, 'x', -1000, 1000).name('Position X');
directionalLightPositionXGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.position.x = value;
});
directionalLightPositionXGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let directionalLightPositionYGui = directionalLightFolder.add(Config.directionalLight, 'y', -1000, 1000).name('Position Y');
directionalLightPositionYGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.position.y = value;
});
directionalLightPositionYGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let directionalLightPositionZGui = directionalLightFolder.add(Config.directionalLight, 'z', -1000, 1000).name('Position Z');
directionalLightPositionZGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.position.z = value;
});
directionalLightPositionZGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
// Shadow Map
let shadowFolder = gui.addFolder('Shadow Map');
shadowFolder.add(Config.shadow, 'enabled').name('Enabled').onChange((value) => {
this.light.directionalLight.castShadow = value;
});
shadowFolder.add(Config.shadow, 'helperEnabled').name('Helper Enabled').onChange((value) => {
this.light.directionalLightHelper.visible = value;
});
let shadowNearGui = shadowFolder.add(Config.shadow, 'near', 0, 100).name('Near');
shadowNearGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.shadow.camera.near = value;
});
shadowNearGui.onFinishChange((value) => {
this.controls.enableRotate = true;
this.light.directionalLight.shadow.map.dispose();
this.light.directionalLight.shadow.map = null;
this.light.directionalLightHelper.update();
});
let shadowFarGui = shadowFolder.add(Config.shadow, 'far', 0, 1200).name('Far');
shadowFarGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.shadow.camera.far = value;
});
shadowFarGui.onFinishChange((value) => {
this.controls.enableRotate = true;
this.light.directionalLight.shadow.map.dispose();
this.light.directionalLight.shadow.map = null;
this.light.directionalLightHelper.update();
});
let shadowTopGui = shadowFolder.add(Config.shadow, 'top', -400, 400).name('Top');
shadowTopGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.shadow.camera.top = value;
});
shadowTopGui.onFinishChange((value) => {
this.controls.enableRotate = true;
this.light.directionalLight.shadow.map.dispose();
this.light.directionalLight.shadow.map = null;
this.light.directionalLightHelper.update();
});
let shadowRightGui = shadowFolder.add(Config.shadow, 'right', -400, 400).name('Right');
shadowRightGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.shadow.camera.right = value;
});
shadowRightGui.onFinishChange((value) => {
this.controls.enableRotate = true;
this.light.directionalLight.shadow.map.dispose();
this.light.directionalLight.shadow.map = null;
this.light.directionalLightHelper.update();
});
let shadowBottomGui = shadowFolder.add(Config.shadow, 'bottom', -400, 400).name('Bottom');
shadowBottomGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.shadow.camera.bottom = value;
});
shadowBottomGui.onFinishChange((value) => {
this.controls.enableRotate = true;
this.light.directionalLight.shadow.map.dispose();
this.light.directionalLight.shadow.map = null;
this.light.directionalLightHelper.update();
});
let shadowLeftGui = shadowFolder.add(Config.shadow, 'left', -400, 400).name('Left');
shadowLeftGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.shadow.camera.left = value;
});
shadowLeftGui.onFinishChange((value) => {
this.controls.enableRotate = true;
this.light.directionalLight.shadow.map.dispose();
this.light.directionalLight.shadow.map = null;
this.light.directionalLightHelper.update();
});
let shadowBiasGui = shadowFolder.add(Config.shadow, 'bias', -0.000010, 1).name('Bias');
shadowBiasGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.directionalLight.shadow.bias = value;
});
shadowBiasGui.onFinishChange((value) => {
this.controls.enableRotate = true;
this.light.directionalLight.shadow.map.dispose();
this.light.directionalLight.shadow.map = null;
this.light.directionalLightHelper.update();
});
// Point Light
let pointLightFolder = gui.addFolder('Point Light');
pointLightFolder.add(Config.pointLight, 'enabled').name('Enabled').onChange((value) => {
this.light.pointLight.visible = value;
});
pointLightFolder.addColor(Config.pointLight, 'color').name('Color').onChange((value) => {
this.light.pointLight.color.setHex(value);
});
let pointLightIntensityGui = pointLightFolder.add(Config.pointLight, 'intensity', 0, 2).name('Intensity');
pointLightIntensityGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.pointLight.intensity = value;
});
pointLightIntensityGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let pointLightDistanceGui = pointLightFolder.add(Config.pointLight, 'distance', 0, 1000).name('Distance');
pointLightDistanceGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.pointLight.distance = value;
});
pointLightDistanceGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let pointLightPositionXGui = pointLightFolder.add(Config.pointLight, 'x', -1000, 1000).name('Position X');
pointLightPositionXGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.pointLight.position.x = value;
});
pointLightPositionXGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let pointLightPositionYGui = pointLightFolder.add(Config.pointLight, 'y', -1000, 1000).name('Position Y');
pointLightPositionYGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.pointLight.position.y = value;
});
pointLightPositionYGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let pointLightPositionZGui = pointLightFolder.add(Config.pointLight, 'z', -1000, 1000).name('Position Z');
pointLightPositionZGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.pointLight.position.z = value;
});
pointLightPositionZGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
// Hemi Light
let hemiLightFolder = gui.addFolder('Hemi Light');
hemiLightFolder.add(Config.hemiLight, 'enabled').name('Enabled').onChange((value) => {
this.light.hemiLight.visible = value;
});
hemiLightFolder.addColor(Config.hemiLight, 'color').name('Color').onChange((value) => {
this.light.hemiLight.color.setHex(value);
});
hemiLightFolder.addColor(Config.hemiLight, 'groundColor').name('ground Color').onChange((value) => {
this.light.hemiLight.groundColor.setHex(value);
});
let hemiLightIntensityGui = hemiLightFolder.add(Config.hemiLight, 'intensity', 0, 2).name('Intensity');
hemiLightIntensityGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.hemiLight.intensity = value;
});
hemiLightIntensityGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let hemiLightPositionXGui = hemiLightFolder.add(Config.hemiLight, 'x', -1000, 1000).name('Position X');
hemiLightPositionXGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.hemiLight.position.x = value;
});
hemiLightPositionXGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let hemiLightPositionYGui = hemiLightFolder.add(Config.hemiLight, 'y', -500, 1000).name('Position Y');
hemiLightPositionYGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.hemiLight.position.y = value;
});
hemiLightPositionYGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
let hemiLightPositionZGui = hemiLightFolder.add(Config.hemiLight, 'z', -1000, 1000).name('Position Z');
hemiLightPositionZGui.onChange((value) => {
this.controls.enableRotate = false;
this.light.hemiLight.position.z = value;
});
hemiLightPositionZGui.onFinishChange((value) => {
this.controls.enableRotate = true;
});
}
handleColorChange(color) {
return (value) => {
if(typeof value === 'string') {
value = value.replace('#', '0x');
}
color.setHex(value);
};
}
needsUpdate(material, geometry) {
return function() {
material.shading = +material.shading; //Ensure number
material.vertexColors = +material.vertexColors; //Ensure number
material.side = +material.side; //Ensure number
material.needsUpdate = true;
geometry.verticesNeedUpdate = true;
geometry.normalsNeedUpdate = true;
geometry.colorsNeedUpdate = true;
};
}
updateTexture(material, materialKey, textures) {
return function(key) {
material[materialKey] = textures[key];
material.needsUpdate = true;
};
}
update() {
this.needsUpdate(mesh.material, mesh.geometry);
}
}

23
src/js/app/helper.js Normal file
View File

@ -0,0 +1,23 @@
import THREE from 'three';
export default class Helper {
constructor(scene, mesh) {
let wireframe = new THREE.WireframeGeometry(mesh.geometry);
let wireLine = new THREE.LineSegments(wireframe);
wireLine.material.depthTest = false;
wireLine.material.opacity = 0.25;
wireLine.material.transparent = true;
mesh.add(wireLine);
let edges = new THREE.EdgesGeometry(mesh.geometry);
let edgesLine = new THREE.LineSegments(edges);
edgesLine.material.depthTest = false;
edgesLine.material.opacity = 0.25;
edgesLine.material.transparent = true;
mesh.add(edgesLine);
scene.add(new THREE.BoxHelper(mesh));
scene.add(new THREE.FaceNormalsHelper(mesh, 2));
scene.add(new THREE.VertexNormalsHelper(mesh, 2));
}
}

54
src/js/app/interaction.js Normal file
View File

@ -0,0 +1,54 @@
import THREE from 'three';
import Keyboard from './../utils/keyboard';
import Helpers from './../utils/helpers';
import Config from './../data/config';
export default class Interaction {
constructor(renderer, scene, camera, controls) {
this.renderer = renderer;
this.scene = scene;
this.camera = camera;
this.controls = controls;
this.keyboard = new Keyboard();
// listeners
// mouse events
this.renderer.domElement.addEventListener('mouseup', (event) => this.onMouseUp(event), false);
this.renderer.domElement.addEventListener('mousemove', (event) => Helpers.throttle(this.onMouseMove(event), 250), false);
this.renderer.domElement.addEventListener('mouseenter', (event) => this.onMouseEnter(event), false);
this.renderer.domElement.addEventListener('mouseleave', (event) => this.onMouseLeave(event), false);
this.renderer.domElement.addEventListener('mouseover', (event) => this.onMouseOver(event), false);
// keyboard events
this.keyboard.domElement.addEventListener('keydown', (event) => {
if(event.repeat) {
return;
}
if(this.keyboard.eventMatches(event, 'escape')) {
console.log('Escape pressed');
}
});
}
onMouseEnter(event) {
event.preventDefault();
}
onMouseOver(event) {
event.preventDefault();
}
onMouseLeave(event) {
event.preventDefault();
}
onMouseMove(event) {
event.preventDefault();
}
onMouseUp(event) {
event.preventDefault();
}
}

69
src/js/app/light.js Normal file
View File

@ -0,0 +1,69 @@
import THREE from 'three';
import Config from './../data/config';
export default class Light {
constructor(scene) {
this.scene = scene;
this.init();
}
init() {
// ambient
this.ambientLight = new THREE.AmbientLight(Config.ambientLight.color);
this.ambientLight.visible = Config.ambientLight.enabled;
// point light
this.pointLight = new THREE.PointLight(Config.pointLight.color, Config.pointLight.intensity, Config.pointLight.distance);
this.pointLight.position.set(Config.pointLight.x, Config.pointLight.y, Config.pointLight.z);
this.pointLight.visible = Config.pointLight.enabled;
// directional light
this.directionalLight = new THREE.DirectionalLight(Config.directionalLight.color, Config.directionalLight.intensity);
this.directionalLight.position.set(Config.directionalLight.x, Config.directionalLight.y, Config.directionalLight.z);
this.directionalLight.visible = Config.directionalLight.enabled;
// shadow map
this.directionalLight.castShadow = Config.shadow.enabled;
this.directionalLight.shadow.bias = Config.shadow.bias;
this.directionalLight.shadow.camera.near = Config.shadow.near;
this.directionalLight.shadow.camera.far = Config.shadow.far;
this.directionalLight.shadow.camera.left = Config.shadow.left;
this.directionalLight.shadow.camera.right = Config.shadow.right;
this.directionalLight.shadow.camera.top = Config.shadow.top;
this.directionalLight.shadow.camera.bottom = Config.shadow.bottom;
this.directionalLight.shadow.mapSize.width = Config.shadow.mapWidth;
this.directionalLight.shadow.mapSize.height = Config.shadow.mapHeight;
// shadow camera helper
this.directionalLightHelper = new THREE.CameraHelper(this.directionalLight.shadow.camera);
this.directionalLightHelper.visible = Config.shadow.helperEnabled;
// hemisphere light
this.hemiLight = new THREE.HemisphereLight(Config.hemiLight.color, Config.hemiLight.groundColor, Config.hemiLight.intensity);
this.hemiLight.position.set(Config.hemiLight.x, Config.hemiLight.y, Config.hemiLight.z);
this.hemiLight.visible = Config.hemiLight.enabled;
}
place(lightName) {
switch(lightName) {
case 'ambient':
this.scene.add(this.ambientLight);
break;
case 'directional':
this.scene.add(this.directionalLight);
this.scene.add(this.directionalLightHelper);
break;
case 'point':
this.scene.add(this.pointLight);
break;
case 'hemi':
this.scene.add(this.hemiLight);
break;
}
}
}

147
src/js/app/main.js Normal file
View File

@ -0,0 +1,147 @@
// global imports
import THREE from 'three';
import TWEEN from 'tween.js';
// local imports
import Renderer from './renderer';
import Camera from './camera';
import Light from './light';
import Controls from './controls';
import Geometry from './geometry';
import Texture from './texture';
import Model from './model';
import Interaction from './interaction';
import GUI from './gui';
// data
import Config from './../data/config';
// stats
let rS, bS, glS, tS;
export default class Main {
constructor(container) {
this.container = container;
// Start Three clock
this.clock = new THREE.Clock();
// Main scene
this.scene = new THREE.Scene();
this.scene.fog = new THREE.FogExp2(Config.fog.color, Config.fog.near);
// Get Device Pixel Ratio first
if(window.devicePixelRatio) {
Config.dpr = window.devicePixelRatio;
}
// Main renderer
this.renderer = new Renderer(container, this.scene);
// Components
this.camera = new Camera(this.renderer.threeRenderer);
this.controls = new Controls(this.camera.threeCamera, this.container);
this.light = new Light(this.scene);
// Place lights
const lights = ['ambient', 'directional', 'point', 'hemi'];
for(let i = 0; i < lights.length; i++) {
this.light.place(lights[i]);
}
// Place geo
this.geometry = new Geometry(this.scene);
this.geometry.make('plane')(100, 100, 10, 10);
this.geometry.place([0, -20, 0], [Math.PI/2, 0, 0]);
// Set up stats if dev
if(Config.isDev) {
bS = new BrowserStats();
glS = new glStats();
tS = new threeStats(this.renderer.threeRenderer);
rS = new rStats({
CSSPath: '/assets/css/',
userTimingAPI: true,
values: {
frame: { caption: 'Total frame time (ms)', over: 16, average: true, avgMs: 100 },
fps: { caption: 'Framerate (FPS)', below: 30 },
calls: { caption: 'Calls (three.js)', over: 3000 },
raf: { caption: 'Time since last rAF (ms)', average: true, avgMs: 100 },
rstats: { caption: 'rStats update (ms)', average: true, avgMs: 100 },
texture: { caption: 'GenTex', average: true, avgMs: 100 }
},
groups: [
{ caption: 'Framerate', values: [ 'fps', 'raf' ] },
{ caption: 'Frame Budget', values: [ 'frame', 'texture', 'setup', 'render' ] }
],
fractions: [
{ base: 'frame', steps: [ 'texture', 'setup', 'render' ] }
],
plugins: [bS, tS, glS]
});
}
this.texture = new Texture();
// Start loading the textures
this.texture.load().then(() => {
this.manager = new THREE.LoadingManager();
// Textures loaded, load main model
this.model = new Model(this.scene, this.manager, this.texture.textures);
this.model.load();
// onProgress
this.manager.onProgress = (item, loaded, total) => {
console.log(`${item}: ${loaded} ${total}`);
};
// All loaders done
this.manager.onLoad = () => {
// Set up interaction with app
new Interaction(this.renderer.threeRenderer, this.scene, this.camera.threeCamera, this.controls.threeControls);
if(Config.isDev) {
new GUI(this, this.model.obj);
}
Config.isLoaded = true;
};
});
this.render();
}
render() {
const delta = this.clock.getDelta();
if(Config.isDev) {
rS('frame').start();
glS.start();
rS('rAF').tick();
rS('FPS').frame();
rS('render').start();
}
// Clear renderer
this.renderer.threeRenderer.clear();
this.renderer.render(this.scene, this.camera.threeCamera);
if(Config.isDev) {
rS('render').end();
rS('frame').end();
rS('rStats').start();
rS().update();
rS('rStats').end();
}
// Updates
TWEEN.update();
this.controls.threeControls.update();
// raf
requestAnimationFrame(this.render.bind(this));
}
}

24
src/js/app/material.js Normal file
View File

@ -0,0 +1,24 @@
import THREE from 'three';
import Config from './../data/config';
export default class Material {
constructor() {
this.emissive = new THREE.MeshBasicMaterial({
color: 0xeeeeee,
side: THREE.DoubleSide,
fog: false
});
this.standard = new THREE.MeshStandardMaterial({
shading: THREE.FlatShading,
roughness: 1,
metalness: 0,
side: THREE.DoubleSide,
fog: false
});
this.wire = new THREE.MeshBasicMaterial({wireframe: true});
}
}

57
src/js/app/model.js Normal file
View File

@ -0,0 +1,57 @@
import THREE from 'three';
import Material from './material';
import Helper from './helper';
import Config from './../data/config';
export default class Model {
constructor(scene, manager, textures) {
this.scene = scene;
this.textures = textures;
this.loader = new THREE.ObjectLoader(manager);
this.obj = null;
}
load() {
// load a resource
this.loader.load(Config.model.path, (obj) => {
obj.traverse((child) => {
if(child instanceof THREE.Mesh) {
let material = new Material().standard;
material.map = this.textures.UV;
child.material = material;
if(Config.shadow.enabled) {
child.receiveShadow = true;
child.castShadow = true;
}
}
});
if(Config.isDev && Config.mesh.enableHelper) {
new Helper(this.scene, obj);
}
// set prop to obj
this.obj = obj;
obj.scale.multiplyScalar(Config.model.scale);
// add object to scene
this.scene.add(obj);
}, Model.onProgress, Model.onError);
}
static onProgress(xhr) {
if(xhr.lengthComputable) {
let percentComplete = xhr.loaded / xhr.total * 100;
console.log(Math.round(percentComplete, 2) + '% downloaded');
}
};
static onError(xhr) {
console.error(xhr);
};
}

44
src/js/app/renderer.js Normal file
View File

@ -0,0 +1,44 @@
import THREE from 'three';
import Config from './../data/config';
export default class Renderer {
constructor(container, scene) {
this.container = container;
this.scene = scene;
this.threeRenderer = new THREE.WebGLRenderer({antialias: true});
//this.renderer.setClearColor(0x000000, 0);
this.threeRenderer.setClearColor(scene.fog.color);
this.threeRenderer.setPixelRatio(window.devicePixelRatio);
container.appendChild(this.threeRenderer.domElement);
this.threeRenderer.gammaInput = true;
this.threeRenderer.gammaOutput = true;
// shadow
this.threeRenderer.shadowMap.enabled = true;
this.threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.threeRenderer.shadowMapSoft = true;
this.threeRenderer.autoClear = false;
Config.maxAnisotropy = this.threeRenderer.getMaxAnisotropy();
this.updateSize();
// Listeners
document.addEventListener('DOMContentLoaded', () => this.updateSize(), false);
window.addEventListener('resize', () => this.updateSize(), false);
}
updateSize() {
this.threeRenderer.setSize(this.container.offsetWidth, this.container.offsetHeight);
}
render(scene, camera) {
this.threeRenderer.render(scene, camera);
}
}

48
src/js/app/texture.js Normal file
View File

@ -0,0 +1,48 @@
import THREE from 'three';
import { Promise } from 'es6-promise';
import Config from './../data/config';
export default class Texture {
constructor() {
this.textures = {};
}
load() {
const loader = new THREE.TextureLoader();
const maxAnisotropy = Config.maxAnisotropy;
const imageFiles = Config.texture.imageFiles;
let promiseArray = [];
loader.setPath(Config.texture.path);
imageFiles.forEach((imageFile) => {
promiseArray.push(new Promise((resolve, reject) => {
loader.load(imageFile.image,
function(texture) {
texture.anisotropy = maxAnisotropy;
var modelOBJ = {};
modelOBJ[imageFile.name] = texture;
if(modelOBJ[imageFile.name] instanceof THREE.Texture)
resolve(modelOBJ);
},
function(xhr) {
console.log(( xhr.loaded / xhr.total * 100 ) + '% loaded');
},
function(xhr) {
reject(new Error(xhr + 'An error occurred loading while loading ' + imageFile.image));
}
)
}));
});
return Promise.all(promiseArray).then((textures) => {
for(var i = 0; i < textures.length; i++) {
this.textures[Object.keys(textures[i])[0]] = textures[i][Object.keys(textures[i])[0]];
}
});
}
}

109
src/js/data/config.js Normal file
View File

@ -0,0 +1,109 @@
import TWEEN from 'tween.js';
export default {
isDev: false,
isLoaded: false,
isTweening: false,
isRotating: true,
isMouseMoving: false,
isMouseOver: false,
maxAnisotropy: 1,
dpr: 1,
easing: TWEEN.Easing.Quadratic.InOut,
duration: 500,
model: {
path: '/assets/models/teapot-claraio.json',
scale: 20
},
texture: {
path: '/assets/textures/',
imageFiles: [
{name: 'UV', image: 'UV_Grid_Sm.jpg'}
]
},
mesh: {
enableHelper: false,
wireframe: false,
translucent: false,
material: {
color: 0xffffff,
emissive: 0xffffff
}
},
fog: {
color: 0xffffff,
near: 0.0008
},
camera: {
fov: 40,
near: 2,
far: 1000,
aspect: 1,
posX: 0,
posY: 30,
posZ: 40
},
controls: {
autoRotate: true,
autoRotateSpeed: -0.5,
rotateSpeed: 0.5,
zoomSpeed: 0.8,
minDistance: 200,
maxDistance: 600,
minPolarAngle: Math.PI / 5,
maxPolarAngle: Math.PI / 2,
minAzimuthAngle: -Infinity,
maxAzimuthAngle: Infinity,
enableDamping: true,
dampingFactor: 0.5,
enableZoom: true,
target: {
x: 0,
y: 0,
z: 0
}
},
ambientLight: {
enabled: false,
color: 0x141414
},
directionalLight: {
enabled: true,
color: 0xf0f0f0,
intensity: 0.4,
x: -75,
y: 280,
z: 150
},
shadow: {
enabled: true,
helperEnabled: true,
bias: -0.00025,
mapWidth: 1024,
mapHeight: 1024,
near: 200,
far: 400,
top: 150,
right: 150,
bottom: -150,
left: -150
},
pointLight: {
enabled: true,
color: 0xffffff,
intensity: 0.34,
distance: 115,
x: 0,
y: 0,
z: 0
},
hemiLight: {
enabled: true,
color: 0xc8c8c8,
groundColor: 0xffffff,
intensity: 0.3,
x: -275,
y: 145,
z: 0
}
};

71
src/js/utils/detector.js Normal file
View File

@ -0,0 +1,71 @@
/**
* @author alteredq / http://alteredqualia.com/
* @author mr.doob / http://mrdoob.com/
*/
export default {
canvas: !!window.CanvasRenderingContext2D,
webgl: (function() {
try {
var canvas = document.createElement('canvas');
return !!( window.WebGLRenderingContext && ( canvas.getContext('webgl') || canvas.getContext('experimental-webgl') ) );
} catch(e) {
return false;
}
})(),
workers: !!window.Worker,
fileapi: window.File && window.FileReader && window.FileList && window.Blob,
getWebGLErrorMessage: function() {
var element = document.createElement('div');
element.id = 'webgl-error-message';
element.style.fontFamily = 'monospace';
element.style.fontSize = '13px';
element.style.fontWeight = 'normal';
element.style.textAlign = 'center';
element.style.background = '#fff';
element.style.color = '#000';
element.style.padding = '1.5em';
element.style.width = '400px';
element.style.margin = '5em auto 0';
if(!this.webgl) {
element.innerHTML = window.WebGLRenderingContext ? [
'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000000">WebGL</a>.<br />',
'Find out how to get it <a href="http://get.webgl.org/" style="color:#000000">here</a>.'
].join('\n') : [
'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000000">WebGL</a>.<br/>',
'Find out how to get it <a href="http://get.webgl.org/" style="color:#000000">here</a>.'
].join('\n');
}
return element;
},
addGetWebGLMessage: function(parameters) {
var parent, id, element;
parameters = parameters || {};
parent = parameters.parent !== undefined ? parameters.parent : document.body;
id = parameters.id !== undefined ? parameters.id : 'oldie';
element = this.getWebGLErrorMessage();
element.id = id;
parent.appendChild(element);
}
};

26
src/js/utils/helpers.js Normal file
View File

@ -0,0 +1,26 @@
export default class Helpers {
static throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last, deferTimer;
return function() {
var context = scope || this;
var now = +new Date,
args = arguments;
if(last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function() {
last = now;
fn.apply(context, args);
}, threshhold);
}
else {
last = now;
fn.apply(context, args);
}
};
}
}

93
src/js/utils/keyboard.js Normal file
View File

@ -0,0 +1,93 @@
const ALIAS = {
'left' : 37,
'up' : 38,
'right' : 39,
'down' : 40,
'space' : 32,
'pageup' : 33,
'pagedown': 34,
'tab' : 9,
'escape' : 27
};
export default class Keyboard {
constructor(domElement) {
this.domElement = domElement || document;
this.keyCodes = {};
// bind keyEvents
this.domElement.addEventListener('keydown', () => this.onKeyChange(event), false);
this.domElement.addEventListener('keyup', () => this.onKeyChange(event), false);
// bind window blur
window.addEventListener('blur', () => this.onBlur, false);
}
destroy() {
this.domElement.removeEventListener('keydown', () => this.onKeyChange(event), false);
this.domElement.removeEventListener('keyup', () => this.onKeyChange(event), false);
// unbind window blur event
window.removeEventListener('blur', () => this.onBlur, false);
}
onBlur() {
for(let prop in this.keyCodes)
this.keyCodes[prop] = false;
}
onKeyChange(event) {
// log to debug
//console.log('onKeyChange', event, event.keyCode, event.shiftKey, event.ctrlKey, event.altKey, event.metaKey)
// update this.keyCodes
let keyCode = event.keyCode;
this.keyCodes[keyCode] = event.type === 'keydown';
}
pressed(keyDesc) {
let keys = keyDesc.split('+');
for(let i = 0; i < keys.length; i++) {
let key = keys[i];
let pressed = false;
if(Object.keys(ALIAS).indexOf(key) != -1) {
pressed = this.keyCodes[ALIAS[key]];
} else {
pressed = this.keyCodes[key.toUpperCase().charCodeAt(0)];
}
if(!pressed)
return false;
}
return true;
}
eventMatches(event, keyDesc) {
let aliases = ALIAS;
let aliasKeys = Object.keys(aliases);
let keys = keyDesc.split('+');
// log to debug
// console.log('eventMatches', event, event.keyCode, event.shiftKey, event.ctrlKey, event.altKey, event.metaKey)
for(let i = 0; i < keys.length; i++) {
let key = keys[i];
let pressed = false;
if(key === 'shift') {
pressed = (event.shiftKey ? true : false);
} else if(key === 'ctrl') {
pressed = (event.ctrlKey ? true : false);
} else if(key === 'alt') {
pressed = (event.altKey ? true : false);
} else if(key === 'meta') {
pressed = (event.metaKey ? true : false);
} else if(aliasKeys.indexOf(key) !== -1) {
pressed = (event.keyCode === aliases[key]);
} else if(event.keyCode === key.toUpperCase().charCodeAt(0)) {
pressed = true;
}
if(!pressed)
return false;
}
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
.main {
width: 100%;
height: 100vh;
}

View File

@ -0,0 +1,73 @@
.rs-base{
position: absolute;
z-index: 10000;
padding: 10px;
background-color: #222;
font-size: 10px;
line-height: 1.2em;
width: 350px;
font-family: 'Roboto Condensed', tahoma, sans-serif;
left: 0;
top: 0;
overflow: hidden;
}
.rs-base h1{
margin: 0;
padding: 0;
font-size: 1.4em;
color: #fff;
margin-bottom: 5px;
cursor: pointer;
}
.rs-base div.rs-group{
margin-bottom: 10px;
}
.rs-base div.rs-group.hidden{
display: none;
}
.rs-base div.rs-fraction{
position: relative;
margin-bottom: 5px;
}
.rs-base div.rs-fraction p{
width: 120px;
text-align: right;
margin: 0;
padding: 0;
}
.rs-base div.rs-legend{
position: absolute;
line-height: 1em;
}
.rs-base div.rs-counter-base{
position: relative;
margin: 2px 0;
height: 1em;
}
.rs-base span.rs-counter-id{
position: absolute;
left: 0;
top: 0;
}
.rs-base div.rs-counter-value{
position: absolute;
left: 90px;
width: 30px;
height: 1em;
top: 0;
text-align: right;
}
.rs-base canvas.rs-canvas{
position: absolute;
right: 0;
}

200
src/public/assets/js/app.js Normal file

File diff suppressed because one or more lines are too long

95
src/public/assets/js/dat.gui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,719 @@
// performance.now() polyfill from https://gist.github.com/paulirish/5438650
'use strict';
( function () {
// prepare base perf object
if ( typeof window.performance === 'undefined' ) {
window.performance = {};
}
if ( !window.performance.now ) {
var nowOffset = Date.now();
if ( performance.timing && performance.timing.navigationStart ) {
nowOffset = performance.timing.navigationStart;
}
window.performance.now = function now () {
return Date.now() - nowOffset;
};
}
if( !window.performance.mark ) {
window.performance.mark = function(){}
}
if( !window.performance.measure ) {
window.performance.measure = function(){}
}
} )();
window.rStats = function rStats ( settings ) {
function iterateKeys ( array, callback ) {
var keys = Object.keys( array );
for ( var j = 0, l = keys.length; j < l; j++ ) {
callback( keys[ j ] );
}
}
function importCSS ( url ) {
var element = document.createElement( 'link' );
element.href = url;
element.rel = 'stylesheet';
element.type = 'text/css';
document.getElementsByTagName( 'head' )[ 0 ].appendChild( element );
}
var _settings = settings || {};
var _colours = _settings.colours || [ '#850700', '#c74900', '#fcb300', '#284280', '#4c7c0c' ];
var _cssFont = 'https://fonts.googleapis.com/css?family=Roboto+Condensed:400,700,300';
var _cssRStats = ( _settings.CSSPath ? _settings.CSSPath : '' ) + 'rStats.css';
var _css = _settings.css || [ _cssFont, _cssRStats ];
_css.forEach(function (uri) {
importCSS( uri );
});
if ( !_settings.values ) _settings.values = {};
var _base, _div, _elHeight = 10, _elWidth = 200;
var _perfCounters = {};
function Graph ( _dom, _id, _defArg ) {
var _def = _defArg || {};
var _canvas = document.createElement( 'canvas' ),
_ctx = _canvas.getContext( '2d' ),
_max = 0,
_current = 0;
var c = _def.color ? _def.color : '#666666';
var _dotCanvas = document.createElement( 'canvas' ),
_dotCtx = _dotCanvas.getContext( '2d' );
_dotCanvas.width = 1;
_dotCanvas.height = 2 * _elHeight;
_dotCtx.fillStyle = '#444444';
_dotCtx.fillRect( 0, 0, 1, 2 * _elHeight );
_dotCtx.fillStyle = c;
_dotCtx.fillRect( 0, _elHeight, 1, _elHeight );
_dotCtx.fillStyle = '#ffffff';
_dotCtx.globalAlpha = 0.5;
_dotCtx.fillRect( 0, _elHeight, 1, 1 );
_dotCtx.globalAlpha = 1;
var _alarmCanvas = document.createElement( 'canvas' ),
_alarmCtx = _alarmCanvas.getContext( '2d' );
_alarmCanvas.width = 1;
_alarmCanvas.height = 2 * _elHeight;
_alarmCtx.fillStyle = '#444444';
_alarmCtx.fillRect( 0, 0, 1, 2 * _elHeight );
_alarmCtx.fillStyle = '#b70000';
_alarmCtx.fillRect( 0, _elHeight, 1, _elHeight );
_alarmCtx.globalAlpha = 0.5;
_alarmCtx.fillStyle = '#ffffff';
_alarmCtx.fillRect( 0, _elHeight, 1, 1 );
_alarmCtx.globalAlpha = 1;
function _init () {
_canvas.width = _elWidth;
_canvas.height = _elHeight;
_canvas.style.width = _canvas.width + 'px';
_canvas.style.height = _canvas.height + 'px';
_canvas.className = 'rs-canvas';
_dom.appendChild( _canvas );
_ctx.fillStyle = '#444444';
_ctx.fillRect( 0, 0, _canvas.width, _canvas.height );
}
function _draw ( v, alarm ) {
_current += ( v - _current ) * 0.1;
_max *= 0.99;
if ( _current > _max ) _max = _current;
_ctx.drawImage( _canvas, 1, 0, _canvas.width - 1, _canvas.height, 0, 0, _canvas.width - 1, _canvas.height );
if ( alarm ) {
_ctx.drawImage( _alarmCanvas, _canvas.width - 1, _canvas.height - _current * _canvas.height / _max - _elHeight );
} else {
_ctx.drawImage( _dotCanvas, _canvas.width - 1, _canvas.height - _current * _canvas.height / _max - _elHeight );
}
}
_init();
return {
draw: _draw
};
}
function StackGraph ( _dom, _num ) {
var _canvas = document.createElement( 'canvas' ),
_ctx = _canvas.getContext( '2d' );
function _init () {
_canvas.width = _elWidth;
_canvas.height = _elHeight * _num;
_canvas.style.width = _canvas.width + 'px';
_canvas.style.height = _canvas.height + 'px';
_canvas.className = 'rs-canvas';
_dom.appendChild( _canvas );
_ctx.fillStyle = '#444444';
_ctx.fillRect( 0, 0, _canvas.width, _canvas.height );
}
function _draw ( v ) {
_ctx.drawImage( _canvas, 1, 0, _canvas.width - 1, _canvas.height, 0, 0, _canvas.width - 1, _canvas.height );
var th = 0;
iterateKeys( v, function ( j ) {
var h = v[ j ] * _canvas.height;
_ctx.fillStyle = _colours[ j ];
_ctx.fillRect( _canvas.width - 1, th, 1, h );
th += h;
} );
}
_init();
return {
draw: _draw
};
}
function PerfCounter ( id, group ) {
var _id = id,
_time,
_value = 0,
_total = 0,
_averageValue = 0,
_accumValue = 0,
_accumStart = performance.now(),
_accumSamples = 0,
_dom = document.createElement( 'div' ),
_spanId = document.createElement( 'span' ),
_spanValue = document.createElement( 'div' ),
_spanValueText = document.createTextNode( '' ),
_def = _settings ? _settings.values[ _id.toLowerCase() ] : null,
_graph = new Graph( _dom, _id, _def ),
_started = false;
_dom.className = 'rs-counter-base';
_spanId.className = 'rs-counter-id';
_spanId.textContent = ( _def && _def.caption ) ? _def.caption : _id;
_spanValue.className = 'rs-counter-value';
_spanValue.appendChild( _spanValueText );
_dom.appendChild( _spanId );
_dom.appendChild( _spanValue );
if ( group ) group.div.appendChild( _dom );
else _div.appendChild( _dom );
_time = performance.now();
function _average ( v ) {
if ( _def && _def.average ) {
_accumValue += v;
_accumSamples++;
var t = performance.now();
if ( t - _accumStart >= ( _def.avgMs || 1000 ) ) {
_averageValue = _accumValue / _accumSamples;
_accumValue = 0;
_accumStart = t;
_accumSamples = 0;
}
}
}
function _start () {
_time = performance.now();
if( _settings.userTimingAPI ) performance.mark( _id + '-start' );
_started = true;
}
function _end () {
_value = performance.now() - _time;
if( _settings.userTimingAPI ) {
performance.mark( _id + '-end' );
if( _started ) {
performance.measure( _id, _id + '-start', _id + '-end' );
}
}
_average( _value );
}
function _tick () {
_end();
_start();
}
function _draw () {
var v = ( _def && _def.average ) ? _averageValue : _value;
_spanValueText.nodeValue = Math.round( v * 100 ) / 100;
var a = ( _def && ( ( _def.below && _value < _def.below ) || ( _def.over && _value > _def.over ) ) );
_graph.draw( _value, a );
_dom.style.color = a ? '#b70000' : '#ffffff';
}
function _frame () {
var t = performance.now();
var e = t - _time;
_total++;
if ( e > 1000 ) {
if ( _def && _def.interpolate === false ) {
_value = _total;
} else {
_value = _total * 1000 / e;
}
_total = 0;
_time = t;
_average( _value );
}
}
function _set ( v ) {
_value = v;
_average( _value );
}
return {
set: _set,
start: _start,
tick: _tick,
end: _end,
frame: _frame,
value: function () {
return _value;
},
draw: _draw
};
}
function sample () {
var _value = 0;
function _set ( v ) {
_value = v;
}
return {
set: _set,
value: function () {
return _value;
}
};
}
function _perf ( idArg ) {
var id = idArg.toLowerCase();
if ( id === undefined ) id = 'default';
if ( _perfCounters[ id ] ) return _perfCounters[ id ];
var group = null;
if ( _settings && _settings.groups ) {
iterateKeys( _settings.groups, function ( j ) {
var g = _settings.groups[ parseInt( j, 10 ) ];
if ( !group && g.values.indexOf( id.toLowerCase() ) !== -1 ) {
group = g;
}
} );
}
var p = new PerfCounter( id, group );
_perfCounters[ id ] = p;
return p;
}
function _init () {
if ( _settings.plugins ) {
if ( !_settings.values ) _settings.values = {};
if ( !_settings.groups ) _settings.groups = [];
if ( !_settings.fractions ) _settings.fractions = [];
for ( var j = 0; j < _settings.plugins.length; j++ ) {
_settings.plugins[ j ].attach( _perf );
iterateKeys( _settings.plugins[ j ].values, function ( k ) {
_settings.values[ k ] = _settings.plugins[ j ].values[ k ];
} );
_settings.groups = _settings.groups.concat( _settings.plugins[ j ].groups );
_settings.fractions = _settings.fractions.concat( _settings.plugins[ j ].fractions );
}
} else {
_settings.plugins = {};
}
_base = document.createElement( 'div' );
_base.className = 'rs-base';
_div = document.createElement( 'div' );
_div.className = 'rs-container';
_div.style.height = 'auto';
_base.appendChild( _div );
document.body.appendChild( _base );
if ( !_settings ) return;
if ( _settings.groups ) {
iterateKeys( _settings.groups, function ( j ) {
var g = _settings.groups[ parseInt( j, 10 ) ];
var div = document.createElement( 'div' );
div.className = 'rs-group';
g.div = div;
var h1 = document.createElement( 'h1' );
h1.textContent = g.caption;
h1.addEventListener( 'click', function ( e ) {
this.classList.toggle( 'hidden' );
e.preventDefault();
}.bind( div ) );
_div.appendChild( h1 );
_div.appendChild( div );
} );
}
if ( _settings.fractions ) {
iterateKeys( _settings.fractions, function ( j ) {
var f = _settings.fractions[ parseInt( j, 10 ) ];
var div = document.createElement( 'div' );
div.className = 'rs-fraction';
var legend = document.createElement( 'div' );
legend.className = 'rs-legend';
var h = 0;
iterateKeys( _settings.fractions[ j ].steps, function ( k ) {
var p = document.createElement( 'p' );
p.textContent = _settings.fractions[ j ].steps[ k ];
p.style.color = _colours[ h ];
legend.appendChild( p );
h++;
} );
div.appendChild( legend );
div.style.height = h * _elHeight + 'px';
f.div = div;
var graph = new StackGraph( div, h );
f.graph = graph;
_div.appendChild( div );
} );
}
}
function _update () {
iterateKeys( _settings.plugins, function ( j ) {
_settings.plugins[ j ].update();
} );
iterateKeys( _perfCounters, function ( j ) {
_perfCounters[ j ].draw();
} );
if ( _settings && _settings.fractions ) {
iterateKeys( _settings.fractions, function ( j ) {
var f = _settings.fractions[ parseInt( j, 10 ) ];
var v = [];
var base = _perfCounters[ f.base.toLowerCase() ];
if ( base ) {
base = base.value();
iterateKeys( _settings.fractions[ j ].steps, function ( k ) {
var s = _settings.fractions[ j ].steps[ parseInt( k, 10 ) ].toLowerCase();
var val = _perfCounters[ s ];
if ( val ) {
v.push( val.value() / base );
}
} );
}
f.graph.draw( v );
} );
}
/*if( _height != _div.clientHeight ) {
_height = _div.clientHeight;
_base.style.height = _height + 2 * _elHeight + 'px';
console.log( _base.clientHeight );
}*/
}
_init();
return function ( id ) {
if ( id ) return _perf( id );
return {
element: _base,
update: _update
};
};
}
if (typeof module === 'object') {
module.exports = window.rStats;
}
window.glStats = function () {
var _rS = null;
var _totalDrawArraysCalls = 0,
_totalDrawElementsCalls = 0,
_totalUseProgramCalls = 0,
_totalFaces = 0,
_totalVertices = 0,
_totalPoints = 0,
_totalBindTexures = 0;
function _h ( f, c ) {
return function () {
c.apply( this, arguments );
f.apply( this, arguments );
};
}
WebGLRenderingContext.prototype.drawArrays = _h( WebGLRenderingContext.prototype.drawArrays, function () {
_totalDrawArraysCalls++;
if ( arguments[ 0 ] == this.POINTS ) _totalPoints += arguments[ 2 ];
else _totalVertices += arguments[ 2 ];
} );
WebGLRenderingContext.prototype.drawElements = _h( WebGLRenderingContext.prototype.drawElements, function () {
_totalDrawElementsCalls++;
_totalFaces += arguments[ 1 ] / 3;
_totalVertices += arguments[ 1 ];
} );
WebGLRenderingContext.prototype.useProgram = _h( WebGLRenderingContext.prototype.useProgram, function () {
_totalUseProgramCalls++;
} );
WebGLRenderingContext.prototype.bindTexture = _h( WebGLRenderingContext.prototype.bindTexture, function () {
_totalBindTexures++;
} );
var _values = {
allcalls: {
over: 3000,
caption: 'Calls (hook)'
},
drawelements: {
caption: 'drawElements (hook)'
},
drawarrays: {
caption: 'drawArrays (hook)'
}
};
var _groups = [ {
caption: 'WebGL',
values: [ 'allcalls', 'drawelements', 'drawarrays', 'useprogram', 'bindtexture', 'glfaces', 'glvertices', 'glpoints' ]
} ];
var _fractions = [ {
base: 'allcalls',
steps: [ 'drawelements', 'drawarrays' ]
} ];
function _update () {
_rS( 'allcalls' ).set( _totalDrawArraysCalls + _totalDrawElementsCalls );
_rS( 'drawElements' ).set( _totalDrawElementsCalls );
_rS( 'drawArrays' ).set( _totalDrawArraysCalls );
_rS( 'bindTexture' ).set( _totalBindTexures );
_rS( 'useProgram' ).set( _totalUseProgramCalls );
_rS( 'glfaces' ).set( _totalFaces );
_rS( 'glvertices' ).set( _totalVertices );
_rS( 'glpoints' ).set( _totalPoints );
}
function _start () {
_totalDrawArraysCalls = 0;
_totalDrawElementsCalls = 0;
_totalUseProgramCalls = 0;
_totalFaces = 0;
_totalVertices = 0;
_totalPoints = 0;
_totalBindTexures = 0;
}
function _end () {}
function _attach ( r ) {
_rS = r;
}
return {
update: _update,
start: _start,
end: _end,
attach: _attach,
values: _values,
groups: _groups,
fractions: _fractions
};
};
window.threeStats = function ( renderer ) {
var _rS = null;
var _values = {
'renderer.info.memory.geometries': {
caption: 'Geometries'
},
'renderer.info.memory.textures': {
caption: 'Textures'
},
'renderer.info.programs': {
caption: 'Programs'
},
'renderer.info.render.calls': {
caption: 'Calls'
},
'renderer.info.render.faces': {
caption: 'Faces',
over: 1000
},
'renderer.info.render.points': {
caption: 'Points'
},
'renderer.info.render.vertices': {
caption: 'Vertices'
}
};
var _groups = [ {
caption: 'Three.js - Memory',
values: [ 'renderer.info.memory.geometries', 'renderer.info.programs', 'renderer.info.memory.textures' ]
}, {
caption: 'Three.js - Render',
values: [ 'renderer.info.render.calls', 'renderer.info.render.faces', 'renderer.info.render.points', 'renderer.info.render.vertices' ]
} ];
var _fractions = [];
function _update () {
_rS( 'renderer.info.memory.geometries' ).set( renderer.info.memory.geometries );
//_rS( 'renderer.info.programs' ).set( renderer.info.programs.length );
_rS( 'renderer.info.memory.textures' ).set( renderer.info.memory.textures );
_rS( 'renderer.info.render.calls' ).set( renderer.info.render.calls );
_rS( 'renderer.info.render.faces' ).set( renderer.info.render.faces );
_rS( 'renderer.info.render.points' ).set( renderer.info.render.points );
_rS( 'renderer.info.render.vertices' ).set( renderer.info.render.vertices );
}
function _start () {}
function _end () {}
function _attach ( r ) {
_rS = r;
}
return {
update: _update,
start: _start,
end: _end,
attach: _attach,
values: _values,
groups: _groups,
fractions: _fractions
};
};
/*
* From https://github.com/paulirish/memory-stats.js
*/
window.BrowserStats = function () {
var _rS = null;
var _usedJSHeapSize = 0,
_totalJSHeapSize = 0;
var memory = {
usedJSHeapSize: 0,
totalJSHeapSize: 0
};
if ( window.performance && performance.memory )
memory = performance.memory;
if ( memory.totalJSHeapSize === 0 ) {
console.warn( 'totalJSHeapSize === 0... performance.memory is only available in Chrome .' );
}
var _values = {
memory: {
caption: 'Used Memory',
average: true,
avgMs: 1000,
over: 22
},
total: {
caption: 'Total Memory'
}
};
var _groups = [ {
caption: 'Browser',
values: [ 'memory', 'total' ]
} ];
var _fractions = [ {
base: 'total',
steps: [ 'memory' ]
} ];
var log1024 = Math.log( 1024 );
function _size ( v ) {
var precision = 100; //Math.pow(10, 2);
var i = Math.floor( Math.log( v ) / log1024 );
if( v === 0 ) i = 1;
return Math.round( v * precision / Math.pow( 1024, i ) ) / precision; // + ' ' + sizes[i];
}
function _update () {
_usedJSHeapSize = _size( memory.usedJSHeapSize );
_totalJSHeapSize = _size( memory.totalJSHeapSize );
_rS( 'memory' ).set( _usedJSHeapSize );
_rS( 'total' ).set( _totalJSHeapSize );
}
function _start () {
_usedJSHeapSize = 0;
}
function _end () {}
function _attach ( r ) {
_rS = r;
}
return {
update: _update,
start: _start,
end: _end,
attach: _attach,
values: _values,
groups: _groups,
fractions: _fractions
};
};
if (typeof module === 'object') {
module.exports = {
glStats: window.glStats,
threeStats: window.threeStats,
BrowserStats: window.BrowserStats
};
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

19
src/public/index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Three.js Webpack ES6 Boilerplate</title>
<link rel="stylesheet" type="text/css" href="/assets/css/app.css">
</head>
<body>
<div class="content">
<div id="appContainer" class="main">
</div>
</div>
<script src="/assets/js/rStats.js"></script>
<script src="/assets/js/dat.gui.min.js"></script>
<script src="/assets/js/app.js"></script>
</body>
</html>

95
webpack.config.js Normal file
View File

@ -0,0 +1,95 @@
var webpack = require('webpack'),
path = require('path');
var entry = './src/js/app.js',
includePath = path.join(__dirname, 'src/js'),
nodeModulesPath = path.join(__dirname, 'node_modules'),
outputPath = path.join(__dirname, 'src/public/assets/js');
var PROD = JSON.parse(process.env.NODE_ENV || 0);
var env = 'dev',
time = Date.now(),
devtool = 'eval',
debug = true,
plugins = [
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
__ENV__: JSON.stringify(env),
___BUILD_TIME___: time
})
];
if(PROD) {
env = 'prod';
devtool = 'hidden-source-map';
debug = false;
outputPath = __dirname + '/build/public/assets/js';
uglifyOptions = {
sourceMap: false,
mangle: true,
compress: {
drop_console: true
},
output: {
comments: false
}
};
plugins.push(new webpack.optimize.UglifyJsPlugin(uglifyOptions));
}
console.log('Webpack build - ENV: ' + env + ' V: ' + time);
console.log(' - outputPath ', outputPath);
console.log(' - includePath ', includePath);
console.log(' - nodeModulesPath ', nodeModulesPath);
module.exports = {
stats: {
colors: true
},
debug: debug,
devtool: devtool,
devServer: {
contentBase: 'src/public'
},
entry: [
entry
],
output: {
path: outputPath,
publicPath: 'assets/js',
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.js?$/,
loader: 'babel-loader',
query: {
presets: ['es2015']
},
include: [
includePath, nodeModulesPath
]
}
]
},
sassLoader: {
outputStyle: 'compressed',
outFile: __dirname + '/src/public/assets/css'
},
plugins: plugins,
resolve: {
alias: {}
}
};