Skip to content

File math_util.hpp

File List > illixr > math_util.hpp

Go to the documentation of this file

#pragma once
#ifndef _USE_MATH_DEFINES
#    define _USE_MATH_DEFINES
#endif

#include 
#ifndef M_PI
#    define M_PI 3.14159265358979323846
#endif
#ifdef Success
#    undef Success
#endif
#ifdef __ANDROID__
#    include 
#else
#    include 
#endif

namespace ILLIXR::math_util {
inline void projection(Eigen::Matrix4f* result, const float tan_left, const float tan_right, const float tan_up,
                       float const tan_down, const float near_z, const float far_z) {
    const float tan_width  = tan_right - tan_left;
    const float tan_height = tan_up - tan_down;

    // https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/building-basic-perspective-projection-matrix
    (*result)(0, 0) = 2 / tan_width;
    (*result)(0, 1) = 0;
    (*result)(0, 2) = (tan_right + tan_left) / tan_width;
    (*result)(0, 3) = 0;

    (*result)(1, 0) = 0;
    (*result)(1, 1) = 2 / tan_height;
    (*result)(1, 2) = (tan_up + tan_down) / tan_height;
    (*result)(1, 3) = 0;

    (*result)(2, 0) = 0;
    (*result)(2, 1) = 0;
    (*result)(2, 2) = -far_z / (far_z - near_z);
    (*result)(2, 3) = -(far_z * near_z) / (far_z - near_z);

    (*result)(3, 0) = 0;
    (*result)(3, 1) = 0;
    (*result)(3, 2) = -1;
    (*result)(3, 3) = 0;
}

inline void projection_reverse_z(Eigen::Matrix4f* result, const float tan_left, const float tan_right, const float tan_up,
                                 float const tan_down, const float near_z, const float far_z) {
    const float tan_width  = tan_right - tan_left;
    const float tan_height = tan_up - tan_down;

    // https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/building-basic-perspective-projection-matrix
    (*result)(0, 0) = 2 / tan_width;
    (*result)(0, 1) = 0;
    (*result)(0, 2) = (tan_right + tan_left) / tan_width;
    (*result)(0, 3) = 0;

    (*result)(1, 0) = 0;
    (*result)(1, 1) = 2 / tan_height;
    (*result)(1, 2) = (tan_up + tan_down) / tan_height;
    (*result)(1, 3) = 0;

    (*result)(2, 0) = 0;
    (*result)(2, 1) = 0;
    (*result)(2, 2) = near_z / (far_z - near_z);
    (*result)(2, 3) = (far_z * near_z) / (far_z - near_z);

    (*result)(3, 0) = 0;
    (*result)(3, 1) = 0;
    (*result)(3, 2) = -1;
    (*result)(3, 3) = 0;
}

inline void projection_fov(Eigen::Matrix4f* result, const float fov_left, const float fov_right, const float fov_up,
                           const float fov_down, const float near_z, const float far_z, bool reverse_z = false) {
    const float tan_left  = -tanf(static_cast<float>(fov_left * (M_PI / 180.0f)));
    const float tan_right = tanf(static_cast<float>(fov_right * (M_PI / 180.0f)));

    const float tan_down = -tanf(static_cast<float>(fov_down * (M_PI / 180.0f)));
    const float tan_up   = tanf(static_cast<float>(fov_up * (M_PI / 180.0f)));

    if (reverse_z) {
        projection_reverse_z(result, tan_left, tan_right, tan_up, tan_down, near_z, far_z);
    } else {
        projection(result, tan_left, tan_right, tan_up, tan_down, near_z, far_z);
    }
}

// Expects FoVs in radians
inline void unreal_projection(Eigen::Matrix4f* result, const float fov_left, const float fov_right, const float fov_up,
                              const float fov_down) {
    // Unreal uses a far plane at infinity and a near plane of 10 centimeters (0.1 meters)
    constexpr float near_z = 0.1f;

    const float angle_left  = tanf(static_cast<float>(fov_left));
    const float angle_right = tanf(static_cast<float>(fov_right));
    const float angle_up    = tanf(static_cast<float>(fov_up));
    const float angle_down  = tanf(static_cast<float>(fov_down));

    const float sum_rl = angle_left + angle_right;
    const float sum_tb = angle_up + angle_down;
    const float inv_rl = 1.0f / (angle_right - angle_left);
    const float inv_tb = 1.0f / (angle_up - angle_down);

    (*result)(0, 0) = 2 * inv_rl;
    (*result)(0, 1) = 0;
    (*result)(0, 2) = sum_rl * (-inv_rl);
    (*result)(0, 3) = 0;

    (*result)(1, 0) = 0;
    (*result)(1, 1) = 2 * inv_tb;
    (*result)(1, 2) = sum_tb * (-inv_tb);
    (*result)(1, 3) = 0;

    (*result)(2, 0) = 0;
    (*result)(2, 1) = 0;
    (*result)(2, 2) = 0;
    (*result)(2, 3) = near_z;

    (*result)(3, 0) = 0;
    (*result)(3, 1) = 0;
    (*result)(3, 2) = -1;
    (*result)(3, 3) = 0;
}

// TODO: this is just a complicated version to achieve reverse Z with a finite far plane.
[[maybe_unused]] inline void godot_projection(Eigen::Matrix4f* result, const float fov_left, const float fov_right,
                                              const float fov_up, const float fov_down) {
    // Godot's default far and near planes are 4000m and 0.05m respectively.
    // https://github.com/godotengine/godot/blob/e96ad5af98547df71b50c4c4695ac348638113e0/modules/openxr/openxr_util.cpp#L97
    // The Vulkan implementation passes in GRAPHICS_OPENGL for some reason.
    constexpr float near_z   = 0.05f;
    constexpr float far_z    = 4000.f;
    constexpr float offset_z = near_z;

    const float angle_left  = tanf(static_cast<float>(fov_left));
    const float angle_right = tanf(static_cast<float>(fov_right));
    const float angle_up    = tanf(static_cast<float>(fov_up));
    const float angle_down  = tanf(static_cast<float>(fov_down));

    const float angle_width  = angle_right - angle_left;
    const float angle_height = angle_up - angle_down;

    Eigen::Matrix4f openxr_matrix;

    openxr_matrix(0, 0) = 2 / angle_width;
    openxr_matrix(0, 1) = 0;
    openxr_matrix(0, 2) = (angle_right + angle_left) / angle_width;
    openxr_matrix(0, 3) = 0;

    openxr_matrix(1, 0) = 0;
    openxr_matrix(1, 1) = 2 / angle_height;
    openxr_matrix(1, 2) = (angle_up + angle_down) / angle_height;
    openxr_matrix(1, 3) = 0;

    openxr_matrix(2, 0) = 0;
    openxr_matrix(2, 1) = 0;
    openxr_matrix(2, 2) = -(far_z + offset_z) / (far_z - near_z);
    openxr_matrix(2, 3) = -(far_z * (near_z + offset_z)) / (far_z - near_z);

    openxr_matrix(3, 0) = 0;
    openxr_matrix(3, 1) = 0;
    openxr_matrix(3, 2) = -1;
    openxr_matrix(3, 3) = 0;

    // Godot then remaps the matrix...
    // https://github.com/Khasehemwy/godot/blob/d950f5f83819240771aebb602bfdd4875363edce/core/math/projection.cpp#L722
    Eigen::Matrix4f remap_z;

    remap_z(0, 0) = 1;
    remap_z(0, 1) = 0;
    remap_z(0, 2) = 0;
    remap_z(0, 3) = 0;

    remap_z(1, 0) = 0;
    remap_z(1, 1) = 1;
    remap_z(1, 2) = 0;
    remap_z(1, 3) = 0;

    remap_z(2, 0) = 0;
    remap_z(2, 1) = 0;
    remap_z(2, 2) = -0.5;
    remap_z(2, 3) = 0.5;

    remap_z(3, 0) = 0;
    remap_z(3, 1) = 0;
    remap_z(3, 2) = 0;
    remap_z(3, 3) = 1;

    (*result) = remap_z * openxr_matrix;
}

/*
 * Rotation matrix to convert a point from one coordinate system to another, e.g. left hand y up to right hand y up
 *
 */
inline Eigen::Matrix3f rotation(const float alpha, const float beta, const float gamma) {
    Eigen::Matrix3f rot;
    double          ra = alpha * M_PI / 180.;
    double          rb = beta * M_PI / 180.;
    double          rg = gamma * M_PI / 180;
    rot << static_cast<float>(cos(rg) * cos(rb)), static_cast<float>(cos(rg) * sin(rb) * sin(ra) - sin(rg) * cos(ra)),
        static_cast<float>(cos(rg) * sin(rb) * cos(ra) + sin(rg) * sin(ra)), static_cast<float>(sin(rg) * cos(rb)),
        static_cast<float>(sin(rg) * sin(rb) * sin(ra) + cos(rg) * cos(ra)),
        static_cast<float>(sin(rg) * sin(rb) * cos(ra) - cos(rg) * sin(ra)), static_cast<float>(-sin(rb)),
        static_cast<float>(cos(rb) * sin(ra)), static_cast<float>(cos(rb) * cos(ra));
    return rot;
}

const Eigen::Matrix3f invert_x = (Eigen::Matrix3f() << -1., 0., 0., 0., 1., 0., 0., 0., 1.).finished();
const Eigen::Matrix3f invert_y = (Eigen::Matrix3f() << 1., 0., 0., 0., -1., 0., 0., 0., 1.).finished();
const Eigen::Matrix3f invert_z = (Eigen::Matrix3f() << 1., 0., 0., 0., 1., 0., 0., 0., -1.).finished();
const Eigen::Matrix3f identity = Eigen::Matrix3f::Identity();

// from:      IM (image)                        LHYU (left hand y up)              RHYU (right hand y up)             RHZU
// (right hand z up LHZU (left hand z up)               RHZUXF (right hand z up x forward)                 to:
const Eigen::Matrix3f conversion[6][6] = {
    {identity, invert_y, rotation(180., 0., 0.), rotation(-90., 0., 0.), rotation(-90., 0., 90.) * invert_z,
     rotation(-90., 0., -90.)}, // IM
    {invert_y, identity, invert_z, rotation(-90., 0., 0.) * invert_y, rotation(90., 0., 90.),
     rotation(-90., 0., -90.) * invert_y}, // LHYU
    {rotation(180., 0., 0.), invert_z, identity, rotation(90., 0., 0.), rotation(90., 0., 90.) * invert_z,
     rotation(-90., 0., 90.) * invert_x* invert_y}, // RHYU
    {rotation(90., 0., 0.), invert_y* rotation(90., 0., 0.), rotation(-90., 0., 0.), identity, rotation(0., 0., 90.) * invert_y,
     rotation(0., 0., -90.)}, // RHZU
    {rotation(90., -90., 0.) * invert_y, rotation(-90., -90., 0.), rotation(0., 90., 90.) * invert_y,
     invert_x* rotation(0, 0., 90), identity, invert_y}, // LHZU
    {rotation(90., -90., 0.), rotation(-90., -90., 0.) * invert_y, rotation(0., 90., 90.), rotation(0, 0., 90), invert_y,
     identity}}; // RHZUXF

} // namespace ILLIXR::math_util