Code viewer for World: User-controlled Model World


// ==== 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;
	
	// Scrap the Run/Step/Pause controls

	

//---- global constants: -------------------------------------------------------

// number of skeletons: 

//	const NO_SKELETONS = 1;
	const NO_SKELETONS = 50;


	const OBJ_SKELETON 	= '/uploads/starter/skelet.obj' ;
 
// skeleton credit
// formerly here:
// http://tf3dm.com/3d-model/skeleton-with-organs-91102.html


	const OBJPATH = "/uploads/starter/";	// path of OBJ and MTL (Peter Parker model)
	const OBJNAME = "Peter_Parker.obj";
	const MTLNAME = "Peter_Parker.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 );