Code viewer for World: v0.95 Pool Predictor Prototype

// Cloned by Ian Gilligan on 25 Feb 2019 from World "v0.94 Pool Predictor Prototype" by lillisl2 
// Please leave this clone trail here.
 


// Cloned by lillisl2 on 25 Feb 2019 from World "v0.94 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 25 Feb 2019 from World "v0.92 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 20 Feb 2019 from World "v0.91 Pool Predictor Prototype" by lillisl2 
// Please leave this clone trail here.
 


// Cloned by lillisl2 on 20 Feb 2019 from World "v0.9 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 20 Feb 2019 from World "v0.8 Pool Predictor Prototype" by lillisl2 
// Please leave this clone trail here.
 


// Cloned by lillisl2 on 19 Feb 2019 from World "v0.7 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 18 Feb 2019 from World "v0.6 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 16 Feb 2019 from World "v0.5 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 14 Feb 2019 from World "v0.44 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 12 Feb 2019 from World "v0.43 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 5 Feb 2019 from World "v0.3 Pool Predictor Prototype" by lillisl2 
// Please leave this clone trail here.
 


// Cloned by lillisl2 on 4 Feb 2019 from World "v0.2 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 


// Cloned by Ian Gilligan on 1 Feb 2019 from World "lillisl2 0.2 Pool Predictor Prototype" by lillisl2 
// Please leave this clone trail here.
 


// Cloned by lillisl2 on 1 Feb 2019 from World "0.1 Pool Predictor Prototype" by Ian Gilligan 
// Please leave this clone trail here.
 
// Cloned by Ian Gilligan on 30 Jan 2019 from World "Pool Predictor Prototype" by lillisl2 
// Please leave this clone trail here.
 
// 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           = 50;   
  	// Take screenshot on this step. (All resources should have finished loading.) Default 50.


// ===================================================================================================================
// === Variables & Constants =========================================================================================
// ===================================================================================================================

const OBJPATH = "/uploads/gilligi2/";	// path of OBJ and MTL 
const OBJNAME = "PoolTable.obj";
const MTLNAME = "pooltable.mtl";

const MODELLENGTH 			= 11054;							
const MODELWIDTH			= 6279; 
const SCALE                 = 0.009;

const BALLSIZE              = 2;                   // size of each ball
const BALLPOS               = BALLSIZE * 100; 		// x,z start position of balls is random in this interval
const BALLHEIGHT            = BALLSIZE * 2; 		// y start position of balls 
const HEAVYIMPACT           = 10;     			// heavy impacts have y velocity greater than this 

const GROUNDSIZE            = 150;                  // size of table
const startRadius           = MODELWIDTH * 0.02;       // camera start distance
const maxRadius             = MODELLENGTH * 0.03;      // camera max distance
	
const TEXTURE_GROUND 	    = "/uploads/gilligi2/8451323128.png";   //colour of ground (green)
const TEXTURE_WALL          = "/uploads/lillisl2/8451232143.png";   //colour of walls (brown)
const SKYCOLOR              = 0xffffff;    //colour of sky / background (white)

// friction and restitution between 0 and 1: 
var   GROUND_FRICTION 		= 0.99;      // friction of ground
const GROUND_RESTITUTION 	= 0.5;     // restitution of ground
const WALL_FRICTION 		= 0.99;      // friction of wall
const WALL_RESTITUTION  	= 0.9;      // restitution of wall
const BALL_FRICTION 		= 0.99;      // friction of ball
const BALL_RESTITUTION 		= 0.8;      // restitution of ball

var   LIMIT                 = [7,7,1,1];    // limit values of each type of balls [yellow, red, black, white]
var   BALL_CHOICE           = 0;            // the ball that the user has currently selected
const BALL_TYPE             = [             // what each value of ball choice (0-3) represent
                            "Yellow",
                            "Red", 
                            "Black",
                            "White"];
                            
// define gravity along x,y,z dimensions:
var   gravity               = new THREE.Vector3 (0, -150, 0);
const BALL_MASS             = 10;        // mass of ball

const BASE_POWER            = 750;
var   MIN_POWER             = 1;
var   MAX_POWER             = 5;

var   difficulty            = "Easy";
var   YOURCOLOR             = "None";

var   CALCULATED            = false;
var   CALCULATING           = false;

// == Balls Placed ==
var   BALL_COUNT            = 0;
var   BALLS                 = [];
var   WHITE                 = -1;

// == Ball Colors ==
const BALL_COLORS           = {
                            0 : "Yellow",
                            1 : "Red",
                            2 : "Black",
                            3 : "White"};
const BALL_COLORS_R         = {
                            "Yellow" : 0,
                            "Red"    : 1,
                            "Black"  : 2,
                            "White"  : 3};

// == Ball Movement ==
var   movFin                = false;
const SLOWFACTOR            = 0.1;

// == Score Calculation ==
var   WHITE_POT             = false;
var   BLACK_POT             = false;
var   firstHit              = true;
var   YOURSCORE             = 0;
var   OTHERSCORE            = 0;
var   FOUL                  = false;
var   setUpScene;
var   SCORE                 = 50;

// == Shot Choice ==
var   BALLS_TO_POT          = 0;
var   BALLS_ATTEMPTED       = 0;
var   BALLS_CHOSEN          = 0;

// == Camera ==
ABWorld.drawCameraControls  = false; 
var cameraPocketCounter = 0;

// == UI ==
var   PAGE_SELECT = 1;
var   CURRENT_PAGE = 0;
const line4_1 = ("<p align=\"center\"><button id='yellowButton'>Yellow</button> <button id='redButton'>Red</button> <button id='blackButton'>Black</button> <button id='whiteButton'>White</button></p>");
const line4_2 = ("<p align=\"center\"><button id='decreaseFrictionButton'>-</button> <button id='increaseFrictionButton'>+</button></p> ");
const line4_3 = ("<p align=\"center\"><button id='easyButton'>Easy</button> <button id='mediumButton'>Medium</button> <button id='hardButton'>Hard</button></p>");
const line6_1 = ("<p align=\"center\"><button id='yourNButton'>None</button> <button id='yourYButton'>Yellow</button> <button id='yourRButton'>Red</button></p>");
const line6_2 = ("<p align=\"center\"><button id='decreasePowerMinButton'>- Min</button> <button id='increasePowerMinButton'>+ Min</button> <button id='decreasePowerMaxButton'>- Max</button> <button id='increasePowerMaxButton'>+ Max</button></p> ");
const line6_3 = ("<p align=\"center\"><button id='cameraFlatButton'>Flat Down View</button> <button id='cameraCornerButton'>Pocket View</button></p> ");

// == Save ==
BALL_LAYOUT = [];
BEST_SCORES = [];

var toggleBasic = false;
var toggledBasic = false;

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

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

// ===================================================================================================================
// === Inits & Loads =================================================================================================
// ===================================================================================================================


var resourcesLoaded = false;
var splashClicked = false;

function World() 
{ 
	var ground_texture;
	var self = this;
	var GROUND;
	var table;
	var p_height = 25;
	
	// --- Ball ----------------------------------------------------------------------
	var ball_material_0 = Physijs.createMaterial(new THREE.MeshLambertMaterial({color : "yellow"}), BALL_FRICTION, BALL_RESTITUTION);
	var ball_material_1 = Physijs.createMaterial(new THREE.MeshLambertMaterial({color : "red"}), BALL_FRICTION, BALL_RESTITUTION);
	var ball_material_2 = Physijs.createMaterial(new THREE.MeshLambertMaterial({color : "black"}), BALL_FRICTION, BALL_RESTITUTION);
	var ball_material_3 = Physijs.createMaterial(new THREE.MeshLambertMaterial({color : "white"}), BALL_FRICTION, BALL_RESTITUTION);
	var ball_geometry = new THREE.SphereGeometry(BALLSIZE, 100, 100);
	
function loadResources()		// asynchronous file loads - call initScene() when all finished 
{
    var m = new THREE.MTLLoader();
	m.setTexturePath (OBJPATH);
	m.setPath        (OBJPATH);
	
	m.load ( MTLNAME, function ( materials ) 
	{
		materials.preload(); 
		var o = new THREE.OBJLoader();
		o.setMaterials ( materials );
		o.setPath ( OBJPATH );
		
		o.load ( OBJNAME, function ( object ) 
		{
			table = object;
			if ( asynchFinished() )	initScene();		 
		});
	});}

function asynchFinished()		// all file loads returned 
{
	if (! table){
	    return false;}
	return true;}
	
function initScene()			// all file loads have returned 
{
	// --- Light ------------------------------------------------------------------
	var light = new THREE.DirectionalLight(0xFFFFFF, 0.4);
	// close to origin, high up, works best for shadows
	light.position.set(0, 40, 0);
	light.target.position.copy(ABWorld.scene.position);
	light.castShadow = true;
	// how far away to draw shadows:
	light.shadow.camera.left    = -20;
	light.shadow.camera.right   =  20;
	light.shadow.camera.bottom  = -20;
	light.shadow.camera.top     =  20;
    // higher quality shadows at expense of computation time:
	light.shadow.mapSize.width  = 2048;
	light.shadow.mapSize.height = 2048;
	light.shadow.bias           = -0.1;
	ABWorld.scene.add(light);
	var light1 = new THREE.DirectionalLight(0xFFFFFF, 0.4);
	var light2 = new THREE.DirectionalLight(0xFFFFFF, 0.4);
	var light3 = new THREE.DirectionalLight(0xFFFFFF, 0.4);
	var light4 = new THREE.DirectionalLight(0xFFFFFF, 0.4);
	light1.position.set(100, 30, 0);
	light2.position.set(-100, 30, 0);
	light3.position.set(0, 30, 100);
	light4.position.set(0, 30, -100);
	ABWorld.scene.add(light1);
	ABWorld.scene.add(light2);
	ABWorld.scene.add(light3);
	ABWorld.scene.add(light4);

	// --- Table -------------------------------------------------------------------
	table.position.y = 0;
    table.position.x = -15;
    table.position.z = 0; 
    ABWorld.scene.add(table);

	// --- Ground ------------------------------------------------------------------
	var ground_material = Physijs.createMaterial(new THREE.MeshStandardMaterial({wireframe : true}), GROUND_FRICTION, GROUND_RESTITUTION);
	// ground as plane allows bounces but seems to be infinite plane
	var ground = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE, 1, MODELWIDTH * SCALE), ground_material, 0);
	ground.position.y = p_height;
	ground.collisions = 0;
	ground.receiveShadow = true;
	//ABWorld.scene.add(ground);
	GROUND = ground;
	
	// --- Wall --------------------------------------------------------------------
	var wall_material = Physijs.createMaterial(new THREE.MeshStandardMaterial({wireframe : true}), WALL_FRICTION, WALL_RESTITUTION);
	var wall1 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 1.03, 5.9, MODELWIDTH * SCALE * 0.055), wall_material, 0);
	wall1.position.z = 27.5;
    wall1.collisions = 0;
	wall1.receiveShadow = true;
	var wall2 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 1.03, 5.9, MODELWIDTH * SCALE * 0.055), wall_material, 0);
	wall2.position.z = -27.7;
    wall2.collisions = 0;
	wall2.receiveShadow = true;
	var wall3 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 0.02, 5.9, MODELWIDTH * SCALE * 0.92), wall_material, 0);
	wall3.position.x = 52.2;
	wall3.collisions = 0;
	wall3.receiveShadow = true;
	var wall4 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 0.02, 5.9, MODELWIDTH * SCALE * 0.92), wall_material, 0);
	wall4.position.x = -52.2;
    wall4.collisions = 0;
	wall4.receiveShadow = true;

    // --- Bumper --------------------------------------------------------------------
    var bumper1 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 0.33, 5.9, MODELWIDTH * SCALE * 0.075 * 1.65), wall_material, 0);
	bumper1.position.z = 23.5;
	bumper1.position.x = 23.5;
	bumper1.position.y = -0.1;
	bumper1.collisions = 0;
	bumper1.receiveShadow = true;
    var bumper2 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 0.33, 5.9, MODELWIDTH * SCALE * 0.075 * 1.65), wall_material, 0);
	bumper2.position.z = 23.5;
	bumper2.position.x = -23;
	bumper2.collisions = 0;
	bumper2.receiveShadow = true;
	var bumper3 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 0.33, 5.9, MODELWIDTH * SCALE * 0.075 * 1.58), wall_material, 0);
	bumper3.position.z = -23.5;
	bumper3.position.x = 23.5;
	bumper3.position.y = -0.1;
	bumper3.collisions = 0;
	bumper3.receiveShadow = true;
	var bumper4 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 0.33, 5.9, MODELWIDTH * SCALE * 0.075 * 1.58), wall_material, 0);
	bumper4.position.z = -23.5;
	bumper4.position.x = -23;
	bumper4.collisions = 0;
	bumper4.receiveShadow = true;
	var bumper5 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 0.047, 5.9, MODELWIDTH * SCALE * 0.56), wall_material, 0);
	bumper5.position.z = 0;
	bumper5.position.x = 49;
	bumper5.receiveShadow = true;
	var bumper6 = new Physijs.BoxMesh(new THREE.BoxGeometry(MODELLENGTH * SCALE * 0.047, 5.9, MODELWIDTH * SCALE * 0.56), wall_material, 0);
	bumper6.position.z = 0;
	bumper6.position.x = -49;
	bumper6.receiveShadow = true;
	ground.add(wall1);
	ground.add(wall2);
	ground.add(wall3);
	ground.add(wall4);
	ground.add(bumper1);
	ground.add(bumper2);
	ground.add(bumper3);
	ground.add(bumper4);
	ground.add(bumper5);
	ground.add(bumper6);
	//ground.visible = false;
	ABWorld.scene.add(ground);

    // --- Pocket --------------------------------------------------------------------
	var pocket_material = Physijs.createMaterial(new THREE.MeshStandardMaterial());
    var pocket = new Physijs.SphereMesh(new THREE.SphereGeometry(1), pocket_material, 0);
    var pocket1 = new Physijs.SphereMesh(new THREE.SphereGeometry(3, 15, 15), pocket_material, 0);
    pocket1.position.y = p_height;
    pocket1.position.z = 21.5;
    pocket1.position.x = 47;
    var pocket2 = new Physijs.SphereMesh(new THREE.SphereGeometry(3, 15, 15), pocket_material, 0);
    pocket2.position.y = p_height;
    pocket2.position.z = -21.5;
    pocket2.position.x = 47;
    var pocket3 = new Physijs.SphereMesh(new THREE.SphereGeometry(3, 15, 15), pocket_material, 0);
    pocket3.position.y = p_height;
    pocket3.position.z = 24;
    var pocket4 = new Physijs.SphereMesh(new THREE.SphereGeometry(3, 15, 15), pocket_material, 0);
    pocket4.position.y = p_height;
    pocket4.position.z = 21.5;
    pocket4.position.x = -47;
    var pocket5 = new Physijs.SphereMesh(new THREE.SphereGeometry(3, 15, 15), pocket_material, 0);
    pocket5.position.y = p_height;
    pocket5.position.z = -21.5;
    pocket5.position.x = -47;
    var pocket6 = new Physijs.SphereMesh(new THREE.SphereGeometry(3, 15, 15), pocket_material, 0);
    pocket6.position.y = p_height;
    pocket6.position.z = -24;
    pocket.add(pocket1);
    pocket.add(pocket2);
    pocket.add(pocket3);
    pocket.add(pocket4);
    pocket.add(pocket5);
    pocket.add(pocket6);
    pocket.addEventListener('collision', function(other_object, relative_velocity, relative_rotation, contact_normal) 
	{
        removeBall(other_object);
	});
	pocket.visible = false;
    ABWorld.scene.add(pocket);
	ABWorld.render();
	console.log("Resources loaded.");
	resourcesLoaded = true;}

// ===================================================================================================================
// === Button Functions ==============================================================================================
// ===================================================================================================================

function increaseFriction(){
    if (GROUND_FRICTION + 0.05 < 1.0){
        GROUND_FRICTION += 0.05;}
    else    {GROUND_FRICTION = 1.0;}
}

function decreaseFriction(){
    if (GROUND_FRICTION - 0.05 > 0.0){
        GROUND_FRICTION -= 0.05;}
    else    {GROUND_FRICTION = 0.0;}
}

function decreasePowerMin(){
    if (MIN_POWER > 1) {MIN_POWER -= 1;}
}

function increasePowerMin(){
    if (MIN_POWER < MAX_POWER) {MIN_POWER += 1;}
}

function decreasePowerMax(){
    if (MIN_POWER < MAX_POWER) {MAX_POWER -= 1;}
}
    
function increasePowerMax(){
    if (MAX_POWER < 5) {MAX_POWER += 1;}
}

function selectBall(x)
{
    BALL_CHOICE = x;
}
    
function setDifficulty(x){
    difficulty = x;
}

function setColor(x){
    YOURCOLOR = x;
}

function pageChange(x){
    PAGE_SELECT = x;
}

function cameraFlatDown(){
    ABWorld.camera.position.set(0,100,0);
}

function cameraSomeCornerPocket(){

    if (cameraPocketCounter % 4 === 0){
        ABWorld.camera.position.set(50,50,30);
    }
    else if (cameraPocketCounter % 4 == 1){
        ABWorld.camera.position.set(50,50,-30);
    }
    else if(cameraPocketCounter % 4 == 2){
        ABWorld.camera.position.set(-50,50,-30);
    }
    else if(cameraPocketCounter % 4 == 3){
       ABWorld.camera.position.set(-50,50, 30); 
    }
    cameraPocketCounter = cameraPocketCounter + 1;
}

// ===================================================================================================================
// === Shot Calculations =============================================================================================
// ===================================================================================================================


function calculateShot()
{
    prepareHit();
    CALCULATING = true;
    hitBall();
}

function prepareHit()
{
    if (YOURCOLOR == "None"){
        BALLS_TO_POT = 14;
    }
    else if (YOURCOLOR != "None"){
        BALLS_TO_POT = colorCount(YOURCOLOR);
        if (BALLS_TO_POT === 0){
            BALLS_TO_POT = 1;
        }
    }
    BALLS_ATTEMPTED = 0;
}

function hitBall()
{
    movFin = false;
    ABWorld.scene.addEventListener("update", function(){
        if (movementFinished()){
            SCORE = calculateScore(YOURSCORE, OTHERSCORE, WHITE_POT, BLACK_POT);
            console.log("Score for this turn: " + SCORE + ".");
            clearTable();
        }
    });
    if (validSetUp() === true){
        firstHit = true;
        var angle = ballChoice();
        if (WHITE != -1){
            cue = makeCue();
            WHITE.applyCentralForce(angle);
            removeCue(cue);
        }
    }
}

function clearTable()
{
    while (BALLS.length !== 0){
        LIMIT[BALL_COLORS_R[BALLS[0].name]] += 1;
        BALLS[0].name = "None";
        BALLS[0].geometry.dispose();
        BALLS[0].material.dispose();
        ABWorld.scene.remove(BALLS[0]);
        BALLS.splice(0, 1);
    }
    reset();
}

function reset()
{
    BALL_COUNT = 0;
    YOURCOLOR = "None";
    basicSetUp();
}

function calculateScore(x, y, w, b)
{
    var score = 50;
    if (b === true && (w === true || YOURCOLOR == "None" || LIMIT[BALL_COLORS_R[YOURCOLOR]] != 7)){
        score = -1;
    }
    else if (b === true){
        score = 100;
    }
    if ((FOUL === true || w === true) && y === 0){
        score += (-8) + (x * 7);
    }
    else{
        score += (-9 * y) + (x * 7);
    }
    return(score);
}

function validSetUp()
{
    if (LIMIT[2] + LIMIT[3] > 0){
        console.log("Invalid Set Up! Black or white ball missing.");
        return(false);
    }
    else if (YOURCOLOR == "None" && LIMIT[0] + LIMIT[1] + LIMIT[2] + LIMIT[3] > 0){
        console.log("Invalid Set Up! Incorrect setting of your color, should be Yellow or Red.");
        return(false);
    }
    else if ((YOURCOLOR == "Red" || YOURCOLOR == "Yellow") && LIMIT[0] + LIMIT[1] === 0){
        console.log("Invalid Set Up! Incorrect setting of your color, should be None.");
        return(false);
    }
    else{
        return(true);
    }
}

function colorCount(color)
{
    var x = 0;
    for (var i in BALLS){
        if (BALLS[i].name == color){
            x += 1;
        }
    }
    return(x);
}

function makeCue()
{
    firstHit = true;
    if (WHITE != -1){
        //The Cue
        var cue_material = Physijs.createMaterial(new THREE.MeshStandardMaterial({color : "#c96120"}));
        var cue = new THREE.Mesh(new THREE.CylinderGeometry(0.5,1.5,100, 100), cue_material , 0);
        cue.position.x = WHITE.position.x - 50;
        cue.position.y = p_height + 5;
        cue.position.z = WHITE.position.z;
        cue.rotation.y = (Math.PI / 2) * 3;
        cue.rotation.z = (Math.PI / 2) * 3 - 0.05;
        ABWorld.scene.add(cue);
        return cue;
        //hitball();
        //WHITE.applyCentralForce(new THREE.Vector3 (BASE_POWER * (MIN_POWER * 2), 0, 0));

    }
}

function waiting(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function removeCue(object){
        await waiting(2000);
        //console.log("Waited 2s");
        object.geometry.dispose();
        object.material.dispose();
        ABWorld.scene.remove(object);    
}


function isValid(ball){
    if (YOURCOLOR == "None"){
        if (ball.name == "Yellow" || ball.name == "Red"){
            console.log("Valid Selection");
            return(true);
        }
    }
    else if (ball.name == YOURCOLOR){
        console.log("Valid Selection");
        return(true);
    }
    else if (ball.name == "Black" && LIMIT[BALL_COLORS_R[YOURCOLOR]] == 7){
        return(true);
    }
    return(false);
}

function ballChoice()
{
    // choose a valid ball to calculate angle
    var valid_balls = 0;
    for (var i in BALLS){
        if (isValid(BALLS[i])){
            if (valid_balls == BALLS_ATTEMPTED){
                BALLS_ATTEMPTED += 1;
                return(pickAngle(BALLS[i]));
            }
            valid_balls += 1;
        }
    }
}

function pickAngle(ball)
{
    // calculate angle to hit ball
    if (checkSpace(ball)){
        return(new THREE.Vector3 ((BASE_POWER * MIN_POWER * (ball.position.x - WHITE.position.x)), -100, (BASE_POWER * MIN_POWER * (ball.position.z - WHITE.position.z))));
    }
}

function checkSpace(ball)
{
    var x_material = Physijs.createMaterial(new THREE.MeshStandardMaterial({color : "#c96120"}));
    var length = (Math.sqrt(Math.pow((WHITE.position.x - ball.position.x), 2) + Math.pow((WHITE.position.z - ball.position.z), 2))) - 4.5;
    var x_geometry = new THREE.BoxGeometry(length, 1, 4);
    var x = new Physijs.BoxMesh(x_geometry, x_material, 0.001);
    x.position.x = (ball.position.x + WHITE.position.x) / 2;
    x.position.y = p_height + 4;
    x.position.z = (ball.position.z + WHITE.position.z) / 2;
    ABWorld.scene.add(x);
    x.geometry.dispose();
    x.material.dispose();
    ABWorld.scene.remove(x);
    return(true);
}

function slowBall(ball)
{
    //slow X
    if (ball.getAngularVelocity().x > -0.1 && ball.getAngularVelocity().x < 0.1){
        ball.setAngularVelocity(new THREE.Vector3(0,0, ball.getAngularVelocity().z));
    }
    else if (ball.getAngularVelocity().x >= 0.1) {
        ball.setAngularVelocity(new THREE.Vector3((ball.getAngularVelocity().x - SLOWFACTOR),0, ball.getAngularVelocity().z));
    }
    else{
        ball.setAngularVelocity(new THREE.Vector3((ball.getAngularVelocity().x + SLOWFACTOR),0, ball.getAngularVelocity().z));
    }
    //slow Z
    if (ball.getAngularVelocity().z > -0.1 && ball.getAngularVelocity().z < 0.1){
        ball.setAngularVelocity(new THREE.Vector3(ball.getAngularVelocity().x, 0, 0));
    }
    else if (ball.getAngularVelocity().z >= 0.1) {
        ball.setAngularVelocity(new THREE.Vector3(ball.getAngularVelocity().x, 0, (ball.getAngularVelocity().z - SLOWFACTOR)));
    }
    else{
        ball.setAngularVelocity(new THREE.Vector3(ball.getAngularVelocity().x, 0, (ball.getAngularVelocity().z + SLOWFACTOR)));
    }
}

function movementFinished()
{
    if (firstHit === false && movFin === false){
        var vel = 0;
        for (var x in BALLS){
            vel += BALLS[x].getAngularVelocity().length();
        }
        if (vel < 0.7){
            console.log("Movement Finished!");
            movFin = true;
            return(true);
        }
        return(false);
    }
}

// ===================================================================================================================
// === Ball Creation =================================================================================================
// ===================================================================================================================


function basicSetUp()
{
    if (BALL_COUNT === 0){
        createBall(-30, p_height + 0.5, 0, 3); //white
        createBall(30, p_height + 0.5, 0, 2); //black
        createBall(23, p_height + 0.5, 0, 1); //red
        createBall(26.5, p_height + 0.5, -2, 1); //red
        createBall(30, p_height + 0.5, 4, 1); //red
        createBall(33.5, p_height + 0.5, 2, 1); //red
        createBall(33.5, p_height + 0.5, -6, 1); // red
        createBall(37, p_height + 0.5, -4, 1); //red
        createBall(37, p_height + 0.5, 8, 1); //red
        createBall(26.5, p_height + 0.5, 2, 0); //yellow
        createBall(30, p_height + 0.5, -4, 0); //yellow
        createBall(33.5, p_height + 0.5, -2, 0); //yellow
        createBall(33.5, p_height + 0.5, 6, 0); //yellow
        createBall(37, p_height + 0.5, 0, 0); //yellow
        createBall(37, p_height + 0.5, 4, 0); //yellow
        createBall(37, p_height + 0.5, -8, 0); //yellow
        
    }
    if (CALCULATING === true && BALLS_TO_POT > BALLS_ATTEMPTED){
        hitBall();
    }
}

function createBall(x, y, z, CURRENT_COLOR) 	 
{
	
 if ( resourcesLoaded && LIMIT[CURRENT_COLOR] !== 0 && splashClicked)		  
 {  
    var ball;
    if (CURRENT_COLOR === 0){
        ball = new Physijs.SphereMesh(ball_geometry, ball_material_0, BALL_MASS);
    }
    else if (CURRENT_COLOR == 1){
        ball = new Physijs.SphereMesh(ball_geometry, ball_material_1, BALL_MASS);
    }
    else if (CURRENT_COLOR == 2){
        ball = new Physijs.SphereMesh(ball_geometry, ball_material_2, BALL_MASS);
    }
    else{
        ball = new Physijs.SphereMesh(ball_geometry, ball_material_3, BALL_MASS);
    }
	ball.collisions = 0;
	ball.castShadow = true;
	//ball.position.set(1, 40, 1);
	ball.position.set(x, y + 2, z);
	
	ball.name = BALL_COLORS[CURRENT_COLOR];
    ABWorld.scene.addEventListener("update", function(){
        if (ball.name != "None"){
            slowBall(ball);
        }
    });
	ball.addEventListener('collision', function(other_object, relative_velocity, relative_rotation, contact_normal) 
	{
	    if (ball == WHITE && other_object != GROUND){
	        if (firstHit === true){
	            if(other_object.name == YOURCOLOR || YOURCOLOR == "None"){
	                console.log("Valid Hit");
	            }
	            else if (LIMIT[BALL_COLORS_R[YOURCOLOR]] == 7 && other_object.name == "Black"){
	                console.log("Valid Hit");
	            }
	            else{
	                console.log("Invalid Hit");
	                FOUL = true;
	            }
	            firstHit = false;

	       }
        }
		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();
		}
	});
	BALLS.push(ball);
	if (CURRENT_COLOR == 3){
	    WHITE = ball;
	}
	BALL_COUNT += 1;
	ABWorld.scene.add(ball);
	LIMIT[CURRENT_COLOR] -= 1;
 }
}

function removeBall(ball)
{
    var index = BALLS.indexOf(ball);
    if (index > -1) {
        BALLS.splice(index, 1);
    }
    if (ball.name == YOURCOLOR){
        console.log("Good Shot!");
        YOURSCORE += 1;
    }
    else if (YOURCOLOR == "None" && (ball.name !== "Black" && ball.name !== "White")){
        console.log("You are now " + ball.name + "s.");
        YOURCOLOR = ball.name;
        YOURSCORE += 1;
    }
    else if (ball.name == "Black"){
        BLACK_POT = true;
    }
    else if (ball.name == "White"){
        WHITE_POT = true;
        console.log("Foul Shot.");
    }
    else{
        console.log("Bad Shot :(");
        OTHERSCORE += 1;
    }
    LIMIT[BALL_COLORS_R[ball.name]] += 1;
    ball.name = "None";
    ball.geometry.dispose();
    ball.material.dispose();
    ABWorld.scene.remove(ball);

}

// ===================================================================================================================
// === Keyboard & Mouse Control ======================================================================================
// ===================================================================================================================


var OURKEYS = [49, 50, 51, 52];
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 )     {selectBall(0);}
    else if ( event.keyCode == 50 )     {selectBall(1);} 
    else if ( event.keyCode == 51 )     {selectBall(2);}
    else if ( event.keyCode == 52 )     {selectBall(3);} 
    event.stopPropagation(); event.preventDefault(); return false;
}

var startX, startY; 
var dragevents;         // number of events in the current drag 

function click(event)
{
    var object = (ABWorld.hitsObjectPoint(event.x, event.y, GROUND));  // returns null if no hit
    if (object !== null){
        createBall(object.x, object.y, object.z, BALL_CHOICE);}
}

//for future drag functionality
function initTouch (event)
{
    console.log(event);
    var object = (ABWorld.hitsObjectPoint(x, y, GROUND));  // returns null if no hit
    if (object !== null){
        createBall(object.x, object.y, object.z, BALL_CHOICE);}
}

// ===================================================================================================================
// === User Interface ================================================================================================
// ===================================================================================================================


function initPreOptions()
{
    $("#user_span1").html("<p align=\"center\">Page: <b>"+ PAGE_SELECT + "</b></p>");
    $("#user_span2").html("<p align=\"center\"><button id='page1'>Page 1</button> <button id='page2'>Page 2</button> <button id='page3'>Page 3</button></p>");
    $("#user_span4").html(line4_1);
    $("#user_span6").html(line6_1);
    s = "<p align=\"center\"><button id='calculateShot'>GO!</button></p>";
    $("#user_span10").html(s);
    $(document).ready(function(){
        //Html css
        //$("html").css("background-color", "green");
        //Button Functionality
        $("#page1").click(function(){pageChange(1);});
        $("#page2").click(function(){pageChange(2);});
        $("#page3").click(function(){pageChange(3);});
        //Button CSS
        $("#page1").css("background-color", "#99ff33");
        $("#page2").css("background-color", "#99ff33");
        $("#page3").css("background-color", "#99ff33");
    });
    //place holders
    document.getElementById("calculateShot").addEventListener("click", function() { calculateShot();}, false);
}

function initPostOptions()
{
    
}

function updatePreHUD()
{

    $("#user_span1").html("<p align=\"center\">Page: <b>"+ PAGE_SELECT + "</b></p>");
    if (PAGE_SELECT == 1){
        $("#user_span3").html("<p align=\"center\">Ball Selected: <b>"+ BALL_TYPE[BALL_CHOICE] + "</b></p>");
        $("#user_span5").html("<p align=\"center\">The Colour you are playing as is set to: <b>" + YOURCOLOR + "</b></p>");
        if (CURRENT_PAGE !== PAGE_SELECT){
            $("#user_span4").html(line4_1);
            $("#user_span6").html(line6_1);
            
            $(document).ready(function(){
                //Button Functionality
                $("#yellowButton").click(function(){selectBall(0);});
                $("#redButton").click(function(){selectBall(1);});
                $("#blackButton").click(function(){selectBall(2);});
                $("#whiteButton").click(function(){selectBall(3);});
                $("#yourNButton").click(function(){setDifficulty(setColor("None"));});
                $("#yourYButton").click(function(){setDifficulty(setColor("Yellow"));});
                $("#yourRButton").click(function(){setDifficulty(setColor("Red"));});
                //Button CSS
                $("#yellowButton").css("background-color", "yellow");
                $("#redButton").css({"background-color" : "red" , "color" : "white"});
                $("#blackButton").css({"background-color" : "black" , "color" : "white"});
                $("#whiteButton").css("background-color", "white");
                $("#yourNButton").css("background-color", "grey");
                $("#yourYButton").css("background-color", "yellow");
                $("#yourRButton").css({"background-color" : "red" , "color" : "white"});
            });   
        }
    }
    
    else if (PAGE_SELECT == 2){
        $("#user_span3").html("<p align=\"center\">Ground Friction: <b>" + Number.parseFloat(GROUND_FRICTION).toFixed(2) + "</b></p>");
        $("#user_span5").html("<p align=\"center\">Power: <b>" + MIN_POWER + " to " + MAX_POWER + "</b></p>");
        if (CURRENT_PAGE !== PAGE_SELECT){
            $("#user_span4").html(line4_2);
            $("#user_span6").html(line6_2);
            $(document).ready(function(){
                //Button Functionality
                $("#decreaseFrictionButton").click(function(){decreaseFriction();});
                $("#increaseFrictionButton").click(function(){increaseFriction();});
                $("#decreasePowerMinButton").click(function(){decreasePowerMin();});
                $("#increasePowerMinButton").click(function(){increasePowerMin();});
                $("#decreasePowerMaxButton").click(function(){decreasePowerMax();});
                $("#increasePowerMaxButton").click(function(){increasePowerMax();});
                //Button CSS
                $("#decreaseFrictionButton").css({"background-color" : "red" , "color" : "white"});
                $("#increaseFrictionButton").css("background-color", "#00ff00");
                $("#decreasePowerMinButton").css({"background-color" : "black" , "color" : "white"});
                $("#increasePowerMinButton").css({"background-color" : "black" , "color" : "white"});
                $("#decreasePowerMaxButton").css({"background-color" : "black" , "color" : "white"});
                $("#increasePowerMaxButton").css({"background-color" : "black" , "color" : "white"});
            });   
        }
    }
    
    else {
        $("#user_span3").html("<p align=\"center\">The Difficultly is set to: <b>" + difficulty + "</b></p>");
        $("#user_span5").html("<p align=\"center\">The Camera Settings :</p>");
        if (CURRENT_PAGE !== PAGE_SELECT){
            $("#user_span4").html(line4_3);
            $("#user_span6").html(line6_3);
            $(document).ready(function(){
                //Button Functionality
                $("#easyButton").click(function(){setDifficulty("Easy");});
                $("#mediumButton").click(function(){setDifficulty("Medium");});
                $("#hardButton").click(function(){setDifficulty("Hard");});
                $("#cameraFlatButton").click(function(){cameraFlatDown();});
                $("#cameraCornerButton").click(function(){cameraSomeCornerPocket();});
                //Button CSS
                $("#easyButton").css("background-color", "#00ff00");
                $("#mediumButton").css("background-color", "yellow");
                $("#hardButton").css({"background-color" : "red" , "color" : "white"});
                $("#cameraFlatButton").css("background-color", "#668cff");
                $("#cameraCornerButton").css("background-color", "#c68c53");
            });   
        }
    }
    CURRENT_PAGE = PAGE_SELECT;
}

// ===================================================================================================================
// === Ancient Brain Functions =======================================================================================
// ===================================================================================================================

	
this.newRun = function() 
{
	ABWorld.init3d(startRadius, maxRadius, SKYCOLOR); 			// sets up renderer, scene, camera
	initPreOptions();
 // 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
	// user actions
    document.onkeydown          = keyHandler;
    document.ondblclick         = click;
    ABHandler.ontouchstart      = initTouch;
    //ABHandler.initMouseDrag   = initDrag;
    //ABHandler.mouseDrag       = drag
};

this.nextStep = function()		// not used 
{
    if (splashClicked === true && toggleBasic === true){
        basicSetUp();
        toggleBasic = false;
        toggledBasic = true;
    }

    if (CALCULATED === false){
        updatePreHUD();}
    else if (CALCULATING === true){
        
    }
    else{
        initPostOptions();
        updatePostHUD();
    }
};

}
	
// --- Splash screen --------------------------------------------------------------
	AB.newSplash ("<p>Optional Start Settings</p><p align=\"center\"><button id='basicSetUp1'>Initial arrangment</button></p>");
	document.getElementById("basicSetUp1").addEventListener("click", function() {toggleBasic = true;}, false);
	// when user clicks/touches button on splash screen, audio starts and run starts:
	$("#splashbutton").click (function()        
	{
		AB.removeSplash();			// remove splash screen 
		splashClicked = true;
	});