更多有趣示例 尽在 小红砖社区
示例
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.marginRi