Source: fs-fly.js

/**
 * Initializes the flying controls
 */
async function initFlying() {

  // as further the mouse is right/left of the cross the more the plane is moving right/left
  // headingTo = { right: int, up: int } stores values from 0 to 100 
  // headingTo = { right: -80, up: 5 } would move the plane a bit up and strongly to the left

  // change cursor to crosshair
  document.body.style.cursor = "crosshair";

  window.addEventListener("mousemove", event => {
    headingTo.right = invertedControls ? (event.clientX - window.innerWidth / 2) / 2 : - (event.clientX - window.innerWidth / 2) / 2;
    headingTo.up = invertedControls ? (window.innerHeight / 2 - event.clientY) / 2 : - (window.innerHeight / 2 - event.clientY) / 2;
    document.body.style.cursor = "crosshair";
    if (headingTo.right > 100) { headingTo.right = 100; document.body.style.cursor = "e-resize"; }
    if (headingTo.right < -100) { headingTo.right = -100; document.body.style.cursor = "w-resize"; }
    if (headingTo.up > 100) { headingTo.up = 100; document.body.style.cursor = "n-resize"; }
    if (headingTo.up < -100) { headingTo.up = -100; document.body.style.cursor = "s-resize"; }
  });

  // if its a mobile device, use touch controls
  if ((typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1)) {
    let touchStartX = 0, touchStartY = 0, touchEndX = 0, touchEndY = 0;
    window.addEventListener("touchstart", event => {
      touchStartX = event.touches[0].clientX;
      touchStartY = event.touches[0].clientY;
    });
    window.addEventListener("touchmove", event => {
      touchEndX = event.touches[0].clientX;
      touchEndY = event.touches[0].clientY;
      headingTo.right = invertedControls ? (touchEndX - touchStartX) * 1 : -(touchEndX - touchStartX) * 1;
      headingTo.up = invertedControls ? (touchStartY - touchEndY) * 1 : -(touchStartY - touchEndY) * 1;
      if (headingTo.right > 100) { headingTo.right = 100; }
      if (headingTo.right < -100) { headingTo.right = -100; }
      if (headingTo.up > 100) { headingTo.up = 100; }
      if (headingTo.up < -100) { headingTo.up = -100; }
    });
  }

  await createModelPlane();

  camera.lookAt(sceneObjects.modelPlane.position);
}


/**
 * Moves the Plane and the Camera
 */
function handleFlying() {

  if (!sceneObjects.modelPlane) return;

  // get the planes lookAt vector by its quaternion
  let quaternion = sceneObjects.modelPlane.quaternion;
  let planeLookAt = new THREE.Vector3(0, 0, 1);
  planeLookAt.applyQuaternion(quaternion);
  planeLookAt.normalize();

  // planeRotationFactor
  let planeRotationFactor = basePlaneRotateFactor;
  if (planeIsUpsideDown) {
    planeRotationFactor = -basePlaneRotateFactor;
  }

  // manipulate the lookAt vector by the headingTo values
  let turnedBeyondYAxis = false;
  planeLookAt = turnVectorAroundVerticalAxis(planeLookAt, degToRad(headingTo.right * - planeRotationFactor));
  let horizontalTurn = turnVectorAroundHorizontalAxis(planeLookAt, degToRad(headingTo.up * planeRotationFactor));
  planeLookAt = horizontalTurn.newVector;
  turnedBeyondYAxis = horizontalTurn.turnedBeyondYAxis;
  if (turnedBeyondYAxis) planeIsUpsideDown = !planeIsUpsideDown;
  planeLookAt.normalize();

  // set the new lookAt vector
  let newPointToLookAt = new THREE.Vector3(sceneObjects.modelPlane.position.x + planeLookAt.x, sceneObjects.modelPlane.position.y + planeLookAt.y, sceneObjects.modelPlane.position.z + planeLookAt.z);
  sceneObjects.modelPlane.lookAt(newPointToLookAt);

  // move the plane forward (always)
  speed = calcSpeed(speed, planeLookAt.y);
  let newPlanePosition = sceneObjects.modelPlane.position.clone();
  newPlanePosition.addScaledVector(planeLookAt, speed * deltaTime);

  // apply the new position
  sceneObjects.modelPlane.position.set(newPlanePosition.x, newPlanePosition.y, newPlanePosition.z);

  // turn the camera and plane
  if (turnedBeyondYAxis) {
    camera.up.set(0, -camera.up.y, 0);
  }
  if (planeIsUpsideDown) {
    sceneObjects.modelPlane.rotateOnWorldAxis(planeLookAt, degToRad(180));
  }

  // move the camera behind the plane -lookAt
  camera.position.set(newPlanePosition.x, newPlanePosition.y, newPlanePosition.z);
  camera.position.addScaledVector(planeLookAt, -distanceOfCameraFromPlane);
  camera.lookAt(newPlanePosition);

  // tend the plane a little bit to the right/left depending on the headingTo.right value
  sceneObjects.modelPlane.rotateOnWorldAxis(planeLookAt, degToRad(headingTo.right * 0.5));
  sceneObjects.modelPlane.updateMatrixWorld();

}


/**
 * Creates the plane model and adds it to the scene
 */
async function createModelPlane() {

  const planeStartPoint = new THREE.Vector3(torusSpawnRadius * 0.5 + 2, 8, torusSpawnRadius * 0.5 + 2);

  // load the plane model
  const modelPlane = await getMeshFromBlenderModel("./low-poly_airplane.glb-low", "https://download1492.mediafire.com/07ni12qd4mjg/7jumu3xf1vdz13y/low-poly_airplane.glb");
  scene.add(modelPlane);

  /** @type { THREE.Mesh } */
  sceneObjects.modelPlane = modelPlane;

  // set the plane position
  sceneObjects.modelPlane.position.set(planeStartPoint.x, planeStartPoint.y, planeStartPoint.z);
  sceneObjects.modelPlane.scale.set(0.002, 0.003, 0.003);
  sceneObjects.modelPlane.lookAt(planeStartPoint.x - 1, planeStartPoint.y, planeStartPoint.z - 1);
}


/**
 * Calculates the speed depending on the y value of the planeLookAt vector and the previous speed
 * @param {number} v0 previous speed
 * @param {*} y y value of the planeLookAt vector 1 = straight up, -1 = straight down
 */
function calcSpeed(v0, y) {

  const g = 9.81;
  const aGravity = g * -y;
  const aThrust = 10;
  const aAirResistance = - v0 * v0 * 0.9;

  const a = aGravity + aThrust + aAirResistance;

  const v1 = v0 + a * deltaTime;

  return v1;
}