Code viewer for World: Collision World

// ==== Starter World =================================================================================================
// This code is designed for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.
// ====================================================================================================================





// Demo of Physijs based World

// From here, with many changes:
// http://chandlerprall.github.io/Physijs/examples/collisions.html



	
	

// ===================================================================================================================
// === Start of tweaker's box ======================================================================================== 
// ===================================================================================================================

// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?


AB.clockTick       = 100;    

	// Speed of run: Step every n milliseconds. Default 100.
	
AB.maxSteps        = 2000;    

	// Length of run: Maximum length of run in steps. Default 1000.

AB.screenshotStep  = 200;   
  
	// Take screenshot on this step. (All resources should have finished loading.) Default 50.


const BOXSIZE       = 5 ;

const BOXPOS        = BOXSIZE * 2 ; 	    	// range of box x,z start positions 
const BOXHEIGHT     = BOXSIZE * 10 ; 			// y start position of boxes 

const HEAVYIMPACT   = 30 ;   				 	// heavy impacts have y velocity greater than this 

const GROUNDSIZE    = BOXSIZE * 40;
				
const startRadius   = BOXSIZE * 15 ;
const maxRadius     = BOXSIZE * 250 ;

	
	const TEXTURE_FILE_0   	= '/uploads/starter/smiley.png' ;
	const TEXTURE_FILE_1   	= '/uploads/starter/ghost.3.png' ;
	
// https://commons.wikimedia.org/wiki/Smiley
	
	
	const TEXTURE_GROUND 	= '/uploads/starter/rocks.jpg' ;

	
	const SOUND_COLLISION 	= '/uploads/starter/wooden.mp3' ;
 
 // credit:
 // http://soundbible.com/715-Dropped-Wooden-Floor.html
 
 	const SOUND_BEEP 	= '/uploads/starter/beep.mp3' ;
 
 
const SKYCOLOR  = 0xffffcc;    

const GROUND_FRICTION 		= 0.8;
const GROUND_RESTITUTION 	= 0.3;

const BOX_FRICTION 			= 0.6;
const BOX_RESTITUTION 		= 0.3;

  
// define gravity along x,y,z dimensions:
	var gravity = new THREE.Vector3 ( 0,   -30, 0 );
//	var gravity = new THREE.Vector3 ( -30, -30, 0 );


function nextboxin()
// Speed of box creation 
// Create next box in some random (m to n) milliseconds time.
{
	return ( AB.randomIntAtoB ( 2000, 4000 ) );		 
//	return ( AB.randomIntAtoB ( 200, 1000 ) );	
}


// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================


// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.








	var resourcesLoaded = false;
	var splashClicked = false;
	

 	

	var box_texture_0, box_texture_1 ;
	var ground_texture ;

	var boxno = 0;
	
	var currentbox ;
	
		
		
	
function loadResources()		// asynchronous file loads - call initScene() when all finished 
{
	var loader1 = new THREE.TextureLoader();
	var loader2 = new THREE.TextureLoader();
	var loader3 = new THREE.TextureLoader();
	
	loader1.load ( TEXTURE_GROUND, function ( thetexture )  		
	{
		thetexture.minFilter  = THREE.LinearFilter;
		ground_texture = thetexture;
		if ( asynchFinished() )	initScene();		// if all file loads have returned 
	});
		
	loader2.load ( TEXTURE_FILE_0, function ( thetexture )  	 
	{
		thetexture.minFilter  = THREE.LinearFilter;
		box_texture_0 = thetexture;
		if ( asynchFinished() )	initScene();		 
	});	
	
	loader3.load ( TEXTURE_FILE_1, function ( thetexture )  
	{
		thetexture.minFilter  = THREE.LinearFilter;
		box_texture_1 = thetexture;
		if ( asynchFinished() )	initScene();		 
	});
}


function asynchFinished()		// all file loads returned 
{
	if ( ground_texture && box_texture_0 && box_texture_1 )    return true;  
	else return false;
}	
	
	

function initScene()		// all file loads have returned 
{
	// --- Light ------------------------------------------------------------------

	var  light  = new THREE.DirectionalLight( 0xFFFFFF, 1.3 );

	light.position.set ( BOXHEIGHT, (BOXHEIGHT * 2), 0 );
	light.target.position.copy( ABWorld.scene.position );

	light.castShadow = true;
	
	// how far away to draw shadows:
	light.shadow.camera.left    = -GROUNDSIZE;
	light.shadow.camera.right   =  GROUNDSIZE;
	light.shadow.camera.bottom  = -GROUNDSIZE;
	light.shadow.camera.top     =  GROUNDSIZE;

	light.shadow.bias = -0.0001;
	
    // higher quality shadows at expense of computation time
	// slower PCs can struggle if this is too high
	// World author could make it an option at run-time - high or low graphics option - or just shadows on/off
	light.shadow.mapSize.width  = 2048;
	light.shadow.mapSize.height = 2048;

	ABWorld.scene.add( light );


	// --- Ground ------------------------------------------------------------------

	var ground_material = Physijs.createMaterial(
			new THREE.MeshLambertMaterial({ map: ground_texture }),
			GROUND_FRICTION,
			GROUND_RESTITUTION );

	ground_material.map.wrapS = THREE.RepeatWrapping;
	ground_material.map.wrapT = THREE.RepeatWrapping;
	ground_material.map.repeat.set( 3, 3 );
		
	var ground = new Physijs.BoxMesh(
			new THREE.BoxGeometry( GROUNDSIZE, 1, GROUNDSIZE ),
			ground_material,
			0 );					// mass
			
	ground.receiveShadow = true;
	ABWorld.scene.add( ground );
	
	
	// start infinite loop - but maybe neutralised until splashClicked is true 
	
	ABWorld.render();

	console.log ( "Resources loaded." );
	resourcesLoaded = true;
	
	createBox(); 		
	
}


 
function createBox() 							 
{
 // has to be started by resourcesLoaded code 
 // cannot be started by splashClicked code since that is outside World 
 // needs both to be true 
 
 if ( resourcesLoaded && splashClicked ) 		  
 {
	// think each box needs its own material 
	
	var box_material_0  = Physijs.createMaterial(
					new THREE.MeshLambertMaterial({ map: box_texture_0 }), 
					BOX_FRICTION,
					BOX_RESTITUTION	);
												
	var box_geometry = new THREE.BoxGeometry( BOXSIZE, BOXSIZE, BOXSIZE );

	var box = new Physijs.BoxMesh (	box_geometry, box_material_0 );
	
	box.collisions = 0;
	box.castShadow = true;
				
	box.position.set(  AB.randomIntAtoB(-BOXPOS,BOXPOS),		BOXHEIGHT,		AB.randomIntAtoB(-BOXPOS,BOXPOS)	);
				
	box.rotation.set( 	Math.random() * Math.PI,	Math.random() * Math.PI,	Math.random() * Math.PI		);
				
				
	box.addEventListener( 'collision', function( other_object, relative_velocity, relative_rotation, contact_normal ) 
    // box has collided with other_object with an impact speed of relative_velocity and a rotational force of relative_rotation and at normal contact_normal
	{
		// console.log ( relative_velocity );
		var mainImpact = relative_velocity.y;		// impact in direction of gravity 
 		// console.log ( Math.abs(mainImpact) );
 		if (  Math.abs(mainImpact) > HEAVYIMPACT )			// main impact, not lesser ones as it settles  
		{
			soundCollision();
			
			var box_material_1 = Physijs.createMaterial(
					new THREE.MeshLambertMaterial({ map: box_texture_1 }),
					BOX_FRICTION,
					BOX_RESTITUTION	);

			this.material = box_material_1;
		}
	});
	
	
	ABWorld.scene.add( box );
	
	currentbox = box;
	boxno++;
 }
	
	// --- call createBox again -----------------------------------------------------
	
	setTimeout ( createBox, nextboxin() );
	
		// Create next box in some random time.
		// Put this in createBox, not nextStep - runs on independent timer.	 
}




 

	
AB.world.newRun = function() 
{
	ABWorld.init3d ( startRadius, maxRadius, SKYCOLOR  ); 			// sets up renderer, scene, camera
 
 // can adjust renderer:
 
	ABWorld.renderer.shadowMap.enabled = true;

 // scene is Physijs.Scene, not THREE.Scene
 // can adjust scene - change gravity from default:
 
	ABWorld.scene.setGravity ( gravity );
  
// multiply mouse scrolls by some factor to speed up/slow down movement:
//	  ABHandler.SCROLLFACTOR 	= 0.1;
	  
	  
	loadResources();	// asynchronous file loads 
						// calls initScene() when all done 
	

	ABWorld.lookat.copy ( ABWorld.scene.position );  
	 
	ABWorld.scene.simulate();		// Physics simulate - runs on independent timer to AB nextStep

};



AB.world.nextStep = function()
{
	// not much to do each step
	// physics simulate runs on independent timer
	// create box runs on another independent timer

	// for camera control, follow current falling box, whose position changes constantly:  
	if ( currentbox )
		ABWorld.follow.copy ( currentbox.position );           
	
	if ( boxno > 0 )
	{
//	  AB.msg ( "Box " + boxno + " position: <tt> " + AB.vector3toString ( currentbox.position ) + "</tt>" );
	  AB.msg ( "Box " + boxno + " height:   <tt> " + Math.floor(currentbox.position.y)    + "</tt>" );
	}
};

 





	 
// --- audio --------------------------------------------------------------

function soundCollision()
{
    var audio = new Audio( SOUND_COLLISION );
    audio.play();
}
 
function soundBeep()
{
    var audio = new Audio( SOUND_BEEP );
    audio.play();
}
 
 
 
	
// --- Splash screen --------------------------------------------------------------
// Splash screen is to try to get audio events to autoplay on mobile/Chrome.
// This is a simple version. It just tries one audio event (beep) after user interaction, and hopes other audio will now work.

// This simple approach works on some mobile browsers - touch splash screen allows all subsequent audios.
// But does not work on other mobile browsers - touch splash screen plays first audio, but subsequent audios are still silent. 

	
	// display splash screen 
	
	AB.newSplash ( "Demo of Physics API" );	

	
	// when user clicks/touches button on splash screen, audio starts and run starts:
		
	AB.splashClick ( function ()        
	{
		soundBeep();				// audio linked to user interaction    
		AB.removeSplash();			// remove splash screen 

		splashClicked = true;		// will cause createBox to work (if resourcesLoaded is true)
		
	});