// Cloned by Michael Walsh on 29 Oct 2023 from World "Infinite World Ground + Sun" by Enhanced
// Please leave this clone trail here.
//==============================================================================
// 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
'use strict';
const squaresize = 7 ; // The world is like a infinite grid of square. squaresize is the size of a square
const MOVESPEED = 7; // Speed of the player
const viewDistance = 2; // determinate the maximum distance to see ground (viewDistance * squaresize * groundMultiplier)
const groundMultiplier = 551; // The world is divided by zone (grounds) that size groundMultiplier*squaresize
const 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
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;
ABHandler.MAXCAMERAPOS = MAXPOS * 10 ;// allow camera go far away
ABWorld.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.
//==============================================================================
// Defines the THREE.PointerLockControls class, source at https://threejs.org/
//==============================================================================
class PointerLock { // extends THREE.PointerLockControls
pitchObject = new THREE.Object3D();
yawObject = new THREE.Object3D();
attachedObject = new THREE.Object3D();
PI_2 = Math.PI / 2;
// assumes the camera itself is not rotated
direction = new THREE.Vector3( 0, 0, - 1 );
rotation = new THREE.Euler( 0, 0, 0, 'YXZ' );
enabled = false;
constructor(camera) {
camera.rotation.set( 0, 0, 0 );
this.pitchObject.add( camera );
this.yawObject.position.y = 10;
this.yawObject.add( this.pitchObject );
this.yawObject.add(this.attachedObject);
this.attachedObject.position.set(0, -this.yawObject.position.y, -150);
document.addEventListener( 'mousemove', this.onMouseMove, false );
}
onMouseMove = (event) => {
if ( this.enabled === false ) return;
let movementX = event.movementX || 0;
let movementY = event.movementY || 0;
this.yawObject.rotation.y -= movementX * 0.002;
this.pitchObject.rotation.x -= movementY * 0.002;
this.pitchObject.rotation.x = Math.max( - this.PI_2, Math.min( this.PI_2, this.pitchObject.rotation.x ) );
};
getAttachedObject() {
return this.attachedObject;
}
dispose() {
document.removeEventListener( 'mousemove', this.onMouseMove, false );
}
getObject() {
return this.yawObject;
}
getDirection(v) {
rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 );
v.copy( this.direction ).applyEuler( this.rotation );
return v;
}
}
//==============================================================================
// Sun Class
//==============================================================================
// Here is the Sun Object.
class Sun
{
target;
size;
speed;
skyDistance;
angle = 0;
object;
d = 1000;
stars = [];
starMaterial;
starGyroscope = new THREE.Mesh();
object = new THREE.DirectionalLight( 0xffffff, 0.1 );
// target (Object3D) : the player, or camera if you are in first person view. The sun need to be relative to a point
// size (number): the size of the sun & stars
// speed (number): the speed of the rotation of the sun
// skyDistance (number): the distance of the sky from the target.
constructor(target, size, speed, skyDistance) {
this.target = target;
this.size = size;
this.speed = speed;
this.skyDistance = skyDistance;
this.object.position.set(0,
Math.cos(this.angle) * this.skyDistance,
Math.sin(this.angle) * this.skyDistance
);
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;
this.object.shadow.camera.left = -this.d;
this.object.shadow.camera.right = this.d;
this.object.shadow.camera.top = this.d;
this.object.shadow.camera.bottom = -this.d;
this.object.shadow.bias = -0.0001;
ABWorld.scene.add(this.object);
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(this.size, 32, 32),
new THREE.MeshBasicMaterial({color : "yellow", fog: false}) );
this.object.add( sunball );
this.target.add( this.starGyroscope);
//Create all this.stars
for (let i = 0; i < NBSTARS; i++)
{
let radius = AB.randomFloatAtoB ( this.size/25, this.size/8 );
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), this.skyDistance*Math.cos(t), this.skyDistance*Math.sin(s)*Math.sin(t));
this.stars.push(star);
this.starGyroscope.add(star);
}
}
map(n, start1, stop1, start2, stop2)
{
return ((n-start1)/(stop1-start1))*(stop2-start2)+start2;
}
//Use this in nextStep to make the sun move
animate()
{
this.angle += 0.001 * this.speed;
//normalize this.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(this.target.position.x, Math.cos(this.angle)*this.skyDistance, Math.sin(this.angle)*this.skyDistance + this.target.position.z);
this.object.intensity = this.getSunIntensity();
let c = new THREE.Color(this.getSkyColor());
ABWorld.scene.background = c;
this.setStarsOpacity();
this.starGyroscope.rotation.set(-this.target.rotation.x, -this.target.rotation.y, -this.target.rotation.z);
}
//change star opacity and fog depending of the position of the sun
setStarsOpacity()
{
if (this.angle > Math.PI/2 && this.angle < Math.PI*3/4)
{
this.starMaterial.opacity = this.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)
{
this.starMaterial.opacity = this.map(this.angle, -Math.PI*3/4, -Math.PI/2, 0.8, 0);
}
console.log(Math.round(this.angle,1), Math.round((this.angle / Math.PI) * 8, 1), this.starMaterial.opacity);
}
/**
* 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}
*/
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);
}
//return the color of the sky depending of the position of the sun (to get the sunrise)
getSkyColor()
{
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 this.lerpColor(0x7ec0ee, 0xfd5e53, this.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 this.lerpColor(0xfd5e53, 0x0c3166, this.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 this.lerpColor(0x0c3166, 0xfd5e53, this.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 this.lerpColor(0xfd5e53, 0x7ec0ee, this.map(this.angle, -Math.PI/2, -Math.PI*3/8, 0, 1));
}
}
//return intensity of the sun
getSunIntensity()
{
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 this.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 this.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 this.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 this.map(this.angle, -Math.PI/2, -Math.PI*3/8, 1, 2);
}
}
}
//==============================================================================
// World Definition
//==============================================================================
class World {
//==============================================================================
// All variables
//==============================================================================
groundTexture; // Texture of the ground
grounds; // Array2d : Stock all ground of the world
pai;
paj; // past position in grounds of the player
raycaster; // to determine direction of a click
controls;
moveForward = false;
moveBackward = false;
moveLeft = false;
moveRight = false;
canJump = false;
prevTime = performance.now();
velocity = new THREE.Vector3();
sun;//sun and all functions with it (also stars)
endCondition = false;
//==============================================================================
//==============================================================================
//==============================================================================
//==============================================================================
//Just return a ground Elements that receive shadow
createGround() {
let gr = new THREE.Mesh (
new THREE.PlaneGeometry ( squaresize*groundMultiplier, squaresize*groundMultiplier ),
this.groundTexture );
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 this.grounds
//around the world to fake a loop
initGround() {
for(let i = 0; i < worldSize; i++)
{
for(let j = 0; j < worldSize; j++)
{
this.grounds[i][j] = this.createGround();
this.grounds[i][j].position.set((i- Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j- Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
ABWorld.scene.add(this.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 = this.grounds[worldSize + i][worldSize + j].clone();
}
else if(j >= worldSize)
{
tmp = this.grounds[worldSize + i][j - worldSize].clone();
}
else
{
tmp = this.grounds[worldSize + i][j].clone();
}
}
else if(i >= worldSize)
{
if(j < 0)
{
tmp = this.grounds[i - worldSize][worldSize + j].clone();
}
else if(j >= worldSize)
{
tmp = this.grounds[i - worldSize][j - worldSize].clone();
}
else
{
tmp = this.grounds[i - worldSize][j].clone();
}
}
else if(i >= worldSize)
{
if(j < 0)
{
tmp = this.grounds[i - worldSize][worldSize + j].clone();
}
else if(j >= worldSize)
{
tmp = this.grounds[i - worldSize][j - worldSize].clone();
}
else
{
tmp = this.grounds[i - worldSize][j].clone();
}
}
else if( j < 0)
{
tmp = this.grounds[i][worldSize + j].clone();
}
else if(j >= worldSize)
{
tmp = this.grounds[i][j - worldSize].clone();
}
tmp.position.set((i - Math.trunc(worldSize/2)) * squaresize * groundMultiplier,0, (j - Math.trunc(worldSize/2)) * squaresize*groundMultiplier);
ABWorld.scene.add(tmp);
}
}
}
}
//==============================================================================
//==============================================================================
//==============================================================================
// Functions link to an event
//==============================================================================
//Create a new infinite world. First destroy the previous one and then recreate one.
createNewInfiniteWorld() {
while (ABWorld.scene.children.length)
{
ABWorld.scene.remove(ABWorld.scene.children[0]);
}
ABWorld.scene.add(this.controls.getObject());
this.init();
}
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
handleKeyUp (e) {
if((e.keyCode == 38) || (e.keyCode == 40))
{
this.velocity = 0;
}
}
//==============================================================================
//==============================================================================
//Load all you need
loader()
{
let planeTex = new THREE.TextureLoader().load( "/uploads/meak/grass.jpg" );
planeTex.wrapS = planeTex.wrapT = THREE.RepeatWrapping;
planeTex.repeat.set( 8, 8 );
this.groundTexture = new THREE.MeshPhongMaterial({map: planeTex });
//this.groundTexture.wrapAround = true;
this.groundTexture.dithering = true;
this.groundTexture.castShadow = true;
this.groundTexture.receiveShadow = true;
}
//Initalization before running the world
init()
{
this.grounds = new Array(worldSize);
for (let i = 0; i < worldSize; i++)
{
this.grounds[i] = new Array(worldSize);
for (let j = 0; j < worldSize; j++)
{
this.grounds[i][j] = null;
}
}
let pos = new THREE.Vector3();
pos = this.camera.getWorldPosition(pos);
this.pai = parseInt(worldSize/2 + pos.x/(squaresize*groundMultiplier));
this.paj = parseInt(worldSize/2 + pos.z/(squaresize*groundMultiplier));
this.initGround();
// LIGHTS
let hemiLight = new THREE.HemisphereLight( SKYCOLOR, SKYCOLOR, 0.4 );
hemiLight.position.set( 0, 50, 0 );
ABWorld.scene.add( hemiLight );
}
checkPositionPlayer()
{
let ai, aj;
let pos = new THREE.Vector3();
pos = this.camera.getWorldPosition(pos);
//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 + pos.x/(squaresize*groundMultiplier) < 0)
ai = -Math.ceil(-(worldSize/2 + pos.x/(squaresize*groundMultiplier)));
else
ai = Math.trunc(worldSize/2 + pos.x/(squaresize*groundMultiplier));
if(worldSize/2 + pos.z/(squaresize*groundMultiplier) < 0)
aj = -Math.ceil(-(worldSize/2 + pos.z/(squaresize*groundMultiplier)));
else
aj = Math.trunc(worldSize/2 + pos.z/(squaresize*groundMultiplier));
if(ai < 0)
this.controls.getObject().position.x = (worldSize/2) * groundMultiplier * squaresize - 0.01;
if(ai >= worldSize)
this.controls.getObject().position.x = - worldSize/2 * groundMultiplier * squaresize;
if(aj >= worldSize)
this.controls.getObject().position.z = - worldSize/2 * groundMultiplier * squaresize;
if(aj < 0)
this.controls.getObject().position.z = (worldSize/2) * groundMultiplier * squaresize - 0.01;
if(ai != this.pai || aj != this.paj)
{
this.pai = ai;
this.paj = aj;
}
}
//==============================================================================
//==============================================================================
//==============================================================================
// Function newRun, init and endRun
//==============================================================================
onKeyDown = (event) => {
switch ( event.keyCode ) {
case 38: // up
case 87: // w
this.moveForward = true;
break;
case 37: // left
case 65: // a
this.moveLeft = true; break;
case 40: // down
case 83: // s
this.moveBackward = true;
break;
case 39: // right
case 68: // d
this.moveRight = true;
break;
case 32: // space
if ( this.canJump === true || true ) this.velocity.y += 350;
this.canJump = false;
break;
}
};
onKeyUp = ( event ) => {
switch( event.keyCode ) {
case 38: // up
case 87: // w
this.moveForward = false;
break;
case 37: // left
case 65: // a
this.moveLeft = false;
break;
case 40: // down
case 83: // s
this.moveBackward = false;
break;
case 39: // right
case 68: // d
this.moveRight = false;
break;
}
};
newRun()
{
this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, SKYDISTANCE );
ABWorld.camera = this.camera;
ABWorld.init3d ( 0, 0, SKYCOLOR );
// can adjust renderer:
ABWorld.renderer.shadowMap.enabled = true;
ABWorld.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//load ground texture
this.loader();
//First person controller
this.controls = new PointerLock( this.camera );
//This will handle key presses
document.addEventListener('keydown', this.onKeyDown, false );
document.addEventListener('keyup', this.onKeyUp, false );
this.raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 );
//The following handles pointer locking when clicking the window
if ( 'pointerLockElement' in document ) {
let element = document.body;
let pointerlockchange = ( event ) => {
if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element )
{
if(!this.controls.enabled && this.controls.getAttachedObject().children.length !== 0)
{
this.controls.getAttachedObject().remove(this.controls.getAttachedObject().children[1]);
this.controls.getAttachedObject().remove(this.controls.getAttachedObject().children[0]);
}
if(!this.controls.enabled)
{
this.controls.enabled = true;
$("#user_span10").html("");
}
} else {
this.controls.enabled = false;
$("#user_span10").html("<p><b>Click screen to enable mouse this.controls</b></p>");
}
};
let pointerlockerror = ( event ) => {
console.error("pointerlockerror");
};
// Hook pointer lock state change events
document.addEventListener( 'pointerlockchange', pointerlockchange, false );
document.addEventListener( 'pointerlockerror', 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>");
$("#user_span10").html("<p><b>Click screen to enable mouse this.controls</b></p>");
}
else
{
$("#user_span1").html( "<p><b>This World currently only works on desktop.</b></p>" );
}
this.createNewInfiniteWorld();
//add this.sun
this.sun = new Sun(this.controls.getObject(), 100, 3, SKYDISTANCE);
};
nextStep() {
//======================================================================
//This will handle moving the player and the camera
//======================================================================
this.sun.animate();
this.raycaster.ray.origin.copy( this.controls.getObject().position );
this.raycaster.ray.origin.y -= 10;
let time = performance.now();
let delta = ( time - this.prevTime ) / 1000;
this.velocity.x -= this.velocity.x * 10.0 * delta;
this.velocity.z -= this.velocity.z * 10.0 * delta;
this.velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
const direction = new THREE.Vector3();
direction.z = Number( this.moveForward ) - Number( this.moveBackward );
direction.x = Number( this.moveLeft ) - Number( this.moveRight );
direction.normalize(); // this ensures consistent movements in all directions
/*if(this.moveLeft && !this.moveRight) {
this.controls.getObject().rotation.y += 0.02;
} else if(!this.moveLeft && this.moveRight) {
this.controls.getObject().rotation.y -= 0.02;
}*/
this.controls.getObject().rotation.y += direction.x * 0.03;
//let q = new THREE.Vector3();
//this.camera.getWorldDirection (q);
//console.log(q);
if ( this.moveForward || this.moveBackward ) this.velocity.z -= direction.z * 400.0 * MOVESPEED * delta;
//if ( this.moveLeft || this.moveRight ) this.velocity.x -= direction.x * 400.0 * MOVESPEED * delta;
//this.controls.getObject().translateX( this.velocity.x * delta );
this.controls.getObject().translateY( this.velocity.y * delta );
this.controls.getObject().translateZ( this.velocity.z * delta );
if ( this.controls.getObject().position.y < 10 ) {
this.velocity.y = 0;
this.controls.getObject().position.y = 10;
this.canJump = true;
}
this.prevTime = time;
//Function to do the loop if the player is at the edge of the world
this.checkPositionPlayer();
};
//==============================================================================
//==============================================================================
}
//AB.world = new World();
//AB.world.newRun();