Code viewer for World: User-controlled Model Worl...
// Cloned by DistroByte on 24 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;

// Scrap the Run/Step/Pause controls

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

// number of skeletons:
const numPlayers = 50;
const playerObject = "/uploads/distro/player.obj";

const objPath = "/uploads/distro/"; // path of OBJ and MTL (Peter Parker model)
const objName = "doll.obj";
const mtlName = "doll.mtl";

// multiply model sizes by some amount:

const scalePlayer = 100;

// random size skeleton with each run:
const scaleDoll = AB.randomFloatAtoB(1, 6);

const dollTexture = "/uploads/distro/doll.png";

const maxWidth = 3500; // length of one side of the arena

const skyColour = 0xffffcc; // a number, not a string
const lightColour = 0xffffff;

const startRadiusConst = maxWidth; // distance from centre to start the camera at
const maxRadiusConst = maxWidth * 10; // maximum distance from camera we will render things

// how much agent moves each step
const agentStep = 100;

// how much enemy moves each step
const enemyStep = 0;

// this is as close as models come to other models
const minDistanceLimit = 50;

//--- 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 followY= scalePlayer * 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 lookatY = scaleDoll * 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 = -scalePlayer * 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 rotateConstant = 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",
];

// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================

// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.

var players = new Array(numPlayers);

var player, doll;

var agentRotation = 0;
var enemyRotation = 0;

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(playerObject, function (object) {
    player = 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) {
      doll = object;
      if (asynchFinished()) initScene();
    });
  });

  // load textures

  var loader2 = new THREE.TextureLoader();

  loader2.load(dollTexture, function (texture) {
    texture.minFilter = THREE.LinearFilter;
    dollTexture = texture;
    if (asynchFinished()) initScene();
  });
}

function asynchFinished() {
  // all file loads returned
  if (dollTexture && doll && player) return true;
  else return false;
}

function initScene() {
  // all file loads have returned
  // add agent model
  // start at center

  player.position.y = 0;
  player.position.x = 0;
  player.position.z = 0;

  player.scale.multiplyScalar(scalePlayer); // scale it
  ABWorld.scene.add(player);

  // 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(player.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(player.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, player.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,
      player.position.x,
      player.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
    player.position.x =
      player.position.x + AGENT_STEP * Math.sin(agentRotation);
    player.position.z =
      player.position.z + AGENT_STEP * Math.cos(agentRotation);
  }

  if (event.keyCode == KEY_LEFT) {
    // rotate in place
    agentRotation = agentRotation + ROTATE_AMOUNT;
    player.rotation.set(0, agentRotation, 0);
  }

  if (event.keyCode == KEY_RIGHT) {
    agentRotation = agentRotation - ROTATE_AMOUNT;
    player.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, skyColour);

  loadResources(); // aynch file loads
  // calls initScene() when it returns

  // light
  var ambient = new THREE.AmbientLight();
  ABWorld.scene.add(ambient);

  var stageLight = new THREE.DirectionalLight(lightColour, 3);
  stageLight.position.set(startRadiusConst, startRadiusConst, startRadiusConst);
  ABWorld.scene.add(stageLight);

  document.onkeydown = keyHandler;
};

AB.world.nextStep = function () {
  moveEnemy(); // enemies moves on their own clock
};