Code viewer for World: Complete Infinite World
//==============================================================================
//  Welcome to the Infinite world !
//==============================================================================

// 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 world isn't a kind of game with specific goal, just enjoy the landscapes and all objects !
// 
// The first purpose of this world was to create an exploration world to just enjoy good views and the fact that it is infinite 
// Finally it became a sort of Minecraft, I wanted to create that type of game but with complex 3D Objects, not cubes.
// I wanted to demonstrate the power of javascript and Three.js : we can create big world with a lot of objects even in a browser
// Also, the aim is to encourage people using the first person view on worlds or the sun that you can find on Enhanced World.
// If you want to change the world by yourself, it would be a bit hard to get into 2000 lines of code, 
// but you can simply change some letiables and see the effects. Here are those letiables :

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

const squaresize = 5 ; // 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 

const SKYDISTANCE = 3000; // Distance of stars and sun
const NBSTARS = 100; //number of stars

//Where all textures and objects are
const PATHTEXTURES = "/uploads/meak/";

//Limit between rare and small objects (in term of size, obviously here a castle size is bigger than 4 but a rock is not)
//Care it depends on the squaresize letiable. If you increase squaresize, you should also decrease this letiable
const limitSizeForRareObjects = 2;

//Number of maximum element that can be pose in each ground. You can change this after running the world
var maxElementsForest = 300;
var maxElementsSnow = 90;
var maxElementsSand = 60;

//Determine if the world can or not generate different type of ground. You can change this after running the world
var enableSand = true;
var enableSnow = true;
var enableForest = true;

//Path of sounds that are played during the game depending on what type of ground you are walking on
const SOUNDFOREST = "/uploads/meak/forestsound.mp3";
const SOUNDSNOW = "/uploads/meak/snowsound.mp3";
const SOUNDSAND = "/uploads/meak/sandsound.mp3";
const SOUNDNIGHT = "/uploads/meak/nightsound.mp3";

//Objects that are loaded for each type of ground. Add some Objects that you found on the internet and see !
//For that it need to be an Object3D (.obj) with a material (.mtl).
//If the material need some texture, upload those textures too.
const ARRAYOBJECTSFOREST = {
    "tree2":40,
    "lowpolytree":30,
    "castle-tower":0.5,
    "WoodenCabinObj":1,
    "houseA_obj":1,
    "tree13":30
    };

const ARRAYOBJECTSSNOW = {
    "castle-tower":0.5,
    "Stone":15,
    "kardanadam":2,
    "WoodenCabinObj":1,
    "Rock2":1
    };
    
const ARRAYOBJECTSSAND = {
    "castle-tower":0.5,
    "Cactus":10,
    "Stone":15,
    "WoodenCabinObj":1,
    "houseA_obj":1,
    "Rock2":1
    };

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

//all type of Grounds
const TYPEGROUND = ["SNOW", "FOREST", "SAND"];

//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 objects; // Stock all the prefabs of objects
var groundTexture; // Texture of the ground
var snowTexture; // Texture of the ground
var sandTexture; // Texture of the ground
var transitionTextureSnow;
var transitionTextureSand;
var transitionTextureForest;
var loaded = false; // True when everything is loaded
var grounds; // Array2d : Stock all ground of the world, with objects on it (recurrentObjects...)
var ai, aj; // position in grounds of the player
var pai, paj; // past position in grounds of the player
var modeOfView = 0; //0 : first person --- 1 : third person
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();

var sun;//sun and all functions with it (also stars)
var loadingManager;//loading manager to load everything and thenrun the program

var soundManager;//Object that manage the sound depending of the position of the player
var saving; //Object that store all informations to save the world

var offsetGround = 0.001;//letiable to avoid that grounds "bug" when they to parcels are overlapping
var userObjects = [];//store all objects that were pose by the player in edit mode
var allObjects = [];//allTypeOfObjects tht can be created by the player in edit mode
var indexObjects = 0;//Usefull to manage userObjects

var rayToDestroy;
var selectionBox =  new THREE.Mesh();
var selectionObject = null;
var editMode = null;


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;
        
        if(editMode == 1)
	    {
	        if(controls.getAttachedObject().children.length != 0)
            {
    	        controls.getAttachedObject().remove(controls.getAttachedObject().children[1]);
                controls.getAttachedObject().remove(controls.getAttachedObject().children[0]);
            }
            DestroyObject(event);
	    }
	    
		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 save the world
class SaveWorld
{
  constructor(dim, mult, viewDistance) 
  {
      this.worldSize = dim;
      this.groundMultiplier = mult;
      this.viewDistance = viewDistance;
      
      this.grounds = new Array(dim);
      for(let i = 0; i < this.grounds.length; i++)
      {
          this.grounds[i] = new Array(dim);
          for(let j = 0; j < this.grounds[i].length; j++)
          {
              this.grounds[i][j] = {biom: "", elements:[]};
          }
      }
      
      this.userObjects = [];
  }
}

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

//Class that determine if a ground is active or not
//And stock the ground with all the objects attached
class Ground
{
  constructor(act, pos) 
  {
      this.active = act;
      this.ground;
      this.type;
  }
}

//Class that stock all the objects of all types
class ObjectsConteneur
{
  constructor() 
  {
      this.objectsForest = new ObjectsOfType("FOREST");
      this.objectsSand = new ObjectsOfType("SAND");
      this.objectsSnow = new ObjectsOfType("SNOW");
  }
}

//Class that stock all the objects of the different type
class ObjectsOfType
{
  constructor(type) 
  {
      this.type = type;
      this.recurrentObjects = [];
      this.rareObjects = [];
  }
}

//Class that deal with the sound
class SoundManager
{
  constructor() 
  {
    let listener = new THREE.AudioListener();
    threeworld.camera.add( listener );
        
    // create a global audio source
    this.soundForest = new THREE.Audio( listener );
    this.soundSand = new THREE.Audio( listener );
    this.soundSnow = new THREE.Audio( listener );
    this.soundNight = new THREE.Audio( listener );

    this.currentSound = null;
    this.isNight = false;
  }
  
  playCurrentSound()
  {
      if(this.currentSound != null)
        {
            if(this.isNight)
            {
                this.soundNight.setVolume(0);
                this.soundNight.play();
            }
            else if(this.currentSound == "FOREST")
            {
                this.soundForest.setVolume(0);
                this.soundForest.play();
            }
            else if(this.currentSound == "SAND")
            {
                this.soundSand.setVolume(0);
                this.soundSand.play();
            }
            else if(this.currentSound == "SNOW")
            {
                this.soundSnow.setVolume(0);
                this.soundSnow.play();
            }
        }
  }
  
  updateSound()
  {
    if(this.currentSound != null)
    {
        if(this.isNight && this.soundNight.getVolume() < 0.5)
        {
            this.soundNight.setVolume(this.soundNight.getVolume() + 0.01);
        }
        else if(this.currentSound == "FOREST" && this.soundForest.getVolume() < 0.5)
        {
            this.soundForest.setVolume(this.soundForest.getVolume() + 0.01);
        }
        else if(this.currentSound == "SAND" && this.soundSand.getVolume() < 0.5)
        {
            this.soundSand.setVolume(this.soundSand.getVolume() + 0.01);
        }
        else if(this.currentSound == "SNOW" && this.soundSnow.getVolume() < 0.5)
        {
            this.soundSnow.setVolume(this.soundSnow.getVolume() + 0.01);
        }
        if((this.currentSound != "FOREST" || this.isNight) && this.soundForest.getVolume() > 0)
        {
            this.soundForest.setVolume(this.soundForest.getVolume() - 0.01);
            if(this.soundForest.getVolume() <= 0)
            {
                this.soundForest.stop();
            }
        }
        if((this.currentSound != "SAND" || this.isNight) && this.soundSand.getVolume() > 0)
        {
            this.soundSand.setVolume(this.soundSand.getVolume() - 0.01);
            if(this.soundSand.getVolume() <= 0)
            {
                this.soundSand.stop();
            }
        }
        if((this.currentSound != "SNOW" || this.isNight) && this.soundSnow.getVolume() > 0)
        {
            this.soundSnow.setVolume(this.soundSnow.getVolume() - 0.01);
            if(this.soundSnow.getVolume() <= 0)
            {
                this.soundSnow.stop();
            }
        }
        if(!this.isNight && this.soundNight.getVolume() > 0)
        {
            this.soundNight.setVolume(this.soundNight.getVolume() - 0.01);
            if(this.soundNight.getVolume() <= 0)
            {
                this.soundNight.stop();
            }
        }
    }
  }
}

//Sun, used like a class
let Sun = function()
{
        this.angle = 0;

    	this.object = new THREE.DirectionalLight( 0xffffff, 0.1 );
        this.object.position.set(0, Math.cos(this.angle)*3000, Math.sin(this.angle)*3000);
        this.object.castShadow = true;
		this.object.shadow.mapSize.width = 1024;
		this.object.shadow.mapSize.height = 1024;
		this.object.shadow.camera.near = 10;
		this.object.shadow.camera.far = 4000;
        
		let d = 1000;

		this.object.shadow.camera.left = -d;
		this.object.shadow.camera.right = d;
		this.object.shadow.camera.top = d;
		this.object.shadow.camera.bottom = -d;
		this.object.shadow.bias = -0.0001;
		
		this.object.target = controls.getObject();
		threeworld.scene.add(this.object);
        
        this.stars = [];
        this.starMaterial = new THREE.MeshBasicMaterial({color : "white", fog: false}) ;
        this.starMaterial.transparent = true;
        this.starMaterial.opacity = 0;
        
        let sunball = new THREE.Mesh( new THREE.SphereGeometry(100,32,32), new THREE.MeshBasicMaterial({color : "yellow", fog: false}) );
        this.object.add( sunball );
        this.starGyroscope = new THREE.Mesh();
        controls.getObject().add( this.starGyroscope );
    
        
        for (let i = 0; i<NBSTARS; i++)
        {
            let radius = AB.randomFloatAtoB ( 5, 20 );
            let star = new THREE.Mesh( new THREE.SphereGeometry(radius,8,8), this.starMaterial);
            let s = AB.randomFloatAtoB ( 0, Math.PI*2 );
            let t = AB.randomFloatAtoB ( 0, Math.PI/2 );
            star.position.set(SKYDISTANCE*Math.cos(s)*Math.sin(t), SKYDISTANCE*Math.cos(t), SKYDISTANCE*Math.sin(s)*Math.sin(t));
            this.stars.push(star);
            this.starGyroscope.add(star);
        }
        
        //Use this in nextStep to make the sun move
        this.animate = function()
        {
            this.angle+=0.001;
            
            //normalize angle between -PI and PI
            while (this.angle <= -Math.PI) this.angle += Math.PI*2;
            while (this.angle > Math.PI) this.angle -= Math.PI*2;
            
            this.object.position.set(controls.getObject().position.x, Math.cos(this.angle)*3000, Math.sin(this.angle)*3000 + controls.getObject().position.z);
            
            this.object.intensity = this.getSunIntensity();
            
            let c = new THREE.Color(this.getSkyColor());
		    threeworld.scene.background = c;
		    threeworld.scene.fog.color = c;
		    
		    this.setStarsOpacityAndFog();
		    this.starGyroscope.rotation.set(-controls.getObject().rotation.x, -controls.getObject().rotation.y, -controls.getObject().rotation.z);
        }
    
        //change star opacity and fog depending of the position of the sun
        this.setStarsOpacityAndFog = function()
        {
            if (this.angle > Math.PI/2  &&  this.angle < Math.PI*3/4)
            {
                soundManager.isNight = true;
                soundManager.playCurrentSound();
                this.starMaterial.opacity = map(this.angle, Math.PI/2, Math.PI*3/4, 0, 0.8);
                threeworld.scene.fog.far = 1000 + SKYDISTANCE * (1 - map(this.angle, Math.PI/2, Math.PI*3/4, 0, 0.8));
            }
            else if (this.angle > -Math.PI*3/4  &&  this.angle < -Math.PI/2)
            {
                soundManager.isNight = false;
                this.starMaterial.opacity = map(this.angle, -Math.PI*3/4, -Math.PI/2, 0.8, 0);
                threeworld.scene.fog.far = 1000 + SKYDISTANCE * (1 - map(this.angle, -Math.PI*3/4, -Math.PI/2, 0.8, 0));
            }
        }
        
        //return the color of the sky depending of the position of the sun (to get the sunrise)
        this.getSkyColor = function()
        {
            if (this.angle > -Math.PI*3/8  &&  this.angle < Math.PI*3/8)
            {
                // console.log("day");
                return 0x7ec0ee;
            }
            else if (this.angle > Math.PI*3/8  &&  this.angle < Math.PI/2)
            {
                // console.log("Sunset 1");
                return lerpColor(0x7ec0ee, 0xfd5e53, map(this.angle, Math.PI*3/8,Math.PI/2,0,1));
            }
            else if (this.angle > Math.PI/2  &&  this.angle < Math.PI*5/8)
            {
                // console.log("Sunset 2");
                return lerpColor(0xfd5e53, 0x0c3166, map(this.angle, Math.PI/2,Math.PI*5/8,0,1));
            }
            else if (this.angle > Math.PI*5/8  ||  this.angle < -Math.PI*3/4)
            {
                // console.log("night");
                return 0x0c3166;
            }
            else if (this.angle > -Math.PI*3/4  &&  this.angle < -Math.PI/2)
            {
                // console.log("Sunrise 1");
                return lerpColor(0x0c3166, 0xfd5e53, map(this.angle, -Math.PI*3/4,-Math.PI/2,0,1));
            }
            else if (this.angle > -Math.PI/2  &&  this.angle < -Math.PI*3/8)
            {
                // console.log("Sunrise 2");
                return lerpColor(0xfd5e53, 0x7ec0ee, map(this.angle, -Math.PI/2, -Math.PI*3/8, 0, 1));
            }
        }
        
        //return intensity of the sun
        this.getSunIntensity = function()
        {
            
            if (this.angle > -Math.PI*3/8  &&  this.angle < Math.PI*3/8)
            {
                return 2;
            }
            else if (this.angle > Math.PI*3/8  &&  this.angle < Math.PI/2)
            {
                return map(this.angle, Math.PI*3/8,Math.PI/2,2,1);
            }
            else if (this.angle > Math.PI/2  &&  this.angle < Math.PI*3/4)
            {
                return map(this.angle, Math.PI/2,Math.PI*3/4,1,0);
            }
            else if (this.angle > Math.PI*3/4  ||  this.angle < -Math.PI*3/4)
            {
                return 0;
            }
            else if (this.angle > -Math.PI*3/4  &&  this.angle < -Math.PI/2)
            {
                return map(this.angle, -Math.PI*3/4,-Math.PI/2,0,1);
            }
            else if (this.angle > -Math.PI/2  &&  this.angle < -Math.PI*3/8)
            {
                return map(this.angle, -Math.PI/2, -Math.PI*3/8, 1, 2);
            }
        }
}

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

//==============================================================================
// Functions to create/generate infinite world
//==============================================================================

//return a random element of the argument type. If wantRareObject is true, it will return a rare Object
function getRandomElement(type, wantRareObject)
{
    if(type === "FOREST")
    {
        if(wantRareObject)
        {
            return objects.objectsForest.rareObjects[AB.randomIntAtoB(0,objects.objectsForest.rareObjects.length - 1)];
        }
        else
        {
            return objects.objectsForest.recurrentObjects[AB.randomIntAtoB(0,objects.objectsForest.recurrentObjects.length - 1)];
        }
        
    }
    else if(type === "SAND")
    {
        if(wantRareObject)
        {
            return objects.objectsSand.rareObjects[AB.randomIntAtoB(0,objects.objectsSand.rareObjects.length - 1)];
        }
        else
        {
            return objects.objectsSand.recurrentObjects[AB.randomIntAtoB(0,objects.objectsSand.recurrentObjects.length - 1)];
        }
        
    }
    else if(type === "SNOW")
    {
        if(wantRareObject)
        {
            return objects.objectsSnow.rareObjects[AB.randomIntAtoB(0,objects.objectsSnow.rareObjects.length - 1)];
        }
        else
        {
            return objects.objectsSnow.recurrentObjects[AB.randomIntAtoB(0,objects.objectsSnow.recurrentObjects.length - 1)];
        }
        
    }
    
}

//Detect what type is in Position x,y. If those coordinates are not 
//in the dimension of the world, it change so that it loop like in the infinite world
function checkTypeGroundAround(x, y, type)
{
    if(x < 0) x = worldSize - 1;
    if(y < 0) y = worldSize - 1;
    if(x >= worldSize) x = 0;
    if(y >= worldSize) y = 0;
    return grounds[x][y].type == type;
}

//Everytime the player move to a ground that is not yet created, 
//it calls this function to create a Ground in a specific position pos.
//x and y are for the coordinates in the array2d grounds.
//type is to determine the type of the ground.
//elements is when we load a world that alrdy had a ground with elements at this position.
function createGround(pos, type, x, y, elements = null) 
{
    let conteneur = new THREE.Mesh();
    conteneur.name = "ground";
    let texture;
    let transitionTexture;
    let numberElement = 0;
    let hasRareObject = (AB.randomIntAtoB(0,20) > 18);
    
    saving.grounds[x][y].biom = type;
    
    if(type === "FOREST")
    {
        transitionTexture = transitionTextureForest;
        texture = groundTexture;
        numberElement = AB.randomIntAtoB(0,maxElementsForest);
    }
    else if(type === "SAND")
    {
        transitionTexture = transitionTextureSand;
        texture = sandTexture;
        numberElement = AB.randomIntAtoB(0,maxElementsSand);
    }
    else if(type === "SNOW")
    {
        transitionTexture = transitionTextureSnow;
        texture = snowTexture;
        numberElement = AB.randomIntAtoB(0,maxElementsSnow);
    }
    
    let gr = new THREE.Mesh (
        new THREE.PlaneGeometry ( squaresize*groundMultiplier, squaresize*groundMultiplier ),
        texture );
    gr.receiveShadow = true;
    
    gr.rotation.x = (Math.PI / 2) * 3;
    gr.position = pos;
    

    if(!checkTypeGroundAround(x - 1, y, type))
    {
        let a1 = new THREE.Mesh (
        new THREE.PlaneGeometry ( squaresize*groundMultiplier, squaresize*groundMultiplier/8 ),transitionTexture );
        conteneur.add(a1);
        a1.position.set(-squaresize*groundMultiplier/2 , offsetGround, 0);
        a1.rotation.x = (Math.PI / 2) * 3;
        a1.rotation.z = Math.PI/2;
        a1.receiveShadow = true;
        offsetGround += 0.01;
        a1.name = "ground";
    }
    
    if(!checkTypeGroundAround(x + 1, y, type))
    {
        let a2 = new THREE.Mesh (
            new THREE.PlaneGeometry ( squaresize*groundMultiplier, squaresize*groundMultiplier/8 ),transitionTexture);
        conteneur.add(a2);
        a2.position.set(squaresize*groundMultiplier/2 , offsetGround, 0);
        a2.rotation.z = Math.PI/2;
        a2.rotation.x = (Math.PI / 2) * 3;
        a2.receiveShadow = true;
        offsetGround += 0.01;
        a2.name = "ground";
    }
    
    if(!checkTypeGroundAround(x, y - 1, type))
    {
        let a3 = new THREE.Mesh (
            new THREE.PlaneGeometry ( squaresize*groundMultiplier, squaresize*groundMultiplier/8 ), transitionTexture);
        conteneur.add(a3);
        a3.position.set(0 , offsetGround, -squaresize*groundMultiplier/2);
        a3.rotation.x = (Math.PI / 2) * 3;
        a3.receiveShadow = true;
        offsetGround += 0.01;
        a3.name = "ground";
    }
    
    if(!checkTypeGroundAround(x, y + 1, type))
    {
        let a4 = new THREE.Mesh (
            new THREE.PlaneGeometry ( squaresize*groundMultiplier, squaresize*groundMultiplier/8 ), transitionTexture);
        conteneur.add(a4);
        a4.position.set(0, offsetGround , squaresize*groundMultiplier/2);
        a4.rotation.x = (Math.PI / 2) * 3;
        a4.receiveShadow = true;
        offsetGround += 0.01;
        a4.name = "ground";
    }
    
    if(offsetGround > 0.2)
    {
        offsetGround = 0.001;
    }
    
    gr.add(conteneur);
    conteneur.rotation.x = -(Math.PI / 2) * 3;
    if(elements == null)
    {
        //add all elements to a group, 
        let placeAvailables = [];
        let tmp = [];
        for (let i = 0; i < groundMultiplier; i++) 
        {
            tmp[i] = [];
            for (let j = 0; j < groundMultiplier; j++) 
            {
                tmp[i][j] = false;
            }
        }
        
        let numberOfTry = 0;
        while(numberElement > 0 && numberOfTry != 500)
        {
            let randomX = AB.randomIntAtoB(0,groundMultiplier-1);
            let randomY = AB.randomIntAtoB(0,groundMultiplier-1);
            let wasPosed = false;
            
            while(!wasPosed && numberOfTry != 500)
            {
                numberOfTry++;
                if(tmp[randomX][randomY] === false)
                {
                    let t = getRandomElement(type, hasRareObject);
                    let randomScale = AB.randomFloatAtoB(0.5,1.5);
                    if(t != null)
                    {
                        let canPoseObject = true;
                        for(let i = randomX - Math.ceil(((t.sizeX * randomScale)-1)/2); i <= randomX + Math.ceil(((t.sizeX* randomScale)-1)/2); i++)
                        {
                            for(let j = randomY - Math.ceil(((t.sizeZ* randomScale)-1)/2); j <= randomY + Math.ceil(((t.sizeZ* randomScale)-1)/2); j++)
                            {
                                if(i >= 0 && j >= 0 && i < groundMultiplier && j < groundMultiplier)
                                {
                                    if(tmp[i][j])
                                    {
                                        canPoseObject = false;
                                        break;
                                    }
                                }
                                else
                                {
                                    canPoseObject = false;
                                    break;
                                }
                            }
                        }
                        if(canPoseObject)
                        {
                            let obj = t.obj.clone();
                            if(!hasRareObject) obj.rotation.y = AB.randomIntAtoB(0,360);
                            obj.scale.multiplyScalar(randomScale);
                            obj.position.set((randomX - parseInt(groundMultiplier/2)) * squaresize, t.sizeY* randomScale, (randomY - parseInt(groundMultiplier/2)) * squaresize);
                            conteneur.add(obj);
                            for(let i = randomX - Math.ceil(((t.sizeX* randomScale)-1)/2); i <= randomX + Math.ceil(((t.sizeX* randomScale)-1)/2); i++)
                            {
                                for(let j = randomY - Math.ceil(((t.sizeZ* randomScale)-1)/2); j <= randomY + Math.ceil(((t.sizeZ* randomScale)-1)/2); j++)
                                {
                                    if(i >= 0 && j >= 0 && i < groundMultiplier && j < groundMultiplier)
                                    {
                                        tmp[i][j] = true;
                                    }
                                }
                            }
                            if(hasRareObject)
                            {
                                hasRareObject = false;
                            }
                            
                            let o = 
                            {
                                s: Number((randomScale).toFixed(1)),
                                x: Number(((randomX - parseInt(groundMultiplier/2)) * squaresize).toFixed(1)),
                                y: Number((t.sizeY* randomScale).toFixed(1)),
                                z: Number(((randomY - parseInt(groundMultiplier/2)) * squaresize).toFixed(1)),
                                o: t.path,
                            }
                            saving.grounds[x][y].elements.push(o);
                            
                            wasPosed = true;
                            numberElement--;
                            numberOfTry = 0;
                        }
                        else
                        {
                            randomX = AB.randomIntAtoB(0,groundMultiplier-1);
                            randomY = AB.randomIntAtoB(0,groundMultiplier-1);
                        }
                    }
                    else
                    {
                        if(hasRareObject)
                        {
                            hasRareObject = false;
                        }
                    }
                }
                else
                {
                    randomX = AB.randomIntAtoB(0,groundMultiplier-1);
                    randomY = AB.randomIntAtoB(0,groundMultiplier-1);
                }
            }
        }
    }
    else
    {
        for(let i = 0; i < elements.length; i++)
        {
            let t = findObjectLoad(elements[i].o);
            if(t !== null)
            {
                let obj = t.obj.clone();
                obj.scale.multiplyScalar(elements[i].s);
                obj.position.set(elements[i].x, elements[i].y, elements[i].z);
                conteneur.add(obj);
            }
        }
    }
    gr.name = "ground";
    return gr;
}

//With the name of an object, it find the object to recreate it.
//If it wasn't loaded(code changed) it returns null
function findObjectLoad(path)
{
    for(let i = 0; i < allObjects.length; i++)
    {
        if(path == allObjects[i].path) return allObjects[i];
    }
    console.log("The Object", path, "was not find in allObjects array that stock all objects that have been loaded");
    return null;
}

//Return the type that is the most present around the current position.
function getTypeAround(array,x,y)
{
    let sand = 0;
    let snow = 0;
    let forest = 0;
    
    if(x - 1 >= 0 && array[x - 1][y] !== null)
    {
        if(array[x - 1][y] == "SAND") sand++;
        if(array[x - 1][y] == "FOREST") forest++;
        if(array[x - 1][y] == "SNOW") snow++;
    }
    if(y - 1 >= 0 && array[x][y - 1] !== null)
    {
        if(array[x][y - 1] == "SAND") sand++;
        if(array[x][y - 1] == "FOREST") forest++;
        if(array[x][y - 1] == "SNOW") snow++;
    }
    if(x + 1 < array.length && array[x + 1][y] !== null)
    {
        if(array[x + 1][y] == "SAND") sand++;
        if(array[x + 1][y] == "FOREST") forest++;
        if(array[x + 1][y] == "SNOW") snow++;
    }
    if(y + 1 < array.length && array[x][y + 1] !== null)
    {
        if(array[x][y + 1] == "SAND") sand++;
        if(array[x][y + 1] == "FOREST") forest++;
        if(array[x][y + 1] == "SNOW") snow++;
    }
    if(sand == 0 && snow == 0 && forest == 0)
    {
        return null;
    }
    else if(sand > forest && sand > snow && enableSand)
    {
        return "SAND";
    }
    else if(snow > forest && snow > sand && enableSnow)
    {
        return "SNOW";
    }
    else if(forest > snow && forest > sand && enableForest)
    {
        return "FOREST";
    }
    else
    {
        return null;
    }
}

//initialize the ground of the world, to determine all type nd create fake grounds
//around the world to fake a loop
function initGround()
{
    let tmp = new Array(worldSize);
    for (let i = 0; i < worldSize; i++) 
    {
        tmp[i] = new Array(worldSize);
        for (let j = 0; j < worldSize; j++) 
        {
            tmp[i][j] = null;
        }
    }
    for(let i = 0; i < worldSize; i++)
    {
        for(let j = 0; j < worldSize; j++)
        {
            let typeAround = getTypeAround(tmp,i,j);
            
            let rand = AB.randomIntAtoB(0,5);
            if(rand < 3 && typeAround !== null)
            {
                tmp[i][j] = typeAround;
            }
            else
            {
                let t = TYPEGROUND[AB.randomIntAtoB(0,TYPEGROUND.length-1)];
                while(!((t == "FOREST" && enableForest) || (t == "SAND" && enableSand) || (t == "SNOW" && enableSnow)))
                {
                    t = TYPEGROUND[AB.randomIntAtoB(0,TYPEGROUND.length-1)];
                }
                tmp[i][j] = t;
            }
        }
    }
   
    for(let i = 0; i < worldSize; i++)
    {
        for(let j = 0; j < worldSize; j++)
        {
            grounds[i][j] = new Ground(false, new THREE.Vector3(0,0,0));
            grounds[i][j].type = tmp[i][j];
            grounds[i][j].active = false;
            saving.grounds[i][j].biom = tmp[i][j];
        }
    }
    for(let i = 0; i < worldSize; i++)
    {
        for(let j = 0; j < worldSize; j++)
        {
            if(i < viewDistance + 1 || i >= worldSize - viewDistance || j >= worldSize - viewDistance || j < viewDistance + 1)
            {
                grounds[i][j].ground = createGround(new THREE.Vector3(0,0,0), grounds[i][j].type, i, j);
                grounds[i][j].ground.position.set((i- Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j- Math.trunc(worldSize/2)) * squaresize*groundMultiplier)
            }
        }
    }
    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].ground.clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[worldSize + i][j - worldSize].ground.clone();
        			}
        			else
        			{
        				tmp = grounds[worldSize + i][j].ground.clone();
        			}
        			
        		}
        		else if(i >= worldSize)
        		{
        			if(j < 0)
        			{
        				tmp = grounds[i - worldSize][worldSize + j].ground.clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[i - worldSize][j - worldSize].ground.clone();
        			}
        			else
        			{
        				tmp = grounds[i - worldSize][j].ground.clone();
        			}
        		}
        		else if(i >= worldSize)
        		{
        			if(j < 0)
        			{
        				tmp = grounds[i - worldSize][worldSize + j].ground.clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[i - worldSize][j - worldSize].ground.clone();
        			}
        			else
        			{
        				tmp = grounds[i - worldSize][j].ground.clone();
        			}
        		}
        		else if( j < 0)
        		{
        			tmp = grounds[i][worldSize + j].ground.clone();
        		}
        		else if(j >= worldSize)
        		{
        			tmp = grounds[i][j - worldSize].ground.clone();
        		}
        		tmp.position.set((i - Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j - Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
                threeworld.scene.add(tmp);
            }
        }
    }
}

//This function is called each time the player moves to another ground to check if 
//there is a need to create a ground depending on the viewDistance
function updateGround()
{
    for(let i = ai - viewDistance - 1; i < ai + viewDistance + 2; i++)
    {
        for(let j = aj - viewDistance - 1; j < aj + viewDistance + 2; j++)
        {
            if(!(i < 0 || i >= worldSize || j >= worldSize || j < 0))
            {
               if(i == ai - viewDistance - 1 || j == aj - viewDistance - 1 || i == ai + viewDistance + 1 || j == aj + viewDistance + 1)
                {
                    if(grounds[i][j] !== null && grounds[i][j].active)
                    {
                        grounds[i][j].active = false;            
                    }
                }
                else
                {
                   if(typeof grounds[i][j].ground == 'undefined' || grounds[i][j].ground === null)
                    {
                        grounds[i][j].ground = createGround(new THREE.Vector3(0,0,0), grounds[i][j].type, i, j);
                        grounds[i][j].ground.position.set((i- Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j- Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
                        grounds[i][j].active = true;
                        threeworld.scene.add(grounds[i][j].ground);
                    }
                    else if(!grounds[i][j].active)
                    {
                        grounds[i][j].active = true;
                        threeworld.scene.add(grounds[i][j].ground);
                    }
                } 
            }
        }
    }
}

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

//==============================================================================
// 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;
    
    saving = new SaveWorld(worldSize,groundMultiplier, viewDistance);
    
    while (threeworld.scene.children.length)
    {
        threeworld.scene.remove(threeworld.scene.children[0]);
    }
    threeworld.scene.add(controls.getObject());
    threeworld.scene.fog = new THREE.Fog(SKYCOLOR, 0, SKYDISTANCE);
    // LIGHTS
	let hemiLight = new THREE.HemisphereLight( SKYCOLOR, SKYCOLOR, 0.4 );
	hemiLight.position.set( 0, 50, 0 );
	threeworld.scene.add( hemiLight );
    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;
    }
}

//Called on scrool of the wheel's mouse
function onScroll(e)
{
    if(controls.getAttachedObject().children.length !== 0)
    {
        controls.getAttachedObject().remove(controls.getAttachedObject().children[1]);
        controls.getAttachedObject().remove(controls.getAttachedObject().children[0]);
        let delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
        if(delta == 1 && indexObjects + 1 < allObjects.length)
        {
            indexObjects++;
        }
        else if(delta == 1)
        {
            indexObjects = 0;
        }
        else if(delta == -1 && indexObjects - 1 >= 0)
        {
            indexObjects--;
        }
        else if(delta == -1)
        {
            indexObjects = allObjects.length-1;
        }
        let t = allObjects[indexObjects];
        let o = t.obj.clone();
        
        let bbox = getBoundingBoxObject(o, 0x008000);
        controls.getAttachedObject().add(bbox);
        
        controls.getAttachedObject().add(o);
        o.position.y = t.sizeY;
    }
}

function OnClick(e)
{
    if(editMode !== null)
    {
        if(editMode === 0)
        {
            selectionObject = null;
            CreateObject(e);
        }
        else if(editMode == 1)
        {
            
            if(controls.getAttachedObject().children.length != 0)
            {
                controls.getAttachedObject().remove(controls.getAttachedObject().children[1]);
                controls.getAttachedObject().remove(controls.getAttachedObject().children[0]);
            }
            if(e != null && e.which == 3)
            {
                editMode = null;
            }
            if(selectionObject !== null)
            {
                removeObject(threeworld.scene, selectionObject);
                while (selectionBox.children.length)
                {
                    selectionBox.remove(selectionBox.children[0]);
                }
            }
        }
    }
}

function DestroyObject(e)
{
    if(e.type !== 'click')
    {
        rayToDestroy = new THREE.Raycaster( controls.getObject().position, threeworld.camera.getWorldDirection(), 0, 800);
        let inter = rayToDestroy.intersectObjects(threeworld.scene.children, true);
        if(inter.length > 0)
        {
            let i = 0;
            while(i < inter.length && (inter[i].object.name == "ground" || inter[i].object == selectionBox)) i++;
            if(i < inter.length && inter[i].object != selectionObject)
            {   
                let o = getParent(inter[0].object);
                
                if(selectionObject != o && o != selectionBox)
                {
                    while (selectionBox.children.length)
                    {
                        selectionBox.remove(selectionBox.children[0]);
                    }
                    let bbox = getBoundingBoxObject(o, 0xFF0000);
                    selectionBox.add(bbox);
                    bbox.position.x = o.getWorldPosition().x;
                    bbox.position.z = o.getWorldPosition().z;
                    
                    selectionObject = o;
                }
                
                
            }
        }
    }
}

function removeObject(objConteneur, objToDestroy)
{
    for(let i = 0; i < objConteneur.children.length; i++)
    {
        if(objConteneur.children[i] == objToDestroy)
        {
            objConteneur.remove(objToDestroy);
        }
        else
        {
            removeObject(objConteneur.children[i], objToDestroy);
        }
    }
    
}


//Called when you click left with the nosue after having entered the edit mode with a left click.
function CreateObject(e = null)
{
    editMode = 0;
    if(e != null && e.which == 3 && controls.getAttachedObject().children.length !== 0)
    {
        controls.getAttachedObject().remove(controls.getAttachedObject().children[1]);
        controls.getAttachedObject().remove(controls.getAttachedObject().children[0]);
        editMode = null;
    }
    else if(e != null && e.which == 3)
    {
        //TODO Maybe ?
    }
    else
    {
        if(controls.getAttachedObject().children.length !== 0)
        {
            let o = controls.getAttachedObject().children[1].clone();
            userObjects.push(o);
            threeworld.scene.add(o);
            let pos = controls.getAttachedObject().children[1].getWorldPosition();
            let rot = controls.getAttachedObject().children[1].getWorldRotation();
            o.rotation.set();
            o.position.set(pos.x, pos.y, pos.z);
            o.rotation.set(rot.x, rot.y, rot.z);
            
            let t = allObjects[indexObjects];
            
            let s = 
            {
                s: 1,
                x: Number((pos.x).toFixed(1)),
                y: Number((pos.y).toFixed(1)),
                z: Number((pos.z).toFixed(1)),
                o: t.path,
            }
            saving.userObjects.push(s);
        }
        else
        {
            let t = allObjects[indexObjects];
            let o = t.obj.clone();
            
            let bbox = getBoundingBoxObject(o, 0x008000);
            controls.getAttachedObject().add(bbox);
            
            controls.getAttachedObject().add(o);
            o.position.y = t.sizeY;
        }
    }
}

//Fonction that return an cube that determine the bouding box of an object
function getBoundingBoxObject(object, colorBox)
{
    let bbox = new THREE.Box3().setFromObject(object);

    let cube = new THREE.Mesh(
        new THREE.BoxGeometry(bbox.size().x,bbox.size().y,bbox.size().z),
        new THREE.MeshBasicMaterial({
            color: colorBox,
            transparent: true,
            opacity: 0.5
    }));
    cube.position.y = bbox.size().y/2 ;
    return cube;
}

//Return highest parent in the hierarchy that is not a ground or the scene.
//Useful for models that were not well designed.
function getParent(object)
{
    if(object.parent !== null && object.parent.name != "ground" && object.parent != threeworld.scene)
    {
        return getParent(object.parent);
    }
    return object;
}
//==============================================================================
//==============================================================================

//==============================================================================
// Function that load Initialize the world
//==============================================================================

//Load all objects3D and textures before initialazing the world
function loaderObjects()
{
    soundManager = new SoundManager();
    
    // load a sound and set it as the Audio object's buffer
    let audioLoaderForest = new THREE.AudioLoader(loadingManager);
    
    audioLoaderForest.load( SOUNDFOREST, function(buffer) {
        	soundManager.soundForest.setBuffer(buffer);
        	soundManager.soundForest.setLoop(true);
        	soundManager.soundForest.setVolume(0);
    });
    
    let audioLoaderSand = new THREE.AudioLoader(loadingManager);
    
    audioLoaderSand.load( SOUNDSAND, function( buffer ) {
        	soundManager.soundSand.setBuffer( buffer );
        	soundManager.soundSand.setLoop( true );
        	soundManager.soundSand.setVolume(0);
    });
    
    let audioLoaderSnow = new THREE.AudioLoader(loadingManager);
    
    audioLoaderSnow.load( SOUNDSNOW, function( buffer ) {
        	soundManager.soundSnow.setBuffer( buffer );
        	soundManager.soundSnow.setLoop( true );
        	soundManager.soundSnow.setVolume(0);
    });
    
    let audioLoaderNight = new THREE.AudioLoader(loadingManager);
    
    audioLoaderNight.load( SOUNDSNOW, function( buffer ) {
        	soundManager.soundNight.setBuffer( buffer );
        	soundManager.soundNight.setLoop( true );
        	soundManager.soundNight.setVolume(0);
    });
    
    let planeTex = THREE.ImageUtils.loadTexture( "/uploads/meak/transitionsnow.png" );
    planeTex.wrapS = planeTex.wrapT = THREE.RepeatWrapping;
    planeTex.repeat.set( 8, 8 );
    
    transitionTextureSnow = new THREE.MeshPhongMaterial({map: planeTex, transparent: true, dithering: true });
    transitionTextureSnow.wrapAround = true;
    transitionTextureSnow.receiveShadow = true;
    
    planeTex = THREE.ImageUtils.loadTexture( "/uploads/meak/transitionsand.png" );
    planeTex.wrapS = planeTex.wrapT = THREE.RepeatWrapping;
    planeTex.repeat.set( 8, 8 );
    
    transitionTextureSand = new THREE.MeshPhongMaterial({map: planeTex, transparent: true, dithering: true });
    transitionTextureSand.wrapAround = true;
    transitionTextureSand.receiveShadow = true;
    
    planeTex = THREE.ImageUtils.loadTexture( "/uploads/meak/transitiongrass.png" );
    planeTex.wrapS = planeTex.wrapT = THREE.RepeatWrapping;
    planeTex.repeat.set( 8, 8 );
    
    transitionTextureForest = new THREE.MeshPhongMaterial({map: planeTex, transparent: true, dithering: true });
    transitionTextureForest.wrapAround = true;
    transitionTextureForest.receiveShadow = true;
    
    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;
    
    planeTex = THREE.ImageUtils.loadTexture( "/uploads/meak/sand.jpg" );
    planeTex.wrapS = planeTex.wrapT = THREE.RepeatWrapping;
    planeTex.repeat.set( 8, 8 );
    
    sandTexture = new THREE.MeshPhongMaterial({map: planeTex, dithering: true });
    sandTexture.wrapAround = true;
    sandTexture.castShadow = true;
    sandTexture.receiveShadow = true;
    
    snowTexture = new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture( "/uploads/meak/snow.png" ), dithering: true });
    snowTexture.wrapAround = true;
    snowTexture.castShadow = true;
    snowTexture.receiveShadow = true;
    
    console.log("loading " + Object.keys(ARRAYOBJECTSFOREST).length + " elements ");
    
    for (let key in ARRAYOBJECTSFOREST) 
    {
        let loaderMat = new THREE.MTLLoader(loadingManager);
        let loaderObj = new THREE.OBJLoader(loadingManager);
        loaderMat.setTexturePath(PATHTEXTURES);
        let pathMat = PATHTEXTURES + key + '.mtl';
        let pathObj = PATHTEXTURES + key + '.obj';
        let value = ARRAYOBJECTSFOREST[key];
        let nameObj = key;
        console.log("loading " + pathMat + " and " + pathObj);
        loaderMat.load
        (
        	// resource URL
        	pathMat,
        	// called when resource is loaded
        	function (materials) 
            {
        	    materials.preload();
        	    
        	    loaderObj.setMaterials(materials);
        	    // load a resource
                loaderObj.load
                (
                	// resource URL
                	pathObj,
                	// called when resource is loaded
                	function ( object ) 
                    {
                        let boundingBox = new THREE.Box3().setFromObject(object);
                        
                        object.traverse( function ( child ) {
                            if ( child instanceof THREE.Mesh ) {
                                child.castShadow = true;
                                object.receiveShadow = true;
                            }
                        } );
                        
                	    object.position.set(0,0,0);
                	    
                	    let sizeX = Math.abs(boundingBox.min.x)+Math.abs(boundingBox.max.x);
                	    let sizeY = Math.abs(boundingBox.min.y)+Math.abs(boundingBox.max.y);
                	    let sizeZ = Math.abs(boundingBox.min.z)+Math.abs(boundingBox.max.z);
                	    
                        let tmp  = new Obj(nameObj, 1 + Math.ceil((value * sizeX / squaresize) * 2),Math.abs(boundingBox.min.y)*value, 1 + Math.ceil((value * sizeZ / squaresize) * 2));
                        object.scale.multiplyScalar(value);
                        tmp.obj = object;
                        if( Math.ceil((sizeZ-1)/2) < limitSizeForRareObjects && Math.ceil((sizeX-1)/2) < limitSizeForRareObjects )
                        {
                            objects.objectsForest.recurrentObjects.push(tmp);
                        }
                        else
                        {
                            objects.objectsForest.rareObjects.push(tmp);
                        }
                        allObjects.push(tmp);
                	}
                );
        	}
        );
    }
    console.log("loading " + Object.keys(ARRAYOBJECTSSAND).length + " elements ");
    
    for (let key in ARRAYOBJECTSSAND) 
    {
        let loaderMat = new THREE.MTLLoader(loadingManager);
        let loaderObj = new THREE.OBJLoader(loadingManager);
        loaderMat.setTexturePath(PATHTEXTURES);
        let pathMat = PATHTEXTURES + key + '.mtl';
        let pathObj = PATHTEXTURES + key + '.obj';
        let value = ARRAYOBJECTSSAND[key];
        let nameObj = key;
        console.log("loading " + pathMat + " and " + pathObj);
        loaderMat.load
        (
        	// resource URL
        	pathMat,
        	// called when resource is loaded
        	function ( materials ) 
            {
        	    materials.preload();
        	    loaderObj.setMaterials(materials);
        	    // load a resource
                loaderObj.load
                (
                	// resource URL
                	pathObj,
                	// called when resource is loaded
                	function ( object ) 
                    {
                        let boundingBox = new THREE.Box3().setFromObject(object);
                        
                        object.traverse( function ( child ) {
                            if ( child instanceof THREE.Mesh ) {
                                child.castShadow = true;
                                object.receiveShadow = true;
                            }
                        } );
                        
                	    object.position.set(0,0,0);
                	    
                	    let sizeX = Math.abs(boundingBox.min.x)+Math.abs(boundingBox.max.x);
                	    let sizeY = Math.abs(boundingBox.min.y)+Math.abs(boundingBox.max.y);
                	    let sizeZ = Math.abs(boundingBox.min.z)+Math.abs(boundingBox.max.z);
                	    
                        let tmp  = new Obj(nameObj, 1 + Math.ceil((value * sizeX / squaresize) * 2),Math.abs(boundingBox.min.y)*value, 1 + Math.ceil((value * sizeZ / squaresize) * 2));
                        object.scale.multiplyScalar(value);
                        tmp.obj = object;
                	    
                	    if( Math.ceil((sizeZ-1)/2) < limitSizeForRareObjects && Math.ceil((sizeX-1)/2) < limitSizeForRareObjects )
                        {
                            objects.objectsSand.recurrentObjects.push(tmp);
                        }
                        else
                        {
                            objects.objectsSand.rareObjects.push(tmp);
                        }
                        allObjects.push(tmp);
                	}
                );
        	}
        );
    }
    console.log("loading " + Object.keys(ARRAYOBJECTSSNOW).length + " elements ");
    
    for (let key in ARRAYOBJECTSSNOW) 
    {
        let loaderMat = new THREE.MTLLoader(loadingManager);
        let loaderObj = new THREE.OBJLoader(loadingManager);
        loaderMat.setTexturePath(PATHTEXTURES);
        let pathMat = PATHTEXTURES + key + '.mtl';
        let pathObj = PATHTEXTURES + key + '.obj';
        let value = ARRAYOBJECTSSNOW[key];
        let nameObj = key;
        console.log("loading " + pathMat + " and " + pathObj);
        loaderMat.load
        (
        	// resource URL
        	pathMat,
        	// called when resource is loaded
        	function ( materials ) 
            {
        	    materials.preload();
        	    loaderObj.setMaterials(materials);
        	    // load a resource
                loaderObj.load
                (
                	// resource URL
                	pathObj,
                	// called when resource is loaded
                	function ( object ) 
                    {
                        let boundingBox = new THREE.Box3().setFromObject(object);
                        
                        object.traverse( function ( child ) {
                            if ( child instanceof THREE.Mesh ) {
                                child.castShadow = true;
                                object.receiveShadow = true;
                            }
                        } );
                        
                	    object.position.set(0,0,0);
                	    
                	    let sizeX = Math.abs(boundingBox.min.x)+Math.abs(boundingBox.max.x);
                	    let sizeY = Math.abs(boundingBox.min.y)+Math.abs(boundingBox.max.y);
                	    let sizeZ = Math.abs(boundingBox.min.z)+Math.abs(boundingBox.max.z);
                	    
                        let tmp  = new Obj(nameObj, 1 + Math.ceil((value * sizeX / squaresize) * 2),Math.abs(boundingBox.min.y)*value, 1 + Math.ceil((value * sizeZ / squaresize) * 2));
                        object.scale.multiplyScalar(value);
                        tmp.obj = object;
                	    
                	    if( Math.ceil((sizeZ-1)/2) < limitSizeForRareObjects && Math.ceil((sizeX-1)/2) < limitSizeForRareObjects )
                        {
                            objects.objectsSnow.recurrentObjects.push(tmp);
                        }
                        else
                        {
                            objects.objectsSnow.rareObjects.push(tmp);
                        }
                        allObjects.push(tmp);
                	}
                );
        	}
        );
    }
}

//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();
    updateGround();
    soundManager.currentSound = grounds[ai][aj].type;
    soundManager.playCurrentSound();
    threeworld.scene.add(selectionBox);

}

//Initialization of the loading Manager. When everything is loaded, create all HUD elements and start to initialize the world
function initLoadingManager() 
{
  loadingManager = new THREE.LoadingManager();
  AB.loadingScreen();
  
  loadingManager.onLoad = function () {
    	console.log( 'Loading complete!');
    	
    	if ( AB.onDesktop() )
        {
                $("#user_span1").html("<p>Use WASD or Arrows to move, mouse to look around and space to jump.</p>"+
                "<p>Tap '1' to enter first edit mode, then click left to create Objects</p>"+
                "<p>You can change the object by scrolling !</p>"+
                "<p>Tap '2' to enter second edit mode, then click left to destroy objects</p>"+
                "<p>click right to cancel each edit mode. (Creation or Destruction)</p>"+
                "<p> If you want to re-generate the world, you can press R !</p>");
                if (AB.runloggedin)
                {
                        // Definitely can save, not sure if can restore:
                        $("#user_span2").html ( " <button onclick='AB.saveData();' >Save your work</button> " );
                        
                        // Check if any data exists, if so make restore button:
                        AB.queryDataExists();           // will call World.queryDataExists when done 
                }       
                else    
                {
                    $("#user_span2").html(  " <p> <b> To save your work, go to the World page and run this \"logged in\". </b> </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;
                }
                
                $("#user_span7").html("<p>Enable snow <input type=\"checkBox\" id=\"enableSnowHtml\" checked> Max element for one section <input type=\"number\" id=\"maxElementsSnowHtml\" min=\"0\" max=\"300\" value=\"" + maxElementsSnow + "\"></p>");
                
                document.getElementById("maxElementsSnowHtml").onchange = function() {
                    maxElementsSnow = this.value;
                    HUDEVENT = true;
                }
                document.getElementById("maxElementsSnowHtml").onkeypress = function() {
                    maxElementsSnow = this.value;
                    HUDEVENT = true;
                }
                
                document.getElementById("enableSnowHtml").onchange = function() {
                    if(!enableSand && !enableForest)
                    {
                        this.checked = true;
                    }
                    else
                    {
                        enableSnow = !enableSnow;
                    }
                    this.releasePointerCapture();
                    HUDEVENT = true;
                }
                
                $("#user_span8").html("<p>Enable sand <input type=\"checkBox\" id=\"enableSandHtml\" checked> Max element for one section <input type=\"number\" id=\"maxElementsSandHtml\" min=\"0\" max=\"300\" value=\"" + maxElementsSand + "\"></p>");
                
                document.getElementById("maxElementsSandHtml").onchange = function() {
                    maxElementsSand = this.value;
                    HUDEVENT = true;
                }
                document.getElementById("maxElementsSandHtml").onkeypress = function() {
                    maxElementsSand = this.value;
                    HUDEVENT = true;
                }
                
                document.getElementById("enableSandHtml").onchange = function() {
                    if(!enableSnow && !enableForest)
                    {
                        this.checked = true;
                    }
                    else
                    {
                        enableSand = !enableSand;
                    }
                    HUDEVENT = true;
                }
                
                $("#user_span9").html("<p>Enable forest <input type=\"checkBox\" id=\"enableForestHtml\" checked> Max element for one section <input type=\"number\" id=\"maxElementsForestHtml\" min=\"0\" max=\"300\" value=\"" + maxElementsForest + "\"></p>");
                
                document.getElementById("maxElementsForestHtml").onchange = function() {
                    maxElementsForest = this.value;
                    HUDEVENT = true;
                }
                document.getElementById("maxElementsForestHtml").onkeypress = function() {
                    maxElementsForest = this.value;
                    HUDEVENT = true;
                }
                
                document.getElementById("enableForestHtml").onchange = function() {
                    if(!enableSnow && !enableSand)
                    {
                        this.checked = true;
                    }
                    else
                    {
                        enableForest = !enableForest;
                    }
                    HUDEVENT = true;
                }
        }
        else 
        {
            $("#user_span1").html( "<p> <b> This World currently only works on desktop. </b> </p>" );
        }
 	    loaded = true;
 	    AB.removeLoading();
 	    createNewInfiniteWorld();
 	    sun = new Sun();
    };

    loadingManager.onProgress = function ( url, itemsLoaded, itemsTotal ) {
    	let s = 'Loading file: ' + itemsLoaded + ' of ' + itemsTotal + ' files.';
 	    $("#user_span1").html( s );
    };
}

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

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

this.newRun = function() 
{
    camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, SKYDISTANCE );
    threeworld.camera = camera;
    
    let vector = new THREE.Vector3(0, 0, -1);
    vector = camera.localToWorld(vector);
    vector.sub(camera.position); // Now vector is a unit vector with the same direction as the camera
    

    rayToDestroy = new THREE.Raycaster( camera.position, vector);
    
    threeworld.init3d ( 0, 0, SKYCOLOR  ); 
    // can adjust renderer:
	threeworld.renderer.shadowMap.enabled = true;
	threeworld.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
	
	//Initialize the Object that will contain all possible 3D Objects in the world
    objects = new ObjectsConteneur();
    
    //position of mouse in the canvas
    mouse = new THREE.Vector2();
    
    //First person controller
    controls = new THREE.PointerLockControls( camera );
    
    //Start loading objects
    initLoadingManager();
    loaderObjects();

 	 //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 49:
				case 97: // Create Object (1)
				    CreateObject();
				    break;
				    
			    case 50:
				case 98: // Destroy Object (2)
				    editMode = 1;
				    break;    
				    
				case 82: //Re-generateTheworld (R)
				    createNewInfiniteWorld();
				    threeworld.scene.add(sun.object);
				    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 );
	document.addEventListener('click', OnClick, false);
	document.addEventListener('mousewheel', onScroll, false);
	document.addEventListener('DOMMouseScroll', onScroll, false);
	document.addEventListener('onmousewheel', onScroll, 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>');

		}
		
};




this.nextStep = function()
{
    
    if(!loaded)
    {
        console.warn( 'Charging' );
    }
    else
    {
        //======================================================================
        //This will handle moving the player and the camera
        //======================================================================
        
            sun.animate();

			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;
			
		//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;
            updateGround();
            if(grounds[ai][aj].type != soundManager.currentSound)
            {
                soundManager.currentSound = grounds[ai][aj].type;
                soundManager.playCurrentSound();
            }
        }
        ///
        
        //Update of the sound depending of the actual zone where the player is.
        soundManager.updateSound();
    }
};

this.endRun = function()
{
};

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



//==============================================================================
// Functions to store/restore data
//==============================================================================

this.restoreData = function ( a )          // process object returned from server  
{
    console.log ( "Restore Data");
    
    //We store saving data so that the player can update his save
    saving = a;
    
    //We re-generate the world that was saved
    RecreateInfiniteWorld(a);
};
 
 
 this.queryDataExists = function ( exists )
 {
        if ( exists ) 
          $("#user_span3").html ( "<p> <button onclick='AB.restoreData();' >Restore your work</button> </p>" );
 };


this.saveData = function()                 // defines what object to save to server
{
    HUDEVENT = true;
    return (saving);  
};

//Function to recreate a world that was saved on the server.
//a 
function RecreateInfiniteWorld(data)
{
    worldSize = data.worldSize;
    groundMultiplier = data.groundMultiplier;
    viewDistance = data.viewDistance;
    
    while (threeworld.scene.children.length)
    {
        threeworld.scene.remove(threeworld.scene.children[0]);
    }
    threeworld.scene.add(controls.getObject());
    threeworld.scene.add(sun.object);
    threeworld.scene.fog = new THREE.Fog(SKYCOLOR, 0, SKYDISTANCE);
    
    // LIGHTS
	let hemiLight = new THREE.HemisphereLight( SKYCOLOR, SKYCOLOR, 0.4 );
	hemiLight.position.set( 0, 50, 0 );
	threeworld.scene.add( hemiLight );
    
    for(let i = 0; i < worldSize; i++)
    {
        for(let j = 0; j < worldSize; j++)
        {
            grounds[i][j] = new Ground(false, new THREE.Vector3(0,0,0));
            grounds[i][j].type = data.grounds[i][j].biom;
            grounds[i][j].active = false;
            saving.grounds[i][j].biom = data.grounds[i][j].biom;
        }
    }
    for(let i = 0; i < worldSize; i++)
    {
        for(let j = 0; j < worldSize; j++)
        {
            if(data.grounds[i][j].elements.length !== 0)
            {
                grounds[i][j].ground = createGround(new THREE.Vector3(0,0,0), grounds[i][j].type, i, j, data.grounds[i][j].elements);
                grounds[i][j].ground.position.set((i- Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j- Math.trunc(worldSize/2)) * squaresize*groundMultiplier)
                threeworld.scene.add(grounds[i][j].ground);
            }
        }
    }
    for(let i = 0; i < data.userObjects.length; i++)
    {
        let t = findObjectLoad(data.userObjects[i].o);
        if(t !== null)
        {
                let obj = t.obj.clone();
                obj.scale.multiplyScalar(data.userObjects[i].s);
                obj.position.set(data.userObjects[i].x, data.userObjects[i].y, data.userObjects[i].z);
                threeworld.scene.add(obj);
        } 
    }
    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].ground.clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[worldSize + i][j - worldSize].ground.clone();
        			}
        			else
        			{
        				tmp = grounds[worldSize + i][j].ground.clone();
        			}
        			
        		}
        		else if(i >= worldSize)
        		{
        			if(j < 0)
        			{
        				tmp = grounds[i - worldSize][worldSize + j].ground.clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[i - worldSize][j - worldSize].ground.clone();
        			}
        			else
        			{
        				tmp = grounds[i - worldSize][j].ground.clone();
        			}
        		}
        		else if(i >= worldSize)
        		{
        			if(j < 0)
        			{
        				tmp = grounds[i - worldSize][worldSize + j].ground.clone();
        			}
        			else if(j >= worldSize)
        			{
        				tmp = grounds[i - worldSize][j - worldSize].ground.clone();
        			}
        			else
        			{
        				tmp = grounds[i - worldSize][j].ground.clone();
        			}
        		}
        		else if( j < 0)
        		{
        			tmp = grounds[i][worldSize + j].ground.clone();
        		}
        		else if(j >= worldSize)
        		{
        			tmp = grounds[i][j - worldSize].ground.clone();
        		}
        		tmp.position.set((i - Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j - Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
                threeworld.scene.add(tmp);
            }
        }
    }
}

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

}