Code viewer for World: Infinite World Only Ground
//==============================================================================
//  Welcome to the Infinite world with only grounds !
//==============================================================================

// This program was made by Nathan Bonnard.
// In this world, you can generate an infinite world ! 
// But this isn't really infinite, it would be impossible, 
// the world do a simple loop when the player reach the border. Like in real life !
// This is a little version of the real infinite world that you can find aswell on the enhanced page

//------------------------------ TWEAKER BOX ------------------------------//

const squaresize = 7 ; // The world is like a infinite grid of square. squaresize is the size of a square
var MOVESPEED = 7; // Speed of the player

var viewDistance = 2; // determinate the maximum distance to see ground (viewDistance * squaresize * groundMultiplier)
var groundMultiplier = 551; // The world is divided by zone (grounds) that size groundMultiplier*squaresize
var worldSize = 13; // dimension of the world, it is not really infinite but big and can loop. There is worldSize * worldSize Grounds 

//------------------------------ END OF TWEAKER BOX ------------------------------//

var newviewDistance = viewDistance; // letiables to re-generate the world with new letiables 
var newGroundMultiplier = groundMultiplier;
var newworldSize = worldSize;

const MAXPOS                = 4000 ;                                 
const startRadiusConst	 	= MAXPOS * 0.5 ;		// distance from centre to start the camera at
const maxRadiusConst 		= MAXPOS * 5 ;		// maximum distance from camera we will render things  

const SKYCOLOR 	= 0x6495ED;
const LIGHTCOLOR = 0xffffff ;

//used to manage the click and give back control to the user when you click on a button/text box 
//and don't want to enter into first person mode
var HUDEVENT = false;
var mouse; // vector2 : position of the mouse on the screen

threehandler.MAXCAMERAPOS = MAXPOS * 10 ;// allow camera go far away 
threeworld.drawCameraControls = false; 

AB.clockTick       = 20;    

// Speed of run: Step every n milliseconds. Default 100.

AB.maxSteps        = 1000000;    

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

AB.screenshotStep  = 50;   

// Take screenshot on this step. (All resources should have finished loading.) Default 50.

/**
 * A linear interpolator for hexadecimal colors
 * @param {Int} a
 * @param {Int} b
 * @param {Number} amount
 * @example
 * // returns 0x7F7F7F
 * lerpColor(0x000000, 0xffffff, 0.5)
 * @returns {Int}
 */
function lerpColor(a, b, amount) { 

    let ah = a;
        ar = ah >> 16, ag = ah >> 8 & 0xff, ab = ah & 0xff,
        bh = b;
        br = bh >> 16, bg = bh >> 8 & 0xff, bb = bh & 0xff,
        rr = ar + amount * (br - ar),
        rg = ag + amount * (bg - ag),
        rb = ab + amount * (bb - ab);

    return  ((1 << 24) + (rr << 16) + (rg << 8) + rb | 0);
}

/**/
function map(n, start1, stop1, start2, stop2) 
{
    return ((n-start1)/(stop1-start1))*(stop2-start2)+start2;
}



//==============================================================================
//  World Definition
//==============================================================================
function World() { 

//==============================================================================
//  All variables
//==============================================================================

var groundTexture; // Texture of the ground

var grounds; // Array2d : Stock all ground of the world
var ai, aj; // position in grounds of the player
var pai, paj; // past position in grounds of the player
var timeRunning = 0; // time updated 
var raycaster; // to determine direction of a click

var camera, controls;

var moveForward = false;
var moveBackward = false;
var moveLeft = false;
var moveRight = false;
var canJump = false;

var prevTime = performance.now();
var velocity = new THREE.Vector3();
var direction = new THREE.Vector3();
var vertex = new THREE.Vector3();
var color = new THREE.Color();

this.endCondition = false;

//==============================================================================
//==============================================================================

//==============================================================================
//  All Class
//==============================================================================

//==============================================================================
//  Defines the THREE.PointerLockControls class, source at https://threejs.org/
//==============================================================================

THREE.PointerLockControls = function(camera) {

	let scope = this;

	camera.rotation.set( 0, 0, 0 );

	let pitchObject = new THREE.Object3D();
	pitchObject.add( camera );

	let yawObject = new THREE.Object3D();
	yawObject.position.y = 10;
	yawObject.add( pitchObject );
    
    let attachedObject = new THREE.Object3D();
    yawObject.add(attachedObject);
    attachedObject.position.set(0,-yawObject.position.y,-150);
    
    
	let PI_2 = Math.PI / 2;

	let onMouseMove = function ( event ) {
		if ( scope.enabled === false ) return;
	    
		let movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
		let movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;

		yawObject.rotation.y -= movementX * 0.002;
		pitchObject.rotation.x -= movementY * 0.002;

		pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) );

	};
    
    this.getAttachedObject = function () {

		return attachedObject;

	};

	this.dispose = function () {

		document.removeEventListener( 'mousemove', onMouseMove, false );
	};

	document.addEventListener( 'mousemove', onMouseMove, false );

	this.enabled = false;

	this.getObject = function () {

		return yawObject;

	};

	this.getDirection = function () {

		// assumes the camera itself is not rotated

		let direction = new THREE.Vector3( 0, 0, - 1 );
		let rotation = new THREE.Euler( 0, 0, 0, 'YXZ' );

		return function ( v ) {

			rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 );

			v.copy( direction ).applyEuler( rotation );

			return v;

		};

	}();

};
//==============================================================================



// Class to stock Each Objects that were loaded 
// with their combobox size to place them in space
// and to avoid collision ( objects at same place)
class Obj
{
  constructor(nameDoc, sX, sY, sZ) 
  {
      this.path = nameDoc;
      this.sizeX = sX;
      this.sizeY = sY;
      this.sizeZ = sZ;
      this.obj;
  }
}


//==============================================================================
//==============================================================================

//Just return a ground Elements that receive shadow
function createGround() 
{
    let texture = groundTexture;
    let gr = new THREE.Mesh (
        new THREE.PlaneGeometry ( squaresize*groundMultiplier, squaresize*groundMultiplier ),
        texture );
    gr.receiveShadow = true;
    gr.rotation.x = (Math.PI / 2) * 3;
    return gr;
}

//initialize the ground of the world, to determine all type nd create fake grounds
//around the world to fake a loop
function initGround()
{
    for(let i = 0; i < worldSize; i++)
    {
        for(let j = 0; j < worldSize; j++)
        {
            grounds[i][j] = createGround();
            grounds[i][j].position.set((i- Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j- Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
            threeworld.scene.add(grounds[i][j]);
        }
    }
    
    for(let i = - viewDistance; i < worldSize + viewDistance; i++)
    {
        for(let j = - viewDistance; j < worldSize + viewDistance; j++)
        {
            let b = (worldSize + viewDistance);
            
            if(i < 0 || j < 0 || i >= worldSize || j >= worldSize)
            {
                let tmp;
                if( i < 0)
        		{
        			if(j < 0)
        			{
        				tmp = grounds[worldSize + i][worldSize + j].clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[worldSize + i][j - worldSize].clone();
        			}
        			else
        			{
        				tmp = grounds[worldSize + i][j].clone();
        			}
        			
        		}
        		else if(i >= worldSize)
        		{
        			if(j < 0)
        			{
        				tmp = grounds[i - worldSize][worldSize + j].clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[i - worldSize][j - worldSize].clone();
        			}
        			else
        			{
        				tmp = grounds[i - worldSize][j].clone();
        			}
        		}
        		else if(i >= worldSize)
        		{
        			if(j < 0)
        			{
        				tmp = grounds[i - worldSize][worldSize + j].clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[i - worldSize][j - worldSize].clone();
        			}
        			else
        			{
        				tmp = grounds[i - worldSize][j].clone();
        			}
        		}
        		else if( j < 0)
        		{
        			tmp = grounds[i][worldSize + j].clone();
        		}
        		else if(j >= worldSize)
        		{
        			tmp = grounds[i][j - worldSize].clone();
        		}
        		tmp.position.set((i - Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j - Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
                threeworld.scene.add(tmp);
            }
        }
    }
}

//==============================================================================
//==============================================================================

//==============================================================================
// Functions link to an event
//==============================================================================

//Create a new infinite world. First destroy the previous one and then recreate one.
function createNewInfiniteWorld()  
{
    viewDistance =  newviewDistance;
    worldSize = newworldSize;
    groundMultiplier = newGroundMultiplier;
    
    while (threeworld.scene.children.length)
    {
        threeworld.scene.remove(threeworld.scene.children[0]);
    }
    threeworld.scene.add(controls.getObject());
    init();
}

function onDocumentTouchStart( event ) 
{
    console.log("onDocumentTouchStart");
	event.preventDefault();

	event.clientX = event.touches[0].clientX;
    event.clientY = event.touches[0].clientY;
	onDocumentMouseDown( event );
}

//Called when a key is up
function handleKeyUp (e)
{
    if((e.keyCode == 38) || (e.keyCode == 40))
    {
        velocity = 0;
    }
    if(e.keyCode == 37 || e.keyCode == 39)
    {
        speedRotation = 0;
    }
    if(e.keyCode == 37 || e.keyCode == 39)
    {
        speedRotation = 0;
    }
}

//==============================================================================
//==============================================================================

//Load all you need
function loader()
{
    planeTex = THREE.ImageUtils.loadTexture( "/uploads/meak/grass.jpg" );
    planeTex.wrapS = planeTex.wrapT = THREE.RepeatWrapping;
    planeTex.repeat.set( 8, 8 );
    
    groundTexture = new THREE.MeshPhongMaterial({map: planeTex, dithering: true });
    groundTexture.wrapAround = true;
    groundTexture.castShadow = true;
    groundTexture.receiveShadow = true;
}

//Initalization before running the world
function init()		 
{
    grounds = new Array(worldSize);
    for (let i = 0; i < worldSize; i++) 
    {
        grounds[i] = new Array(worldSize);
        for (let j = 0; j < worldSize; j++) 
        {
            grounds[i][j] = null;
        }
    }
    ai = parseInt(worldSize/2 + camera.getWorldPosition().x/(squaresize*groundMultiplier));
    aj = parseInt(worldSize/2 + camera.getWorldPosition().z/(squaresize*groundMultiplier));

    pai = ai;
    paj = aj;
    initGround();
    
    // LIGHTS
	let hemiLight = new THREE.HemisphereLight( SKYCOLOR, SKYCOLOR, 1 );
	hemiLight.position.set( 0, 50, 0 );
	threeworld.scene.add( hemiLight );
}

function checkPositionPlayer()
{
    //Update the position of the payer in the world so that it can generate ground around the player, depending on the viewDistance
        if(worldSize/2 + camera.getWorldPosition().x/(squaresize*groundMultiplier) < 0)
        {
            ai = -Math.ceil(-(worldSize/2 + camera.getWorldPosition().x/(squaresize*groundMultiplier)));
        }
        else
        {
            ai = Math.trunc(worldSize/2 + camera.getWorldPosition().x/(squaresize*groundMultiplier));
        }
        if(worldSize/2 + camera.getWorldPosition().z/(squaresize*groundMultiplier) < 0)
        {
            aj = -Math.ceil(-(worldSize/2 + camera.getWorldPosition().z/(squaresize*groundMultiplier)));
        }
        else
        {
            aj = Math.trunc(worldSize/2 + camera.getWorldPosition().z/(squaresize*groundMultiplier));
        }
        
        if(ai < 0)
        {
            controls.getObject().position.x = (worldSize/2) * groundMultiplier * squaresize - 0.01;
        }
        if(ai >= worldSize)
        {
            controls.getObject().position.x = - worldSize/2 * groundMultiplier * squaresize;
        }
        if(aj >= worldSize)
        {
            controls.getObject().position.z = - worldSize/2 * groundMultiplier * squaresize;
        }
        if(aj < 0)
        {
           controls.getObject().position.z = (worldSize/2) * groundMultiplier * squaresize - 0.01;
        }
        if(ai != pai || aj != paj)
        {
            pai = ai;
            paj = aj;
        }
}
//==============================================================================
//==============================================================================

//==============================================================================
// Function newRun ,init and endRun
//==============================================================================

this.newRun = function() 
{
    camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
    threeworld.camera = camera;
    
    threeworld.init3d ( 0, 0, SKYCOLOR  ); 
    // can adjust renderer:
	threeworld.renderer.shadowMap.enabled = true;
	threeworld.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    
    //load ground texture 
    loader();
    
    //position of mouse in the canvas
    mouse = new THREE.Vector2();
    
    //First person controller
    controls = new THREE.PointerLockControls( camera );
    
 	 //This will handle key presses
    
        let onKeyDown = function ( event ) {

	    	switch ( event.keyCode ) {

				case 38: // up
				case 87: // w
					moveForward = true;
					break;

				case 37: // left
				case 65: // a
					moveLeft = true; break;

				case 40: // down
				case 83: // s
					moveBackward = true;
					break;

				case 39: // right
				case 68: // d
					moveRight = true;
					break;

				case 32: // space
					if ( canJump === true || true ) velocity.y += 350;
					canJump = false; 
					break;
				
				    
				case 82: //Re-generateTheworld (R)
				    createNewInfiniteWorld();
				    break;
			}

		};

		let onKeyUp = function ( event ) {

			switch( event.keyCode ) {

				case 38: // up
				case 87: // w
					moveForward = false;
					break;

				case 37: // left
				case 65: // a
					moveLeft = false;
					break;

				case 40: // down
				case 83: // s
					moveBackward = false;
                    break;

				case 39: // right
				case 68: // d
					moveRight = false;
					break;

			}

		};
 	
 	document.addEventListener('keydown', onKeyDown, false );
	document.addEventListener('keyup', onKeyUp, false );
	
	raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 );

    //The following handles pointer locking when clicking the window
	    
	let havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
    console.log(havePointerLock);
    if ( havePointerLock ) {

		let element = document.body;

		let pointerlockchange = function ( event ) {

			if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element ) 
			{
			    if(HUDEVENT)
			    {
			        document.exitPointerLock();
			        HUDEVENT = false;
			    }
			    else
			    {
			        if(!controls.enabled && controls.getAttachedObject().children.length !== 0)
                    {
                        controls.getAttachedObject().remove(controls.getAttachedObject().children[1]);
                        controls.getAttachedObject().remove(controls.getAttachedObject().children[0]);
                    }
			        if(!controls.enabled)
                    {
                        controls.enabled = true;
                        $("#user_span10").html("");
                    }
			    }
			} else
			{
				controls.enabled = false;
				$("#user_span10").html("<p><b>Click screen to enable mouse controls</b></p>");

			}

		};
	let pointerlockerror = function ( event ) {

				console.error("pointerlockerror");
			};

			// Hook pointer lock state change events
			document.addEventListener( 'pointerlockchange', pointerlockchange, false );
			document.addEventListener( 'mozpointerlockchange', pointerlockchange, false );
			document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false );

			document.addEventListener( 'pointerlockerror', pointerlockerror, false );
			document.addEventListener( 'mozpointerlockerror', pointerlockerror, false );
			document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false );

			document.addEventListener( 'click', function ( event ) {

				// Ask the browser to lock the pointer
				element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
				element.requestPointerLock();

			}, false );

		} else {

			$("#user_span1").html('<p>Your browser doesn\'t seem to support Pointer Lock API</p>');

		}
		    	if ( AB.onDesktop() )
        {
                $("#user_span1").html("<p>Use WASD or Arrows to move, mouse to look around and space to jump.</p>"+
                "<p> If you want to re-generate the world, you can press R !</p>");
                
                $("#user_span10").html("<p><b>Click screen to enable mouse controls</b></p>");
                
                $("#user_span4").html("<p> Size of each parcel of the world (1 to 1000) <input type=\"number\" min=\"1\" max=\"1000\" value=\""+ newGroundMultiplier +"\" class=\"slider\" id=\"groundMultiplierHtml\"></p>");
                
                document.getElementById("groundMultiplierHtml").onchange = function() {
                    newGroundMultiplier = +this.value ;
                    console.log("groundMultiplierHtml " + newGroundMultiplier);
                    HUDEVENT = true;
                }
                document.getElementById("groundMultiplierHtml").onkeypress = function() {
                    newGroundMultiplier = +this.value ;
                    console.log("groundMultiplierHtml " + newGroundMultiplier);
                    HUDEVENT = true;
                }
                
                
                $("#user_span5").html("<p> Size of the world (1 to 50) <input type=\"number\" min=\"1\" max=\"50\" value=\""+ newworldSize +"\" class=\"slider\" id=\"worldSizeHtml\"></p>");
                
                document.getElementById("worldSizeHtml").onchange = function() {
                    newworldSize = +this.value;
                    if((newworldSize - 1) /2 < newviewDistance)
                    {
                        newviewDistance = 0;
                        document.getElementById("viewDistanceHtml").value = 0;
                    }
                    HUDEVENT = true;
                    console.log("worldSizeHtml " + newworldSize);
                }
                document.getElementById("worldSizeHtml").onkeypress = function() {
                    newworldSize = +this.value;
                    if((newworldSize - 1) /2 < newviewDistance)
                    {
                        newviewDistance = 0;
                        document.getElementById("viewDistanceHtml").value = 0;
                    }
                    HUDEVENT = true;
                    console.log("worldSizeHtml " + newworldSize);
                }
                
                $("#user_span6").html("<p> viewDistance in term of parcel (0 to 10) <input type=\"number\" min=\"0\" max=\"10\" value=\""+ newviewDistance +"\" class=\"slider\" id=\"viewDistanceHtml\"></p>");
                
                document.getElementById("viewDistanceHtml").onchange = function() {
                    if(newworldSize/2 >= +this.value)
                    {
                        newviewDistance = +this.value ;
                    }
                    console.log("viewDistanceHtml " + newviewDistance);
                    HUDEVENT = true;
                }
                document.getElementById("viewDistanceHtml").onkeypress = function() {
                    if(newworldSize/2 >= +this.value)
                    {
                        newviewDistance = +this.value ;
                    }
                    console.log("viewDistanceHtml " + newviewDistance);
                    HUDEVENT = true;
                }
        }
        else 
        {
            $("#user_span1").html( "<p> <b> This World currently only works on desktop. </b> </p>" );
        }
 	    createNewInfiniteWorld();
};


this.nextStep = function()
{
        //======================================================================
        //This will handle moving the player and the camera
        //======================================================================
    
			raycaster.ray.origin.copy( controls.getObject().position );
			raycaster.ray.origin.y -= 10;

			let time = performance.now();
			let delta = ( time - prevTime ) / 1000;

			velocity.x -= velocity.x * 10.0 * delta;
	    	velocity.z -= velocity.z * 10.0 * delta;

			velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass

			direction.z = Number( moveForward ) - Number( moveBackward );
			direction.x = Number( moveLeft ) - Number( moveRight );
			direction.normalize(); // this ensures consistent movements in all directions

			if ( moveForward || moveBackward ) velocity.z -= direction.z * 400.0 * MOVESPEED * delta;
			if ( moveLeft || moveRight ) velocity.x -= direction.x * 400.0 * MOVESPEED * delta;

			controls.getObject().translateX( velocity.x * delta );
			controls.getObject().translateY( velocity.y * delta );
			controls.getObject().translateZ( velocity.z * delta );

			if ( controls.getObject().position.y < 10 ) {

				velocity.y = 0;
				controls.getObject().position.y = 10;

				canJump = true;

			}
			prevTime = time;
			
			//Function to do the loop if the player is at the edge of the world
            checkPositionPlayer();
};
//==============================================================================
//==============================================================================

}