//==============================================================================
// Welcome to the Infinite world with only grounds !
//==============================================================================
// This program was made by Nathan Bonnard.
// In this world, you can generate an infinite world !
// But this isn't really infinite, it would be impossible,
// the world do a simple loop when the player reach the border. Like in real life !
// This is a little version of the real infinite world that you can find aswell on the enhanced page
//------------------------------ TWEAKER BOX ------------------------------//
const squaresize = 7 ; // The world is like a infinite grid of square. squaresize is the size of a square
var MOVESPEED = 7; // Speed of the player
var viewDistance = 2; // determinate the maximum distance to see ground (viewDistance * squaresize * groundMultiplier)
var groundMultiplier = 551; // The world is divided by zone (grounds) that size groundMultiplier*squaresize
var worldSize = 13; // dimension of the world, it is not really infinite but big and can loop. There is worldSize * worldSize Grounds
//------------------------------ END OF TWEAKER BOX ------------------------------//
var newviewDistance = viewDistance; // letiables to re-generate the world with new letiables
var newGroundMultiplier = groundMultiplier;
var newworldSize = worldSize;
const MAXPOS = 4000 ;
const startRadiusConst = MAXPOS * 0.5 ; // distance from centre to start the camera at
const maxRadiusConst = MAXPOS * 5 ; // maximum distance from camera we will render things
const SKYCOLOR = 0x6495ED;
const LIGHTCOLOR = 0xffffff ;
//used to manage the click and give back control to the user when you click on a button/text box
//and don't want to enter into first person mode
var HUDEVENT = false;
var mouse; // vector2 : position of the mouse on the screen
threehandler.MAXCAMERAPOS = MAXPOS * 10 ;// allow camera go far away
threeworld.drawCameraControls = false;
AB.clockTick = 20;
// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps = 1000000;
// Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep = 50;
// Take screenshot on this step. (All resources should have finished loading.) Default 50.
/**
* A linear interpolator for hexadecimal colors
* @param {Int} a
* @param {Int} b
* @param {Number} amount
* @example
* // returns 0x7F7F7F
* lerpColor(0x000000, 0xffffff, 0.5)
* @returns {Int}
*/
function lerpColor(a, b, amount) {
let ah = a;
ar = ah >> 16, ag = ah >> 8 & 0xff, ab = ah & 0xff,
bh = b;
br = bh >> 16, bg = bh >> 8 & 0xff, bb = bh & 0xff,
rr = ar + amount * (br - ar),
rg = ag + amount * (bg - ag),
rb = ab + amount * (bb - ab);
return ((1 << 24) + (rr << 16) + (rg << 8) + rb | 0);
}
/**/
function map(n, start1, stop1, start2, stop2)
{
return ((n-start1)/(stop1-start1))*(stop2-start2)+start2;
}
//==============================================================================
// World Definition
//==============================================================================
function World() {
//==============================================================================
// All variables
//==============================================================================
var groundTexture; // Texture of the ground
var grounds; // Array2d : Stock all ground of the world
var ai, aj; // position in grounds of the player
var pai, paj; // past position in grounds of the player
var timeRunning = 0; // time updated
var raycaster; // to determine direction of a click
var camera, controls;
var moveForward = false;
var moveBackward = false;
var moveLeft = false;
var moveRight = false;
var canJump = false;
var prevTime = performance.now();
var velocity = new THREE.Vector3();
var direction = new THREE.Vector3();
var vertex = new THREE.Vector3();
var color = new THREE.Color();
this.endCondition = false;
//==============================================================================
//==============================================================================
//==============================================================================
// All Class
//==============================================================================
//==============================================================================
// Defines the THREE.PointerLockControls class, source at https://threejs.org/
//==============================================================================
THREE.PointerLockControls = function(camera) {
let scope = this;
camera.rotation.set( 0, 0, 0 );
let pitchObject = new THREE.Object3D();
pitchObject.add( camera );
let yawObject = new THREE.Object3D();
yawObject.position.y = 10;
yawObject.add( pitchObject );
let attachedObject = new THREE.Object3D();
yawObject.add(attachedObject);
attachedObject.position.set(0,-yawObject.position.y,-150);
let PI_2 = Math.PI / 2;
let onMouseMove = function ( event ) {
if ( scope.enabled === false ) return;
let movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
let movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
yawObject.rotation.y -= movementX * 0.002;
pitchObject.rotation.x -= movementY * 0.002;
pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) );
};
this.getAttachedObject = function () {
return attachedObject;
};
this.dispose = function () {
document.removeEventListener( 'mousemove', onMouseMove, false );
};
document.addEventListener( 'mousemove', onMouseMove, false );
this.enabled = false;
this.getObject = function () {
return yawObject;
};
this.getDirection = function () {
// assumes the camera itself is not rotated
let direction = new THREE.Vector3( 0, 0, - 1 );
let rotation = new THREE.Euler( 0, 0, 0, 'YXZ' );
return function ( v ) {
rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 );
v.copy( direction ).applyEuler( rotation );
return v;
};
}();
};
//==============================================================================
// Class to stock Each Objects that were loaded
// with their combobox size to place them in space
// and to avoid collision ( objects at same place)
class Obj
{
constructor(nameDoc, sX, sY, sZ)
{
this.path = nameDoc;
this.sizeX = sX;
this.sizeY = sY;
this.sizeZ = sZ;
this.obj;
}
}
//==============================================================================
//==============================================================================
//Just return a ground Elements that receive shadow
function createGround()
{
let texture = groundTexture;
let gr = new THREE.Mesh (
new THREE.PlaneGeometry ( squaresize*groundMultiplier, squaresize*groundMultiplier ),
texture );
gr.receiveShadow = true;
gr.rotation.x = (Math.PI / 2) * 3;
return gr;
}
//initialize the ground of the world, to determine all type nd create fake grounds
//around the world to fake a loop
function initGround()
{
for(let i = 0; i < worldSize; i++)
{
for(let j = 0; j < worldSize; j++)
{
grounds[i][j] = createGround();
grounds[i][j].position.set((i- Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j- Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
threeworld.scene.add(grounds[i][j]);
}
}
for(let i = - viewDistance; i < worldSize + viewDistance; i++)
{
for(let j = - viewDistance; j < worldSize + viewDistance; j++)
{
let b = (worldSize + viewDistance);
if(i < 0 || j < 0 || i >= worldSize || j >= worldSize)
{
let tmp;
if( i < 0)
{
if(j < 0)
{
tmp = grounds[worldSize + i][worldSize + j].clone();
}
else if(j >= worldSize)
{
tmp = grounds[worldSize + i][j - worldSize].clone();
}
else
{
tmp = grounds[worldSize + i][j].clone();
}
}
else if(i >= worldSize)
{
if(j < 0)
{
tmp = grounds[i - worldSize][worldSize + j].clone();
}
else if(j >= worldSize)
{
tmp = grounds[i - worldSize][j - worldSize].clone();
}
else
{
tmp = grounds[i - worldSize][j].clone();
}
}
else if(i >= worldSize)
{
if(j < 0)
{
tmp = grounds[i - worldSize][worldSize + j].clone();
}
else if(j >= worldSize)
{
tmp = grounds[i - worldSize][j - worldSize].clone();
}
else
{
tmp = grounds[i - worldSize][j].clone();
}
}
else if( j < 0)
{
tmp = grounds[i][worldSize + j].clone();
}
else if(j >= worldSize)
{
tmp = grounds[i][j - worldSize].clone();
}
tmp.position.set((i - Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j - Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
threeworld.scene.add(tmp);
}
}
}
}
//==============================================================================
//==============================================================================
//==============================================================================
// Functions link to an event
//==============================================================================
//Create a new infinite world. First destroy the previous one and then recreate one.
function createNewInfiniteWorld()
{
viewDistance = newviewDistance;
worldSize = newworldSize;
groundMultiplier = newGroundMultiplier;
while (threeworld.scene.children.length)
{
threeworld.scene.remove(threeworld.scene.children[0]);
}
threeworld.scene.add(controls.getObject());
init();
}
function onDocumentTouchStart( event )
{
console.log("onDocumentTouchStart");
event.preventDefault();
event.clientX = event.touches[0].clientX;
event.clientY = event.touches[0].clientY;
onDocumentMouseDown( event );
}
//Called when a key is up
function handleKeyUp (e)
{
if((e.keyCode == 38) || (e.keyCode == 40))
{
velocity = 0;
}
if(e.keyCode == 37 || e.keyCode == 39)
{
speedRotation = 0;
}
if(e.keyCode == 37 || e.keyCode == 39)
{
speedRotation = 0;
}
}
//==============================================================================
//==============================================================================
//Load all you need
function loader()
{
planeTex = THREE.ImageUtils.loadTexture( "/uploads/meak/grass.jpg" );
planeTex.wrapS = planeTex.wrapT = THREE.RepeatWrapping;
planeTex.repeat.set( 8, 8 );
groundTexture = new THREE.MeshPhongMaterial({map: planeTex, dithering: true });
groundTexture.wrapAround = true;
groundTexture.castShadow = true;
groundTexture.receiveShadow = true;
}
//Initalization before running the world
function init()
{
grounds = new Array(worldSize);
for (let i = 0; i < worldSize; i++)
{
grounds[i] = new Array(worldSize);
for (let j = 0; j < worldSize; j++)
{
grounds[i][j] = null;
}
}
ai = parseInt(worldSize/2 + camera.getWorldPosition().x/(squaresize*groundMultiplier));
aj = parseInt(worldSize/2 + camera.getWorldPosition().z/(squaresize*groundMultiplier));
pai = ai;
paj = aj;
initGround();
// LIGHTS
let hemiLight = new THREE.HemisphereLight( SKYCOLOR, SKYCOLOR, 1 );
hemiLight.position.set( 0, 50, 0 );
threeworld.scene.add( hemiLight );
}
function checkPositionPlayer()
{
//Update the position of the payer in the world so that it can generate ground around the player, depending on the viewDistance
if(worldSize/2 + camera.getWorldPosition().x/(squaresize*groundMultiplier) < 0)
{
ai = -Math.ceil(-(worldSize/2 + camera.getWorldPosition().x/(squaresize*groundMultiplier)));
}
else
{
ai = Math.trunc(worldSize/2 + camera.getWorldPosition().x/(squaresize*groundMultiplier));
}
if(worldSize/2 + camera.getWorldPosition().z/(squaresize*groundMultiplier) < 0)
{
aj = -Math.ceil(-(worldSize/2 + camera.getWorldPosition().z/(squaresize*groundMultiplier)));
}
else
{
aj = Math.trunc(worldSize/2 + camera.getWorldPosition().z/(squaresize*groundMultiplier));
}
if(ai < 0)
{
controls.getObject().position.x = (worldSize/2) * groundMultiplier * squaresize - 0.01;
}
if(ai >= worldSize)
{
controls.getObject().position.x = - worldSize/2 * groundMultiplier * squaresize;
}
if(aj >= worldSize)
{
controls.getObject().position.z = - worldSize/2 * groundMultiplier * squaresize;
}
if(aj < 0)
{
controls.getObject().position.z = (worldSize/2) * groundMultiplier * squaresize - 0.01;
}
if(ai != pai || aj != paj)
{
pai = ai;
paj = aj;
}
}
//==============================================================================
//==============================================================================
//==============================================================================
// Function newRun ,init and endRun
//==============================================================================
this.newRun = function()
{
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
threeworld.camera = camera;
threeworld.init3d ( 0, 0, SKYCOLOR );
// can adjust renderer:
threeworld.renderer.shadowMap.enabled = true;
threeworld.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//load ground texture
loader();
//position of mouse in the canvas
mouse = new THREE.Vector2();
//First person controller
controls = new THREE.PointerLockControls( camera );
//This will handle key presses
let onKeyDown = function ( event ) {
switch ( event.keyCode ) {
case 38: // up
case 87: // w
moveForward = true;
break;
case 37: // left
case 65: // a
moveLeft = true; break;
case 40: // down
case 83: // s
moveBackward = true;
break;
case 39: // right
case 68: // d
moveRight = true;
break;
case 32: // space
if ( canJump === true || true ) velocity.y += 350;
canJump = false;
break;
case 82: //Re-generateTheworld (R)
createNewInfiniteWorld();
break;
}
};
let onKeyUp = function ( event ) {
switch( event.keyCode ) {
case 38: // up
case 87: // w
moveForward = false;
break;
case 37: // left
case 65: // a
moveLeft = false;
break;
case 40: // down
case 83: // s
moveBackward = false;
break;
case 39: // right
case 68: // d
moveRight = false;
break;
}
};
document.addEventListener('keydown', onKeyDown, false );
document.addEventListener('keyup', onKeyUp, false );
raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 );
//The following handles pointer locking when clicking the window
let havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
console.log(havePointerLock);
if ( havePointerLock ) {
let element = document.body;
let pointerlockchange = function ( event ) {
if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element )
{
if(HUDEVENT)
{
document.exitPointerLock();
HUDEVENT = false;
}
else
{
if(!controls.enabled && controls.getAttachedObject().children.length !== 0)
{
controls.getAttachedObject().remove(controls.getAttachedObject().children[1]);
controls.getAttachedObject().remove(controls.getAttachedObject().children[0]);
}
if(!controls.enabled)
{
controls.enabled = true;
$("#user_span10").html("");
}
}
} else
{
controls.enabled = false;
$("#user_span10").html("<p><b>Click screen to enable mouse controls</b></p>");
}
};
let pointerlockerror = function ( event ) {
console.error("pointerlockerror");
};
// Hook pointer lock state change events
document.addEventListener( 'pointerlockchange', pointerlockchange, false );
document.addEventListener( 'mozpointerlockchange', pointerlockchange, false );
document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false );
document.addEventListener( 'pointerlockerror', pointerlockerror, false );
document.addEventListener( 'mozpointerlockerror', pointerlockerror, false );
document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false );
document.addEventListener( 'click', function ( event ) {
// Ask the browser to lock the pointer
element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
element.requestPointerLock();
}, false );
} else {
$("#user_span1").html('<p>Your browser doesn\'t seem to support Pointer Lock API</p>');
}
if ( AB.onDesktop() )
{
$("#user_span1").html("<p>Use WASD or Arrows to move, mouse to look around and space to jump.</p>"+
"<p> If you want to re-generate the world, you can press R !</p>");
$("#user_span10").html("<p><b>Click screen to enable mouse controls</b></p>");
$("#user_span4").html("<p> Size of each parcel of the world (1 to 1000) <input type=\"number\" min=\"1\" max=\"1000\" value=\""+ newGroundMultiplier +"\" class=\"slider\" id=\"groundMultiplierHtml\"></p>");
document.getElementById("groundMultiplierHtml").onchange = function() {
newGroundMultiplier = +this.value ;
console.log("groundMultiplierHtml " + newGroundMultiplier);
HUDEVENT = true;
}
document.getElementById("groundMultiplierHtml").onkeypress = function() {
newGroundMultiplier = +this.value ;
console.log("groundMultiplierHtml " + newGroundMultiplier);
HUDEVENT = true;
}
$("#user_span5").html("<p> Size of the world (1 to 50) <input type=\"number\" min=\"1\" max=\"50\" value=\""+ newworldSize +"\" class=\"slider\" id=\"worldSizeHtml\"></p>");
document.getElementById("worldSizeHtml").onchange = function() {
newworldSize = +this.value;
if((newworldSize - 1) /2 < newviewDistance)
{
newviewDistance = 0;
document.getElementById("viewDistanceHtml").value = 0;
}
HUDEVENT = true;
console.log("worldSizeHtml " + newworldSize);
}
document.getElementById("worldSizeHtml").onkeypress = function() {
newworldSize = +this.value;
if((newworldSize - 1) /2 < newviewDistance)
{
newviewDistance = 0;
document.getElementById("viewDistanceHtml").value = 0;
}
HUDEVENT = true;
console.log("worldSizeHtml " + newworldSize);
}
$("#user_span6").html("<p> viewDistance in term of parcel (0 to 10) <input type=\"number\" min=\"0\" max=\"10\" value=\""+ newviewDistance +"\" class=\"slider\" id=\"viewDistanceHtml\"></p>");
document.getElementById("viewDistanceHtml").onchange = function() {
if(newworldSize/2 >= +this.value)
{
newviewDistance = +this.value ;
}
console.log("viewDistanceHtml " + newviewDistance);
HUDEVENT = true;
}
document.getElementById("viewDistanceHtml").onkeypress = function() {
if(newworldSize/2 >= +this.value)
{
newviewDistance = +this.value ;
}
console.log("viewDistanceHtml " + newviewDistance);
HUDEVENT = true;
}
}
else
{
$("#user_span1").html( "<p> <b> This World currently only works on desktop. </b> </p>" );
}
createNewInfiniteWorld();
};
this.nextStep = function()
{
//======================================================================
//This will handle moving the player and the camera
//======================================================================
raycaster.ray.origin.copy( controls.getObject().position );
raycaster.ray.origin.y -= 10;
let time = performance.now();
let delta = ( time - prevTime ) / 1000;
velocity.x -= velocity.x * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
direction.z = Number( moveForward ) - Number( moveBackward );
direction.x = Number( moveLeft ) - Number( moveRight );
direction.normalize(); // this ensures consistent movements in all directions
if ( moveForward || moveBackward ) velocity.z -= direction.z * 400.0 * MOVESPEED * delta;
if ( moveLeft || moveRight ) velocity.x -= direction.x * 400.0 * MOVESPEED * delta;
controls.getObject().translateX( velocity.x * delta );
controls.getObject().translateY( velocity.y * delta );
controls.getObject().translateZ( velocity.z * delta );
if ( controls.getObject().position.y < 10 ) {
velocity.y = 0;
controls.getObject().position.y = 10;
canJump = true;
}
prevTime = time;
//Function to do the loop if the player is at the edge of the world
checkPositionPlayer();
};
//==============================================================================
//==============================================================================
}