Code viewer for World: Pool Predictor Prototype

// Cloned by lillisl2 on 28 Jan 2019 from World "Cloned Bouncy Balls" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 28 Jan 2019 from World "Bouncy Balls" by Starter user 
// Please leave this clone trail here.
 


// ==== Starter World ===============================================================================================
// (c) Ancient Brain Ltd. All rights reserved.
// This code is only for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// This code may not be copied, re-published or used on any other website.
// To include a run of this code on another website, see the "Embed code" links provided on the Ancient Brain site.
// ==================================================================================================================



// Physijs based World
// Bouncy balls 






// ===================================================================================================================
// === 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.screenshotStep  = 200;   
  
	// Take screenshot on this step. (All resources should have finished loading.) Default 50.



const BALLSIZE = 5 ;

const BALLPOS       = BALLSIZE * 2 ; 		// x,z start position of balls is random in this interval
const BALLHEIGHT    = BALLSIZE * 1 ; 		// y start position of balls 

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


const GROUNDSIZE    = 150;

const startRadius   = BALLSIZE * 30 ;
const maxRadius     = BALLSIZE * 250 ;

	
	const TEXTURE_GROUND 	= '/uploads/gilligi2/8451323128.png' ;
//	const TEXTURE_GROUND 	= '/uploads/starter/latin.jpg' ;
    const TEXTURE_WALL = "/uploads/lillisl2/8451232143.png";

	const SOUND_COLLISION 	= '/uploads/starter/bounce.mp3' ;
 
 // credit:
 // http://soundbible.com/1343-Jump.html
 
 
const SKYCOLOR  = 0xffffff;    


// friction and restitution between 0 and 1: 

const GROUND_FRICTION 		= 0.4;
const GROUND_RESTITUTION 	= 0.95;

const WALL_FRICTION 		= 0.6;
const WALL_RESTITUTION 	= 0.3;

const BALL_FRICTION 		= 0.4;
const BALL_RESTITUTION 		= 0.5;



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

	const BALL_MASS = 6;

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


 const FILE_ARRAY = [
                "/uploads/gilligi2/8451323595.png",
                "/uploads/gilligi2/8451323612.png"
                ];

				
ABWorld.drawCameraControls = false; 


// ===================================================================================================================
// === 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;
	

 	
function World() 
{ 

	var textureArray = new Array ( FILE_ARRAY.length );
	var ground_texture ;
 
	var self = this;	
	
	
function loadResources()		// asynchronous file loads - call initScene() when all finished 
{
	var loader = new THREE.TextureLoader();
	
	loader.load ( TEXTURE_GROUND, function ( thetexture )  		// asynchronous file load
	{
		thetexture.minFilter  = THREE.LinearFilter;
		ground_texture = thetexture;
		if ( asynchFinished() ) initScene();		 
	});
		
	for ( var i = 0; i < FILE_ARRAY.length; i++ ) 
	  startFileLoad ( i );		  								// start n more asynchronous file loads 
	  
	  
	var loader2 = new THREE.TextureLoader();
	
	loader2.load ( TEXTURE_WALL, function ( thetexture )  		// asynchronous file load
	{
		thetexture.minFilter  = THREE.LinearFilter;
		wall_texture = thetexture;
		if ( asynchFinished() ) initScene();		 
	});
		
	for ( var j = 0; j < FILE_ARRAY.length; j++ ) 
	  startFileLoad ( j );	
}


function startFileLoad ( n )				// asynchronous file load of texture n 
{
	var loader = new THREE.TextureLoader();

	loader.load ( FILE_ARRAY[n], function ( thetexture )  	 
	{
		thetexture.minFilter  = THREE.LinearFilter;
		textureArray[n] = thetexture;
		if ( asynchFinished() ) initScene();		 
	});	
}
 
 
function asynchFinished()		// all file loads returned 
{
	if ( ! ground_texture ) return false; 
	
	for ( var i = 0; i < FILE_ARRAY.length; i++ ) 
		if ( ! textureArray[i] ) 
			return false;
		
	return true;
}


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

	var light = new THREE.DirectionalLight( 0xFFFFFF, 1.3 );
	
	// close to origin, high up, works best for shadows
	light.position.set ( BALLHEIGHT, (BALLHEIGHT * 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;

    // higher quality shadows at expense of computation time:
	light.shadow.mapSize.width  = 2048;
	light.shadow.mapSize.height = 2048;
	
	light.shadow.bias = -0.0001;

	ABWorld.scene.add( light );


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

	var ground_material = Physijs.createMaterial(
			// new THREE.MeshBasicMaterial( { color: 0xdddddd  } ),
			new THREE.MeshStandardMaterial({  map: ground_texture }),
			GROUND_FRICTION,
			GROUND_RESTITUTION );
		
	ground_material.map.wrapS = THREE.RepeatWrapping;
	ground_material.map.wrapT = THREE.RepeatWrapping;
	ground_material.map.repeat.set( 50, 50 );
		
		
	// ground as plane allows bounces but seems to be finite box

	var ground = new Physijs.BoxMesh (
			new THREE.BoxGeometry ( GROUNDSIZE, 1, GROUNDSIZE*2 ),
			ground_material,0 );
			
			

    //ground.rotation.x = (Math.PI / 2) * 3;

	/*
	// ground as box is finite (beyond it, things fall into the void)
	// but it does not seem to allow bounces

		ground = new Physijs.BoxMesh (
			new THREE.BoxGeometry ( GROUNDSIZE, 1, GROUNDSIZE ),
			ground_material, 
			0 );				 
	*/

	ground.receiveShadow = true;
	ABWorld.scene.add( ground );
	
	var wall_material = Physijs.createMaterial(
			// new THREE.MeshBasicMaterial( { color: 0xdddddd  } ),
			new THREE.MeshStandardMaterial({  map: wall_texture }),
			WALL_FRICTION,
			WALL_RESTITUTION );
			
	wall_material.map.wrapS = THREE.RepeatWrapping;
	wall_material.map.wrapT = THREE.RepeatWrapping;
	wall_material.map.repeat.set( 50, 50 );


	var wall = new Physijs.BoxMesh (
	        new THREE.BoxGeometry(6, 25, GROUNDSIZE*2),
	        wall_material,0 );
	
	wall.position.set((GROUNDSIZE / 2),0,0);
	wall.receiveShadow = true;
	ABWorld.scene.add( wall );
	
    wall.addEventListener('collision', function(object) {
    console.log("Object " + this.id + " collided with " + object.id);
    });
	
	var wall2 = new Physijs.BoxMesh (
	        new THREE.BoxGeometry(GROUNDSIZE, 25, 6),
	        wall_material,0 );
	
	wall2.position.set(0,0,(GROUNDSIZE));
	wall2.receiveShadow = true;
	ABWorld.scene.add( wall2 );
	
	wall2.addEventListener('collision', function(object) {
    console.log("Object " + this.id + " collided with " + object.id);
    });
	
	var wall3 = new Physijs.BoxMesh (
	        new THREE.BoxGeometry(6, 25, GROUNDSIZE*2),
	        wall_material,0 );
	
	wall3.position.set(-(GROUNDSIZE / 2),0,0);
	wall3.receiveShadow = true;
	ABWorld.scene.add( wall3 );
	
	wall3.addEventListener('collision', function(object) {
    console.log("Object " + this.id + " collided with " + object.id);
    });
	
	var wall4 = new Physijs.BoxMesh (
	        new THREE.BoxGeometry(GROUNDSIZE, 25, 6),
	        wall_material,0 );
	
	wall4.position.set(0,0,-(GROUNDSIZE));
	wall4.receiveShadow = true;
	ABWorld.scene.add( wall4 );
	
	wall4.addEventListener('collision', function(object) {
    console.log("Object " + this.id + " collided with " + object.id);
    });
	// start infinite loop - but maybe neutralised until splashClicked is true 
	
	ABWorld.render();

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

 

function createBall() 	 
{
	
 if ( resourcesLoaded && splashClicked ) 		  
 {
	 
 // (subtle bug) apparently each ball needs its own material if it is to bounce separately
 
    var ball_material =    Physijs.createMaterial (
            new THREE.MeshLambertMaterial({ map:  AB.randomElementOfArray(textureArray)  }),  
            BALL_FRICTION,
		    BALL_RESTITUTION );
	
	var ball_geometry = new THREE.SphereGeometry ( BALLSIZE, 50, 50 );
	
    var ball = new Physijs.SphereMesh ( ball_geometry, ball_material, BALL_MASS );                   
        
	ball.collisions = 0;
	ball.castShadow = true;
				
	ball.position.set(  AB.randomIntAtoB(-BALLPOS,BALLPOS),		BALLHEIGHT,		AB.randomIntAtoB(-BALLPOS,BALLPOS)	);
				
	ball.addEventListener( 'collision', function( other_object, relative_velocity, relative_rotation, contact_normal ) 
	{
		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();
		}
	});
	
	ABWorld.scene.add( ball );
 
 }
 
 	// --- call createBall again -----------------------------------------------------

	setTimeout ( createBall, nextballin()  );		 		// set to create next ball in some random time 
}


// --- keyboard and touch handling functions: ----------------------------------------


var OURKEYS = [49,50];

function ourKeys ( event ) { return ( OURKEYS.includes ( event.keyCode ) ); }
        
function keyHandler ( event )           
{
        if ( ! ABRun.runReady ) return true;            // not ready yet 

    // if not handling this key, send it to default: 
        
        if ( ! ourKeys ( event ) ) return true;
        
        // else handle it and prevent default:
        
        if ( event.keyCode == 49 )   createBall();   
        if ( event.keyCode == 50 )   createBall();    
        
        event.stopPropagation(); event.preventDefault(); return false;
}







// --- public interface ----------------------------------------------------------------------

	
this.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 );
	  
	  
	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

    document.onkeydown                      = keyHandler;

};



this.nextStep = function()		// not used 
{
};

 
}


	 

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

// This is a more sophisticated solution to get audio autoplay on mobile/Chrome.
// Splash screen forces user interaction, which marks *multiple* audio global variables as "approved" for later play.
// Then when we need to play audio, find one of these global variables that is not already playing, and play it.
 

// global variable audio objects:

var theaudio1 = new Audio( SOUND_COLLISION );
var theaudio2 = new Audio( SOUND_COLLISION );
var theaudio3 = new Audio( SOUND_COLLISION );
var theaudio4 = new Audio( SOUND_COLLISION );
var theaudio5 = new Audio( SOUND_COLLISION );



function soundCollision()		// triggered by JS, not by user interaction 
{
	
	if ( ! AB.audioIsPlaying ( theaudio1 ) ) 	{ theaudio1.play(); return; }
	if ( ! AB.audioIsPlaying ( theaudio2 ) ) 	{ theaudio2.play(); return; }
	if ( ! AB.audioIsPlaying ( theaudio3 ) ) 	{ theaudio3.play(); return; }
	if ( ! AB.audioIsPlaying ( theaudio4 ) ) 	{ theaudio4.play(); return; }
	if ( ! AB.audioIsPlaying ( theaudio5 ) ) 	{ theaudio5.play(); return; }
	
	// else try to make a new one, local variable, without user interaction
	// but this will not play on some browsers:

	var a = new Audio( SOUND_COLLISION );		
	a.play();
}
 
  
  
 
	
// --- Splash screen --------------------------------------------------------------

	
	AB.newSplash ( "Demo of Physics API" );	

	
	// when user clicks/touches button on splash screen, audio starts and run starts:
		
	$("#splashbutton").click ( function ()        
	{
		// mark multiple audio objects as good now for JS to call play() without further user interaction:
		theaudio1.play();	theaudio1.pause();		   
		theaudio2.play();	theaudio2.pause();		   
		theaudio3.play();	theaudio3.pause();		   
		theaudio4.play();	theaudio4.pause();		   
		theaudio5.play();	theaudio5.pause();		   

		AB.removeSplash();			// remove splash screen 
		
		splashClicked = true;
		
	});