// Cloned by David Jusev on 1 Dec 2022 from World "Infinite World - V1" by Nathan Bonnard
// Please leave this clone trail here.
//==============================================================================
// 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 = 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
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
var saving; //Object that store all informations to save the world
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 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);
initThreeLights();
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 lights
function initThreeLights()
{
// LIGHTS
let hemiLight = new THREE.HemisphereLight( SKYCOLOR, SKYCOLOR, 0.4 );
hemiLight.position.set( 0, 50, 0 );
threeworld.scene.add( hemiLight );
sun = new Sun();
}
//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();
};
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 97: // Create Object (1)
CreateObject();
break;
case 98: // Destroy Object (2)
editMode = 1;
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 );
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();
raycaster.ray.origin.copy( controls.getObject().position );
raycaster.ray.origin.y -= 10;
//let intersections = raycaster.intersectObjects( objects );
//let onObject = intersections.length > 0;
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;
/*if ( onObject === true ) {
velocity.y = Math.max( 0, velocity.y );
canJump = true;
}*/
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.fog = new THREE.Fog(SKYCOLOR, 0, SKYDISTANCE);
initThreeLights();
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);
}
}
}
}
//==============================================================================
//==============================================================================
}