// Cloned by Liam on 29 Nov 2022 from World "User-controlled Model World" by Starter user
// Please leave this clone trail here.
// ==== Starter World =================================================================================================
// This code is designed for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.
// ====================================================================================================================
// User controlled (not Mind controlled) 3D model World with various features:
// - User controlled UP/LEFT/RIGHT arrows move model
// - "First Person View" - Camera rotates with direction you are facing
// Best effect is with "Move with" camera
// - Can have one or multiple skeletons chasing you
// Smooth movement
// UP moves forward in whatever angle you are at
// LEFT/RIGHT rotate by small angle
// Uses x,y,z rather than grid of squares
// Has collision detection for skeleton moves
// Things to do:
// - Initialise skeletons so they are not already colliding
// - Collision detection for agent moves
// ===================================================================================================================
// === Start of tweaker's box ========================================================================================
// ===================================================================================================================
// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?
AB.clockTick = 100;
// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps = 10000;
// Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep = 100;
// Take screenshot on this step. (All resources should have finished loading.) Default 50.
AB.drawRunControls = false;
const floorTextureFile = "/uploads/goobert/grass2.png"; // const floorTextureFile = "/uploads/mathias/grass.jpg"
// Scrap the Run/Step/Pause controls
function World() {
var camera, controls;
var objects = [];
var raycaster;
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.newRun = function()
{
// loadModels();
// loadStayModels();
// this handles key presses
var onKeyDown = function ( event ) {
switch ( event.keyCode ) {
case 38: // up
case 87: // w
moveForward = true;
break;
case 38: // up
case 16: // shift
MOVESPEED = 5; //
break;
// case 38: // up
case 80: // P
FOV = 500;
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 ) velocity.y += 350;
canJump = false;
break;
}
};
var onKeyUp = function ( event ) {
switch( event.keyCode ) {
case 38: // up
case 87: // w
moveForward = false;
break;
case 38: // up
case 16: // shift
MOVESPEED = 10; //
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;
}
};
camera = new THREE.PerspectiveCamera( FOV );
threeworld.camera = camera;
threeworld.init3d ( 0,0, 0x7ec0ee );
var light = new THREE.HemisphereLight( 0xeeeeff, 0x777788, 0.75 );
light.position.set( 0.5, 1, 0.75 );
threeworld.scene.add( light );
threeworld.scene.fog = new THREE.Fog( 0xffffff, 0, FogDistance );
controls = new THREE.PointerLockControls( camera );
threeworld.scene.add( controls.getObject() );
// event listener for key presses
document.addEventListener( 'keyup', onKeyUp, false );
document.addEventListener( 'keydown', onKeyDown, false );
raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 );
$("#user_span1").html("Use <b>WASD</b> or <b>Arrows</b> to move and <b>shift</b> to run any direction.");
// var blocker = $("#user_span2");
$("#user_span2").html("<p><b>Click screen to enable mouse controls</b></p>");
//The following handles pointer locking when clicking the window
// handles the pointer locking (game return feature) when clicking back onto the game.
var havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
console.log(havePointerLock);
if ( havePointerLock ) {
var element = document.body;
var pointerlockchange = function ( event ) {
if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element ) {
controls.enabled = true;
$("#user_span2").html("");
} else
{
controls.enabled = false;
$("#user_span2").html("<p><b>Click onto the game to return playing.</b></p>");
}
};
var 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>');
}
}
//The following draws a simple scene
//floor
var WorldSize = 20000;
var BlockSize = 50;
var floorGeometry = new THREE.PlaneBufferGeometry( WorldSize, WorldSize );
floorGeometry.rotateX( - Math.PI / 2 );
// loading the floor texture
var floorTexture = new THREE.ImageUtils.loadTexture ( floorTextureFile );
floorTexture.minFilter = THREE.LinearFilter;
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
// world floor texture size and location
floorTexture.offset.set( 0, 0 );
floorTexture.repeat.set( WorldSize / BlockSize, WorldSize / BlockSize );
var floor = new THREE.Mesh(floorGeometry, new THREE.MeshBasicMaterial({map : floorTexture}));
floor.position.set(0,-20,0);
threeworld.scene.add(floor);
};
//---- global constants: -------------------------------------------------------
// number of skeletons:
// const NO_SKELETONS = 1;
const NO_SKELETONS = 50;
const OBJ_SKELETON = '/uploads/liam/tank.obj' ;
// skeleton credit
// formerly here:
// http://tf3dm.com/3d-model/skeleton-with-organs-91102.html
const OBJPATH = "/uploads/liam/"; // path of OBJ and MTL (Peter Parker model)
const OBJNAME = "tank.obj";
const MTLNAME = "tank.mtl";
// Peter Parker credit
// formerly here:
// https://free3d.com/3d-model/spider-man-4998.html
// multiply model sizes by some amount:
const SCALE_HERO = 70;
// random size skeleton with each run:
const SCALE_SKELETON = AB.randomFloatAtoB ( 1, 6 );
// const SCALE_SKELETON = 4;
const TEXTURE_SKELETON = '/uploads/starter/ghost.3.png' ;
// credits:
// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_icons
const MUSIC_BACK = '/uploads/starter/SuspenseStrings.mp3' ;
// music credit
// http://www.dl-sounds.com/royalty-free/suspense-strings/
const MAXPOS = 3500; // length of one side of the arena
const SKYCOLOR = 0xffffcc; // a number, not a string
const LIGHTCOLOR = 0xffffff ;
const startRadiusConst = MAXPOS ; // distance from centre to start the camera at
const maxRadiusConst = MAXPOS * 10 ; // maximum distance from camera we will render things
// how much agent moves each step
const AGENT_STEP = 100;
// how much enemy moves each step
const ENEMY_STEP = 50;
// this is as close as models come to other models
const CLOSE_LIMIT = 100;
//--- lookat and follow -----------------------------------------------------------------
// camera on "Move With" should move up/down in y axis as we re-scale objects
// to place the follow camera just above the agent, something like:
const FOLLOW_Y = SCALE_HERO * 4 ; // going high up (or forward/back) means we get it out of agent's hair
// to point the camera at skeleton's face, something like:
const LOOKAT_Y = SCALE_SKELETON * 40;
// const CAMERASHIFT = 0 ; // put camera exactly inside agent's head
// const CAMERASHIFT = SCALE_HERO * 2 ; // shift camera ahead of agent along line of sight
const CAMERASHIFT = - SCALE_HERO * 2 ; // shift camera behind agent, looking past agent along line of sight
//--- change ABWorld defaults: -------------------------------
ABHandler.MAXCAMERAPOS = maxRadiusConst ;
ABHandler.GROUNDZERO = true; // "ground" exists at altitude zero
// --- Rotations -----------------------
// rotate by some amount of radians from the normal position
// default is 0 which has the model facing DOWN (towards increasing z value) as far as the initial camera sees
const ROTATE_AMOUNT = Math.PI / 20 ; // rotate amount in radians (PI = 180 degrees)
//--- skybox: -------------------------------
// urban photographic skyboxes, credit:
// http://opengameart.org/content/urban-skyboxes
const SKYBOX_ARRAY = [
"/uploads/starter/posx.jpg",
"/uploads/starter/negx.jpg",
"/uploads/starter/posy.jpg",
"/uploads/starter/negy.jpg",
"/uploads/starter/posz.jpg",
"/uploads/starter/negz.jpg"
];
/*
// space skybox, credit:
// http://en.spaceengine.org/forum/21-514-1
const SKYBOX_ARRAY = [
"/uploads/starter/sky_pos_z.jpg",
"/uploads/starter/sky_neg_z.jpg",
"/uploads/starter/sky_pos_y.jpg",
"/uploads/starter/sky_neg_y.jpg",
"/uploads/starter/sky_pos_x.jpg",
"/uploads/starter/sky_neg_x.jpg"
];
*/
// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================
// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.
var THESKELETONS = new Array ( NO_SKELETONS );
var theagent, theenemy;
var agentRotation = 0 ;
var enemyRotation = 0 ;
var skeleton_texture;
function loadResources() // asynchronous file loads - call initScene() when all finished
{
// load skeleton - OBJ that we will paint with a texture
var loader = new THREE.OBJLoader ( new THREE.LoadingManager() );
loader.load ( OBJ_SKELETON, function ( object )
{
theenemy = object; // canonical enemy object - may be copied multiple times
if ( asynchFinished() ) initScene();
});
// load Peter Parker model - OBJ plus MTL (plus TGA files)
// old code:
// THREE.Loader.Handlers.add ( /.tga$/i, new THREE.TGALoader() );
THREE.DefaultLoadingManager.addHandler ( /\.tga$/i, new THREE.TGALoader() );
var m = new THREE.MTLLoader();
m.setResourcePath ( OBJPATH );
m.setPath ( OBJPATH );
m.load ( MTLNAME, function ( materials )
{
materials.preload();
var o = new THREE.OBJLoader();
o.setMaterials ( materials );
o.setPath ( OBJPATH );
o.load ( OBJNAME, function ( object )
{
theagent = object;
if ( asynchFinished() ) initScene();
});
});
// load textures
var loader2 = new THREE.TextureLoader();
loader2.load ( TEXTURE_SKELETON, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
skeleton_texture = thetexture;
if ( asynchFinished() ) initScene();
});
}
function asynchFinished() // all file loads returned
{
if ( skeleton_texture && theenemy && theagent ) return true;
else return false;
}
function initScene() // all file loads have returned
{
// add agent model
// start at center
theagent.position.y = 0;
theagent.position.x = 0;
theagent.position.z = 0;
theagent.scale.multiplyScalar ( SCALE_HERO ); // scale it
ABWorld.scene.add( theagent );
// set up lookat and follow in direction agent is pointing in
setLookatFollow();
// add enemies
// first paint and size the canonical enemy object
theenemy.traverse ( function ( child )
{
if ( child instanceof THREE.Mesh )
child.material.map = skeleton_texture ;
});
theenemy.scale.multiplyScalar ( SCALE_SKELETON ); // scale it
// add perhaps multiple enemy models, starting near outside of arena
for ( var i = 0; i < NO_SKELETONS ; i++ )
{
var object = theenemy.clone(); // copy the object multiple times
object.position.y = 0;
object.position.x = AB.randomPick ( AB.randomIntAtoB ( -MAXPOS/2, -MAXPOS/3 ), AB.randomIntAtoB ( MAXPOS/3, MAXPOS/2 ) );
object.position.z = AB.randomPick ( AB.randomIntAtoB ( -MAXPOS/2, -MAXPOS/3 ), AB.randomIntAtoB ( MAXPOS/3, MAXPOS/2 ) );
ABWorld.scene.add( object );
// save in array for later
THESKELETONS[i] = object;
}
// finally skybox
ABWorld.scene.background = new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY, function()
{
ABWorld.render();
AB.removeLoading();
AB.runReady = true;
});
}
// --- lookat and follow -----------------------------------
// we do NOT automatically look at enemy / enemies
// instead we look in direction we are facing
function setLookatFollow() // set up lookat and follow
{
// follow - camera position
// start with agent centre, then adjust it by small amount
ABWorld.follow.copy ( theagent.position );
ABWorld.follow.y = FOLLOW_Y ;
// shifted forward/back along line linking agent with direction it is facing
ABWorld.follow.x = ABWorld.follow.x + ( CAMERASHIFT * Math.sin(agentRotation) );
ABWorld.follow.z = ABWorld.follow.z + ( CAMERASHIFT * Math.cos(agentRotation) );
// lookat - look at point in distance along line we are facing
// start with agent centre, then adjust it along line by huge amount
ABWorld.lookat.copy ( theagent.position );
ABWorld.lookat.y = LOOKAT_Y ;
ABWorld.lookat.x = ABWorld.lookat.x + ( (startRadiusConst * 3) * Math.sin(agentRotation) );
ABWorld.lookat.z = ABWorld.lookat.z + ( (startRadiusConst * 3) * Math.cos(agentRotation) );
}
// --- enemy move -----------------------------------
// angle between two points (x1,y1) and (x2,y2)
function angleTwoPoints ( x1, y1, x2, y2 )
{
return Math.atan2 ( x2 - x1, y2 - y1 );
}
function dist ( a, b ) // distance between them when a,b are Three vectors
{
return ( a.distanceTo ( b ) );
}
function collision ( proposedMove, k ) // proposed move FOR skeleton k
{
if ( dist ( proposedMove, theagent.position ) < CLOSE_LIMIT )
return true;
for ( var i=0 ; i < NO_SKELETONS ; i++ )
if ( i != k )
if ( dist ( proposedMove, THESKELETONS[i].position ) < CLOSE_LIMIT )
return true;
// else
return false;
}
function moveEnemy()
{
for ( var i = 0; i < NO_SKELETONS ; i++ )
{
var e = THESKELETONS[i] ;
// rotate enemy to face agent
enemyRotation = angleTwoPoints ( e.position.x, e.position.z, theagent.position.x, theagent.position.z );
e.rotation.set ( 0, enemyRotation, 0 );
// move along that line, if no collision with any other model
var proposedMove = new THREE.Vector3
(
e.position.x + ( ENEMY_STEP * Math.sin(enemyRotation) ),
e.position.y,
e.position.z + ( ENEMY_STEP * Math.cos(enemyRotation) )
);
if ( ! collision ( proposedMove, i ) )
e.position.copy ( proposedMove );
// else enemy i just misses a turn
}
}
//--- key control of agent -------------------------------------------------------------
const KEY_UP = 38;
const KEY_LEFT = 37;
const KEY_RIGHT = 39;
var OURKEYS = [ KEY_UP, KEY_LEFT, KEY_RIGHT ];
function ourKeys ( event ) { return ( OURKEYS.includes ( event.keyCode ) ); }
// UP moves forward in whatever angle you are at
// LEFT/RIGHT rotate by small angle
function keyHandler ( event )
{
if ( ! AB.runReady ) return true; // not ready yet
// if not handling this key, send it to default:
if ( ! ourKeys ( event ) ) return true;
// else handle it and prevent default:
if ( event.keyCode == KEY_UP ) // move a bit along angle we are facing
{
theagent.position.x = theagent.position.x + ( AGENT_STEP * Math.sin(agentRotation) );
theagent.position.z = theagent.position.z + ( AGENT_STEP * Math.cos(agentRotation) );
}
if ( event.keyCode == KEY_LEFT ) // rotate in place
{
agentRotation = agentRotation + ROTATE_AMOUNT;
theagent.rotation.set ( 0, agentRotation, 0 );
}
if ( event.keyCode == KEY_RIGHT )
{
agentRotation = agentRotation - ROTATE_AMOUNT;
theagent.rotation.set ( 0, agentRotation, 0 );
}
// lookat/follow depend on change in agent position/rotation:
setLookatFollow();
event.stopPropagation(); event.preventDefault(); return false;
}
AB.world.newRun = function()
{
AB.loadingScreen();
AB.runReady = false;
ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
loadResources(); // aynch file loads
// calls initScene() when it returns
// light
var ambient = new THREE.AmbientLight();
ABWorld.scene.add( ambient );
var thelight = new THREE.DirectionalLight ( LIGHTCOLOR, 3 );
thelight.position.set ( startRadiusConst, startRadiusConst, startRadiusConst );
ABWorld.scene.add(thelight);
document.onkeydown = keyHandler;
};
AB.world.nextStep = function()
{
moveEnemy(); // enemies moves on their own clock
};
AB.backgroundMusic ( MUSIC_BACK );