// ==== 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.// ====================================================================================================================// Demo of moving 3d models// Movement is on a semi-visible grid of squares // Cannot go through cubes. Have to go around them. // See "Move With" camera // ===================================================================================================================// === 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 =150;// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps =1000;// 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.//---- global constants: -------------------------------------------------------const OBJ_SKELETON ='/uploads/starter/skelet.obj';// skeleton credit// formerly here:// http://tf3dm.com/3d-model/skeleton-with-organs-91102.htmlconst 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_MAZE ='/uploads/starter/latin.jpg';const TEXTURE_SKELETON ='/uploads/starter/ghost.3.png';// credits:// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_iconsconst MUSIC_BACK ='/uploads/starter/SuspenseStrings.mp3';// music credit// http://www.dl-sounds.com/royalty-free/suspense-strings/const gridsize =30;// number of squares along side of world const squaresize =100;// size of square in pixelsconst MAXPOS = gridsize * squaresize;// length of one side in pixels const NOBOXES =Math.trunc ((gridsize * gridsize)/15);const SKYCOLOR =0xffffcc;// a number, not a string const LIGHTCOLOR =0xffffff;const startRadiusConst = MAXPOS *0.8;// distance from centre to start the camera atconst maxRadiusConst = MAXPOS *5;// maximum distance from camera we will render things // 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;// to point the camera at skeleton's face, something like: const LOOKAT_Y = SCALE_SKELETON *40;//--- change ABWorld defaults: -------------------------------ABHandler.MAXCAMERAPOS = maxRadiusConst ;ABHandler.GROUNDZERO =true;// "ground" exists at altitude zero//--- skybox: -------------------------------// urban photographic skyboxes, credit:// http://opengameart.org/content/urban-skyboxesconst 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"];// ===================================================================================================================// === End of tweaker's box ==========================================================================================// ===================================================================================================================// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.//--- Mind can pick one of these actions -----------------const ACTION_LEFT =0;const ACTION_RIGHT =1;const ACTION_UP =2;const ACTION_DOWN =3;const ACTION_STAYSTILL =4;// contents of a grid squareconst GRID_BLANK =0;const GRID_WALL =1;const GRID_MAZE =2;var GRID =newArray(gridsize);// can query GRID about whether squares are occupied, will in fact be initialised as a 2D array var theagent, theenemy;var agentRotation =0;var enemyRotation =0;// with 3D models, current rotation away from default orientationvar maze_texture, skeleton_texture;// enemy and agent position on squaresvar ei, ej, ai, aj;var badsteps;var goodsteps;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;if( asynchFinished()) initScene();});// load Peter Parker model - OBJ plus MTL (plus TGA files) // old code:// THREE.Loader.Handlers.add ( /.tga$/i, new THREE.TGALoader() );// now gives warning:// Handlers.add() has been removed. Use LoadingManager.addHandler() instead.// example:// manager.addHandler( /\.tga$/i, new 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();var loader3 =new THREE.TextureLoader();
loader2.load ( TEXTURE_MAZE,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
maze_texture = thetexture;if( asynchFinished()) initScene();});
loader3.load ( TEXTURE_SKELETON,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
skeleton_texture = thetexture;if( asynchFinished()) initScene();});}function asynchFinished()// all file loads returned {if( maze_texture && skeleton_texture && theenemy && theagent )returntrue;elsereturnfalse;}//--- grid system -------------------------------------------------------------------------------// my numbering is 0 to gridsize-1function occupied ( i, j )// is this square occupied{if(( ei == i )&&( ej == j ))returntrue;// variable objects if(( ai == i )&&( aj == j ))returntrue;if( GRID[i][j]== GRID_WALL )returntrue;// fixed objects if( GRID[i][j]== GRID_MAZE )returntrue;returnfalse;}// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates// logically, coordinates are: y=0, x and z all positive (no negative) // logically my dimensions are all positive 0 to MAXPOS// to centre everything on origin, subtract (MAXPOS/2) from all dimensions function translate ( i, j ){var v =new THREE.Vector3();
v.y =0;
v.x =( i * squaresize )-( MAXPOS/2);
v.z =( j * squaresize )-( MAXPOS/2);return v;}function initScene()// all file loads have returned {var i,j, shape, thecube;// set up GRID as 2D arrayfor( i =0; i < gridsize ; i++)
GRID[i]=newArray(gridsize);// set up invisible walls so cannot move into them (occupied) for( i =0; i < gridsize ; i++)for( j =0; j < gridsize ; j++)if(( i==0)||( i==gridsize-1)||( j==0)||( j==gridsize-1))
GRID[i][j]= GRID_WALL;else
GRID[i][j]= GRID_BLANK;// set up maze for(var c=1; c <= NOBOXES ; c++){
i = AB.randomIntAtoB(2,gridsize-3);// inner squares are 1 to gridsize-2
j = AB.randomIntAtoB(2,gridsize-3);
GRID[i][j]= GRID_MAZE ;
shape =new THREE.BoxGeometry( squaresize, squaresize, squaresize );
thecube =new THREE.Mesh( shape );
thecube.material =new THREE.MeshBasicMaterial({ map: maze_texture });
thecube.position.copy ( translate(i,j));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(thecube);}// add enemy model // start in random locationdo{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);}while( occupied(i,j));// search for empty square
ei = i;
ej = j;// paint the enemy:
theenemy.traverse (function( child ){if( child instanceof THREE.Mesh)
child.material.map = skeleton_texture ;});
theenemy.scale.multiplyScalar ( SCALE_SKELETON );// scale it ABWorld.scene.add( theenemy );
drawEnemy();// add agent model // start in random locationdo{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);}while( occupied(i,j));// search for empty square
ai = i;
aj = j;
theagent.scale.multiplyScalar ( SCALE_HERO );// scale it ABWorld.scene.add( theagent );
drawAgent();// finally skybox ABWorld.scene.background =new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY,function(){ABWorld.render();
AB.removeLoading();
AB.runReady =true;});}// --- draw moving objects -----------------------------------function drawEnemy()// given ei, ej, draw it {
theenemy.position.copy ( translate(ei,ej));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
theenemy.position.y =(- squaresize /2);// adjusted so model feet line up with bottom of cubes - cubes centre on y=0ABWorld.lookat.copy ( theenemy.position );// if camera moving, look back at where the enemy is ABWorld.lookat.y = LOOKAT_Y ;// adjusted }function drawAgent()// given ai, aj, draw it {
theagent.position.copy ( translate(ai,aj));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
theagent.position.y =(- squaresize /2);// adjusted ABWorld.follow.copy ( theagent.position );// follow vector = agent position (for camera following agent)ABWorld.follow.y = FOLLOW_Y ;// adjusted }// --- take actions -----------------------------------function enemyGetAction()// do it like a Mind {if( AB.randomBoolean())// look at i {if( ei < ai )return( ACTION_RIGHT );if( ei > ai )return( ACTION_LEFT );return( ACTION_STAYSTILL );}else// look at j {if( ej < aj )return( ACTION_UP );if( ej > aj )return( ACTION_DOWN );return( ACTION_STAYSTILL );}}function moveLogicalEnemy(){var a = enemyGetAction();var i = ei;var j = ej;if( a == ACTION_LEFT ){ i--;}elseif( a == ACTION_RIGHT ){ i++;}elseif( a == ACTION_UP ){ j++;}elseif( a == ACTION_DOWN ){ j--;}if(! occupied(i,j)){if( a == ACTION_LEFT ){ rotateEnemyTowards (3*(Math.PI /2));}elseif( a == ACTION_RIGHT ){ rotateEnemyTowards (1*(Math.PI /2));}elseif( a == ACTION_UP ){ rotateEnemyTowards (0*(Math.PI /2));}elseif( a == ACTION_DOWN ){ rotateEnemyTowards (2*(Math.PI /2));}
ei = i;
ej = j;}}function moveLogicalAgent( a )// this is called by the infrastructure that gets action a from the Mind {var i = ai;var j = aj;if( a == ACTION_LEFT ){ i--;}elseif( a == ACTION_RIGHT ){ i++;}elseif( a == ACTION_UP ){ j++;}elseif( a == ACTION_DOWN ){ j--;}if(! occupied(i,j)){// if going to actually move, then turn body towards move// rotate by some amount of radians from the normal position // in degrees: +0, +90, +180, +270if( a == ACTION_LEFT ){ rotateAgentTowards (3*(Math.PI /2));}elseif( a == ACTION_RIGHT ){ rotateAgentTowards (1*(Math.PI /2));}elseif( a == ACTION_UP ){ rotateAgentTowards (0*(Math.PI /2));}elseif( a == ACTION_DOWN ){ rotateAgentTowards (2*(Math.PI /2));}
ai = i;
aj = j;}}// --- rotate functions -----------------------------------// Rotate enemy towards agent // This rotates half-way per step:function rotateEnemyTowards ( newRotation ){if( enemyRotation == newRotation )return;// else var x =( enemyRotation + newRotation )/2;
theenemy.rotation.set(0, x,0);
enemyRotation = x;}/*
// Alternative experiments - multiple interim rotations
const INTERIMROT = 10; // number of interim rotations drawn when model turns round
// all rotations positive 0 to 2 PI
function rotateEnemyTowards ( newRotation )
// rotate enemy from current value of "enemyRotation" towards "newRotation" - with interim renders
{
if ( enemyRotation == newRotation ) return;
// else
var delta = ( Math.abs ( enemyRotation - newRotation ) ) / INTERIMROT;
// console.log ( "rotate from " + enemyRotation + " to " + newRotation + " in steps " + delta );
var x;
for ( var i = 1; i <= INTERIMROT; i++ )
{
if ( enemyRotation < newRotation ) x = enemyRotation + (delta * i);
else x = enemyRotation - (delta * i);
theenemy.rotation.set ( 0, x, 0 );
// console.log ( "interim " + x );
ABWorld.render();
}
// console.log ( "end " );
theenemy.rotation.set ( 0, newRotation, 0 );
enemyRotation = newRotation; // new value
}
*/function rotateAgentTowards ( newRotation ){if( agentRotation == newRotation )return;// else var x =( agentRotation + newRotation )/2;
theagent.rotation.set(0, x,0);
agentRotation = x;}// --- score: -----------------------------------function badstep()// is the enemy within one square of the agent{if((Math.abs(ei - ai)<2)&&(Math.abs(ej - aj)<2))returntrue;elsereturnfalse;}function updateStatus(){var score = AB.world.getScore();
AB.msg (" Step: "+ AB.step +" out of "+ AB.maxSteps +". Score: "+ score );}
AB.world.newRun =function(){
AB.loadingScreen();
AB.runReady =false;
badsteps =0;
goodsteps =0;ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
loadResources();// aynch file loads // calls initScene() when it returns // lightvar 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);};
AB.world.getState =function(){var x =[ ai, aj, ei, ej ];return( x );};
AB.world.takeAction =function( a ){
moveLogicalAgent(a);if(( AB.step %2)==0)// slow the enemy down to every nth step
moveLogicalEnemy();if( badstep()) badsteps++;else goodsteps++;
drawAgent();
drawEnemy();
updateStatus();};
AB.world.getScore =function(){return goodsteps;};
AB.backgroundMusic ( MUSIC_BACK );