Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
<script id='vShader' type='x-vertex/x-shader'>
			  uniform float size;
  uniform float t;
  uniform float z;
  uniform float pixelRatio;
  
  varying vec3 vPosition;
  varying vec3 mPosition;//modified position
  varying float gas;
  
  float a,b=0.;
  
  void main(){
  
    vPosition=position;
    
    a=length(position);
    if(t>0.)b=max(0.,(cos(a/20.-t*.02)-.99)*3./a);
    if(z>0.)b=max(0.,cos(a/40.-z*.01+2.));
    mPosition=position*(1.+b*4.);
    vec4 mvPosition=modelViewMatrix*vec4(mPosition,1.);
    gl_Position=mvPosition*projectionMatrix;
    
    gas=max(.0,sin(-a/20.));
    gl_PointSize=pixelRatio*size*(1.+gas*2.)/length(mvPosition.xyz);
  
  }
		</script>
		<script id='fShader' type='x-fragment/x-shader'>
  uniform float z;
      
  varying vec3 vPosition;
  varying vec3 mPosition;
  varying float gas;
      
  void main(){
    
    float a=distance(mPosition,vPosition);
    if(a>0.)a=1.;
    
    float b=max(.32,.0065*length(vPosition));
      
    float c=distance(gl_PointCoord,vec2(.5));
    float starlook=-(c-.5)*1.2*gas; 
    float gaslook=(1.-gas)/(c*10.);
    float texture=starlook+gaslook;
               
    gl_FragColor=vec4(.32,.28,b,1.)*texture*(1.-a*.35);
    if(z>0.)gl_FragColor*=cos(1.57*z/322.)*(1.-.001*length(mPosition));
      
  }
		</script>
      
<div class=layout>
  <div id=info>
    <p class=shadow>Milkyways Industries proudly presents</p>
    <p class=shadow>the Dark EnErgy Pulse Buster</p>
    <span class='black one'></span>
    <span class='black two'></span>
    <span class='black three'></span>
    <span class='black four'></span>
    <span class='black one right'></span>
    <span class='black two right'></span>
    <span class='black three right'></span>
    <span class='black four right'></span>
    <p class=positionning>
      <span id=timeline></span>
    </p>
    <div id=log></div>
    <span class=eye></span>
    <span class=metal id=abort></span>
  </div>
  <div id=instruction>Welcome ! Now we have conquered all the universe, we prefer shooting at galaxies rather than golf balls. There are billions anyway. Click to scan it first.</div>
</div>
<div id=good-person>I shouldn't. It is not the Jedi way.</div>
<div id=howmuch>
  <p class=counter-title id=saved>galaxies saved<p class=counter id=savedresult>0</p></p>
  <p id=good>HERO</p>
  <div id=bg></div>
  <p id=bad>VILAIN</p>
  <p class=counter-title id=destroyed>galaxies destroyed<p class=counter id=destroyedresult>0</p></p>
  <span id=gauge></span>
</div>
<a class=twitter href="https://twitter.com/GPUaccelerated" target='_blank'>Share your result on twitter</a>
<a class=more href="https://codepen.io/collection/nVYEGg/" style='position:absolute; bottom:10px;right:10px;font-family:Arial;color:#33c;margin-right:-500px;transition:margin-right 500ms ease;' target='_blank'>More webgl galaxies</a>
<button  style='position:absolute;width:100%;text-align:center;border-radius:5px;right:10px;font-family:Arial;color:#33c;outline:none;background:none;border:none;text-decoration:underline;font-size:16px;cursor:pointer;'>Had a bad day ?<br/>Destroy this galaxy to undwind</button>
<!-- webglstats.com-->
<script src="https://cdn.webglstats.com/stat.js" defer="defer" async="async"></script>


              
            
!

CSS

              
                body{
  margin:0;
  overflow:hidden;
}
canvas{
  cursor:grab;
  cursor:-webkit-grab;
  cursor:-moz-grab;
}
canvas:active{
  cursor:grabbing;
  cursor:-webkit-grabbing;
  cursor:-moz-grabbing;
}
a.twitter, a.more{
  position:absolute;
  bottom:40px;
  right:10px;
  font-family:Arial;
  color:#33c;
  margin-right:-500px;
  transition:margin-right 500ms ease;
}
p{
  margin:3px;
  position:relative;
  font:normal 15px Arial;
}
p.shadow{
  text-shadow:1px 1px 2px #000 ;
}
div.layout{
  top:-250px;
  width:100%;
  position:absolute;
  transition:top 500ms ease;
}
#instruction{
  z-index:1;
  color:#f90;
  font-family:Arial;
  padding:9px 0 5px 0;
  background-color:darkslategrey;
  top:100%;
  width:50%;
  position:absolute;
  left:25%;
  opacity:.8;
  border-radius:0px 0px 100px 100px;
  text-align:center;
  transition:all 500ms ease;
}
#good-person{
  cursor:pointer;
  color:#f90;
  font-family:Arial;
  padding:9px 0 5px 0;
  background-color:darkslategrey;
  bottom:-50px;
  box-shadow:0px -2px 4px black inset;
  width:50%;
  position:absolute;
  left:25%;
  opacity:.8;
  border-radius:100px 100px 0px 0px ;
  text-align:center;
  transition:all 500ms ease;
}
#info{
  border:solid 4px #444;
  border-top:none;
  border-radius:0px 0px 10px 10px;
  z-index:2;
  box-shadow:0px 5px 5px black ;
  color:#aaa;
  font-family:Arial;
  position:relative;
  text-align:center;
  padding:1px;
  top:0px;
  width:50%;
  min-width:500px;
  margin:auto;
  background:linear-gradient(-78deg ,#222 23%, #446 39%, #222 45%,#222 52%, #334 55%, #222 57%);
}
span.black{
  width:3%;
  height:7%;
  background:black;
  position:absolute;
  left:-1%;
  box-shadow:-2px 0px 5px #222 inset;
}
span.right{
  position:absolute;
  left:98%;
  box-shadow:2px 0px 5px #222 inset;
}
span.one{top:10%}
span.two{top:19%}
span.three{top:28%}
span.four{top:37%}
p.positionning{
  width:50%;
  background:#000;
  height:2px;
  text-align:left;
  margin:10px auto;
}
#timeline{
  height:2px;
  width:0%;
  position:absolute;
  top:0%;
}
.scanning{
  background:#f90;
  /*animation:scan 8s linear;*/ /* way faster on mobile without that*/
}
@keyframes scan{
  0%{
    width:0%;
    box-shadow:0px 0px 20px 5px #f90;
  }
  100%{
    width:100%;
    box-shadow:0px 0px 20px 5px #f90;
  }
}
.waiting{
  background:#3c4;
  /*animation:wait 4s infinite;*/ /*same*/
}
@keyframes wait{
  0%{
    width:100%;
    box-shadow:0px 0px 20px 3px #3c4;
  }
  50%{
    width:100%;
    box-shadow:0px 0px 0px 0px #3c4;
  }
  100%{
    width:100%;
    box-shadow:0px 0px 20px 3px #3c4;
  }
}
.warning{
  background:#f50;
  animation:warn 1s infinite;
}
@keyframes warn{
  0%{
    width:100%;
    box-shadow:0px 0px 0px 0px #f50;
  }
  50%{
    width:100%;
    box-shadow:0px 0px 20px 3px #f50;
  }
  100%{
    width:100%;
    box-shadow:0px 0px 0px 0px #f50;
  }
}
span.eye{
  width:40px;
  height:40px;
  background:-webkit-radial-gradient(orange 0% , red 20%,  black 60%, #555 61%);
  background:-moz-radial-gradient(orange 0% , red 20%,  black 60%, #555 61%);
  background:-ms-radial-gradient(orange 0% , red 20%,  black 60%, #555 61%);
  background:-o-radial-gradient(orange 0% , red 20%,  black 60%, #555 61%);
  border-radius:50%;
  position:absolute;
  left:47px;
  top:7px;
}
span.metal{
  font:bold 10px Arial;
  width:40px;
  height:40px;
  background:-webkit-radial-gradient(#777 15%,#888 35%, #666 50%,#888 55%,#555 65%);
  background:-moz-radial-gradient(#666 0% , #777 15%,#888 35%, #666 50%,#888 55%,#555 65%);
  background:-ms-radial-gradient(#666 0% , #777 15%,#888 35%, #666 50%,#888 55%,#555 65%);
  background:-o-radial-gradient(#666 0% , #777 15%,#888 35%, #666 50%,#888 55%,#555 65%);
  border-radius:50%;
  box-shadow:0px 0px 15px 3px black;
  position:absolute;
  right:47px;
  top:7px;
  color:#222;
}
.clic{
  animation:clic .2s linear 2 alternate;
}
@keyframes clic{
  0%{box-shadow:0px 0px 15px 3px black}
  100%{box-shadow:0px 0px 15px 3px green}
}
span.metal:after{
  position:relative;
  content:'ABORT';
  text-align:center;
  width:100%;
  top:14px
}
.abort{
  animation:aborting .4s linear infinite alternate;
}
@keyframes aborting{
  0%{color:#222;}
  100%{color:red;}
}
#log{
  font:normal 15px Courier;
  border-radius:10px;
  height:36px;
  padding:3px 15px;
  background:black;
}
#howmuch {
  top:20%;
  height:71%;
  width:58px;
  position:absolute;
  text-align:center;
  border:solid 4px #444;
  border-left:none;
  border-radius:0px 10px 10px 0px;
  background:#222;
  padding:2px;
  left:-100px;
  transition:left 500ms ease;
}
#good{
  top:15%;
  color:#590;  
  font:bold 15px Arial;
  left:1px;
}
#bad{
  font:bold 15px Arial;
  color:#a11;
  position:absolute;
  bottom:22%;
  left:4px;
}
#gauge {
  position:absolute;
  top:50%;
  left:11px;
  border-top: 4px solid transparent;  
	border-bottom: 4px solid transparent; 
	border-left: 19px solid #444;
	border-right: 19px solid #444;
  width:2px;
  transition:top 2s ease;
}
#bg{
  width:40px;
  height:40%;
  background:black;
  position:absolute;
  top:30%;
  left:11px;
}
p.counter-title{
  color:#aaa;
  font:normal 10px Arial; 
}
#destroyed{
  position:absolute;
  bottom:0;
  left:-2px;
}
p.counter{
  width:40px;
  height:40px;
  left:7px;
  border-radius:50%;
  border:solid 1px #555;
  position:absolute;
  font:bold 37px Arial;
  color:#aaa;
  background:#222;
}
#destroyedresult{
  bottom:30px;
}
p.change{
  animation:changing 2s linear;
}
@keyframes changing{
  0%{
    background:#ccc;
  }
  100%{
    background:#222;
  }
}
              
            
!

JS

              
                THREE.TrackballControls = function ( object, domElement ) {

	var _this = this;
	var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };

	this.object = object;
	this.domElement = ( domElement !== undefined ) ? domElement : document;

	// API

	this.enabled = true;

	this.screen = { left: 0, top: 0, width: 0, height: 0 };

	this.rotateSpeed = 1.0;
	this.zoomSpeed = 1.2;
	this.panSpeed = 0.3;

	this.noRotate = false;
	this.noZoom = false;
	this.noPan = false;

	this.staticMoving = false;
	this.dynamicDampingFactor = 0.2;

	this.minDistance = 0;
	this.maxDistance = Infinity;

	this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];

	// internals

	this.target = new THREE.Vector3();

	var EPS = 0.000001;

	var lastPosition = new THREE.Vector3();

	var _state = STATE.NONE,
	_prevState = STATE.NONE,

	_eye = new THREE.Vector3(),

	_movePrev = new THREE.Vector2(),
	_moveCurr = new THREE.Vector2(),

	_lastAxis = new THREE.Vector3(),
	_lastAngle = 0,

	_zoomStart = new THREE.Vector2(),
	_zoomEnd = new THREE.Vector2(),

	_touchZoomDistanceStart = 0,
	_touchZoomDistanceEnd = 0,

	_panStart = new THREE.Vector2(),
	_panEnd = new THREE.Vector2();

	// for reset

	this.target0 = this.target.clone();
	this.position0 = this.object.position.clone();
	this.up0 = this.object.up.clone();

	// events

	var changeEvent = { type: 'change' };
	var startEvent = { type: 'start' };
	var endEvent = { type: 'end' };


	// methods

	this.handleResize = function () {

		if ( this.domElement === document ) {

			this.screen.left = 0;
			this.screen.top = 0;
			this.screen.width = window.innerWidth;
			this.screen.height = window.innerHeight;

		} else {

			var box = this.domElement.getBoundingClientRect();
			// adjustments come from similar code in the jquery offset() function
			var d = this.domElement.ownerDocument.documentElement;
			this.screen.left = box.left + window.pageXOffset - d.clientLeft;
			this.screen.top = box.top + window.pageYOffset - d.clientTop;
			this.screen.width = box.width;
			this.screen.height = box.height;

		}

	};

	this.handleEvent = function ( event ) {

		if ( typeof this[ event.type ] == 'function' ) {

			this[ event.type ]( event );

		}

	};

	var getMouseOnScreen = ( function () {

		var vector = new THREE.Vector2();

		return function getMouseOnScreen( pageX, pageY ) {

			vector.set(
				( pageX - _this.screen.left ) / _this.screen.width,
				( pageY - _this.screen.top ) / _this.screen.height
			);

			return vector;

		};

	}() );

	var getMouseOnCircle = ( function () {

		var vector = new THREE.Vector2();

		return function getMouseOnCircle( pageX, pageY ) {

			vector.set(
				( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
				( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
			);

			return vector;

		};

	}() );

	this.rotateCamera = ( function() {

		var axis = new THREE.Vector3(),
			quaternion = new THREE.Quaternion(),
			eyeDirection = new THREE.Vector3(),
			objectUpDirection = new THREE.Vector3(),
			objectSidewaysDirection = new THREE.Vector3(),
			moveDirection = new THREE.Vector3(),
			angle;

		return function rotateCamera() {

			moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
			angle = moveDirection.length();

			if ( angle ) {

				_eye.copy( _this.object.position ).sub( _this.target );

				eyeDirection.copy( _eye ).normalize();
				objectUpDirection.copy( _this.object.up ).normalize();
				objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();

				objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
				objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );

				moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );

				axis.crossVectors( moveDirection, _eye ).normalize();

				angle *= _this.rotateSpeed;
				quaternion.setFromAxisAngle( axis, angle );

				_eye.applyQuaternion( quaternion );
				_this.object.up.applyQuaternion( quaternion );

				_lastAxis.copy( axis );
				_lastAngle = angle;

			} else if ( ! _this.staticMoving && _lastAngle ) {

				_lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor );
				_eye.copy( _this.object.position ).sub( _this.target );
				quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
				_eye.applyQuaternion( quaternion );
				_this.object.up.applyQuaternion( quaternion );

			}

			_movePrev.copy( _moveCurr );

		};

	}() );


	this.zoomCamera = function () {

		var factor;

		if ( _state === STATE.TOUCH_ZOOM_PAN ) {

			factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
			_touchZoomDistanceStart = _touchZoomDistanceEnd;
			_eye.multiplyScalar( factor );

		} else {

			factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;

			if ( factor !== 1.0 && factor > 0.0 ) {

				_eye.multiplyScalar( factor );

				if ( _this.staticMoving ) {

					_zoomStart.copy( _zoomEnd );

				} else {

					_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;

				}

			}

		}

	};

	this.panCamera = ( function() {

		var mouseChange = new THREE.Vector2(),
			objectUp = new THREE.Vector3(),
			pan = new THREE.Vector3();

		return function panCamera() {

			mouseChange.copy( _panEnd ).sub( _panStart );

			if ( mouseChange.lengthSq() ) {

				mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );

				pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
				pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );

				_this.object.position.add( pan );
				_this.target.add( pan );

				if ( _this.staticMoving ) {

					_panStart.copy( _panEnd );

				} else {

					_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );

				}

			}

		};

	}() );

	this.checkDistances = function () {

		if ( ! _this.noZoom || ! _this.noPan ) {

			if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {

				_this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
				_zoomStart.copy( _zoomEnd );

			}

			if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {

				_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
				_zoomStart.copy( _zoomEnd );

			}

		}

	};

	this.update = function () {

		_eye.subVectors( _this.object.position, _this.target );

		if ( ! _this.noRotate ) {

			_this.rotateCamera();

		}

		if ( ! _this.noZoom ) {

			_this.zoomCamera();

		}

		if ( ! _this.noPan ) {

			_this.panCamera();

		}

		_this.object.position.addVectors( _this.target, _eye );

		_this.checkDistances();

		_this.object.lookAt( _this.target );

		if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) {

			_this.dispatchEvent( changeEvent );

			lastPosition.copy( _this.object.position );

		}

	};

	this.reset = function () {

		_state = STATE.NONE;
		_prevState = STATE.NONE;

		_this.target.copy( _this.target0 );
		_this.object.position.copy( _this.position0 );
		_this.object.up.copy( _this.up0 );

		_eye.subVectors( _this.object.position, _this.target );

		_this.object.lookAt( _this.target );

		_this.dispatchEvent( changeEvent );

		lastPosition.copy( _this.object.position );

	};

	// listeners

	function keydown( event ) {

		if ( _this.enabled === false ) return;

		window.removeEventListener( 'keydown', keydown );

		_prevState = _state;

		if ( _state !== STATE.NONE ) {

			return;

		} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) {

			_state = STATE.ROTATE;

		} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) {

			_state = STATE.ZOOM;

		} else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) {

			_state = STATE.PAN;

		}

	}

	function keyup( event ) {

		if ( _this.enabled === false ) return;

		_state = _prevState;

		window.addEventListener( 'keydown', keydown, false );

	}

	function mousedown( event ) {

		if ( _this.enabled === false ) return;

		event.preventDefault();
		event.stopPropagation();

		if ( _state === STATE.NONE ) {

			_state = event.button;

		}

		if ( _state === STATE.ROTATE && ! _this.noRotate ) {

			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
			_movePrev.copy( _moveCurr );

		} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {

			_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
			_zoomEnd.copy( _zoomStart );

		} else if ( _state === STATE.PAN && ! _this.noPan ) {

			_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
			_panEnd.copy( _panStart );

		}

		document.addEventListener( 'mousemove', mousemove, false );
		document.addEventListener( 'mouseup', mouseup, false );

		_this.dispatchEvent( startEvent );

	}

	function mousemove( event ) {

		if ( _this.enabled === false ) return;

		event.preventDefault();
		event.stopPropagation();

		if ( _state === STATE.ROTATE && ! _this.noRotate ) {

			_movePrev.copy( _moveCurr );
			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );

		} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {

			_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );

		} else if ( _state === STATE.PAN && ! _this.noPan ) {

			_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );

		}

	}

	function mouseup( event ) {

		if ( _this.enabled === false ) return;

		event.preventDefault();
		event.stopPropagation();

		_state = STATE.NONE;

		document.removeEventListener( 'mousemove', mousemove );
		document.removeEventListener( 'mouseup', mouseup );
		_this.dispatchEvent( endEvent );

	}

	function mousewheel( event ) {

		if ( _this.enabled === false ) return;

		event.preventDefault();
		event.stopPropagation();

		var delta = 0;

		if ( event.wheelDelta ) {

			// WebKit / Opera / Explorer 9

			delta = event.wheelDelta / 40;

		} else if ( event.detail ) {

			// Firefox

			delta = - event.detail / 3;

		}

		_zoomStart.y += delta * 0.01;
		_this.dispatchEvent( startEvent );
		_this.dispatchEvent( endEvent );

	}

	function touchstart( event ) {

		if ( _this.enabled === false ) return;

		switch ( event.touches.length ) {

			case 1:
				_state = STATE.TOUCH_ROTATE;
				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
				_movePrev.copy( _moveCurr );
				break;

			default: // 2 or more
				_state = STATE.TOUCH_ZOOM_PAN;
				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
				_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );

				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
				_panStart.copy( getMouseOnScreen( x, y ) );
				_panEnd.copy( _panStart );
				break;

		}

		_this.dispatchEvent( startEvent );

	}

	function touchmove( event ) {

		if ( _this.enabled === false ) return;

		event.preventDefault();
		event.stopPropagation();

		switch ( event.touches.length ) {

			case 1:
				_movePrev.copy( _moveCurr );
				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
				break;

			default: // 2 or more
				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
				_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );

				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
				_panEnd.copy( getMouseOnScreen( x, y ) );
				break;

		}

	}

	function touchend( event ) {

		if ( _this.enabled === false ) return;

		switch ( event.touches.length ) {

			case 0:
				_state = STATE.NONE;
				break;

			case 1:
				_state = STATE.TOUCH_ROTATE;
				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
				_movePrev.copy( _moveCurr );
				break;

		}

		_this.dispatchEvent( endEvent );

	}

	function contextmenu( event ) {

		event.preventDefault();

	}

	this.dispose = function() {

		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
		this.domElement.removeEventListener( 'mousedown', mousedown, false );
		this.domElement.removeEventListener( 'mousewheel', mousewheel, false );
		this.domElement.removeEventListener( 'MozMousePixelScroll', mousewheel, false ); // firefox

		this.domElement.removeEventListener( 'touchstart', touchstart, false );
		this.domElement.removeEventListener( 'touchend', touchend, false );
		this.domElement.removeEventListener( 'touchmove', touchmove, false );

		document.removeEventListener( 'mousemove', mousemove, false );
		document.removeEventListener( 'mouseup', mouseup, false );

		window.removeEventListener( 'keydown', keydown, false );
		window.removeEventListener( 'keyup', keyup, false );

	};

	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
	this.domElement.addEventListener( 'mousedown', mousedown, false );
	this.domElement.addEventListener( 'mousewheel', mousewheel, false );
	this.domElement.addEventListener( 'MozMousePixelScroll', mousewheel, false ); // firefox

	this.domElement.addEventListener( 'touchstart', touchstart, false );
	this.domElement.addEventListener( 'touchend', touchend, false );
	this.domElement.addEventListener( 'touchmove', touchmove, false );

	window.addEventListener( 'keydown', keydown, false );
	window.addEventListener( 'keyup', keyup, false );

	this.handleResize();

	// force an update at start
	this.update();

};

THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;

var t=0,z=0,scanPulse=false,destroyPulse=false;
var howMuch=0,times=0,val=0;

setScene();
animate();
/** FUNCTIONS **/

//galaxy generator



function newGalaxy (_n, _axis1, _axis2, _armsAngle, _bulbSize, _ellipticity){
  
  //NOTE : this function misses a better implementation of galactic bulbs. 
  //It's not visible with additive blending but the bulb does not have a correct shape yet.
  //(haven't yet found a function that provides the correct z-profile of the 'ellipticity' degree of the different Hubble galaxies'types)
  //see 'ellipticity'
  
  //number of particles.
  var n=(typeof _n === 'undefined')?10000:_n;
  
  //to get 'arms', the main galaxy shape has to be an ellipse, i.e. axis1/axis2 must raise over a certain % 
  //otherwise, because of the 'ellipticity' z-profile problem, you get a potatoe
  var axis1=(typeof _axis1 === 'undefined')?(60+Math.random()*20):_axis1;
  var axis2=(typeof _axis2 === 'undefined')?(axis1+20+Math.random()*40):_axis2;
  //make sure axis1 is the biggest (excentricity equation fails if they are inverted), and allow the coder no to care about axis order
  var maja,mina;
  axis1>axis2?(maja=axis1,mina=axis2):
    axis1==axis2?(maja=axis1+1,mina=axis2):(maja=axis2,mina=axis1);

  //radians from the center to the end of each arm, proposed value range : between 3 and 13
  var armsAngle=(typeof _armsAngle === 'undefined')?((Math.random()*2-1)>0?1:-1)*12+3:_armsAngle;

  //core proportion in the (x,y) plane, between 0 and 1, proposed value range : between .1 and .8
  var bulbSize=(typeof _bulbSize === 'undefined')?Math.random()*.6:_bulbSize>1?1:_bulbSize<0?0:_bulbSize;

  //'ellipticity' : not found a better word to name the degree of 'elliptic' Hubble type.
  //'ellipticity' is what is mainly responsible of the z-profile in this experiment.
  //Range : between 0 and 1. Proposed : .2 to .4
  //TODO: implement string handling (or value from spacename ?) to create Hubble-class galaxy ala 'SBb'...
  var ellipticity=(typeof _ellipticity === 'undefined')?.2+Math.random()*.2:_ellipticity>1?1:_ellipticity<0?0:_ellipticity;

  var stars=[];

  for(var i=0;i<n;i++){

    var dist=Math.random();
    var angle=(dist-bulbSize)*armsAngle;

    //ellipse parameters
    var a=maja*dist;
    var b=mina*dist;
    var e=Math.sqrt(a*a-b*b)/a;
    var phi=ellipticity*Math.PI/2*(1-dist)*(Math.random()*2-1);

    //create point on the ellipse with polar coordinates
    //1. random angle from the center
    var theta=Math.random()*Math.PI*2;
    //2. deduce radius from theta in polar coordinates, from the CENTER of an ellipse, plus variations
    var radius=Math.sqrt(b*b/(1-e*e*Math.pow(Math.cos(theta),2)))*(1+Math.random()*.1);
    //3. then shift theta with the angle offset to get arms, outside the bulb
    if(dist>bulbSize)theta+=angle;
    
    //convert to cartesian coordinates
    stars.push({
      x:Math.cos(phi)*Math.cos(theta)*radius,
      y:Math.cos(phi)*Math.sin(theta)*radius,
      z:Math.sin(phi)*radius
    });
  }

  return stars;

}

//threejs functions
function setScene(){
  scene=new THREE.Scene();

  camera=new THREE.PerspectiveCamera(70,innerWidth/innerHeight,.5,1500);
  camera.position.set(-20,-155,90);

  renderTarget=new THREE.WebGLRenderTarget(innerWidth,innerHeight);

  renderer=new THREE.WebGLRenderer();
  renderer.setSize(innerWidth,innerHeight);
  
  renderer.setClearColor(0x0000000);
  document.body.appendChild(renderer.domElement);

  controls=new THREE.TrackballControls(camera,renderer.domElement);
  controls.noPan=true;
  controls.noZoom=true;
  controls.rotateSpeed=20;
	controls.dynamicDampingFactor = .5;
  setGalaxy();
  
  var button=document.querySelector('button');
  button.onclick=function(){
    renderer.domElement.style.cursor='pointer';
    document.querySelector('.layout').style.top='0px';
    document.querySelector('#howmuch').style.left='0px';
    addInteraction();
  }
	
	window.addEventListener('resize',function(){
		camera.aspect=innerWidth/innerHeight;
		renderer.setSize(innerWidth,innerHeight);
		camera.updateProjectionMatrix();
		renderer.render(scene,camera);
	},false);
}
function setGalaxy(){
  galaxyMaterial=new THREE.ShaderMaterial({
      vertexShader:document.getElementById('vShader').textContent,
      fragmentShader:document.getElementById('fShader').textContent,
      uniforms:{
        size:{type:'f',value:3.3},
        t:{type:"f",value:0},
        z:{type:"f",value:0},
        pixelRatio:{type:"f",value:innerHeight}
      },
      transparent:true,
      depthTest:false,
      blending:THREE.AdditiveBlending
    });
  var stars1=new THREE.Geometry();
  stars1.vertices=newGalaxy();
  galaxy=new THREE.Points(stars1,galaxyMaterial);
  scene.add(galaxy);
}
function animate(){
  if(scanPulse)t+=.7;
  if(destroyPulse)z+=.7;
  galaxyMaterial.uniforms.t.value=t;
  galaxyMaterial.uniforms.z.value=z;
  requestAnimationFrame(animate);
  renderer.render(scene,camera);
  scene.rotation.z+=.001;
  controls.update();
}
//game stuff
//This part is a bit messy (mainly due to dom & css manipulations without jquery)
function changeLog(){
  var log=document.getElementById('log');
  log.innerHTML='life detected...';
  setTimeout(function(){
    var msg=[
      'a dark Ewok empire has enslaved all lifeforms there !',
      'Arachnids\'territory ! ',
      'medichlorians make people mad in this galaxy',
      'dominant lifeform : raging space cats',
      'full of replicators ! ',
      'pokemon dominate 80% of this galaxy',
      'this is where the TeamRocket finally landed',
      'Cylons have conquered this one',
      'seems Borgs went and destroyed everything here',
      'dominant lifeform : bacterians',
      "this is EVE ! we've finally found them !",
      'the Ancients ! they were not a legend ! ',
      'damned, Oris !',
      "sleeping Wraiths !",
      'Reapers waiting here !',
      "Gallifrey's Time Lords take care of this one" 
    ];
    var rand=Math.floor(Math.random()*msg.length);
    log.innerHTML=msg[rand];
    prepareDestroy();
  },3000);
}
function changeGalaxy(d){
  var log=document.getElementById('log');
    log.innerHTML='NGC - '+(Math.random()*100000000).toFixed()+'<br/>distance : '+(Math.random()*11).toFixed(1)+' Gly';
  var stars2=newGalaxy(); 
  for(var i=0;i<galaxy.geometry.vertices.length;i++){
    TweenLite.to(galaxy.geometry.vertices[i],d,{
      x:stars2[i].x,y:stars2[i].y,z:stars2[i].z,
      onUpdate:function(){galaxy.geometry.verticesNeedUpdate=true},
      ease:Quart.easeInOut
        }
    );
  }
}
function addInteraction(){
  renderer.domElement.addEventListener('touch',scan,false);
  renderer.domElement.addEventListener('click',scan,false);
}
function prepareDestroy(){
  var inst=document.getElementById('instruction');
  inst.style.backgroundColor='#f40';
  inst.style.color='black';
  inst.innerHTML='Yeah ! We don\'t care ! Pulser at 2 000 % ! <br/>Destroy this galaxy ! Click again !';
  setTimeout(function(){
    var no=document.getElementById('good-person');
    no.style.bottom='0px';
    inst.style.top='100%';
    document.getElementById('timeline').className='warning';
    renderer.domElement.style.cursor='pointer';
    renderer.domElement.addEventListener('click',destroy,false);
    renderer.domElement.addEventListener('touch',destroy,false);
    no.addEventListener('click',goodPerson,false);
    no.addEventListener('touch',goodPerson,false);
  },1500)
}
function goodPerson(){
  var inst=document.getElementById('instruction');
  var no=document.getElementById('good-person');
  var abort=document.getElementById('abort');
  
  no.removeEventListener('click',goodPerson,false);
  no.removeEventListener('touch',goodPerson,false);
  renderer.domElement.removeEventListener('click',destroy,false);
  renderer.domElement.removeEventListener('touch',destroy,false);
  document.getElementById('timeline').className='';
  no.style.bottom='-50px';
  inst.style.top='20%';
  renderer.domElement.style.cursor='';
  
  setTimeout(function(){
    document.getElementById('log').innerHTML='I\'m sorry Dave. I\'m afraid i can\'t let you disagree. I shall destroy this galaxy for you.';
  },500);
  var destroyTimeoutID=setTimeout(function(){
    destroy();
    abort.className='metal';
    abort.style.cursor='';
    abort.removeEventListener('click',speedTest,false);
    abort.removeEventListener('touch',speedTest,false);
  },4500);
  var destroyHalID=setTimeout(function(){
    abort.className='metal abort';
    abort.style.cursor='pointer';
    abort.addEventListener('click',speedTest,false);
    abort.addEventListener('touch',speedTest,false);
  },2500);
  function speedTest(){
    abort.className='metal clic';
    clearTimeout(destroyTimeoutID);
    abort.removeEventListener('click',speedTest,false);
    abort.removeEventListener('touch',speedTest,false);
    setTimeout(function(){
      document.getElementById('log').innerHTML='I can feel.... my mind..  going... I can feel it....';
      setTimeout(function(){
        abort.className='metal';
        inst.style.top='100%';
        inst.style.backgroundColor='darkslategrey';
        inst.style.color='#f90';
        inst.innerHTML='You are a hero ! You have just prevented a galactic genocide.';
        setGauge('hero')
      },1300)
    },1000);
    setTimeout(function(){
      addInteraction();
      updateLink()
      inst.innerHTML='Ok, let\'s continue with an other one. Click to scan';
      renderer.domElement.style.cursor='pointer';
      inst.style.top='100%';
      inst.style.backgroundColor='darkslategrey';
      inst.style.color='#f90';
      document.getElementById('timeline').className='waiting';
      changeGalaxy(4);
    },7000);
  }
  
}
function setGauge(param){
  var gauge=document.getElementById('gauge');
  var destroyed=document.getElementById('destroyedresult');
  var saved=document.getElementById('savedresult');
  if(param==='hero'){
    val++;
    saved.innerHTML=(parseInt(saved.innerHTML)+1);
    saved.className='counter change';
    setTimeout(function(){saved.className='counter'},3000);
  }else if(param==='bad'){
    val--;
    destroyed.innerHTML=(parseInt(destroyed.innerHTML)+1);
    setTimeout(function(){destroyed.className='counter'},3000);
    destroyed.className+=' change'
  }  
  times++;
  howMuch=17.5*val/times;
  gauge.style.top=50-howMuch+'%';
}
function destroy(){
  var no=document.getElementById('good-person');
  document.getElementById('timeline').className='';
  renderer.domElement.style.cursor='';
  renderer.domElement.removeEventListener('click',destroy,false);
  renderer.domElement.removeEventListener('touch',destroy,false);
  no.removeEventListener('click',goodPerson,false);
  no.removeEventListener('touch',goodPerson,false);
  var inst=document.getElementById('instruction');
  document.getElementById('instruction');
  inst.style.top='20%';
  no.style.bottom='-50px';
  destroyPulse=true;
  setTimeout(function(){
    document.getElementById('log').innerHTML='Nice shot !';
  },4000);
  setTimeout(function(){
    addInteraction();
    setGauge('bad');
    updateLink()
    inst.innerHTML='No worries, there still are few galaxies. <br/>Here is an other one, click to scan';
    renderer.domElement.style.cursor='pointer';
    inst.style.top='100%';
    inst.style.backgroundColor='darkslategrey';
    inst.style.color='#f90';
    document.getElementById('timeline').className='waiting';
    destroyPulse=false;
    reduceZ();
    function reduceZ(){
      if(z>0){
        z-=3;
        requestAnimationFrame(reduceZ);
      }
    };
    changeGalaxy(4);
  },9000);
}
function scan(){
  renderer.domElement.removeEventListener('click',scan,false);
  renderer.domElement.removeEventListener('touch',scan,false);
  document.getElementById('log').innerHTML='parsing data...'
  document.getElementById('instruction').style.top='20%';
  renderer.domElement.style.cursor='';
  document.getElementById('timeline').className='scanning';
  scanPulse=true;
  setTimeout(function(){
    changeLog();
    scanPulse=false;
    t=0;
  },7000);
}
function updateLink(){
  var l=document.querySelector('.twitter');
  var d=parseInt(document.getElementById('destroyedresult').innerHTML);
  var s=parseInt(document.getElementById('savedresult').innerHTML);
  var iam, did, num,plur;
  if(d>s){
    iam='a%20BAD%20VILAIN';
    did='destroyed';
    num=d;
  }else if(s>d){
    iam='a%20HERO';
    did='saved';
    num=s;
  }else{
    iam='BAD';
    did='let%20destroy';
    num=d;
  }
  plur=num>1?'ies':'y';
  l.style.marginRight='0px';
  document.querySelector('.more').style.marginRight='0px';
  l.href='https://twitter.com/home?status=I%20am%20'+iam+'%20!%20I%20'+did+'%20'+num+'%20galax'+plur+'%20on%20http%3A%2F%2Fcodepen.io%2FAstrak%2Ffull%2FBoBWPB%2F%20%40CodePen%20%23webgl%20%23threejs'
}


              
            
!
999px

Console