Skip to content

Instantly share code, notes, and snippets.

@chandlerprall
Created February 25, 2012 17:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chandlerprall/1909535 to your computer and use it in GitHub Desktop.
Save chandlerprall/1909535 to your computer and use it in GitHub Desktop.
tower source code
'use strict';
var collisionsound1 = new buzz.sound( "assets/sounds/collision1", {
formats: [ 'wav' ]
});
collisionsound1.load();
var collisionsound2 = new buzz.sound( "assets/sounds/collision2", {
formats: [ 'wav' ]
});
collisionsound2.load();
var collisionsound3 = new buzz.sound( "assets/sounds/collision3", {
formats: [ 'wav' ]
});
collisionsound3.load();
var newGame = function() {
document.getElementById('endgame').style.display = 'none';
if ( tower ) {
tower.unload();
}
tower = new Tower();
tower.handleResize();
tower.init();
};
window['requestAnimFrame'] = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
var Tower = function() {
var i;
this.paused = false;
this.pausedtime = null;
this.createBlock = this.createBlock.bind( this );
this.updateHeight = this.updateHeight.bind( this );
this.totaltime = 45;
this.starttime = Math.floor(new Date().getTime() / 1000);
this.timeleft = this.totaltime;
this.height = -2;
this.active_block = null;
this.boxes = [];
this.last_manifolds = 0;
this.intervals = [];
this.timeouts = [];
this.timebonuses = [
{
height: 10,
bonus: 20,
achieved: false,
element: null
},
{
height: 22,
bonus: 20,
achieved: false,
element: null
},
{
height: 37,
bonus: 20,
achieved: false,
element: null
},
{
height: 55,
bonus: 20,
achieved: false,
element: null
}
];
for ( i = 0; i < this.timebonuses.length; i++ ) {
this.timebonuses[i].element = document.createElement('div');
this.timebonuses[i].element.className = 'timebonus';
if ( i === 0 ) {
this.timebonuses[i].element.style.display = 'block';
}
document.getElementById('viewport').appendChild( this.timebonuses[i].element );
}
this.cursor_position = new THREE.Vector2( 0, 10 );
this.initEvents();
};
Tower.prototype.onPopupShowing = function() {};
Tower.prototype.onPopupShown = function() {
this.handleResize();
if (!Detector.webgl) {
var nowebgl = document.getElementById('nowebgl'),
content = document.getElementById('content');
nowebgl.style.display = 'block';
nowebgl.style.left = this.workarea_size.width / 2 - nowebgl.clientWidth / 2 + 'px';
nowebgl.style.top = this.workarea_size.height / 2 - nowebgl.clientHeight / 2 + 'px'
}
if ( this.pausedtime ) {
this.paused = false;
this.totaltime += Math.floor(new Date().getTime() / 1000) - this.pausedtime;
this.pausedtime = null;
}
};
Tower.prototype.onPopupHidden = function() { this.paused = true; this.pausedtime = Math.floor(new Date().getTime() / 1000); };
Tower.prototype.onPopupUnload = function() {};
Tower.prototype.init = function() {
this.initGUI();
this.initThree();
this.initTextures();
this.initScene();
this.start();
};
Tower.prototype.showInstructions = function() {
var instructions_panel = document.getElementById('instructions');
instructions_panel.style.display = 'block';
instructions_panel.style.left = this.workarea_size.width / 2 - instructions_panel.clientWidth / 2 + 'px';
instructions_panel.style.top = this.workarea_size.height / 2 - instructions_panel.clientHeight / 2 + 'px';
this.showPanel( 1 );
};
Tower.prototype.showPanel = function( index ) {
var i, panels = document.getElementsByClassName( 'panel' );
for ( i = 0; i < panels.length; i++ ) {
if ( i === index-1 ) {
panels[i].style.display = 'block';
} else {
panels[i].style.display = 'none';
}
}
}
Tower.prototype.endGame = function() {
var dialog = document.getElementById('endgame');
var response = document.getElementById('endgame_text');
dialog.style.display = 'block';
dialog.style.left = this.workarea_size.width / 2 - dialog.clientWidth / 2 + 'px';
dialog.style.top = this.workarea_size.height / 2 - dialog.clientHeight / 2 + 'px';
response.innerHTML = [
'Oh no, you ran out of time!',
'Your tower finished at <strong>' + (Math.floor(this.height * 100) / 100) + ' meters</strong> tall.',
].join('<br />');
};
Tower.prototype.to2D = function ( pos ) {
var projScreenMat = new THREE.Matrix4();
projScreenMat.multiply( this.camera.projectionMatrix, this.camera.matrixWorldInverse );
projScreenMat.multiplyVector3( pos );
return {
x: ( pos.x + 1 ) * this.renderer.domElement.clientWidth / 2,
y: ( - pos.y + 1) * this.renderer.domElement.clientHeight / 2
};
}
Tower.prototype.initEvents = function() {
this.onPopupShown = this.onPopupShown.bind( this );
this.onPopupUnload = this.onPopupUnload.bind( this );
this.onPopupShowing = this.onPopupShowing.bind( this );
this.onPopupHidden = this.onPopupHidden.bind( this );
};
Tower.prototype.initGUI = function() {
this.gui = {
currentheight: document.getElementById('currentheight'),
timeleft: document.getElementById('timeleft')
};
};
Tower.prototype.updateGUI = function() {
var i, formatted_time, self = this;
// Time left
formatted_time = {
m: Math.floor(this.timeleft / 60),
s: Math.floor((this.timeleft % 60) * 100) / 100
};
this.gui.timeleft.textContent = (formatted_time.m + ':') + ( formatted_time.s < 10 ? '0' + formatted_time.s : formatted_time.s );
// Height
this.gui.currentheight.textContent = Math.floor(this.height * 100) / 100 + 'm';
// Time bonuses
for ( i = 0; i < this.timebonuses.length; i++ ) {
if ( !this.timebonuses[i].achieved ) {
if ( this.height >= this.timebonuses[i].height ) {
this.totaltime += this.timebonuses[i].bonus;
this.timebonuses[i].achieved = true;
(function(timebonus) {
var iterations = 0, interval;
self.intervals.push( interval = setInterval(
function() {
if ( ++iterations == 90 ) {
clearInterval( interval );
timebonus.element.style.display = 'none';
return;
}
timebonus.element.style.webkitTransform = 'scale(' + ( 1 + iterations / 30 ) + ')';
timebonus.element.style.opacity = 1 - iterations / 90;
},
1 / 60
) );
})(this.timebonuses[i]);
if ( this.timebonuses[i+1] ) {
this.timebonuses[i+1].element.style.display = 'block';
}
} else {
this.timebonuses[i].element.style.top = this.to2D( new THREE.Vector3( 0, this.timebonuses[i].height, 0 ) ).y + 'px';
break;
}
}
}
};
Tower.prototype.initThree = function() {
// Projector
this.projector = new THREE.Projector();
// Renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize( this.workarea_size.width, this.workarea_size.height );
document.getElementById('viewport').appendChild( this.renderer.domElement );
this.renderer.shadowMapEnabled = true;
this.renderer.shadowMapSoft = true;
// Event bindings
this.renderer.domElement.addEventListener( 'mousemove', this.handleMouseMove.bind( this, this.renderer.domElement ) );
this.renderer.domElement.addEventListener( 'mouseup', this.handleMouseUp.bind( this, this.renderer.domElement ) );
this.renderer.domElement.addEventListener( 'contextmenu', function( evt) { evt.preventDefault(); } );
this.handleMouseWheel = this.handleMouseWheel.bind( this );
window.document.addEventListener( 'mousewheel', this.handleMouseWheel );
// Scene
this.scene = new THREE.Scene();
// Camera
this.camera = new THREE.PerspectiveCamera(
35,
this.workarea_size.width / this.workarea_size.height,
1,
100
);
this.camera.position.set( 0, 65, 20 );
this.scene.add( this.camera );
};
Tower.prototype.initTextures = function() {
var shader, uniforms, parameters;
this.textures = {};
this.materials = {};
this.materials.empty = new THREE.MeshBasicMaterial;
// Container
this.materials.container = new THREE.MeshBasicMaterial({ color: 0xa6d8ed, transparent: true, opacity: .2 });
// Ground
this.textures.grass = THREE.ImageUtils.loadTexture( 'assets/textures/ground.png' );
this.textures.grass.wrapS = this.textures.grass.wrapT = THREE.RepeatWrapping;
this.textures.grass.repeat.x = 5;
this.textures.grass.repeat.y = 4;
this.materials.ground = new THREE.MeshLambertMaterial({ map: this.textures.grass })
// wood
this.textures.wood = THREE.ImageUtils.loadTexture( 'assets/textures/wood.jpg' );
this.textures.wood.wrapS = this.textures.wood.wrapT = THREE.RepeatWrapping;
this.textures.wood.repeat.x = 1;
this.textures.wood.repeat.y = 1;
this.materials.wood = new THREE.MeshLambertMaterial({ map: this.textures.wood });
// Stone
this.textures.stone_diffuse = THREE.ImageUtils.loadTexture( 'assets/textures/stone_diffuse.png' );
this.textures.stone_diffuse.wrapS = this.textures.stone_diffuse.wrapT = THREE.RepeatWrapping;
this.textures.stone_normal = THREE.ImageUtils.loadTexture( 'assets/textures/stone_normal.png' );
this.textures.stone_normal.wrapS = this.textures.stone_normal.wrapT = THREE.RepeatWrapping;
var shader = THREE.ShaderUtils.lib[ "normal" ];
var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
uniforms[ "enableAO" ].value = false;
uniforms[ "enableDiffuse" ].value = true;
uniforms[ "enableSpecular" ].value = false;
uniforms[ "enableReflection" ].value = false;
uniforms[ "tNormal" ].texture = this.textures.stone_normal;
uniforms[ "tDiffuse" ].texture = this.textures.stone_diffuse;
//uniforms[ "tDisplacement" ].texture = THREE.ImageUtils.loadTexture( "assets/textures/stone_displacement.png" );
uniforms[ "uDisplacementBias" ].value = 0;
uniforms[ "uDisplacementScale" ].value = 0;
uniforms[ "uSpecularColor" ].value.setHex( 0x080808 );
uniforms[ "uAmbientColor" ].value.setHex( 0x050505 );
uniforms[ "uShininess" ].value = 2;
uniforms[ "uRepeat" ].value.set( 1.4, 1.4 );
var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: false };
this.materials.stone = new THREE.ShaderMaterial( parameters );
};
Tower.prototype.initScene = function() {
var i;
// Sky
this.sky = document.getElementById('sky');
// ammo setup
var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
var dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
var overlappingPairCache = new Ammo.btDbvtBroadphase();
var solver = new Ammo.btSequentialImpulseConstraintSolver();
this.scene.world = new Ammo.btDiscreteDynamicsWorld( dispatcher, overlappingPairCache, solver, collisionConfiguration );
this.scene.world.setGravity(new Ammo.btVector3(0, -12, 0));
// light
var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 10, 50, 40 );
light.target.position.set( 0, 0, 0 );
light.castShadow = true;
light.shadowCameraLeft = -20;
light.shadowCameraRight = 20;
light.shadowCameraTop = 20;
light.shadowCameraBottom = -20;
light.shadowBias = .0001;
light.shadowDarkness = 0.35;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
this.scene.add( light );
// ground
var ground = new THREE.Mesh(
new THREE.CubeGeometry( 60, 2, 60, 1, 1, 1, [
this.materials.empty,
this.materials.empty,
this.materials.ground,
this.materials.empty,
this.materials.empty,
this.materials.empty
],
{ px: false, nx: false, py: true, ny: false, pz: false, nz: false }
),
new THREE.MeshFaceMaterial
);
ground.receiveShadow = true;
ground.position.y = -2;
ground.id = 'ground';
var groundShape = new Ammo.btBoxShape(new Ammo.btVector3( 30, 1, 30 ));
var groundTransform = new Ammo.btTransform();
groundTransform.setIdentity();
groundTransform.setOrigin(new Ammo.btVector3( 0, -2, 0 ));
var mass = 0;
var localInertia = new Ammo.btVector3(0, 0, 0);
var myMotionState = new Ammo.btDefaultMotionState( groundTransform );
var rbInfo = new Ammo.btRigidBodyConstructionInfo( 0, myMotionState, groundShape, localInertia );
var groundbody = new Ammo.btRigidBody( rbInfo );
this.scene.world.addRigidBody( groundbody );
groundbody.mesh = ground;
this.scene.add( ground );
// Tower container
this.container = new THREE.Mesh(
new THREE.CubeGeometry(
15, 400, 12, 1, 1, 1, [],
{ px: true, nx: true, py: false, ny: false, pz: false, nz: false}
),
this.materials.container
);
this.container.doubleSided = true;
this.container.position.y = 198;
this.scene.add( this.container );
};
Tower.prototype.handleResize = function() {
this.workarea_size = pokki.getWorkAreaSize();
if ( this.workarea_size.width > 910) this.workarea_size.width = 910;
if ( this.workarea_size.height > 710) this.workarea_size.height = 710;
pokki.setPopupClientSize(this.workarea_size.width, this.workarea_size.height);
// Update content container
document.getElementById('content').style.width = this.workarea_size.width + 'px';
document.getElementById('content').style.height = this.workarea_size.height + 'px';
};
Tower.prototype.start = function() {
this.createBlock();
this.updateCamera({ delay: 2000 });
this.starttime = Math.floor(new Date().getTime() / 1000);
this.render();
this.updateHeight();
};
Tower.prototype.render = function() {
var i, self = this, now = Math.floor(new Date().getTime() / 1000);
if (this.paused) {
requestAnimFrame( function() { self.render(); } );
return false;
}
this.timeleft = this.totaltime - (now - this.starttime);
this.scene.world.stepSimulation( 1 / 60, 20 );
var transform = new Ammo.btTransform();
for ( i = 0; i < this.boxes.length; i++ ) {
this.boxes[i].getMotionState().getWorldTransform( transform );
var origin = transform.getOrigin();
this.boxes[i].mesh.position.x = origin.x();
this.boxes[i].mesh.position.y = origin.y();
this.boxes[i].mesh.position.z = origin.z();
var rotation = transform.getRotation();
this.boxes[i].mesh.quaternion.x = rotation.x();
this.boxes[i].mesh.quaternion.y = rotation.y();
this.boxes[i].mesh.quaternion.z = rotation.z();
this.boxes[i].mesh.quaternion.w = rotation.w();
if ( i >= this.boxes.length - 3 )
this.boxes[i].activate(); // sometimes a box will get stuck from not moving
}
if ( this.active_block ) {
this.active_block.activate(); // sometimes this box will get stuck from not moving
this.active_block.setLinearVelocity( new Ammo.btVector3( 0, 0, 0 ) );
this.active_block.setAngularVelocity( new Ammo.btVector3( 0, 0, 0 ) );
this.active_block.clearForces();
this.active_block.getMotionState().getWorldTransform( transform );
var position = this.cursor_position.x,
halfwidth = this.active_block.rotated ? this.active_block.mesh.geometry.boundingBox.max.y : this.active_block.mesh.geometry.boundingBox.max.x ;
position = Math.max(
Math.min( position, 7.5 - halfwidth ),
-7.5 + halfwidth
);
transform.setOrigin(new Ammo.btVector3( position, this.height + 4, 0 ));
this.active_block.setCenterOfMassTransform( transform );
}
this.updateGUI();
var manifolds = this.scene.world.getDispatcher().getNumManifolds();
if ( manifolds > this.last_manifolds ) {
switch ( Math.floor(Math.random() * 3) ) {
case 0:
collisionsound1.play();
case 1:
collisionsound2.play();
case 2:
collisionsound3.play();
}
};
this.last_manifolds = manifolds;
if (this.timeleft > 0) {
this.renderer.render( this.scene, this.camera );
requestAnimFrame( function() { self.render(); } );
} else {
this.endGame();
}
};
Tower.prototype.handleMouseMove = function( element, evt ) {
var vector = new THREE.Vector3(
( evt.clientX / this.renderer.domElement.clientWidth ) * 2 - 1,
( evt.clientY / this.renderer.domElement.clientHeight ) * 2 - 1,
1
);
this.projector.unprojectVector( vector, this.camera );
vector.subSelf( this.camera.position ).normalize();
vector.y *= -1;
vector.multiplyScalar( -this.camera.position.z / vector.z );
var position = new THREE.Vector3().copy( this.camera.position ).addSelf( vector );
this.cursor_position.set( position.x, position.y );
};
Tower.prototype.handleMouseUp = function( element, evt ) {
switch ( evt.button ) {
case 0:
// main mouse button
if ( this.active_block ) {
console.debug('here');
this.active_block.setLinearVelocity( new Ammo.btVector3( 0, 0, 0 ) );
this.active_block.setAngularVelocity( new Ammo.btVector3( 0, 0, 0 ) );
this.active_block.clearForces();
this.active_block = null;
this.timeouts.push( setTimeout( this.createBlock, 2000 ) );
}
break;
case 2:
// alternate mouse button
if ( this.active_block ) {
var transform = new Ammo.btTransform;
this.active_block.getMotionState().getWorldTransform( transform );
var rotation = transform.getRotation();
if ( this.active_block.rotated ) {
rotation.setEuler( 0, 0, 0 );
this.active_block.rotated = false;
} else {
rotation.setEuler( 0, 0, Math.PI / 2 );
this.active_block.rotated = true;
}
transform.setRotation( rotation );
this.active_block.setWorldTransform( transform );
}
break;
}
evt.preventDefault();
evt.cancelBubble = true;
};
Tower.prototype.handleMouseWheel = function( evt ) {
if ( evt.wheelDelta < 0 ) {
this.updateCamera({ adjust: -8 });
} else {
this.updateCamera({ adjust: 8 });
}
}
Tower.prototype.createBlock = function() {
var mesh,
width, height, mass,
startTransform, localInertia, boxShape, myMotionState, rbInfo, box;
this.height = this.getHeight();
// Update camera position
this.updateCamera();
var block_type = Math.round(Math.random() * 1), material;
switch( block_type ) {
case 1:
// wood
material = this.materials.wood;
width = Math.ceil( .001 + Math.round(Math.random() * 1) );
height = Math.ceil( 4 + Math.random() * 3 );
mass = width * height * 2;
break;
default:
// stone
material = this.materials.stone;
width = Math.ceil( 1 + Math.random() * 4 );
height = Math.ceil( 1 + Math.random() * 3 )
mass = width * height * 4;
break;
}
mesh = new THREE.Mesh(
new THREE.CubeGeometry( width, height, 5 ),
material
);
mesh.geometry.computeBoundingBox();
mesh.geometry.computeTangents();
mesh.receiveShadow = true;
mesh.castShadow = true;
mesh.useQuaternion = true;
this.scene.add( mesh );
startTransform = new Ammo.btTransform();
startTransform.setIdentity();
startTransform.setOrigin(new Ammo.btVector3( this.cursor_position.x, this.height + 4, 0 ));
localInertia = new Ammo.btVector3(0, 0, 0);
boxShape = new Ammo.btBoxShape(new Ammo.btVector3( width / 2, height / 2, 2.5 ));
boxShape.calculateLocalInertia( mass, localInertia );
myMotionState = new Ammo.btDefaultMotionState( startTransform );
rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, myMotionState, boxShape, localInertia );
box = new Ammo.btRigidBody( rbInfo );
this.scene.world.addRigidBody( box );
box.mesh = mesh;
this.boxes.push( box );
this.active_block = box;
return box;
};
Tower.prototype.getHeight = function( exclusive ) {
var i, obj_height, height = 0;
for ( i = 0; i < this.boxes.length - ( exclusive ? 1 : 0 ); i++ ) {
obj_height = this.boxes[i].mesh.position.y + this.boxes[i].mesh.boundRadius;
if ( obj_height > height ) {
height = obj_height;
}
}
return height;
};
Tower.prototype.updateHeight = function() {
this.height = this.getHeight( true );
this._updateHeight = this.timeouts.push( setTimeout( this.updateHeight ) );
};
Tower.prototype.updateCamera = function( options ) {
if ( this._cameratween ) {
this._cameratween.stop();
this._skytween.stop();
}
options = options || {};
var target_height, target_depth;
if ( options.adjust ) {
target_height = this.camera.position.y + options.adjust;
target_depth = 35 + (target_height) / 5;
} else {
target_height = this.height;
target_depth = 35 + this.height / 5;
}
target_height = Math.min( Math.max( 4, target_height ), 65 );
target_depth = Math.min( 40, target_depth );
this._cameratween = new TWEEN.Tween( this.camera.position ).to(
{ y: target_height, z: target_depth },
options.delay || 500
).easing( TWEEN.Easing.Quadratic.EaseOut ).start();
this._skytween = new TWEEN.Tween( this.sky ).to(
{ scrollTop: 3500 - target_height * 60 },
options.delay || 500
).easing( TWEEN.Easing.Quadratic.EaseOut ).start();
};
Tower.prototype.unload = function() {
var i;
for ( i = 0; i < this.timeouts.length; i++ ) {
clearTimeout( this.timeouts[i] );
}
for ( i = 0; i < this.intervals.length; i++ ) {
clearInterval( this.intervals[i] );
}
this.renderer.domElement.parentNode.removeChild( this.renderer.domElement );
window.document.removeEventListener( 'mousewheel', this.handleMouseWheel );
for ( i = 0; i < this.timebonuses.length; i++ ) {
document.getElementById('viewport').removeChild( this.timebonuses[i].element );
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment