#include "OrbitCameraController.hpp"

#include <Magnum/Math/Angle.h>
#include <Magnum/Math/Functions.h>

using namespace Magnum;
using namespace Math::Literals;

namespace osim_viewer {

namespace {
constexpr float PitchLimit = 1.45f;
constexpr float MinDistance = 0.05f;
constexpr float OrbitSensitivity = 0.008f;
constexpr float ZoomSensitivity = 0.15f;
}

void OrbitCameraController::setViewport(const Vector2i& windowSize, const Vector2i& framebufferSize) {
    _windowSize = {Math::max(windowSize.x(), 1), Math::max(windowSize.y(), 1)};
    _framebufferSize = {Math::max(framebufferSize.x(), 1), Math::max(framebufferSize.y(), 1)};
}

void OrbitCameraController::setSceneBounds(const Vector3& center, float radius) {
    _pivot = center;
    _sceneRadius = Math::max(radius, 0.1f);
    _distance = Math::max(_sceneRadius*2.5f, 0.5f);
    _yaw = 0.0f;
    _pitch = 0.35f;
}

void OrbitCameraController::reset() {
    setSceneBounds(_pivot, _sceneRadius);
}

void OrbitCameraController::orbit(const Vector2& deltaPixels) {
    _yaw -= deltaPixels.x()*OrbitSensitivity;
    _pitch = Math::clamp(_pitch - deltaPixels.y()*OrbitSensitivity, -PitchLimit, PitchLimit);
}

void OrbitCameraController::pan(const Vector2& deltaPixels) {
    const Vector3 panRight = right();
    const Vector3 panUp = up();
    const float scale = panScale();
    _pivot += (-panRight*deltaPixels.x() + panUp*deltaPixels.y())*scale;
}

void OrbitCameraController::zoom(float wheelDelta) {
    const float factor = std::exp(-wheelDelta*ZoomSensitivity);
    _distance = Math::max(_distance*factor, MinDistance);
}

Matrix4 OrbitCameraController::cameraTransform() const {
    return Matrix4::lookAt(position(), _pivot, Vector3::yAxis()).invertedRigid();
}

Vector3 OrbitCameraController::position() const {
    const float cosPitch = std::cos(_pitch);
    const Vector3 offset{
        _distance*cosPitch*std::sin(_yaw),
        _distance*std::sin(_pitch),
        _distance*cosPitch*std::cos(_yaw)
    };
    return _pivot + offset;
}

float OrbitCameraController::panScale() const {
    return 2.0f*_distance*Math::tan(35.0_degf/2.0f)/Float(_windowSize.y());
}

Vector3 OrbitCameraController::forward() const {
    return (_pivot - position()).normalized();
}

Vector3 OrbitCameraController::right() const {
    return Math::cross(forward(), Vector3::yAxis()).normalized();
}

Vector3 OrbitCameraController::up() const {
    return Math::cross(right(), forward()).normalized();
}

}
