// ==== 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 3D model// OBJ plus MTL// A "Simple World" like chase inside an invisible arena inside the castle // Chase uses x,y,z rather than grid of squares // ===================================================================================================================// === 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.
AB.maxSteps =500;// Length of run: Maximum length of run in steps.
AB.screenshotStep =50;// Take screenshot on this step. (All resources should have finished loading.) //---- global constants: -------------------------------------------------------const OBJPATH ="/uploads/starter/";// path of OBJ and MTL const OBJNAME ="castle.obj";const MTLNAME ="castle.new.mtl";// castle credit// https://www.script-tutorials.com/webgl-with-three-js-lesson-6/// http://www.script-tutorials.com/demos/409/index2.html// =================================================================================// Bug: Original MTL file fails with new Three.js // The original "castle.mtl" works with old Three.js but fails with new version.// Solution: Edit the MTL file. See note here:// https://ancientbrain.com/docs.three.php// =================================================================================const TEXTURE_AGENT ='/uploads/starter/pacman.jpg';const TEXTURE_ENEMY ='/uploads/starter/ghost.3.png';// credits:// https://commons.wikimedia.org/wiki/Category:Pac-Man_icons// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_iconsconst MUSIC_BACK ='/uploads/starter/Defense.Line.mp3';const SKYCOLOR =0xffffcc;// a number, not a string const LIGHTCOLOR =0xffffff;const squaresize =50;// size of cube length // basic castle model size // see debug line to compute model size below // Xsize: 6158.792236328125 Ysize: 703.5784759521484 Zsize: 3791.05908203125const MODELLENGTH =6159;const MODELWIDTH =3791;const SCALE_CASTLE =0.5;// scale it by this const SCALEDMODELLENGTH = MODELLENGTH * SCALE_CASTLE;const SCALEDMODELWIDTH = MODELWIDTH * SCALE_CASTLE;const startRadiusConst = SCALEDMODELLENGTH *0.5;const maxRadiusConst = SCALEDMODELLENGTH *10;// camera points at 0,0 // where to put castle? // fit bottom LHS corner to here:const CX =-( SCALEDMODELLENGTH *0.3);const CZ =( SCALEDMODELWIDTH *0.3);// how far can the agent and enemy wander inside the castle// experiment to see where the inner box fits inside the model const MIN_X = CX +( SCALEDMODELLENGTH *0.1);const MAX_X = CX +( SCALEDMODELLENGTH *0.55);const MIN_Z = CZ -( SCALEDMODELWIDTH *0.45);const MAX_Z = CZ -( SCALEDMODELWIDTH *0.1);// how much do they move each step const MOVE = squaresize ;// how close do they have to be to lose score const BADCLOSE = squaresize *5;//--- change ABWorld defaults: -------------------------------ABHandler.GROUNDZERO =true;// "ground" exists at altitude zeroABWorld.drawCameraControls =false;// ===================================================================================================================// === End of tweaker's box ==========================================================================================// ===================================================================================================================// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.// we have a splash screen// behind the splash screen, newRun is running, loading resources // do not start the run loop until resources ready AND splash screen is dismissed var resourcesLoaded =false;var splashClicked =false;//--- 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;var thecastle, theagent, theenemy;var agent_texture, enemy_texture;var badsteps;var goodsteps;function loadResources()// asynchronous file loads - call initScene when finished {// load castle model OBJ and materials MTL (which reference JPEGs)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 ){
thecastle = object;if( asynchFinished()) initScene();});});// load cube textures var loader1 =new THREE.TextureLoader();var loader2 =new THREE.TextureLoader();
loader1.load ( TEXTURE_AGENT,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
agent_texture = thetexture;if( asynchFinished()) initScene();});
loader2.load ( TEXTURE_ENEMY,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
enemy_texture = thetexture;if( asynchFinished()) initScene();});}function asynchFinished(){if( thecastle && agent_texture && enemy_texture )returntrue;elsereturnfalse;}function initScene()// file loads have returned {// add castle object to scene
thecastle.scale.multiplyScalar ( SCALE_CASTLE );
thecastle.position.y =-(squaresize *0.5);// adjust so cubes (centred on 0) appear exactly above castle floor
thecastle.position.x = CX;
thecastle.position.z = CZ;ABWorld.scene.add ( thecastle );// calculate OBJ object size:// console.log ( "Xsize: " + ABWorld.objectXsize(thecastle) + " Ysize: " + ABWorld.objectYsize(thecastle) + " Zsize: " + ABWorld.objectZsize(thecastle) );// set up enemy
shape =new THREE.BoxGeometry( squaresize, squaresize, squaresize );
theenemy =new THREE.Mesh( shape );
theenemy.material =new THREE.MeshBasicMaterial({ map: enemy_texture });ABWorld.scene.add(theenemy);// set position
theenemy.position.y =0;
theenemy.position.x = AB.randomIntAtoB( MIN_X, MAX_X );
theenemy.position.z = AB.randomIntAtoB( MIN_Z, MAX_Z );/*
// examine where different points are and are they inside the castle:
theenemy.position.x = 0;
theenemy.position.z = 0;
theenemy.position.x = MIN_X;
theenemy.position.z = MIN_Z;
theenemy.position.x = MAX_X;
theenemy.position.z = MAX_Z;
*/// set up agent
shape =new THREE.BoxGeometry( squaresize, squaresize, squaresize );
theagent =new THREE.Mesh( shape );
theagent.material =new THREE.MeshBasicMaterial({ map: agent_texture });ABWorld.scene.add(theagent);// put agent some distance from enemy do{
x = AB.randomIntAtoB( MIN_X, MAX_X );
z = AB.randomIntAtoB( MIN_Z, MAX_Z );}while( AB.distance2D ( x, z, theenemy.position.x, theenemy.position,z )< BADCLOSE );
theagent.position.y =0;
theagent.position.x = x;
theagent.position.z = z;// ready to start run loop? ABWorld.render();
console.log ("Resources loaded.");
resourcesLoaded =true;if( resourcesLoaded && splashClicked )
AB.runReady =true;// start run loop }// --- check a proposed move is inside castle -----------------------------------function withinBounds ( x, z ){return(( x > MIN_X )&&( x < MAX_X )&&( z > MIN_Z )&&( z < MAX_Z ));}// --- take actions -----------------------------------function moveLogicalEnemy(){// move towards agent // put some randomness in so it won't get stuck with barriers // proposed move var x = theenemy.position.x;var z = theenemy.position.z;if( x < theagent.position.x ) x = x + AB.randomIntAtoB (0, MOVE );else x = x - AB.randomIntAtoB (0, MOVE );if( z < theagent.position.z ) z = z + AB.randomIntAtoB (0, MOVE );else z = z - AB.randomIntAtoB (0, MOVE );if( withinBounds ( x, z ))// if in bounds if( AB.distance2D ( x, z, theagent.position.x, theagent.position.z )> squaresize *2)// limit to how close we go to agent {
theenemy.position.x = x;
theenemy.position.z = z;}// else just miss a turn}function moveLogicalAgent( a )// this is called by the infrastructure that gets action a from the Mind {// proposed movevar x = theagent.position.x;var z = theagent.position.z;if( a == ACTION_LEFT ) x = x - MOVE ;elseif( a == ACTION_RIGHT ) x = x + MOVE ;elseif( a == ACTION_UP ) z = z + MOVE ;elseif( a == ACTION_DOWN ) z = z - MOVE ;if( withinBounds ( x, z ))if( AB.distance2D ( x, z, theenemy.position.x, theenemy.position.z )>= squaresize )// limit to how close we go to enemy {
theagent.position.x = x;
theagent.position.z = z;}// else ignore action }// --- score: -----------------------------------function badstep(){if( AB.distance2D ( theagent.position.x, theagent.position.z, theenemy.position.x, theenemy.position.z )< BADCLOSE )returntrue;elsereturnfalse;}function updateStatus(){var score = AB.world.getScore();
AB.msg ("Step: "+ AB.step +" out of "+ AB.maxSteps +". Score: "+ score );}
AB.world.newRun =function(){
badsteps =0;
goodsteps =0;ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );// newRun can run behind splash screen // do not start run loop until resources ready AND splash screen is dismissed
AB.runReady =false;
loadResources();// aynch file loads // calls initScene() when it returns var thelight =new THREE.DirectionalLight( LIGHTCOLOR,3);
thelight.position.set( startRadiusConst, startRadiusConst, startRadiusConst );ABWorld.scene.add(thelight);};
AB.world.getState =function(){var x =[ theagent.position.x, theagent.position.z, theenemy.position.x, theenemy.position.z ];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++;
updateStatus();};
AB.world.getScore =function(){return goodsteps;};// --- Splash screen --------------------------------------------------------------// Splash screen is to get audio started on mobile/Chrome by user interaction.// This should works on all platforms - plays background music.// display standard splash screen (World title, World image, Start button)
AB.newSplash();// when user clicks/touches button on splash screen, audio starts and run starts:
AB.splashClick (function(){// audio linked to user interaction:
AB.backgroundMusic ( MUSIC_BACK );
AB.removeSplash();// remove splash screen // ready to start run loop?
splashClicked =true;if( resourcesLoaded && splashClicked )
AB.runReady =true;// start run loop });