
#include "camera.h"

const Vec3 UP = Vec3{0.0f, 1.0f, 0.0f};

Camera::Camera(Vec2 dim) {
    reset();
    set_ar(dim);
}

Mat4 Camera::get_view() const { return view; }

Mat4 Camera::get_proj() const { return Mat4::project(vert_fov, aspect_ratio, near_plane); }

Vec3 Camera::pos() const { return position; }

Vec3 Camera::front() const { return (looking_at - position).unit(); }

float Camera::dist() const { return (position - looking_at).norm(); }

void Camera::look_at(Vec3 cent, Vec3 pos) {
    position = pos;
    looking_at = cent;
    radius = (pos - cent).norm();
    if(dot(front(), UP) == -1.0f)
        rot = Quat::euler(Vec3{270.0f, 0.0f, 0.0f});
    else
        rot = Quat::euler(Mat4::rotate_z_to(front()).to_euler());
    update_pos();
}

void Camera::reset() {
    vert_fov = 90.0f;
    aspect_ratio = 1.7778f;
    rot = Quat::euler(Vec3(-45.0f, 45.0f, 0.0f));
    near_plane = 0.01f;
    radius = 5.0f;
    radius_sens = 0.25f;
    move_sens = 0.015f;
    orbit_sens = 0.2f;
    looking_at = Vec3();
    update_pos();
}

void Camera::mouse_orbit(Vec2 off) {
    float up_rot = -off.x * orbit_sens;
    float right_rot = off.y * orbit_sens;

    Vec3 up = rot.rotate(UP);
    Vec3 f = front();
    Vec3 right = cross(f, up).unit();

    rot = Quat::axis_angle(UP, up_rot) * Quat::axis_angle(right, right_rot) * rot;
    update_pos();
}

void Camera::mouse_move(Vec2 off) {
    Vec3 up = rot.rotate(UP);
    Vec3 f = front();
    Vec3 right = cross(f, up).unit();

    looking_at += -right * off.x * move_sens + up * off.y * move_sens;
    update_pos();
}

void Camera::mouse_radius(float off) {
    radius -= off * radius_sens;
    radius = std::max(radius, 2.0f * near_plane);
    update_pos();
}

void Camera::set_fov(float f) { vert_fov = f; }

float Camera::get_h_fov() const {
    float vfov = Radians(vert_fov);
    float hfov = 2.0f * std::atan(aspect_ratio * std::tan(vfov / 2.0f));
    return Degrees(hfov);
}

float Camera::get_fov() const { return vert_fov; }

float Camera::get_ar() const { return aspect_ratio; }

float Camera::get_near() const { return near_plane; }

Vec3 Camera::center() const { return looking_at; }

void Camera::set_ar(float a) { aspect_ratio = a; }

void Camera::set_ar(Vec2 dim) { aspect_ratio = dim.x / dim.y; }

void Camera::update_pos() {
    position = rot.rotate(Vec3{0.0f, 0.0f, 1.0f});
    position = looking_at + radius * position.unit();
    iview = Mat4::translate(position) * rot.to_mat();
    view = iview.inverse();
}
