// my_camera.h   versuch einer einfacheren Kamera

#pragma once
#include "libs/glm/glm.hpp" //opengl-Mathe-Bibliothek
#include "libs/glm/ext/matrix_transform.hpp"
#include "libs/glm/gtc/matrix_transform.hpp"
#include "libs/glm/gtx/rotate_vector.hpp"

const float PI = atanf(1.0f)*4;
const float GRAD = 2*PI/360; //Umrechnungsfaktor von Grad in Radians

float vec3abs(glm::vec3 v) //Absolutwert (Laenge) eines Vektors
{
 return sqrtf(v.x*v.x+v.y*v.y+v.z*v.z);
}

class MyCamera {
protected:
 glm::vec3 position; //Position der Kamera
 glm::vec3 drehpunkt; //Punkt um den sich die Kamera drehen soll (also z.B. Mitte der Figur)
 glm::mat4 projection; //Projektionsmatrix
 glm::mat4 view;     //Umrechnung von Grundposition der Kamera in aktuelle Ansicht?
 glm::mat4 viewProj; //Matrix um von Spielwelt-Koordinaten in Kamerakoordinaten umrechnen
 float fovgrad,breitezuhoehe;
 
 //TODO: die beiden Winkel in Radians statt in Grad speichern (mouseSens.. auch anpassen)
 float yaw;   //Drehwinkel der Kamera fuer Drehung um Vertikalachse
 float pitch; //Nick-Drehwinkel der Kamera, 0 Grad = waagrecht (-89 bis +89)
 const float mouseSensitivity=0.3f;
 
 glm::vec3 lookat; //Vektor in Blickrichtung
 glm::vec3 up; //Vektor der senkrecht nach oben zeigt (in Spielwelt-Koordinaten)
public:
 float getYaw() {return yaw*GRAD;} //Sichtwinkel in Radians umgerechnet
 float getPitch() {return pitch*GRAD;} //Nick-Winkel in Radians
 MyCamera(float fov, float width, float height, bool orthoflag) {
  //fov = Field of view = Sichtfeld in Grad
  //      offenbar in y-Richtung, in x-Richtung entsprechend breite/hoehe groesser
  //width, height = Breite, Hoehe des Grafikfensters in Pixel
  fovgrad = fov*GRAD;
  breitezuhoehe = width/height;
  if(orthoflag)
   {
    float xmax=0.075f, ymax=xmax/width*height;
    float near=0.01f, far=5.0f;
    projection = glm::ortho(-xmax, xmax, -ymax, ymax, near, far);
   }
  else
   {
    float near=1.0f, far=500.0f;
    projection = glm::perspective(fovgrad, width/height, near, far);
   }
  view = glm::mat4(1.0f); //Identitaets-Matrix
  position = glm::vec3(0.0f, 0.0f, 0.1f); //Kamera 0.1 Meter vor Koordinatenursprung (in z-Achse gegen uns)
  drehpunkt = glm::vec3(0.0f, 0.0f, 0.0f);
  up = glm::vec3(0.0f, 1.0f, 0.0f);
  yaw = -90.0f;
  pitch = 0.0f;
  onMouseMoved(0.0f, 0.0f);
  update();
 }

 void setnearfar(float near,float far) {
  projection = glm::perspective(fovgrad, breitezuhoehe, near, far);
  update();
 }
 
 glm::mat4 getView() {return view;}
 glm::mat4 getViewProj() {return viewProj;}
 
 void translate(glm::vec3 v)
 {
  position += v;
  view = glm::translate(view, v * -1.0f);
 }
 
 void setPosition(glm::vec3 v)
 {
  translate(v-position);
  update();
 }
 
 void onMouseMoved(float xRel,float yRel) {
  yaw += xRel * mouseSensitivity;
  pitch -= yRel * mouseSensitivity;
  if(pitch>89.0f) pitch=89.0f;
  else if(pitch < -89.0f) pitch = -89.0f;
  glm::vec3 front; //Richtungsvektor in den die Kamera schaut
  front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
  front.y = sin(glm::radians(pitch));
  front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
  lookat = glm::normalize(front);
  update();
 }

 void onMouseRotate(float xRel,float yRel) {
  //Kamera um die Figur (drehpunkt) herum drehen
  float yangle = xRel * mouseSensitivity; //Rotation von yaw entspricht Rotation um y-Achse
  float xangle = -yRel * mouseSensitivity; //Rotation von pitch entspricht Rotation um x-Achse
  glm::vec3 xachse; //x-Achse von der Kamera aus gesehen
  xachse = glm::normalize(glm::cross(lookat, up));
  yaw -= yangle; //Kamera um sich selbst drehen
  pitch += xangle; //Nickbewegung der Kamera
  if(pitch>89.0f)         {xangle -= pitch-89.0f; pitch = 89.0f;} //auf gueltigen Bereich beschraenken
  else if(pitch < -89.0f) {xangle -= pitch+89.0f; pitch = -89.0f;}
  xangle *= GRAD; //Umrechnung von Grad in Radians
  yangle *= GRAD;
  position -= drehpunkt;
  position = rotate(position, xangle, xachse); //Kamera selbst um x-Achse drehen
  position = rotateY(position, yangle); //Kamera selbst auch um y-Achse drehen
  position += drehpunkt;
  //lookat neu berechnen:
  glm::vec3 front; //Richtungsvektor in den die Kamera schaut
  front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
  front.y = sin(glm::radians(pitch));
  front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
  lookat = glm::normalize(front);
  update();
 }

 glm::vec3 getPosition() {return position;}
 
 void moveToLookat(float yRel) {
  //const float mouseSensitivity2=0.01f; //TODO: eventuell anpassbar machen?
  const float mouseSensitivity2=1.0f; //TODO: eventuell anpassbar machen?
  position += lookat*(yRel*mouseSensitivity2);
  update();
 }
 
 void update() {
  view = glm::lookAt(position, position+lookat, up);
  viewProj = projection * view;
 }

 void moveFront(float amount) {
  //Kamera in Blickrichtung bewegen ohne Hoehe zu aendern
  translate(glm::normalize(glm::vec3(1.0f,0.0f,1.0f)*lookat) * amount);
  update();
 }

 void moveFrontx(float amount) {
  //Kamera quer zur Blickrichtung bewegen ohne Hoehe zu aendern
  translate(glm::normalize(glm::cross(lookat, up)*glm::vec3(1.05,0.0f,1.0f)) * amount);
  update();
 }
 
 void moveSideways(float amount) {
  translate(glm::normalize(glm::cross(lookat, up)) * amount);
  //move(glm::normalize(glm::cross(lookat, up)) * amount);
  update();
 }
 
 void moveUp(float amount) {
  translate(up * amount);
  drehpunkt += up*amount;//test
  //move(up * amount);
  update();
 }
 
 void move(glm::vec3 v) {
  position += v;
  drehpunkt += v;
  update();
 }
};
