"use strict";

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Kind of "abstract" map object, which provides means to compute coordinates 2D coordinates in the map
 * from 3D coordinates in the world. This object is used in Heightmap, and also in GUI for minimap.
 *
 * @author smrceant (Antonin Smrcek)
 *
 * @param {number}          width                  width of the map (px)
 * @param {number}          height                 height of the map (px)
 * @param {THREE.Matrix4}   projectionViewMatrix   projection view matrix which was used to capture the map
 * @constructor
 */
var Map = function (width, height, projectionViewMatrix)
{
  /**
   * Width of the map (px).
   *
   * @type {number}
   */
  this.width  = null;

  /**
   * Height of the map (px).
   * @type {number}
   */
  this.height = null;

  /**
   * Half of the map's width.
   * @type {number}
   */
  this.halfWidth  = null;

  /**
   * Half of the map's height.
   * @type {number}
   */
  this.halfHeight = null;

  this.resize (width, height);

  /**
   *  Projection view matrix which was used to capture the map (most often the matrix which was used to capture the heightmap).
   *
   * @type {THREE.Matrix4}
   */
  this.projectionViewMatrix = projectionViewMatrix;
  this.projectionViewMatrixInv = (projectionViewMatrix !== null) ? new THREE.Matrix4() : null;
  if (projectionViewMatrix !== null)
  {
    this.projectionViewMatrixInv.getInverse (projectionViewMatrix);
  }

  ///////////////////////////////////////////
  // (FOR COMPUTING POSITION IN THE MAP)
  ///////////////////////////////////////////

  this._tmp_playerPosition  = new THREE.Vector4();
  this._tmp_clipCoordinates = new THREE.Vector4();

  this._tmp_unproj_vector = new THREE.Vector4();
  this._tmp_unproj_vectorTransformed = new THREE.Vector4();
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Resizes the map.
 *
 * @param {number} width    new width in px
 * @param {number} height   new height in px
 */
Map.prototype.resize = function (width, height)
{
  this.width  = width;
  this.height = height;

  this.halfWidth  = width  * 0.5;
  this.halfHeight = height * 0.5;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// COORDINATE COMPUTATION (PROJECTION, UNPROJECTION, ...)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Computes 2D coordinates into the map from 3D coordinates in the world. 2D coordinates are in OpenGL format.
 *
 * @param {THREE.Vector3}  coordinates3D   3D position in the world
 * @param {THREE.Vector2}  out             [output] where 2D coordinates into the map will be saved
 */
Map.prototype.computeScreenCoordinates = function (coordinates3D, out)
{
  // get current player position (= current camera position), add 1 to make homogenous 4D vector which can be transformed by 4x4 map projection view matrix
  this._tmp_playerPosition.x = coordinates3D.x;
  this._tmp_playerPosition.y = coordinates3D.y;
  this._tmp_playerPosition.z = coordinates3D.z;
  this._tmp_playerPosition.w = 1;

  // transform player position 4D vector by map projection view matrix (matrix, which was used for "constructing" the map)
  // (how the map is constructed: switch from perspective to ORTHOGRAPHIC projection, position camera high above the world, view is parallel to the ground (horizontal angle = PI, vertical = -PI/2)
  // (save PV matrix, use e.g. command "mat4.str(camera.projectionViewMatrix)")
  // (save current viewpoint for further reference by "camera.getViewpointDefinition()" )
  // (make a screenshot and then use Inkscape or something like that to draw a map from this screenshot. The important thing: we respect the outline, i.e. borders of houses etc. stay same. We only change visual aspects.
  // note: for now, it is recommended NOT to change width of the map, and just crop the bottom part of the image (the bottom 200px or so) ... otherwise it would be harder to correctly compute positions in this map
  // (now, since we know the PV matrix of the camera and have corresponding screen image (our map), we can actually project any 3D from our world to that map and it will display correctly)
  // (we will be, in fact, doing OpenGL transformation by the hand; so now we go into clipping coordinates)

  this._tmp_playerPosition.applyMatrix4 (this.projectionViewMatrix);

  // now we go to the Screen Coordinates (NDC and Viewport transform)

  // perspective division
    // (most of the time, this function is used to compute coordinates into the heightmaps and that is orthographic projection,
    //  thus this._tmp_clipCoordinates[3] = 1 and this has no effect, however, e.g. in case of computing position
    //  of info icons on the screen, projection is perspective and thus division is really needed!)
  this._tmp_playerPosition.x /= this._tmp_playerPosition.w;
  this._tmp_playerPosition.y /= this._tmp_playerPosition.w;

  // mapping to the screen pixels (since NDC ranges from -1..1)
    // (note: rounding is performed, since we want integer pixel positions)
  out.x = Math.round ( this.halfWidth  * (this._tmp_playerPosition.x + 1));
  out.y = Math.round ( this.halfHeight * (this._tmp_playerPosition.y + 1));

  //console.log("computeScreenCoordinates: " + coordinates3D.x + "," + coordinates3D.z + " --> " + out.x + "," + out.y);
};

/**
 * Tuned version of unproject(). Computes 3D position (world X,Z) corresponding to the given 2D position (image X,Y).
 * Input vector is also output vector (it will be transformed by the method). Image Y is stored in the
 * screenCoords as Z, i.e. at the [2] position. Height information, i.e. Y coordinate in 3D, is not computed - it is expected
 * to be obtained by other means (outside of this method).
 *
 * @param {THREE.Vector3}   screenCoords        X and Y coordinate on the screen (in the image), element [0] is X, [2] is Y
 */
Map.prototype.unprojectInplaceXZ = function (screenCoords)
{
  // note: do not use current window dimensions - we need to compute with this map coordinates!
  //this._tmp_unproj_vector[0] = ((screenCoords[0] * 2.0) / state.windowWidth) - 1.0;
  //this._tmp_unproj_vector[1] = ((screenCoords[2] * 2.0) / state.windowHeight) - 1.0;

  this._tmp_unproj_vector.x = ((screenCoords.x * 2.0) / this.width) - 1.0;
  this._tmp_unproj_vector.y = ((screenCoords.z * 2.0) / this.height) - 1.0; // image Y is stored as Z in the input vector
  this._tmp_unproj_vector.z = 0;
  this._tmp_unproj_vector.w = 1;

  this._tmp_unproj_vectorTransformed.copy (this._tmp_unproj_vector);
  this._tmp_unproj_vectorTransformed.applyMatrix4 (this.projectionViewMatrixInv);

  screenCoords.x = this._tmp_unproj_vectorTransformed.x / this._tmp_unproj_vectorTransformed.w;
  // do not compute 3D Y, would get useless value (like -1798 or something like that), will be replaced anyway
  screenCoords.z = this._tmp_unproj_vectorTransformed.z / this._tmp_unproj_vectorTransformed.w;
};
