#ifdef PLATFORM_QUEST_3

#include "camera.h"
#include "data.h"
#include "geometry.h"
#include "global.h"
#include "glsl.h"
#include "input.h"
#include "opengl.h"
#include "settings.h"
#include "shader.h"
#include "texture.h"
#include "ui.h"
#include "xr.h"

#include <array>
#include <android/input.h>
#include <android/log.h>
#include <android/native_window_jni.h> // for native window JNI
#include <android_native_app_glue.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <jni.h>
#include <numeric>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <pthread.h>
#include <sys/prctl.h> // for prctl( PR_SET_NAME )
#include <sys/system_properties.h>
#include <unistd.h>

// Column-major, pre-multiplied. This type does not exist in the OpenXR API and is provided for convenience.
typedef struct XrMatrix4x4f {
    float m[16];
} XrMatrix4x4f;

inline static float XrRcpSqrt(const float x) {
    const float SMALLEST_NON_DENORMAL = 1.1754943508222875e-038f;  // ( 1U << 23 )
    const float rcp = (x >= SMALLEST_NON_DENORMAL) ? 1.0f / sqrtf(x) : 1.0f;
    return rcp;
}

inline static void XrVector3f_Add(XrVector3f* result, const XrVector3f* a, const XrVector3f* b) {
    result->x = a->x + b->x;
    result->y = a->y + b->y;
    result->z = a->z + b->z;
}

inline static void XrVector3f_Sub(XrVector3f* result, const XrVector3f* a, const XrVector3f* b) {
    result->x = a->x - b->x;
    result->y = a->y - b->y;
    result->z = a->z - b->z;
}

inline static void XrVector3f_Scale(XrVector3f* result, const XrVector3f* a, const float scaleFactor) {
    result->x = a->x * scaleFactor;
    result->y = a->y * scaleFactor;
    result->z = a->z * scaleFactor;
}

inline static void XrQuaternionf_Multiply(XrQuaternionf* result, const XrQuaternionf* a, const XrQuaternionf* b) {
    result->x = (b->w * a->x) + (b->x * a->w) + (b->y * a->z) - (b->z * a->y);
    result->y = (b->w * a->y) - (b->x * a->z) + (b->y * a->w) + (b->z * a->x);
    result->z = (b->w * a->z) + (b->x * a->y) - (b->y * a->x) + (b->z * a->w);
    result->w = (b->w * a->w) - (b->x * a->x) - (b->y * a->y) - (b->z * a->z);
}

inline static void XrQuaternionf_Invert(XrQuaternionf* result, const XrQuaternionf* q) {
    result->x = -q->x;
    result->y = -q->y;
    result->z = -q->z;
    result->w = q->w;
}

inline static void XrQuaternionf_RotateVector3f(XrVector3f* result, const XrQuaternionf* a, const XrVector3f* v) {
    XrQuaternionf q = {v->x, v->y, v->z, 0.0f};
    XrQuaternionf aq;
    XrQuaternionf_Multiply(&aq, &q, a);
    XrQuaternionf aInv;
    XrQuaternionf_Invert(&aInv, a);
    XrQuaternionf aqaInv;
    XrQuaternionf_Multiply(&aqaInv, &aInv, &aq);

    result->x = aqaInv.x;
    result->y = aqaInv.y;
    result->z = aqaInv.z;
}

inline static void XrPosef_TransformVector3f(XrVector3f* result, const XrPosef* a, const XrVector3f* v) {
    XrVector3f r0;
    XrQuaternionf_RotateVector3f(&r0, &a->orientation, v);
    XrVector3f_Add(result, &r0, &a->position);
}

inline static void XrPosef_Multiply(XrPosef* result, const XrPosef* a, const XrPosef* b) {
    XrQuaternionf_Multiply(&result->orientation, &b->orientation, &a->orientation);
    XrPosef_TransformVector3f(&result->position, a, &b->position);
}

inline static void XrPosef_Invert(XrPosef* result, const XrPosef* a) {
    XrQuaternionf_Invert(&result->orientation, &a->orientation);
    XrVector3f aPosNeg;
    XrVector3f_Scale(&aPosNeg, &a->position, -1.0f);
    XrQuaternionf_RotateVector3f(&result->position, &result->orientation, &aPosNeg);
}

// Use left-multiplication to accumulate transformations.
inline static void XrMatrix4x4f_Multiply(XrMatrix4x4f* result, const XrMatrix4x4f* a, const XrMatrix4x4f* b) {
    result->m[0] = a->m[0] * b->m[0] + a->m[4] * b->m[1] + a->m[8] * b->m[2] + a->m[12] * b->m[3];
    result->m[1] = a->m[1] * b->m[0] + a->m[5] * b->m[1] + a->m[9] * b->m[2] + a->m[13] * b->m[3];
    result->m[2] = a->m[2] * b->m[0] + a->m[6] * b->m[1] + a->m[10] * b->m[2] + a->m[14] * b->m[3];
    result->m[3] = a->m[3] * b->m[0] + a->m[7] * b->m[1] + a->m[11] * b->m[2] + a->m[15] * b->m[3];

    result->m[4] = a->m[0] * b->m[4] + a->m[4] * b->m[5] + a->m[8] * b->m[6] + a->m[12] * b->m[7];
    result->m[5] = a->m[1] * b->m[4] + a->m[5] * b->m[5] + a->m[9] * b->m[6] + a->m[13] * b->m[7];
    result->m[6] = a->m[2] * b->m[4] + a->m[6] * b->m[5] + a->m[10] * b->m[6] + a->m[14] * b->m[7];
    result->m[7] = a->m[3] * b->m[4] + a->m[7] * b->m[5] + a->m[11] * b->m[6] + a->m[15] * b->m[7];

    result->m[8] = a->m[0] * b->m[8] + a->m[4] * b->m[9] + a->m[8] * b->m[10] + a->m[12] * b->m[11];
    result->m[9] = a->m[1] * b->m[8] + a->m[5] * b->m[9] + a->m[9] * b->m[10] + a->m[13] * b->m[11];
    result->m[10] = a->m[2] * b->m[8] + a->m[6] * b->m[9] + a->m[10] * b->m[10] + a->m[14] * b->m[11];
    result->m[11] = a->m[3] * b->m[8] + a->m[7] * b->m[9] + a->m[11] * b->m[10] + a->m[15] * b->m[11];

    result->m[12] = a->m[0] * b->m[12] + a->m[4] * b->m[13] + a->m[8] * b->m[14] + a->m[12] * b->m[15];
    result->m[13] = a->m[1] * b->m[12] + a->m[5] * b->m[13] + a->m[9] * b->m[14] + a->m[13] * b->m[15];
    result->m[14] = a->m[2] * b->m[12] + a->m[6] * b->m[13] + a->m[10] * b->m[14] + a->m[14] * b->m[15];
    result->m[15] = a->m[3] * b->m[12] + a->m[7] * b->m[13] + a->m[11] * b->m[14] + a->m[15] * b->m[15];
}

// Creates a translation matrix.
inline static void XrMatrix4x4f_CreateTranslation(XrMatrix4x4f* result, const float x, const float y, const float z) {
    result->m[0] = 1.0f;
    result->m[1] = 0.0f;
    result->m[2] = 0.0f;
    result->m[3] = 0.0f;
    result->m[4] = 0.0f;
    result->m[5] = 1.0f;
    result->m[6] = 0.0f;
    result->m[7] = 0.0f;
    result->m[8] = 0.0f;
    result->m[9] = 0.0f;
    result->m[10] = 1.0f;
    result->m[11] = 0.0f;
    result->m[12] = x;
    result->m[13] = y;
    result->m[14] = z;
    result->m[15] = 1.0f;
}

// Creates a rotation matrix.
// If -Z=forward, +Y=up, +X=right, then radiansX=pitch, radiansY=yaw, radiansZ=roll.
inline static void XrMatrix4x4f_CreateRotationRadians(XrMatrix4x4f* result, const float radiansX, const float radiansY,
                                                      const float radiansZ) {
    const float sinX = sinf(radiansX);
    const float cosX = cosf(radiansX);
    const XrMatrix4x4f rotationX = {{1, 0, 0, 0, 0, cosX, sinX, 0, 0, -sinX, cosX, 0, 0, 0, 0, 1}};
    const float sinY = sinf(radiansY);
    const float cosY = cosf(radiansY);
    const XrMatrix4x4f rotationY = {{cosY, 0, -sinY, 0, 0, 1, 0, 0, sinY, 0, cosY, 0, 0, 0, 0, 1}};
    const float sinZ = sinf(radiansZ);
    const float cosZ = cosf(radiansZ);
    const XrMatrix4x4f rotationZ = {{cosZ, sinZ, 0, 0, -sinZ, cosZ, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}};
    XrMatrix4x4f rotationXY;
    XrMatrix4x4f_Multiply(&rotationXY, &rotationY, &rotationX);
    XrMatrix4x4f_Multiply(result, &rotationZ, &rotationXY);
}

// Creates a rotation matrix.
// If -Z=forward, +Y=up, +X=right, then degreesX=pitch, degreesY=yaw, degreesZ=roll.
inline static void XrMatrix4x4f_CreateRotation(XrMatrix4x4f* result, const float degreesX, const float degreesY,
                                               const float degreesZ) {
    XrMatrix4x4f_CreateRotationRadians(result, degreesX * (PI_ / 180.0f), degreesY * (PI_ / 180.0f),
                                       degreesZ * (PI_ / 180.0f));
}

// Creates a scale matrix.
inline static void XrMatrix4x4f_CreateScale(XrMatrix4x4f* result, const float x, const float y, const float z) {
    result->m[0] = x;
    result->m[1] = 0.0f;
    result->m[2] = 0.0f;
    result->m[3] = 0.0f;
    result->m[4] = 0.0f;
    result->m[5] = y;
    result->m[6] = 0.0f;
    result->m[7] = 0.0f;
    result->m[8] = 0.0f;
    result->m[9] = 0.0f;
    result->m[10] = z;
    result->m[11] = 0.0f;
    result->m[12] = 0.0f;
    result->m[13] = 0.0f;
    result->m[14] = 0.0f;
    result->m[15] = 1.0f;
}

// Creates a matrix from a quaternion.
inline static void XrMatrix4x4f_CreateFromQuaternion(XrMatrix4x4f* result, const XrQuaternionf* quat) {
    const float x2 = quat->x + quat->x;
    const float y2 = quat->y + quat->y;
    const float z2 = quat->z + quat->z;

    const float xx2 = quat->x * x2;
    const float yy2 = quat->y * y2;
    const float zz2 = quat->z * z2;

    const float yz2 = quat->y * z2;
    const float wx2 = quat->w * x2;
    const float xy2 = quat->x * y2;
    const float wz2 = quat->w * z2;
    const float xz2 = quat->x * z2;
    const float wy2 = quat->w * y2;

    result->m[0] = 1.0f - yy2 - zz2;
    result->m[1] = xy2 + wz2;
    result->m[2] = xz2 - wy2;
    result->m[3] = 0.0f;

    result->m[4] = xy2 - wz2;
    result->m[5] = 1.0f - xx2 - zz2;
    result->m[6] = yz2 + wx2;
    result->m[7] = 0.0f;

    result->m[8] = xz2 + wy2;
    result->m[9] = yz2 - wx2;
    result->m[10] = 1.0f - xx2 - yy2;
    result->m[11] = 0.0f;

    result->m[12] = 0.0f;
    result->m[13] = 0.0f;
    result->m[14] = 0.0f;
    result->m[15] = 1.0f;
}

// Creates a combined translation(rotation(scale(object))) matrix.
inline static void XrMatrix4x4f_CreateTranslationRotationScale(XrMatrix4x4f* result, const XrVector3f* translation,
                                                               const XrQuaternionf* rotation, const XrVector3f* scale) {
    XrMatrix4x4f scaleMatrix;
    XrMatrix4x4f_CreateScale(&scaleMatrix, scale->x, scale->y, scale->z);

    XrMatrix4x4f rotationMatrix;
    XrMatrix4x4f_CreateFromQuaternion(&rotationMatrix, rotation);

    XrMatrix4x4f translationMatrix;
    XrMatrix4x4f_CreateTranslation(&translationMatrix, translation->x, translation->y, translation->z);

    XrMatrix4x4f combinedMatrix;
    XrMatrix4x4f_Multiply(&combinedMatrix, &rotationMatrix, &scaleMatrix);
    XrMatrix4x4f_Multiply(result, &translationMatrix, &combinedMatrix);
}

inline static void XrMatrix4x4f_CreateFromRigidTransform(XrMatrix4x4f* result, const XrPosef* s) {
    const XrVector3f identityScale = {1.0f, 1.0f, 1.0f};
    XrMatrix4x4f_CreateTranslationRotationScale(result, &s->position, &s->orientation, &identityScale);
}

// Creates a projection matrix based on the specified dimensions.
// The projection matrix transforms -Z=forward, +Y=up, +X=right to the appropriate clip space for the graphics API.
// The far plane is placed at infinity if farZ <= nearZ.
// An infinite projection matrix is preferred for rasterization because, except for
// things *right* up against the near plane, it always provides better precision:
//              "Tightening the Precision of Perspective Rendering"
//              Paul Upchurch, Mathieu Desbrun
//              Journal of Graphics Tools, Volume 16, Issue 1, 2012
inline static void XrMatrix4x4f_CreateProjection(XrMatrix4x4f* result, const float tanAngleLeft,
                                                 const float tanAngleRight, const float tanAngleUp, float const tanAngleDown,
                                                 const float nearZ, const float farZ) {
    const float tanAngleWidth = tanAngleRight - tanAngleLeft;

    const float tanAngleHeight = tanAngleUp - tanAngleDown;

    // Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES).
    // Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal).
    const float offsetZ = nearZ;

    if (farZ <= nearZ) {
        // place the far plane at infinity
        result->m[0] = 2.0f / tanAngleWidth;
        result->m[4] = 0.0f;
        result->m[8] = (tanAngleRight + tanAngleLeft) / tanAngleWidth;
        result->m[12] = 0.0f;

        result->m[1] = 0.0f;
        result->m[5] = 2.0f / tanAngleHeight;
        result->m[9] = (tanAngleUp + tanAngleDown) / tanAngleHeight;
        result->m[13] = 0.0f;

        result->m[2] = 0.0f;
        result->m[6] = 0.0f;
        result->m[10] = -1.0f;
        result->m[14] = -(nearZ + offsetZ);

        result->m[3] = 0.0f;
        result->m[7] = 0.0f;
        result->m[11] = -1.0f;
        result->m[15] = 0.0f;
    } else {
        // normal projection
        result->m[0] = 2.0f / tanAngleWidth;
        result->m[4] = 0.0f;
        result->m[8] = (tanAngleRight + tanAngleLeft) / tanAngleWidth;
        result->m[12] = 0.0f;

        result->m[1] = 0.0f;
        result->m[5] = 2.0f / tanAngleHeight;
        result->m[9] = (tanAngleUp + tanAngleDown) / tanAngleHeight;
        result->m[13] = 0.0f;

        result->m[2] = 0.0f;
        result->m[6] = 0.0f;
        result->m[10] = -(farZ + offsetZ) / (farZ - nearZ);
        result->m[14] = -(farZ * (nearZ + offsetZ)) / (farZ - nearZ);

        result->m[3] = 0.0f;
        result->m[7] = 0.0f;
        result->m[11] = -1.0f;
        result->m[15] = 0.0f;
    }
}

// Creates a projection matrix based on the specified FOV.
inline static void XrMatrix4x4f_CreateProjectionFov(XrMatrix4x4f* result, const XrFovf fov,
                                                    const float nearZ, const float farZ) {
    const float tanLeft = tanf(fov.angleLeft);
    const float tanRight = tanf(fov.angleRight);

    const float tanDown = tanf(fov.angleDown);
    const float tanUp = tanf(fov.angleUp);

    XrMatrix4x4f_CreateProjection(result, tanLeft, tanRight, tanUp, tanDown, nearZ, farZ);
}

// Creates a matrix that transforms the -1 to 1 cube to cover the given 'mins' and 'maxs' transformed with the given 'matrix'.
inline static void XrMatrix4x4f_CreateOffsetScaleForBounds(XrMatrix4x4f* result, const XrMatrix4x4f* matrix, const XrVector3f* mins,
                                                           const XrVector3f* maxs) {
    const XrVector3f offset = {(maxs->x + mins->x) * 0.5f, (maxs->y + mins->y) * 0.5f, (maxs->z + mins->z) * 0.5f};
    const XrVector3f scale = {(maxs->x - mins->x) * 0.5f, (maxs->y - mins->y) * 0.5f, (maxs->z - mins->z) * 0.5f};

    result->m[0] = matrix->m[0] * scale.x;
    result->m[1] = matrix->m[1] * scale.x;
    result->m[2] = matrix->m[2] * scale.x;
    result->m[3] = matrix->m[3] * scale.x;

    result->m[4] = matrix->m[4] * scale.y;
    result->m[5] = matrix->m[5] * scale.y;
    result->m[6] = matrix->m[6] * scale.y;
    result->m[7] = matrix->m[7] * scale.y;

    result->m[8] = matrix->m[8] * scale.z;
    result->m[9] = matrix->m[9] * scale.z;
    result->m[10] = matrix->m[10] * scale.z;
    result->m[11] = matrix->m[11] * scale.z;

    result->m[12] = matrix->m[12] + matrix->m[0] * offset.x + matrix->m[4] * offset.y + matrix->m[8] * offset.z;
    result->m[13] = matrix->m[13] + matrix->m[1] * offset.x + matrix->m[5] * offset.y + matrix->m[9] * offset.z;
    result->m[14] = matrix->m[14] + matrix->m[2] * offset.x + matrix->m[6] * offset.y + matrix->m[10] * offset.z;
    result->m[15] = matrix->m[15] + matrix->m[3] * offset.x + matrix->m[7] * offset.y + matrix->m[11] * offset.z;
}

// Returns true if the given matrix is affine.
inline static bool XrMatrix4x4f_IsAffine(const XrMatrix4x4f* matrix, const float epsilon) {
    return fabsf(matrix->m[3]) <= epsilon && fabsf(matrix->m[7]) <= epsilon && fabsf(matrix->m[11]) <= epsilon &&
           fabsf(matrix->m[15] - 1.0f) <= epsilon;
}

// Returns true if the given matrix is orthogonal.
inline static bool XrMatrix4x4f_IsOrthogonal(const XrMatrix4x4f* matrix, const float epsilon) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (i != j) {
                if (fabsf(matrix->m[4 * i + 0] * matrix->m[4 * j + 0] + matrix->m[4 * i + 1] * matrix->m[4 * j + 1] +
                          matrix->m[4 * i + 2] * matrix->m[4 * j + 2]) > epsilon) {
                    return false;
                }
                if (fabsf(matrix->m[4 * 0 + i] * matrix->m[4 * 0 + j] + matrix->m[4 * 1 + i] * matrix->m[4 * 1 + j] +
                          matrix->m[4 * 2 + i] * matrix->m[4 * 2 + j]) > epsilon) {
                    return false;
                }
            }
        }
    }
    return true;
}

// Returns true if the given matrix is orthonormal.
inline static bool XrMatrix4x4f_IsOrthonormal(const XrMatrix4x4f* matrix, const float epsilon) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            const float kd = (i == j) ? 1.0f : 0.0f;  // Kronecker delta
            if (fabsf(kd - (matrix->m[4 * i + 0] * matrix->m[4 * j + 0] + matrix->m[4 * i + 1] * matrix->m[4 * j + 1] +
                            matrix->m[4 * i + 2] * matrix->m[4 * j + 2])) > epsilon) {
                return false;
            }
            if (fabsf(kd - (matrix->m[4 * 0 + i] * matrix->m[4 * 0 + j] + matrix->m[4 * 1 + i] * matrix->m[4 * 1 + j] +
                            matrix->m[4 * 2 + i] * matrix->m[4 * 2 + j])) > epsilon) {
                return false;
            }
        }
    }
    return true;
}

// Returns true if the given matrix is a rigid body transform.
inline static bool XrMatrix4x4f_IsRigidBody(const XrMatrix4x4f* matrix, const float epsilon) {
    return XrMatrix4x4f_IsAffine(matrix, epsilon) && XrMatrix4x4f_IsOrthonormal(matrix, epsilon);
}

// Get the translation from a combined translation(rotation(scale(object))) matrix.
inline static void XrMatrix4x4f_GetTranslation(XrVector3f* result, const XrMatrix4x4f* src) {
    assert(XrMatrix4x4f_IsAffine(src, 1e-4f));
    assert(XrMatrix4x4f_IsOrthogonal(src, 1e-4f));

    result->x = src->m[12];
    result->y = src->m[13];
    result->z = src->m[14];
}

// Get the rotation from a combined translation(rotation(scale(object))) matrix.
inline static void XrMatrix4x4f_GetRotation(XrQuaternionf* result, const XrMatrix4x4f* src) {
    assert(XrMatrix4x4f_IsAffine(src, 1e-4f));
    assert(XrMatrix4x4f_IsOrthogonal(src, 1e-4f));

    const float rcpScaleX = XrRcpSqrt(src->m[0] * src->m[0] + src->m[1] * src->m[1] + src->m[2] * src->m[2]);
    const float rcpScaleY = XrRcpSqrt(src->m[4] * src->m[4] + src->m[5] * src->m[5] + src->m[6] * src->m[6]);
    const float rcpScaleZ = XrRcpSqrt(src->m[8] * src->m[8] + src->m[9] * src->m[9] + src->m[10] * src->m[10]);
    const float m[9] = {src->m[0] * rcpScaleX, src->m[1] * rcpScaleX, src->m[2] * rcpScaleX,
                        src->m[4] * rcpScaleY, src->m[5] * rcpScaleY, src->m[6] * rcpScaleY,
                        src->m[8] * rcpScaleZ, src->m[9] * rcpScaleZ, src->m[10] * rcpScaleZ};
    if (m[0 * 3 + 0] + m[1 * 3 + 1] + m[2 * 3 + 2] > 0.0f) {
        float t = +m[0 * 3 + 0] + m[1 * 3 + 1] + m[2 * 3 + 2] + 1.0f;
        float s = XrRcpSqrt(t) * 0.5f;
        result->w = s * t;
        result->z = (m[0 * 3 + 1] - m[1 * 3 + 0]) * s;
        result->y = (m[2 * 3 + 0] - m[0 * 3 + 2]) * s;
        result->x = (m[1 * 3 + 2] - m[2 * 3 + 1]) * s;
    } else if (m[0 * 3 + 0] > m[1 * 3 + 1] && m[0 * 3 + 0] > m[2 * 3 + 2]) {
        float t = +m[0 * 3 + 0] - m[1 * 3 + 1] - m[2 * 3 + 2] + 1.0f;
        float s = XrRcpSqrt(t) * 0.5f;
        result->x = s * t;
        result->y = (m[0 * 3 + 1] + m[1 * 3 + 0]) * s;
        result->z = (m[2 * 3 + 0] + m[0 * 3 + 2]) * s;
        result->w = (m[1 * 3 + 2] - m[2 * 3 + 1]) * s;
    } else if (m[1 * 3 + 1] > m[2 * 3 + 2]) {
        float t = -m[0 * 3 + 0] + m[1 * 3 + 1] - m[2 * 3 + 2] + 1.0f;
        float s = XrRcpSqrt(t) * 0.5f;
        result->y = s * t;
        result->x = (m[0 * 3 + 1] + m[1 * 3 + 0]) * s;
        result->w = (m[2 * 3 + 0] - m[0 * 3 + 2]) * s;
        result->z = (m[1 * 3 + 2] + m[2 * 3 + 1]) * s;
    } else {
        float t = -m[0 * 3 + 0] - m[1 * 3 + 1] + m[2 * 3 + 2] + 1.0f;
        float s = XrRcpSqrt(t) * 0.5f;
        result->z = s * t;
        result->w = (m[0 * 3 + 1] - m[1 * 3 + 0]) * s;
        result->x = (m[2 * 3 + 0] + m[0 * 3 + 2]) * s;
        result->y = (m[1 * 3 + 2] + m[2 * 3 + 1]) * s;
    }
}

// Get the scale from a combined translation(rotation(scale(object))) matrix.
inline static void XrMatrix4x4f_GetScale(XrVector3f* result, const XrMatrix4x4f* src) {
    assert(XrMatrix4x4f_IsAffine(src, 1e-4f));
    assert(XrMatrix4x4f_IsOrthogonal(src, 1e-4f));

    result->x = sqrtf(src->m[0] * src->m[0] + src->m[1] * src->m[1] + src->m[2] * src->m[2]);
    result->y = sqrtf(src->m[4] * src->m[4] + src->m[5] * src->m[5] + src->m[6] * src->m[6]);
    result->z = sqrtf(src->m[8] * src->m[8] + src->m[9] * src->m[9] + src->m[10] * src->m[10]);
}

// Transforms a 3D vector.
inline static void XrMatrix4x4f_TransformVector3f(XrVector3f* result, const XrMatrix4x4f* m, const XrVector3f* v) {
    const float w = m->m[3] * v->x + m->m[7] * v->y + m->m[11] * v->z + m->m[15];
    const float rcpW = 1.0f / w;
    result->x = (m->m[0] * v->x + m->m[4] * v->y + m->m[8] * v->z + m->m[12]) * rcpW;
    result->y = (m->m[1] * v->x + m->m[5] * v->y + m->m[9] * v->z + m->m[13]) * rcpW;
    result->z = (m->m[2] * v->x + m->m[6] * v->y + m->m[10] * v->z + m->m[14]) * rcpW;
}

// Transforms a 4D vector.
inline static void XrMatrix4x4f_TransformVector4f(XrVector4f* result, const XrMatrix4x4f* m, const XrVector4f* v) {
    result->x = m->m[0] * v->x + m->m[4] * v->y + m->m[8] * v->z + m->m[12] * v->w;
    result->y = m->m[1] * v->x + m->m[5] * v->y + m->m[9] * v->z + m->m[13] * v->w;
    result->z = m->m[2] * v->x + m->m[6] * v->y + m->m[10] * v->z + m->m[14] * v->w;
    result->w = m->m[3] * v->x + m->m[7] * v->y + m->m[11] * v->z + m->m[15] * v->w;
}

// Transforms the 'mins' and 'maxs' bounds with the given 'matrix'.
inline static void XrMatrix4x4f_TransformBounds(XrVector3f* resultMins, XrVector3f* resultMaxs, const XrMatrix4x4f* matrix,
                                                const XrVector3f* mins, const XrVector3f* maxs) {
    assert(XrMatrix4x4f_IsAffine(matrix, 1e-4f));

    const XrVector3f center = {(mins->x + maxs->x) * 0.5f, (mins->y + maxs->y) * 0.5f, (mins->z + maxs->z) * 0.5f};
    const XrVector3f extents = {maxs->x - center.x, maxs->y - center.y, maxs->z - center.z};
    const XrVector3f newCenter = {matrix->m[0] * center.x + matrix->m[4] * center.y + matrix->m[8] * center.z + matrix->m[12],
                                  matrix->m[1] * center.x + matrix->m[5] * center.y + matrix->m[9] * center.z + matrix->m[13],
                                  matrix->m[2] * center.x + matrix->m[6] * center.y + matrix->m[10] * center.z + matrix->m[14]};
    const XrVector3f newExtents = {
        fabsf(extents.x * matrix->m[0]) + fabsf(extents.y * matrix->m[4]) + fabsf(extents.z * matrix->m[8]),
        fabsf(extents.x * matrix->m[1]) + fabsf(extents.y * matrix->m[5]) + fabsf(extents.z * matrix->m[9]),
        fabsf(extents.x * matrix->m[2]) + fabsf(extents.y * matrix->m[6]) + fabsf(extents.z * matrix->m[10])};
    XrVector3f_Sub(resultMins, &newCenter, &newExtents);
    XrVector3f_Add(resultMaxs, &newCenter, &newExtents);
}

// Returns true if the 'mins' and 'maxs' bounds is completely off to one side of the projection matrix.
inline static bool XrMatrix4x4f_CullBounds(const XrMatrix4x4f* mvp, const XrVector3f* mins, const XrVector3f* maxs) {
    if (maxs->x <= mins->x && maxs->y <= mins->y && maxs->z <= mins->z) {
        return false;
    }

    XrVector4f c[8];
    for (int i = 0; i < 8; i++) {
        const XrVector4f corner = {(i & 1) != 0 ? maxs->x : mins->x, (i & 2) != 0 ? maxs->y : mins->y,
                                   (i & 4) != 0 ? maxs->z : mins->z, 1.0f};
        XrMatrix4x4f_TransformVector4f(&c[i], mvp, &corner);
    }

    int i;
    for (i = 0; i < 8; i++) {
        if (c[i].x > -c[i].w) {
            break;
        }
    }
    if (i == 8) {
        return true;
    }
    for (i = 0; i < 8; i++) {
        if (c[i].x < c[i].w) {
            break;
        }
    }
    if (i == 8) {
        return true;
    }

    for (i = 0; i < 8; i++) {
        if (c[i].y > -c[i].w) {
            break;
        }
    }
    if (i == 8) {
        return true;
    }
    for (i = 0; i < 8; i++) {
        if (c[i].y < c[i].w) {
            break;
        }
    }
    if (i == 8) {
        return true;
    }
    for (i = 0; i < 8; i++) {
        if (c[i].z > -c[i].w) {
            break;
        }
    }
    if (i == 8) {
        return true;
    }
    for (i = 0; i < 8; i++) {
        if (c[i].z < c[i].w) {
            break;
        }
    }
    return i == 8;
}






template <typename T>
inline T clamp(T v, T lo, T hi) {
  return std::max<T>(lo, std::min<T>(hi, v));
}

#define strcpy_s(dest, source) strncpy((dest), (source), sizeof(dest))

namespace Side {
    const int LEFT = 0;
    const int RIGHT = 1;
    const int COUNT = 2;
}  // namespace Side

#define OVRMath_sprintf snprintf

//-------------------------------------------------------------------------------------
// ***** OVR_MATH_DEBUG_BREAK
//
// Independent debug break implementation for OVR_Math.h.

#if !defined(OVR_MATH_DEBUG_BREAK)
#if defined(_DEBUG)
#if defined(_MSC_VER)
#define OVR_MATH_DEBUG_BREAK __debugbreak()
#else
#define OVR_MATH_DEBUG_BREAK __builtin_trap()
#endif
#else
#define OVR_MATH_DEBUG_BREAK ((void)0)
#endif
#endif

//-------------------------------------------------------------------------------------
// ***** OVR_MATH_ASSERT
//
// Independent OVR_MATH_ASSERT implementation for OVR_Math.h.

#if !defined(OVR_MATH_ASSERT)
#if defined(OVR_GTEST)
#define OVR_MATH_ASSERT(p) EXPECT_TRUE((p));
#elif defined(_DEBUG)
#define OVR_MATH_ASSERT(p)    \
    if (!(p)) {               \
        OVR_MATH_DEBUG_BREAK; \
    }
#else
#define OVR_MATH_ASSERT(p) ((void)0)
#endif
#endif

//-------------------------------------------------------------------------------------
// ***** OVR_MATH_STATIC_ASSERT
//
// Independent OVR_MATH_ASSERT implementation for OVR_Math.h.

#if !defined(OVR_MATH_STATIC_ASSERT)
#if defined(__cplusplus) &&                                                                       \
    ((defined(_MSC_VER) && (defined(_MSC_VER) >= 1600)) || defined(__GXX_EXPERIMENTAL_CXX0X__) || \
     (__cplusplus >= 201103L))
#define OVR_MATH_STATIC_ASSERT static_assert
#else
#if !defined(OVR_SA_UNUSED)
#if defined(__GNUC__) || defined(__clang__)
#define OVR_SA_UNUSED __attribute__((unused))
#else
#define OVR_SA_UNUSED
#endif
#define OVR_SA_PASTE(a, b) a##b
#define OVR_SA_HELP(a, b) OVR_SA_PASTE(a, b)
#endif

#define OVR_MATH_STATIC_ASSERT(expression, msg) \
    typedef char OVR_SA_HELP(                   \
        compileTimeAssert, __LINE__)[((expression) != 0) ? 1 : -1] OVR_SA_UNUSED
#endif
#endif

//-------------------------------------------------------------------------------------
// ***** OVR_MATH_UNUSED
//
// Independent OVR_MATH_UNUSED implementation for OVR_Math.h.

#if defined(__GNUC__) || defined(__clang__)
#define OVR_MATH_UNUSED(a)                                 \
    do {                                                   \
        __typeof__(&a) __attribute__((unused)) __tmp = &a; \
    } while (0)
#else
#define OVR_MATH_UNUSED(a) (a)
#endif

namespace OVR {

  template <class T>
  const T OVRMath_Min(const T a, const T b) {
    return (a < b) ? a : b;
  }

  template <class T>
  const T OVRMath_Max(const T a, const T b) {
    return (b < a) ? a : b;
  }

  template <class T>
  const T OVRMath_Clamp(const T v, const T minVal, const T maxVal) {
    return OVRMath_Max<T>(minVal, OVRMath_Min<T>(v, maxVal));
  }

  template <class T>
  void OVRMath_Swap(T& a, T& b) {
    T temp(a);
    a = b;
    b = temp;
  }

  template <typename T, typename F = float>
  T OVRMath_Lerp(T a, T b, F f) {
    return (b - a) * f + a;
  }

  template <typename T, typename F = float>
  F OVRMath_InvLerp(T a, T b, T c) {
    return (c - a) / (b - a);
  }

  //-------------------------------------------------------------------------------------
  // ***** Constants for 3D world/axis definitions.

  // Definitions of axes for coordinate and rotation conversions.
  enum Axis { Axis_X = 0, Axis_Y = 1, Axis_Z = 2 };

  // RotateDirection describes the rotation direction around an axis, interpreted as follows:
  //  CW  - Clockwise while looking "down" from positive axis towards the origin.
  //  CCW - Counter-clockwise while looking from the positive axis towards the origin,
  //        which is in the negative axis direction.
  //  CCW is the default for the RHS coordinate system. Oculus standard RHS coordinate
  //  system defines Y up, X right, and Z back (pointing out from the screen). In this
  //  system Rotate_CCW around Z will specifies counter-clockwise rotation in XY plane.
  enum RotateDirection { Rotate_CCW = 1, Rotate_CW = -1 };

  // Constants for right handed and left handed coordinate systems
  enum HandedSystem { Handed_R = 1, Handed_L = -1 };

  // AxisDirection describes which way the coordinate axis points. Used by WorldAxes.
  enum AxisDirection {
    Axis_Up = 2,
    Axis_Down = -2,
    Axis_Right = 1,
    Axis_Left = -1,
    Axis_In = 3,
    Axis_Out = -3
  };

  struct WorldAxes {
    AxisDirection XAxis, YAxis, ZAxis;

    WorldAxes(AxisDirection x, AxisDirection y, AxisDirection z) : XAxis(x), YAxis(y), ZAxis(z) {
      OVR_MATH_ASSERT(abs(x) != abs(y) && abs(y) != abs(z) && abs(z) != abs(x));
    }
  };

} // namespace OVR

//------------------------------------------------------------------------------------//
// ***** C Compatibility Types

// These declarations are used to support conversion between C types used in
// LibOVR C interfaces and their C++ versions. As an example, they allow passing
// Vector3f into a function that expects ovrVector3f.

typedef struct ovrQuatf_ ovrQuatf;
typedef struct ovrQuatd_ ovrQuatd;
typedef struct ovrSizei_ ovrSizei;
typedef struct ovrSizef_ ovrSizef;
typedef struct ovrSized_ ovrSized;
typedef struct ovrRecti_ ovrRecti;
typedef struct ovrRectf_ ovrRectf;
typedef struct ovrVector2i_ ovrVector2i;
typedef struct ovrVector2f_ ovrVector2f;
typedef struct ovrVector2d_ ovrVector2d;
typedef struct ovrVector3f_ ovrVector3f;
typedef struct ovrVector3d_ ovrVector3d;
typedef struct ovrVector4f_ ovrVector4f;
typedef struct ovrVector4d_ ovrVector4d;
typedef struct ovrVector4s_ ovrVector4s;
typedef struct ovrMatrix2f_ ovrMatrix2f;
typedef struct ovrMatrix2d_ ovrMatrix2d;
typedef struct ovrMatrix3f_ ovrMatrix3f;
typedef struct ovrMatrix3d_ ovrMatrix3d;
typedef struct ovrMatrix4f_ ovrMatrix4f;
typedef struct ovrMatrix4d_ ovrMatrix4d;
typedef struct ovrPosef_ ovrPosef;
typedef struct ovrPosed_ ovrPosed;
typedef struct ovrPoseStatef_ ovrPoseStatef;
typedef struct ovrPoseStated_ ovrPoseStated;

namespace OVR {

  // Forward-declare our templates.
  template <class T>
  class Quat;
  template <class T>
  class Size;
  template <class T>
  class Rect;
  template <class T>
  class Vector2;
  template <class T>
  class Vector3;
  template <class T>
  class Vector4;
  template <class T>
  class Matrix2;
  template <class T>
  class Matrix3;
  template <class T>
  class Matrix4;
  template <class T>
  class Pose;
  template <class T>
  class PoseState;

  // CompatibleTypes::Type is used to lookup a compatible C-version of a C++ class.
  template <class C>
  struct CompatibleTypes {
    // Declaration here seems necessary for MSVC; specializations are
    // used instead.
    typedef struct {
    } Type;
  };

  // Specializations providing CompatibleTypes::Type value.
  template <>
  struct CompatibleTypes<Quat<float>> {
    typedef ovrQuatf Type;
  };
  template <>
  struct CompatibleTypes<Quat<double>> {
    typedef ovrQuatd Type;
  };
  template <>
  struct CompatibleTypes<Matrix2<float>> {
    typedef ovrMatrix2f Type;
  };
  template <>
  struct CompatibleTypes<Matrix2<double>> {
    typedef ovrMatrix2d Type;
  };
  template <>
  struct CompatibleTypes<Matrix3<float>> {
    typedef ovrMatrix3f Type;
  };
  template <>
  struct CompatibleTypes<Matrix3<double>> {
    typedef ovrMatrix3d Type;
  };
  template <>
  struct CompatibleTypes<Matrix4<float>> {
    typedef ovrMatrix4f Type;
  };
  template <>
  struct CompatibleTypes<Matrix4<double>> {
    typedef ovrMatrix4d Type;
  };
  template <>
  struct CompatibleTypes<Size<int>> {
    typedef ovrSizei Type;
  };
  template <>
  struct CompatibleTypes<Size<float>> {
    typedef ovrSizef Type;
  };
  template <>
  struct CompatibleTypes<Size<double>> {
    typedef ovrSized Type;
  };
  template <>
  struct CompatibleTypes<Rect<int>> {
    typedef ovrRecti Type;
  };
  template <>
  struct CompatibleTypes<Rect<float>> {
    typedef ovrRectf Type;
  };
  template <>
  struct CompatibleTypes<Vector2<int>> {
    typedef ovrVector2i Type;
  };
  template <>
  struct CompatibleTypes<Vector2<float>> {
    typedef ovrVector2f Type;
  };
  template <>
  struct CompatibleTypes<Vector2<double>> {
    typedef ovrVector2d Type;
  };
  template <>
  struct CompatibleTypes<Vector3<float>> {
    typedef ovrVector3f Type;
  };
  template <>
  struct CompatibleTypes<Vector3<double>> {
    typedef ovrVector3d Type;
  };
  template <>
  struct CompatibleTypes<Vector4<float>> {
    typedef ovrVector4f Type;
  };
  template <>
  struct CompatibleTypes<Vector4<double>> {
    typedef ovrVector4d Type;
  };
  template <>
  struct CompatibleTypes<Vector4<std::int16_t>> {
    typedef ovrVector4s Type;
  };
  template <>
  struct CompatibleTypes<Pose<float>> {
    typedef ovrPosef Type;
  };
  template <>
  struct CompatibleTypes<Pose<double>> {
    typedef ovrPosed Type;
  };

  //------------------------------------------------------------------------------------//
  // ***** Math
  //
  // Math class contains constants and functions. This class is a template specialized
  // per type, with Math<float> and Math<double> being distinct.
  template <class T>
  class Math {
  public:
    // By default, support explicit conversion to float. This allows Vector2<int> to
    // compile, for example.
    typedef float OtherFloatType;

    static int Tolerance() {
      return 0;
    } // Default value so integer types compile
  };

  //------------------------------------------------------------------------------------//
  // ***** double constants
#define MATH_DOUBLE_PI 3.14159265358979323846
#define MATH_DOUBLE_TWOPI (2 * MATH_DOUBLE_PI)
#define MATH_DOUBLE_PIOVER2 (0.5 * MATH_DOUBLE_PI)
#define MATH_DOUBLE_PIOVER4 (0.25 * MATH_DOUBLE_PI)
#define MATH_FLOAT_MAXVALUE (FLT_MAX)

#define MATH_DOUBLE_RADTODEGREEFACTOR (360.0 / MATH_DOUBLE_TWOPI)
#define MATH_DOUBLE_DEGREETORADFACTOR (MATH_DOUBLE_TWOPI / 360.0)

#define MATH_DOUBLE_E 2.71828182845904523536
#define MATH_DOUBLE_LOG2E 1.44269504088896340736
#define MATH_DOUBLE_LOG10E 0.434294481903251827651
#define MATH_DOUBLE_LN2 0.693147180559945309417
#define MATH_DOUBLE_LN10 2.30258509299404568402

#define MATH_DOUBLE_SQRT2 1.41421356237309504880
#define MATH_DOUBLE_SQRT1_2 0.707106781186547524401

#define MATH_DOUBLE_TOLERANCE \
    1e-12 // a default number for value equality tolerance: about 4500*Epsilon;
#define MATH_DOUBLE_SINGULARITYRADIUS \
    1e-12 // about 1-cos(.0001 degree), for gimbal lock numerical problems

#define MATH_DOUBLE_SMALLEST_NON_DENORMAL 2.2250738585072014e-308 // ( 1ULL << 52 )
#define MATH_DOUBLE_HUGE_NUMBER \
    1.3407807929942596e+154 // ( ( ( 1023ULL * 3 / 2 ) << 52 ) | ( ( 1 << 52 ) - 1 ) )

//------------------------------------------------------------------------------------//
// ***** float constants
#define MATH_FLOAT_PI float(MATH_DOUBLE_PI)
#define MATH_FLOAT_TWOPI float(MATH_DOUBLE_TWOPI)
#define MATH_FLOAT_PIOVER2 float(MATH_DOUBLE_PIOVER2)
#define MATH_FLOAT_PIOVER4 float(MATH_DOUBLE_PIOVER4)

#define MATH_FLOAT_RADTODEGREEFACTOR float(MATH_DOUBLE_RADTODEGREEFACTOR)
#define MATH_FLOAT_DEGREETORADFACTOR float(MATH_DOUBLE_DEGREETORADFACTOR)

#define MATH_FLOAT_E float(MATH_DOUBLE_E)
#define MATH_FLOAT_LOG2E float(MATH_DOUBLE_LOG2E)
#define MATH_FLOAT_LOG10E float(MATH_DOUBLE_LOG10E)
#define MATH_FLOAT_LN2 float(MATH_DOUBLE_LN2)
#define MATH_FLOAT_LN10 float(MATH_DOUBLE_LN10)

#define MATH_FLOAT_SQRT2 float(MATH_DOUBLE_SQRT2)
#define MATH_FLOAT_SQRT1_2 float(MATH_DOUBLE_SQRT1_2)

#define MATH_FLOAT_TOLERANCE \
    1e-5f // a default number for value equality tolerance: 1e-5, about 84*EPSILON;
#define MATH_FLOAT_SINGULARITYRADIUS \
    1e-7f // about 1-cos(.025 degree), for gimbal lock numerical problems

#define MATH_FLOAT_SMALLEST_NON_DENORMAL 1.1754943508222875e-038f // ( 1U << 23 )
#define MATH_FLOAT_HUGE_NUMBER \
    1.8446742974197924e+019f // ( ( ( 127U * 3 / 2 ) << 23 ) | ( ( 1 << 23 ) - 1 ) )

// Single-precision Math constants class.
  template <>
  class Math<float> {
  public:
    typedef double OtherFloatType;

    static inline constexpr float SmallestNonDenormal() {
      return MATH_FLOAT_SMALLEST_NON_DENORMAL;
    }
    static inline constexpr float HugeNumber() {
      return MATH_FLOAT_HUGE_NUMBER;
    }
    static inline constexpr float MaxValue() {
      return FLT_MAX;
    }
    static inline constexpr float Tolerance() {
      return MATH_FLOAT_TOLERANCE;
    } // a default number for value equality tolerance
    static inline constexpr float SingularityRadius() {
      return MATH_FLOAT_SINGULARITYRADIUS;
    } // for gimbal lock numerical problems
  };

  // Double-precision Math constants class
  template <>
  class Math<double> {
  public:
    typedef float OtherFloatType;

    static inline constexpr double SmallestNonDenormal() {
      return MATH_DOUBLE_SMALLEST_NON_DENORMAL;
    }
    static inline constexpr double HugeNumber() {
      return MATH_DOUBLE_HUGE_NUMBER;
    }
    static inline constexpr double Tolerance() {
      return MATH_DOUBLE_TOLERANCE;
    } // a default number for value equality tolerance
    static inline constexpr double SingularityRadius() {
      return MATH_DOUBLE_SINGULARITYRADIUS;
    } // for gimbal lock numerical problems
  };

  typedef Math<float> Mathf;
  typedef Math<double> Mathd;

  // Conversion functions between degrees and radians
  // (non-templated to ensure passing int arguments causes warning)
  inline float RadToDegree(float rad) {
    return rad * MATH_FLOAT_RADTODEGREEFACTOR;
  }
  inline double RadToDegree(double rad) {
    return rad * MATH_DOUBLE_RADTODEGREEFACTOR;
  }

  inline float DegreeToRad(float deg) {
    return deg * MATH_FLOAT_DEGREETORADFACTOR;
  }
  inline double DegreeToRad(double deg) {
    return deg * MATH_DOUBLE_DEGREETORADFACTOR;
  }

  // Square function
  template <class T>
  inline T Sqr(T x) {
    return x * x;
  }

  // MERGE_MOBILE_SDK
  // Safe reciprocal square root.
  template <class T>
  T RcpSqrt(const T f) {
    return (f >= Math<T>::SmallestNonDenormal()) ? static_cast<T>(1.0 / sqrt(f))
      : Math<T>::HugeNumber();
  }
  // MERGE_MOBILE_SDK

  // Sign: returns 0 if x == 0, -1 if x < 0, and 1 if x > 0
  template <class T>
  inline T Sign(T x) {
    return (x != T(0)) ? (x < T(0) ? T(-1) : T(1)) : T(0);
  }

  // Numerically stable acos function
  inline float Acos(float x) {
    return (x > 1.0f) ? 0.0f : (x < -1.0f) ? MATH_FLOAT_PI : acosf(x);
  }
  inline double Acos(double x) {
    return (x > 1.0) ? 0.0 : (x < -1.0) ? MATH_DOUBLE_PI : acos(x);
  }
  template <class T>
  T Acos(T x) {
    return static_cast<T>(Acos(static_cast<double>(x)));
  }

  // Numerically stable asin function
  inline float Asin(float x) {
    return (x > 1.0f) ? MATH_FLOAT_PIOVER2 : (x < -1.0f) ? -MATH_FLOAT_PIOVER2 : asinf(x);
  }
  inline double Asin(double x) {
    return (x > 1.0) ? MATH_DOUBLE_PIOVER2 : (x < -1.0) ? -MATH_DOUBLE_PIOVER2 : asin(x);
  }
  template <class T>
  T Asin(T x) {
    return static_cast<T>(Asin(static_cast<double>(x)));
  }

  inline float Atan2(float o, float a) {
    return atan2f(o, a);
  }
  inline double Atan2(double o, double a) {
    return atan2(o, a);
  }
  template <class T>
  T Atan2(T o, T a) {
    return static_cast<T>(Atan2(static_cast<double>(o), static_cast<double>(a)));
  }

#if defined(_MSC_VER)
  inline int isnan(double x) {
    return ::_isnan(x);
  }
#endif

  template <class T>
  class Quat;

  //-------------------------------------------------------------------------------------
  // ***** Vector2<>

  // Vector2f (Vector2d) represents a 2-dimensional vector or point in space,
  // consisting of coordinates x and y

  template <class T>
  class Vector2 {
  public:
    typedef T ElementType;
    static const size_t ElementCount = 2;

    T x, y;

    Vector2() : x(0), y(0) {}
    Vector2(T x_, T y_) : x(x_), y(y_) {}
    explicit Vector2(T s) : x(s), y(s) {}
    explicit Vector2(const Vector2<typename Math<T>::OtherFloatType>& src)
      : x((T)src.x), y((T)src.y) {}

    static const Vector2 ZERO;

    // C-interop support.
    typedef typename CompatibleTypes<Vector2<T>>::Type CompatibleType;

    Vector2(const CompatibleType& s) : x(s.x), y(s.y) {}
    Vector2(const CompatibleType&& s) : x(s.x), y(s.y) {}

    operator const CompatibleType& () const {
      OVR_MATH_STATIC_ASSERT(
        sizeof(Vector2<T>) == sizeof(CompatibleType), "sizeof(Vector2<T>) failure");
      return reinterpret_cast<const CompatibleType&>(*this);
    }

    bool operator==(const Vector2& b) const {
      return x == b.x && y == b.y;
    }
    bool operator!=(const Vector2& b) const {
      return x != b.x || y != b.y;
    }

    Vector2 operator+(const Vector2& b) const {
      return Vector2(x + b.x, y + b.y);
    }
    Vector2& operator+=(const Vector2& b) {
      x += b.x;
      y += b.y;
      return *this;
    }
    Vector2 operator-(const Vector2& b) const {
      return Vector2(x - b.x, y - b.y);
    }
    Vector2& operator-=(const Vector2& b) {
      x -= b.x;
      y -= b.y;
      return *this;
    }
    Vector2 operator-() const {
      return Vector2(-x, -y);
    }

    // Scalar multiplication/division scales vector.
    Vector2 operator*(T s) const {
      return Vector2(x * s, y * s);
    }
    Vector2& operator*=(T s) {
      x *= s;
      y *= s;
      return *this;
    }

    Vector2 operator/(T s) const {
      T rcp = T(1) / s;
      return Vector2(x * rcp, y * rcp);
    }
    Vector2& operator/=(T s) {
      T rcp = T(1) / s;
      x *= rcp;
      y *= rcp;
      return *this;
    }

    static Vector2 Min(const Vector2& a, const Vector2& b) {
      return Vector2((a.x < b.x) ? a.x : b.x, (a.y < b.y) ? a.y : b.y);
    }
    static Vector2 Max(const Vector2& a, const Vector2& b) {
      return Vector2((a.x > b.x) ? a.x : b.x, (a.y > b.y) ? a.y : b.y);
    }

    Vector2 Clamped(T maxMag) const {
      T magSquared = LengthSq();
      if (magSquared <= Sqr(maxMag))
        return *this;
      else
        return *this * (maxMag / sqrt(magSquared));
    }

    // Compare two vectors for equality with tolerance. Returns true if vectors match within
    // tolerance.
    bool IsEqual(const Vector2& b, T tolerance = Math<T>::Tolerance()) const {
      return (fabs(b.x - x) <= tolerance) && (fabs(b.y - y) <= tolerance);
    }
    bool Compare(const Vector2& b, T tolerance = Math<T>::Tolerance()) const {
      return IsEqual(b, tolerance);
    }

    // Access element by index
    T& operator[](int idx) {
      OVR_MATH_ASSERT(0 <= idx && idx < 2);
      return *(&x + idx);
    }
    const T& operator[](int idx) const {
      OVR_MATH_ASSERT(0 <= idx && idx < 2);
      return *(&x + idx);
    }

    // Entry-wise product of two vectors
    Vector2 EntrywiseMultiply(const Vector2& b) const {
      return Vector2(x * b.x, y * b.y);
    }

    // Multiply and divide operators do entry-wise math. Used Dot() for dot product.
    Vector2 operator*(const Vector2& b) const {
      return Vector2(x * b.x, y * b.y);
    }
    Vector2 operator/(const Vector2& b) const {
      return Vector2(x / b.x, y / b.y);
    }

    // Dot product
    // Used to calculate angle q between two vectors among other things,
    // as (A dot B) = |a||b|cos(q).
    T Dot(const Vector2& b) const {
      return x * b.x + y * b.y;
    }

    // Returns the angle from this vector to b, in radians.
    T Angle(const Vector2& b) const {
      T div = LengthSq() * b.LengthSq();
      OVR_MATH_ASSERT(div != T(0));
      T result = static_cast<T>(Acos((this->Dot(b)) / sqrt(div)));
      return result;
    }

    // Return Length of the vector squared.
    T LengthSq() const {
      return (x * x + y * y);
    }

    // Return vector length.
    T Length() const {
      return static_cast<T>(sqrt(LengthSq()));
    }

    // Returns squared distance between two points represented by vectors.
    T DistanceSq(const Vector2& b) const {
      return (*this - b).LengthSq();
    }

    // Returns distance between two points represented by vectors.
    T Distance(const Vector2& b) const {
      return (*this - b).Length();
    }

    // Determine if this a unit vector.
    bool IsNormalized() const {
      return fabs(LengthSq() - T(1)) < Math<T>::Tolerance();
    }

    // Normalize, convention vector length to 1.
    void Normalize() {
      T s = Length();
      if (s != T(0))
        s = T(1) / s;
      *this *= s;
    }

    // Returns normalized (unit) version of the vector without modifying itself.
    Vector2 Normalized() const {
      T s = Length();
      if (s != T(0))
        s = T(1) / s;
      return *this * s;
    }

    // Linearly interpolates from this vector to another.
    // Factor should be between 0.0 and 1.0, with 0 giving full value to this.
    Vector2 Lerp(const Vector2& b, T f) const {
      return *this * (T(1) - f) + b * f;
    }

    // Projects this vector onto the argument; in other words,
    // A.Project(B) returns projection of vector A onto B.
    Vector2 ProjectTo(const Vector2& b) const {
      T l2 = b.LengthSq();
      OVR_MATH_ASSERT(l2 != T(0));
      return b * (Dot(b) / l2);
    }

    // returns true if vector b is clockwise from this vector
    bool IsClockwise(const Vector2& b) const {
      return (x * b.y - y * b.x) < 0;
    }
  };

  // Implicit instantiation
  template <typename T>
  const Vector2<T> Vector2<T>::ZERO;

  typedef Vector2<float> Vector2f;
  typedef Vector2<double> Vector2d;
  typedef Vector2<int> Vector2i;

  typedef Vector2<float> Point2f;
  typedef Vector2<double> Point2d;
  typedef Vector2<int> Point2i;

  //-------------------------------------------------------------------------------------
  // ***** Vector3<> - 3D vector of {x, y, z}

  //
  // Vector3f (Vector3d) represents a 3-dimensional vector or point in space,
  // consisting of coordinates x, y and z.

  template <class T>
  class Vector3 {
  public:
    typedef T ElementType;
    static const size_t ElementCount = 3;

    T x, y, z;

    // FIXME: default initialization of a vector class can be very expensive in a full-blown
    // application.  A few hundred thousand vector constructions is not unlikely and can add
    // up to milliseconds of time on processors like the PS3 PPU.
    constexpr Vector3() : x(0), y(0), z(0) {}
    constexpr Vector3(T x_, T y_, T z_ = 0) : x(x_), y(y_), z(z_) {}
    constexpr explicit Vector3(T s) : x(s), y(s), z(s) {}
    constexpr explicit Vector3(const Vector3<typename Math<T>::OtherFloatType>& src)
      : x((T)src.x), y((T)src.y), z((T)src.z) {}
    // MERGE_MOBILE_SDK
    constexpr Vector3(const Vector2<T>& xy, const T z_) : x(xy.x), y(xy.y), z(z_) {}
    // MERGE_MOBILE_SDK

    static const Vector3 ZERO;

    static Vector3 Zero() {
      return Vector3(0, 0, 0);
    }

    // C-interop support.
    typedef typename CompatibleTypes<Vector3<T>>::Type CompatibleType;

    Vector3(const CompatibleType& s) : x(s.x), y(s.y), z(s.z) {}
    Vector3(const CompatibleType&& s) : x(s.x), y(s.y), z(s.z) {}

    operator const CompatibleType& () const {
      OVR_MATH_STATIC_ASSERT(
        sizeof(Vector3<T>) == sizeof(CompatibleType), "sizeof(Vector3<T>) failure");
      return reinterpret_cast<const CompatibleType&>(*this);
    }

    bool operator==(const Vector3& b) const {
      return x == b.x && y == b.y && z == b.z;
    }
    bool operator!=(const Vector3& b) const {
      return x != b.x || y != b.y || z != b.z;
    }

    Vector3 operator+(const Vector3& b) const {
      return Vector3(x + b.x, y + b.y, z + b.z);
    }
    Vector3& operator+=(const Vector3& b) {
      x += b.x;
      y += b.y;
      z += b.z;
      return *this;
    }
    Vector3 operator-(const Vector3& b) const {
      return Vector3(x - b.x, y - b.y, z - b.z);
    }
    Vector3& operator-=(const Vector3& b) {
      x -= b.x;
      y -= b.y;
      z -= b.z;
      return *this;
    }
    Vector3 operator-() const {
      return Vector3(-x, -y, -z);
    }

    // Scalar multiplication/division scales vector.
    Vector3 operator*(T s) const {
      return Vector3(x * s, y * s, z * s);
    }
    Vector3& operator*=(T s) {
      x *= s;
      y *= s;
      z *= s;
      return *this;
    }

    Vector3 operator/(T s) const {
      T rcp = T(1) / s;
      return Vector3(x * rcp, y * rcp, z * rcp);
    }
    Vector3& operator/=(T s) {
      T rcp = T(1) / s;
      x *= rcp;
      y *= rcp;
      z *= rcp;
      return *this;
    }

    static Vector3 Min(const Vector3& a, const Vector3& b) {
      return Vector3((a.x < b.x) ? a.x : b.x, (a.y < b.y) ? a.y : b.y, (a.z < b.z) ? a.z : b.z);
    }
    static Vector3 Max(const Vector3& a, const Vector3& b) {
      return Vector3((a.x > b.x) ? a.x : b.x, (a.y > b.y) ? a.y : b.y, (a.z > b.z) ? a.z : b.z);
    }

    Vector3 Clamped(T maxMag) const {
      T magSquared = LengthSq();
      if (magSquared <= Sqr(maxMag))
        return *this;
      else
        return *this * (maxMag / sqrt(magSquared));
    }

    // Compare two vectors for equality with tolerance. Returns true if vectors match within
    // tolerance.
    bool IsEqual(const Vector3& b, T tolerance = Math<T>::Tolerance()) const {
      return (fabs(b.x - x) <= tolerance) && (fabs(b.y - y) <= tolerance) &&
        (fabs(b.z - z) <= tolerance);
    }
    bool Compare(const Vector3& b, T tolerance = Math<T>::Tolerance()) const {
      return IsEqual(b, tolerance);
    }

    T& operator[](int idx) {
      OVR_MATH_ASSERT(0 <= idx && idx < 3);
      return *(&x + idx);
    }

    const T& operator[](int idx) const {
      OVR_MATH_ASSERT(0 <= idx && idx < 3);
      return *(&x + idx);
    }

    // Entrywise product of two vectors
    Vector3 EntrywiseMultiply(const Vector3& b) const {
      return Vector3(x * b.x, y * b.y, z * b.z);
    }

    // Multiply and divide operators do entry-wise math
    Vector3 operator*(const Vector3& b) const {
      return Vector3(x * b.x, y * b.y, z * b.z);
    }

    Vector3 operator/(const Vector3& b) const {
      return Vector3(x / b.x, y / b.y, z / b.z);
    }

    // Dot product
    // Used to calculate angle q between two vectors among other things,
    // as (A dot B) = |a||b|cos(q).
    T Dot(const Vector3& b) const {
      return x * b.x + y * b.y + z * b.z;
    }

    // Compute cross product, which generates a normal vector.
    // Direction vector can be determined by right-hand rule: Pointing index finder in
    // direction a and middle finger in direction b, thumb will point in a.Cross(b).
    Vector3 Cross(const Vector3& b) const {
      return Vector3(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x);
    }

    // Returns the angle from this vector to b, in radians.
    T Angle(const Vector3& b) const {
      T div = LengthSq() * b.LengthSq();
      OVR_MATH_ASSERT(div != T(0));
      T result = static_cast<T>(Acos((this->Dot(b)) / sqrt(div)));
      return result;
    }

    // Return Length of the vector squared.
    T LengthSq() const {
      return (x * x + y * y + z * z);
    }

    // Return vector length.
    T Length() const {
      return (T)sqrt(LengthSq());
    }

    // Returns squared distance between two points represented by vectors.
    T DistanceSq(Vector3 const& b) const {
      return (*this - b).LengthSq();
    }

    // Returns distance between two points represented by vectors.
    T Distance(Vector3 const& b) const {
      return (*this - b).Length();
    }

    bool IsNormalized() const {
      return fabs(LengthSq() - T(1)) < Math<T>::Tolerance();
    }

    // Normalize, convention vector length to 1.
    void Normalize() {
      T s = Length();
      if (s != T(0))
        s = T(1) / s;
      *this *= s;
    }

    // Returns normalized (unit) version of the vector without modifying itself.
    Vector3 Normalized() const {
      T s = Length();
      if (s != T(0))
        s = T(1) / s;
      return *this * s;
    }

    // Linearly interpolates from this vector to another.
    // Factor should be between 0.0 and 1.0, with 0 giving full value to this.
    Vector3 Lerp(const Vector3& b, T f) const {
      return *this * (T(1) - f) + b * f;
    }

    // Projects this vector onto the argument; in other words,
    // A.Project(B) returns projection of vector A onto B.
    Vector3 ProjectTo(const Vector3& b) const {
      T l2 = b.LengthSq();
      OVR_MATH_ASSERT(l2 != T(0));
      return b * (Dot(b) / l2);
    }

    // Projects this vector onto a plane defined by a normal vector
    Vector3 ProjectToPlane(const Vector3& normal) const {
      return *this - this->ProjectTo(normal);
    }

    bool IsNan() const {
      return isnan(x) || isnan(y) || isnan(z);
    }
  };

  // Implicit instantiation
  template <typename T>
  const Vector3<T> Vector3<T>::ZERO;

  // MERGE_MOBILE_SDK
  // allow multiplication in order scalar * vector (member operator handles vector * scalar)
  template <class T>
  Vector3<T> operator*(T s, const Vector3<T>& v) {
    return Vector3<T>(v.x * s, v.y * s, v.z * s);
  }
  // MERGE_MOBILE_SDK

  typedef Vector3<float> Vector3f;
  typedef Vector3<double> Vector3d;
  typedef Vector3<int32_t> Vector3i;

  OVR_MATH_STATIC_ASSERT((sizeof(Vector3f) == 3 * sizeof(float)), "sizeof(Vector3f) failure");
  OVR_MATH_STATIC_ASSERT((sizeof(Vector3d) == 3 * sizeof(double)), "sizeof(Vector3d) failure");
  OVR_MATH_STATIC_ASSERT((sizeof(Vector3i) == 3 * sizeof(int32_t)), "sizeof(Vector3i) failure");

  typedef Vector3<float> Point3f;
  typedef Vector3<double> Point3d;
  typedef Vector3<int32_t> Point3i;

  //-------------------------------------------------------------------------------------
  // ***** Vector4<> - 4D vector of {x, y, z, w}

  //
  // Vector4f (Vector4d) represents a 3-dimensional vector or point in space,
  // consisting of coordinates x, y, z and w.

  template <class T>
  class Vector4 {
  public:
    typedef T ElementType;
    static const size_t ElementCount = 4;

    T x, y, z, w;

    // FIXME: default initialization of a vector class can be very expensive in a full-blown
    // application.  A few hundred thousand vector constructions is not unlikely and can add
    // up to milliseconds of time on processors like the PS3 PPU.
    constexpr Vector4() : x(0), y(0), z(0), w(0) {}
    constexpr Vector4(T x_, T y_, T z_, T w_) : x(x_), y(y_), z(z_), w(w_) {}
    constexpr explicit Vector4(T s) : x(s), y(s), z(s), w(s) {}
    constexpr explicit Vector4(const Vector3<T>& v, const T w_ = T(1))
      : x(v.x), y(v.y), z(v.z), w(w_) {}
    constexpr explicit Vector4(const Vector4<typename Math<T>::OtherFloatType>& src)
      : x((T)src.x), y((T)src.y), z((T)src.z), w((T)src.w) {}

    static const Vector4 ZERO;

    // C-interop support.
    typedef typename CompatibleTypes<Vector4<T>>::Type CompatibleType;

    Vector4(const CompatibleType& s) : x(s.x), y(s.y), z(s.z), w(s.w) {}
    Vector4(const CompatibleType&& s) : x(s.x), y(s.y), z(s.z), w(s.w) {}

    operator const CompatibleType& () const {
      OVR_MATH_STATIC_ASSERT(
        sizeof(Vector4<T>) == sizeof(CompatibleType), "sizeof(Vector4<T>) failure");
      return reinterpret_cast<const CompatibleType&>(*this);
    }

    Vector4& operator=(const Vector3<T>& other) {
      x = other.x;
      y = other.y;
      z = other.z;
      w = 1;
      return *this;
    }
    bool operator==(const Vector4& b) const {
      return x == b.x && y == b.y && z == b.z && w == b.w;
    }
    bool operator!=(const Vector4& b) const {
      return x != b.x || y != b.y || z != b.z || w != b.w;
    }

    Vector4 operator+(const Vector4& b) const {
      return Vector4(x + b.x, y + b.y, z + b.z, w + b.w);
    }
    Vector4& operator+=(const Vector4& b) {
      x += b.x;
      y += b.y;
      z += b.z;
      w += b.w;
      return *this;
    }
    Vector4 operator-(const Vector4& b) const {
      return Vector4(x - b.x, y - b.y, z - b.z, w - b.w);
    }
    Vector4& operator-=(const Vector4& b) {
      x -= b.x;
      y -= b.y;
      z -= b.z;
      w -= b.w;
      return *this;
    }
    Vector4 operator-() const {
      return Vector4(-x, -y, -z, -w);
    }

    // Scalar multiplication/division scales vector.
    Vector4 operator*(T s) const {
      return Vector4(x * s, y * s, z * s, w * s);
    }
    Vector4& operator*=(T s) {
      x *= s;
      y *= s;
      z *= s;
      w *= s;
      return *this;
    }

    Vector4 operator/(T s) const {
      T rcp = T(1) / s;
      return Vector4(x * rcp, y * rcp, z * rcp, w * rcp);
    }
    Vector4& operator/=(T s) {
      T rcp = T(1) / s;
      x *= rcp;
      y *= rcp;
      z *= rcp;
      w *= rcp;
      return *this;
    }

    static Vector4 Min(const Vector4& a, const Vector4& b) {
      return Vector4(
        (a.x < b.x) ? a.x : b.x,
        (a.y < b.y) ? a.y : b.y,
        (a.z < b.z) ? a.z : b.z,
        (a.w < b.w) ? a.w : b.w);
    }
    static Vector4 Max(const Vector4& a, const Vector4& b) {
      return Vector4(
        (a.x > b.x) ? a.x : b.x,
        (a.y > b.y) ? a.y : b.y,
        (a.z > b.z) ? a.z : b.z,
        (a.w > b.w) ? a.w : b.w);
    }

    Vector4 Clamped(T maxMag) const {
      T magSquared = LengthSq();
      if (magSquared <= Sqr(maxMag))
        return *this;
      else
        return *this * (maxMag / sqrt(magSquared));
    }

    // Compare two vectors for equality with tolerance. Returns true if vectors match within
    // tolerance.
    bool IsEqual(const Vector4& b, T tolerance = Math<T>::Tolerance()) const {
      return (fabs(b.x - x) <= tolerance) && (fabs(b.y - y) <= tolerance) &&
        (fabs(b.z - z) <= tolerance) && (fabs(b.w - w) <= tolerance);
    }
    bool Compare(const Vector4& b, T tolerance = Math<T>::Tolerance()) const {
      return IsEqual(b, tolerance);
    }

    T& operator[](int idx) {
      OVR_MATH_ASSERT(0 <= idx && idx < 4);
      return *(&x + idx);
    }

    const T& operator[](int idx) const {
      OVR_MATH_ASSERT(0 <= idx && idx < 4);
      return *(&x + idx);
    }

    // Entry wise product of two vectors
    Vector4 EntrywiseMultiply(const Vector4& b) const {
      return Vector4(x * b.x, y * b.y, z * b.z, w * b.w);
    }

    // Multiply and divide operators do entry-wise math
    Vector4 operator*(const Vector4& b) const {
      return Vector4(x * b.x, y * b.y, z * b.z, w * b.w);
    }

    Vector4 operator/(const Vector4& b) const {
      return Vector4(x / b.x, y / b.y, z / b.z, w / b.w);
    }

    // Dot product
    T Dot(const Vector4& b) const {
      return x * b.x + y * b.y + z * b.z + w * b.w;
    }

    // Return Length of the vector squared.
    T LengthSq() const {
      return (x * x + y * y + z * z + w * w);
    }

    // Return vector length.
    T Length() const {
      return sqrt(LengthSq());
    }

    bool IsNormalized() const {
      return fabs(LengthSq() - T(1)) < Math<T>::Tolerance();
    }

    // Normalize, convention vector length to 1.
    void Normalize() {
      T s = Length();
      if (s != T(0))
        s = T(1) / s;
      *this *= s;
    }

    // Returns normalized (unit) version of the vector without modifying itself.
    Vector4 Normalized() const {
      T s = Length();
      if (s != T(0))
        s = T(1) / s;
      return *this * s;
    }

    // Linearly interpolates from this vector to another.
    // Factor should be between 0.0 and 1.0, with 0 giving full value to this.
    Vector4 Lerp(const Vector4& b, T f) const {
      return *this * (T(1) - f) + b * f;
    }
  };

  // Implicit instantiation
  template <typename T>
  const Vector4<T> Vector4<T>::ZERO;

  typedef Vector4<float> Vector4f;
  typedef Vector4<double> Vector4d;
  typedef Vector4<int> Vector4i;
  typedef Vector4<std::int16_t> Vector4s;

  //-------------------------------------------------------------------------------------
  // ***** Bounds3

  // Bounds class used to describe a 3D axis aligned bounding box.

  template <class T>
  class Bounds3 {
  public:
    // MERGE_MOBILE_SDK
    enum InitType { Init };
    // MERGE_MOBILE_SDK

    Vector3<T> b[2];

    Bounds3() {}

    // MERGE_MOBILE_SDK
    Bounds3(const InitType init) {
      OVR_MATH_UNUSED(init);
      Clear();
    }
    // MERGE_MOBILE_SDK

    Bounds3(const Vector3<T>& mins, const Vector3<T>& maxs) {
      b[0] = mins;
      b[1] = maxs;
    }

    // MERGE_MOBILE_SDK
    Bounds3(const T minx, const T miny, const T minz, const T maxx, const T maxy, const T maxz) {
      b[0].x = minx;
      b[0].y = miny;
      b[0].z = minz;

      b[1].x = maxx;
      b[1].y = maxy;
      b[1].z = maxz;
    }

    Bounds3 operator*(float const s) const {
      return Bounds3<T>(b[0].x * s, b[0].y * s, b[0].z * s, b[1].x * s, b[1].y * s, b[1].z * s);
    }

    Bounds3 operator*(Vector3<T> const& s) const {
      return Bounds3<T>(
        b[0].x * s.x, b[0].y * s.y, b[0].z * s.z, b[1].x * s.x, b[1].y * s.y, b[1].z * s.z);
    }
    // MERGE_MOBILE_SDK

    void Clear() {
      b[0].x = b[0].y = b[0].z = Math<T>::MaxValue();
      b[1].x = b[1].y = b[1].z = -Math<T>::MaxValue();
    }

    void AddPoint(const Vector3<T>& v) {
      b[0].x = OVRMath_Min(b[0].x, v.x);
      b[0].y = OVRMath_Min(b[0].y, v.y);
      b[0].z = OVRMath_Min(b[0].z, v.z);
      b[1].x = OVRMath_Max(b[1].x, v.x);
      b[1].y = OVRMath_Max(b[1].y, v.y);
      b[1].z = OVRMath_Max(b[1].z, v.z);
    }

    // MERGE_MOBILE_SDK
    // return a bounds representing the union of a and b
    static Bounds3 Union(const Bounds3& a, const Bounds3& b) {
      return Bounds3(
        OVRMath_Min(a.b[0].x, b.b[0].x),
        OVRMath_Min(a.b[0].y, b.b[0].y),
        OVRMath_Min(a.b[0].z, b.b[0].z),
        OVRMath_Max(a.b[1].x, b.b[1].x),
        OVRMath_Max(a.b[1].y, b.b[1].y),
        OVRMath_Max(a.b[1].z, b.b[1].z));
    }
    // MERGE_MOBILE_SDK

    const Vector3<T>& GetMins() const {
      return b[0];
    }
    const Vector3<T>& GetMaxs() const {
      return b[1];
    }

    Vector3<T>& GetMins() {
      return b[0];
    }
    Vector3<T>& GetMaxs() {
      return b[1];
    }

    // MERGE_MOBILE_SDK
    Vector3<T> GetSize() const {
      return Vector3f(b[1].x - b[0].x, b[1].y - b[0].y, b[1].z - b[0].z);
    }

    Vector3<T> GetCenter() const {
      return Vector3f(
        (b[0].x + b[1].x) * 0.5f, (b[0].y + b[1].y) * 0.5f, (b[0].z + b[1].z) * 0.5f);
    }

    void Translate(const Vector3<T>& t) {
      b[0] += t;
      b[1] += t;
    }

    bool IsInverted() const {
      return b[0].x > b[1].x && b[0].y > b[1].y && b[0].z > b[1].z;
    }

    bool Contains(const Vector3<T>& point, T expand = 0) const {
      return point.x >= b[0].x - expand && point.y >= b[0].y - expand &&
        point.z >= b[0].z - expand && point.x <= b[1].x + expand &&
        point.y <= b[1].y + expand && point.z <= b[1].z + expand;
    }

    // returns the axial aligned bounds of inputBounds after transformation by pose
    static Bounds3 Transform(const Pose<T>& pose, const Bounds3<T>& inBounds) {
      const Matrix3<T> rotation(pose.Rotation);
      const Vector3<T> center = (inBounds.b[0] + inBounds.b[1]) * 0.5f;
      const Vector3<T> extents = inBounds.b[1] - center;
      const Vector3<T> newCenter = pose.Translation + rotation * center;
      const Vector3<T> newExtents(
        static_cast<T>(
          fabs(extents[0] * rotation.M[0][0]) + fabs(extents[1] * rotation.M[0][1]) +
          fabs(extents[2] * rotation.M[0][2])),
        static_cast<T>(
          fabs(extents[0] * rotation.M[1][0]) + fabs(extents[1] * rotation.M[1][1]) +
          fabs(extents[2] * rotation.M[1][2])),
        static_cast<T>(
          fabs(extents[0] * rotation.M[2][0]) + fabs(extents[1] * rotation.M[2][1]) +
          fabs(extents[2] * rotation.M[2][2])));
      return Bounds3<T>(newCenter - newExtents, newCenter + newExtents);
    }

    static Bounds3 Transform(const Matrix4<T>& matrix, const Bounds3<T>& inBounds) {
      const Vector3<T> center = (inBounds.b[0] + inBounds.b[1]) * 0.5f;
      const Vector3<T> extents = inBounds.b[1] - center;
      const Vector3<T> newCenter = matrix.Transform(center);
      const Vector3<T> newExtents(
        static_cast<T>(
          fabs(extents[0] * matrix.M[0][0]) + fabs(extents[1] * matrix.M[0][1]) +
          fabs(extents[2] * matrix.M[0][2])),
        static_cast<T>(
          fabs(extents[0] * matrix.M[1][0]) + fabs(extents[1] * matrix.M[1][1]) +
          fabs(extents[2] * matrix.M[1][2])),
        static_cast<T>(
          fabs(extents[0] * matrix.M[2][0]) + fabs(extents[1] * matrix.M[2][1]) +
          fabs(extents[2] * matrix.M[2][2])));
      return Bounds3<T>(newCenter - newExtents, newCenter + newExtents);
    }

    static Bounds3<T>
      Expand(const Bounds3<T>& b, const Vector3<T>& minExpand, const Vector3<T>& maxExpand) {
      return Bounds3<T>(b.GetMins() + minExpand, b.GetMaxs() + maxExpand);
    }
    // MERGE_MOBILE_SDK
  };

  typedef Bounds3<float> Bounds3f;
  typedef Bounds3<double> Bounds3d;

  //-------------------------------------------------------------------------------------
  // ***** Size

  // Size class represents 2D size with Width, Height components.
  // Used to describe distentions of render targets, etc.

  template <class T>
  class Size {
  public:
    T w, h;

    Size() : w(0), h(0) {}
    Size(T w_, T h_) : w(w_), h(h_) {}
    explicit Size(T s) : w(s), h(s) {}
    explicit Size(const Size<typename Math<T>::OtherFloatType>& src) : w((T)src.w), h((T)src.h) {}

    // C-interop support.
    typedef typename CompatibleTypes<Size<T>>::Type CompatibleType;

    Size(const CompatibleType& s) : w(s.w), h(s.h) {}

    operator const CompatibleType& () const {
      OVR_MATH_STATIC_ASSERT(
        sizeof(Size<T>) == sizeof(CompatibleType), "sizeof(Size<T>) failure");
      return reinterpret_cast<const CompatibleType&>(*this);
    }

    bool operator==(const Size& b) const {
      return w == b.w && h == b.h;
    }
    bool operator!=(const Size& b) const {
      return w != b.w || h != b.h;
    }

    Size operator+(const Size& b) const {
      return Size(w + b.w, h + b.h);
    }
    Size& operator+=(const Size& b) {
      w += b.w;
      h += b.h;
      return *this;
    }
    Size operator-(const Size& b) const {
      return Size(w - b.w, h - b.h);
    }
    Size& operator-=(const Size& b) {
      w -= b.w;
      h -= b.h;
      return *this;
    }
    Size operator-() const {
      return Size(-w, -h);
    }
    Size operator*(const Size& b) const {
      return Size(w * b.w, h * b.h);
    }
    Size& operator*=(const Size& b) {
      w *= b.w;
      h *= b.h;
      return *this;
    }
    Size operator/(const Size& b) const {
      return Size(w / b.w, h / b.h);
    }
    Size& operator/=(const Size& b) {
      w /= b.w;
      h /= b.h;
      return *this;
    }

    // Scalar multiplication/division scales both components.
    Size operator*(T s) const {
      return Size(w * s, h * s);
    }
    Size& operator*=(T s) {
      w *= s;
      h *= s;
      return *this;
    }
    Size operator/(T s) const {
      return Size(w / s, h / s);
    }
    Size& operator/=(T s) {
      w /= s;
      h /= s;
      return *this;
    }

    static Size Min(const Size& a, const Size& b) {
      return Size((a.w < b.w) ? a.w : b.w, (a.h < b.h) ? a.h : b.h);
    }
    static Size Max(const Size& a, const Size& b) {
      return Size((a.w > b.w) ? a.w : b.w, (a.h > b.h) ? a.h : b.h);
    }

    T Area() const {
      return w * h;
    }

    inline Vector2<T> ToVector() const {
      return Vector2<T>(w, h);
    }
  };

  typedef Size<int> Sizei;
  typedef Size<unsigned> Sizeu;
  typedef Size<float> Sizef;
  typedef Size<double> Sized;

  //-----------------------------------------------------------------------------------
  // ***** Rect

  // Rect describes a rectangular area for rendering, that includes position and size.
  template <class T>
  class Rect {
  public:
    T x, y;
    T w, h;

    Rect() {}
    Rect(T x1, T y1, T w1, T h1) : x(x1), y(y1), w(w1), h(h1) {}
    Rect(const Vector2<T>& pos, const Size<T>& sz) : x(pos.x), y(pos.y), w(sz.w), h(sz.h) {}
    Rect(const Size<T>& sz) : x(0), y(0), w(sz.w), h(sz.h) {}

    // C-interop support.
    typedef typename CompatibleTypes<Rect<T>>::Type CompatibleType;

    Rect(const CompatibleType& s) : x(s.x), y(s.y), w(s.width), h(s.height) {}

    operator const CompatibleType& () const {
      OVR_MATH_STATIC_ASSERT(
        sizeof(Rect<T>) == sizeof(CompatibleType), "sizeof(Rect<T>) failure");
      return reinterpret_cast<const CompatibleType&>(*this);
    }

    Vector2<T> GetPos() const {
      return Vector2<T>(x, y);
    }
    Size<T> GetSize() const {
      return Size<T>(w, h);
    }
    void SetPos(const Vector2<T>& pos) {
      x = pos.x;
      y = pos.y;
    }
    void SetSize(const Size<T>& sz) {
      w = sz.w;
      h = sz.h;
    }

    bool operator==(const Rect& vp) const {
      return (x == vp.x) && (y == vp.y) && (w == vp.w) && (h == vp.h);
    }
    bool operator!=(const Rect& vp) const {
      return !operator==(vp);
    }
  };

  typedef Rect<int> Recti;
  typedef Rect<float> Rectf;

  template <typename T>
  T CosineOfHalfAngleFromCosineOfAngle(const T cosineOfAngle) {
    return (T)(sqrt(((T)1.0 + cosineOfAngle) * (T)0.5));
  }

  template <typename T>
  T SineOfHalfAngleFromCosineOfAngle(const T cosineOfAngle) {
    return (T)(sqrtf(((T)1.0 - cosineOfAngle) * (T)0.5));
  }

  //-------------------------------------------------------------------------------------//
  // ***** Quat
  //
  // Quatf represents a quaternion class used for rotations.
  //
  // Quaternion multiplications are done in right-to-left order, to match the
  // behavior of matrices.

  template <class T>
  class Quat {
  public:
    typedef T ElementType;
    static const size_t ElementCount = 4;

    // x,y,z = axis*sin(angle/2), w = cos(angle/2)
    T x, y, z, w;

    Quat() : x(0), y(0), z(0), w(1) {}
    Quat(T x_, T y_, T z_, T w_) : x(x_), y(y_), z(z_), w(w_) {}
    explicit Quat(const Quat<typename Math<T>::OtherFloatType>& src)
      : x((T)src.x), y((T)src.y), z((T)src.z), w((T)src.w) {
      // NOTE: Converting a normalized Quat<float> to Quat<double>
      // will generally result in an un-normalized quaternion.
      // But we don't normalize here in case the quaternion
      // being converted is not a normalized rotation quaternion.
    }

    typedef typename CompatibleTypes<Quat<T>>::Type CompatibleType;

    // C-interop support.
    Quat(const CompatibleType& s) : x(s.x), y(s.y), z(s.z), w(s.w) {}
    Quat(const CompatibleType&& s) : x(s.x), y(s.y), z(s.z), w(s.w) {}

    operator CompatibleType() const {
      CompatibleType result;
      result.x = x;
      result.y = y;
      result.z = z;
      result.w = w;
      return result;
    }

    // Constructs quaternion for rotation around the axis by an angle.
    Quat(const Vector3<T>& axis, T angle) {
      // Make sure we don't divide by zero.
      if (axis.LengthSq() == T(0)) {
        // Assert if the axis is zero, but the angle isn't
        OVR_MATH_ASSERT(angle == T(0));
        x = y = z = T(0);
        w = T(1);
        return;
      }

      Vector3<T> unitAxis = axis.Normalized();
      T sinHalfAngle = static_cast<T>(sin(angle * T(0.5)));

      w = static_cast<T>(cos(angle * T(0.5)));
      x = unitAxis.x * sinHalfAngle;
      y = unitAxis.y * sinHalfAngle;
      z = unitAxis.z * sinHalfAngle;
    }

    // Constructs quaternion for rotation around one of the coordinate axis by an angle.
    Quat(Axis A, T angle, RotateDirection dEnum = Rotate_CCW, HandedSystem sEnum = Handed_R) {
      const T d = static_cast<T>(dEnum);
      const T s = static_cast<T>(sEnum);
      T sinHalfAngle = s * d * static_cast<T>(sin(angle * T(0.5)));
      T v[3];
      v[0] = v[1] = v[2] = T(0);
      v[A] = sinHalfAngle;

      w = static_cast<T>(cos(angle * T(0.5)));
      x = v[0];
      y = v[1];
      z = v[2];
    }

    // constructs a quaternion from an axis and the cosine of half the rotation angle around that
    // axis. Note that the dot product of two vectors is equal to the cosine of the angle between
    // the vectors, and the magnitude of the cross product of two vectors is the cosine of the half
    // angle between the vectors.
    static Quat FromAxisAndCosineOfAngle(
      const Vector3<T>& axis,
      const T cosineOfAngle,
      const RotateDirection dir) {
      OVR_MATH_ASSERT(axis.LengthSq() > 1e-8f);
      OVR_MATH_ASSERT(axis.IsNormalized());

      const T sineHalfAngle = SineOfHalfAngleFromCosineOfAngle(cosineOfAngle) * (T)(-dir);
      return Quat(
        axis.x * sineHalfAngle,
        axis.y * sineHalfAngle,
        axis.z * sineHalfAngle,
        CosineOfHalfAngleFromCosineOfAngle(cosineOfAngle));
    }

    static Quat LookRotation(const Vector3<T>& fwd, const Vector3<T>& up) {
      return Quat(Matrix4<T>::LookAtRH(Vector3<T>((T)0, (T)0, (T)0), fwd, up)).Inverted();
    }
    // from basis vectors
    static Quat
      FromBasisVectors(const Vector3<T>& fwd, const Vector3<T>& right, const Vector3<T>& /*up*/) {
      const T dot = fwd.Dot(Vector3<T>((T)0, (T)0, (T)-1));
      const RotateDirection dir =
        right.Dot(Vector3<T>((T)0, (T)0, (T)-1)) < (T)0 ? Rotate_CCW : Rotate_CW;
      return FromAxisAndCosineOfAngle(Vector3<T>((T)0, (T)1, (T)0), dot, dir);
    }

    Quat operator-() {
      return Quat(-x, -y, -z, -w);
    } // unary minus

    static Quat Identity() {
      return Quat(0, 0, 0, 1);
    }

    // Compute axis and angle from quaternion
    void GetAxisAngle(Vector3<T>* axis, T* angle) const {
      if (x * x + y * y + z * z > Math<T>::Tolerance() * Math<T>::Tolerance()) {
        *axis = Vector3<T>(x, y, z).Normalized();
        *angle = 2 * Acos(w);
        if (*angle > ((T)MATH_DOUBLE_PI)) // Reduce the magnitude of the angle, if necessary
        {
          *angle = ((T)MATH_DOUBLE_TWOPI) - *angle;
          *axis = *axis * (-1);
        }
      }
      else {
        *axis = Vector3<T>(static_cast<T>(1), static_cast<T>(0), static_cast<T>(0));
        *angle = T(0);
      }
    }

    // Convert a quaternion to a rotation vector, also known as
    // Rodrigues vector, AxisAngle vector, SORA vector, exponential map.
    // A rotation vector describes a rotation about an axis:
    // the axis of rotation is the vector normalized,
    // the angle of rotation is the magnitude of the vector.
    Vector3<T> ToRotationVector() const {
      OVR_MATH_ASSERT(IsNormalized() || LengthSq() == 0);
      T s = T(0);
      T sinHalfAngle = (T)sqrt(x * x + y * y + z * z);
      if (sinHalfAngle > T(0)) {
        T cosHalfAngle = w;
        T halfAngle = (T)atan2(sinHalfAngle, cosHalfAngle);

        // Ensure minimum rotation magnitude
        if (cosHalfAngle < 0)
          halfAngle -= T(MATH_DOUBLE_PI);

        s = T(2) * halfAngle / sinHalfAngle;
      }
      return Vector3<T>(x * s, y * s, z * s);
    }

    // Faster version of the above, optimized for use with small rotations, where rotation angle ~=
    // sin(angle)
    inline OVR::Vector3<T> FastToRotationVector() const {
      OVR_MATH_ASSERT(IsNormalized());
      T s;
      T sinHalfSquared = x * x + y * y + z * z;
      if (sinHalfSquared < T(.0037)) // =~ sin(7/2 degrees)^2
      {
        // Max rotation magnitude error is about .062% at 7 degrees rotation, or about .0043
        // degrees
        s = T(2) * Sign(w);
      }
      else {
        T sinHalfAngle = sqrt(sinHalfSquared);
        T cosHalfAngle = w;
        T halfAngle = atan2(sinHalfAngle, cosHalfAngle);

        // Ensure minimum rotation magnitude
        if (cosHalfAngle < 0)
          halfAngle -= T(MATH_DOUBLE_PI);

        s = T(2) * halfAngle / sinHalfAngle;
      }
      return Vector3<T>(x * s, y * s, z * s);
    }

    // Given a rotation vector of form unitRotationAxis * angle,
    // returns the equivalent quaternion (unitRotationAxis * sin(angle), cos(Angle)).
    static Quat FromRotationVector(const Vector3<T>& v) {
      T angleSquared = v.LengthSq();
      T s = T(0);
      T c = T(1);
      if (angleSquared > T(0)) {
        T angle = (T)sqrt(angleSquared);
        s = (T)sin(angle * T(0.5)) / angle; // normalize
        c = (T)cos(angle * T(0.5));
      }
      return Quat(s * v.x, s * v.y, s * v.z, c);
    }

    // Faster version of above, optimized for use with small rotation magnitudes, where rotation
    // angle =~ sin(angle). If normalize is false, small-angle quaternions are returned
    // un-normalized.
    inline static Quat FastFromRotationVector(const OVR::Vector3<T>& v, bool normalize = true) {
      T s, c;
      T angleSquared = v.LengthSq();
      if (angleSquared < T(0.0076)) // =~ (5 degrees*pi/180)^2
      {
        s = T(0.5);
        c = T(1.0);
        // Max rotation magnitude error (after normalization) is about .064% at 5 degrees
        // rotation, or .0032 degrees
        if (normalize && angleSquared > 0) {
          // sin(angle/2)^2 ~= (angle/2)^2 and cos(angle/2)^2 ~= 1
          T invLen = T(1) / sqrt(angleSquared * T(0.25) + T(1)); // normalize
          s = s * invLen;
          c = c * invLen;
        }
      }
      else {
        T angle = sqrt(angleSquared);
        s = sin(angle * T(0.5)) / angle;
        c = cos(angle * T(0.5));
      }
      return Quat(s * v.x, s * v.y, s * v.z, c);
    }

    // Constructs the quaternion from a rotation matrix
    explicit Quat(const Matrix4<T>& m) {
      T trace = m.M[0][0] + m.M[1][1] + m.M[2][2];

      // In almost all cases, the first part is executed.
      // However, if the trace is not positive, the other
      // cases arise.
      if (trace > T(0)) {
        T s = (T)sqrt(trace + T(1)) * T(2); // s=4*qw
        w = T(0.25) * s;
        x = (m.M[2][1] - m.M[1][2]) / s;
        y = (m.M[0][2] - m.M[2][0]) / s;
        z = (m.M[1][0] - m.M[0][1]) / s;
      }
      else if ((m.M[0][0] > m.M[1][1]) && (m.M[0][0] > m.M[2][2])) {
        T s = (T)sqrt(T(1) + m.M[0][0] - m.M[1][1] - m.M[2][2]) * T(2);
        w = (m.M[2][1] - m.M[1][2]) / s;
        x = T(0.25) * s;
        y = (m.M[0][1] + m.M[1][0]) / s;
        z = (m.M[2][0] + m.M[0][2]) / s;
      }
      else if (m.M[1][1] > m.M[2][2]) {
        T s = (T)sqrt(T(1) + m.M[1][1] - m.M[0][0] - m.M[2][2]) * T(2); // S=4*qy
        w = (m.M[0][2] - m.M[2][0]) / s;
        x = (m.M[0][1] + m.M[1][0]) / s;
        y = T(0.25) * s;
        z = (m.M[1][2] + m.M[2][1]) / s;
      }
      else {
        T s = (T)sqrt(T(1) + m.M[2][2] - m.M[0][0] - m.M[1][1]) * T(2); // S=4*qz
        w = (m.M[1][0] - m.M[0][1]) / s;
        x = (m.M[0][2] + m.M[2][0]) / s;
        y = (m.M[1][2] + m.M[2][1]) / s;
        z = T(0.25) * s;
      }
      OVR_MATH_ASSERT(IsNormalized()); // Ensure input matrix is orthogonal
    }

    // Constructs the quaternion from a rotation matrix
    explicit Quat(const Matrix3<T>& m) {
      T trace = m.M[0][0] + m.M[1][1] + m.M[2][2];

      // In almost all cases, the first part is executed.
      // However, if the trace is not positive, the other
      // cases arise.
      if (trace > T(0)) {
        T s = T(sqrt(trace + T(1))) * T(2); // s=4*qw
        w = T(0.25) * s;
        x = (m.M[2][1] - m.M[1][2]) / s;
        y = (m.M[0][2] - m.M[2][0]) / s;
        z = (m.M[1][0] - m.M[0][1]) / s;
      }
      else if ((m.M[0][0] > m.M[1][1]) && (m.M[0][0] > m.M[2][2])) {
        T s = T(sqrt(T(1) + m.M[0][0] - m.M[1][1] - m.M[2][2])) * T(2);
        w = (m.M[2][1] - m.M[1][2]) / s;
        x = T(0.25) * s;
        y = (m.M[0][1] + m.M[1][0]) / s;
        z = (m.M[2][0] + m.M[0][2]) / s;
      }
      else if (m.M[1][1] > m.M[2][2]) {
        T s = T(sqrt(T(1) + m.M[1][1] - m.M[0][0] - m.M[2][2])) * T(2); // S=4*qy
        w = (m.M[0][2] - m.M[2][0]) / s;
        x = (m.M[0][1] + m.M[1][0]) / s;
        y = T(0.25) * s;
        z = (m.M[1][2] + m.M[2][1]) / s;
      }
      else {
        T s = T(sqrt(T(1) + m.M[2][2] - m.M[0][0] - m.M[1][1])) * T(2); // S=4*qz
        w = (m.M[1][0] - m.M[0][1]) / s;
        x = (m.M[0][2] + m.M[2][0]) / s;
        y = (m.M[1][2] + m.M[2][1]) / s;
        z = T(0.25) * s;
      }
      OVR_MATH_ASSERT(IsNormalized()); // Ensure input matrix is orthogonal
    }

    // MERGE_MOBILE_SDK
    // Constructs a quaternion that rotates 'from' to line up with 'to'.
    explicit Quat(const Vector3<T>& from, const Vector3<T>& to) {
      const T cx = from.y * to.z - from.z * to.y;
      const T cy = from.z * to.x - from.x * to.z;
      const T cz = from.x * to.y - from.y * to.x;
      const T dot = from.x * to.x + from.y * to.y + from.z * to.z;
      const T crossLengthSq = cx * cx + cy * cy + cz * cz;
      const T magnitude = static_cast<T>(sqrt(crossLengthSq + dot * dot));
      const T cw = dot + magnitude;
      if (cw < Math<T>::SmallestNonDenormal()) {
        const T sx = to.y * to.y + to.z * to.z;
        const T sz = to.x * to.x + to.y * to.y;
        if (sx > sz) {
          const T rcpLength = RcpSqrt(sx);
          x = T(0);
          y = to.z * rcpLength;
          z = -to.y * rcpLength;
          w = T(0);
        }
        else {
          const T rcpLength = RcpSqrt(sz);
          x = to.y * rcpLength;
          y = -to.x * rcpLength;
          z = T(0);
          w = T(0);
        }
        return;
      }
      const T rcpLength = RcpSqrt(crossLengthSq + cw * cw);
      x = cx * rcpLength;
      y = cy * rcpLength;
      z = cz * rcpLength;
      w = cw * rcpLength;
    }
    // MERGE_MOBILE_SDK

    bool operator==(const Quat& b) const {
      return x == b.x && y == b.y && z == b.z && w == b.w;
    }
    bool operator!=(const Quat& b) const {
      return x != b.x || y != b.y || z != b.z || w != b.w;
    }

    Quat operator+(const Quat& b) const {
      return Quat(x + b.x, y + b.y, z + b.z, w + b.w);
    }
    Quat& operator+=(const Quat& b) {
      w += b.w;
      x += b.x;
      y += b.y;
      z += b.z;
      return *this;
    }
    Quat operator-(const Quat& b) const {
      return Quat(x - b.x, y - b.y, z - b.z, w - b.w);
    }
    Quat& operator-=(const Quat& b) {
      w -= b.w;
      x -= b.x;
      y -= b.y;
      z -= b.z;
      return *this;
    }

    Quat operator*(T s) const {
      return Quat(x * s, y * s, z * s, w * s);
    }
    Quat& operator*=(T s) {
      w *= s;
      x *= s;
      y *= s;
      z *= s;
      return *this;
    }
    Quat operator/(T s) const {
      T rcp = T(1) / s;
      return Quat(x * rcp, y * rcp, z * rcp, w * rcp);
    }
    Quat& operator/=(T s) {
      T rcp = T(1) / s;
      w *= rcp;
      x *= rcp;
      y *= rcp;
      z *= rcp;
      return *this;
    }

    // Compare two quats for equality within tolerance. Returns true if quats match within
    // tolerance.
    bool IsEqual(const Quat& b, T tolerance = Math<T>::Tolerance()) const {
      return Abs(Dot(b)) >= T(1) - tolerance;
    }

    // Compare two quats for equality within tolerance while checking matching hemispheres. Returns
    // true if quats match within tolerance.
    bool IsEqualMatchHemisphere(Quat b, T tolerance = Math<T>::Tolerance()) const {
      b.EnsureSameHemisphere(*this);
      return Abs(Dot(b)) >= T(1) - tolerance;
    }

    static T Abs(const T v) {
      return (v >= 0) ? v : -v;
    }

    // Get Imaginary part vector
    Vector3<T> Imag() const {
      return Vector3<T>(x, y, z);
    }

    // Get quaternion length.
    T Length() const {
      return static_cast<T>(sqrt(LengthSq()));
    }

    // Get quaternion length squared.
    T LengthSq() const {
      return (x * x + y * y + z * z + w * w);
    }

    // Simple Euclidean distance in R^4 (not SLERP distance, but at least respects Haar measure)
    T Distance(const Quat& q) const {
      T d1 = (*this - q).Length();
      T d2 = (*this + q).Length(); // Antipodal point check
      return (d1 < d2) ? d1 : d2;
    }

    T DistanceSq(const Quat& q) const {
      T d1 = (*this - q).LengthSq();
      T d2 = (*this + q).LengthSq(); // Antipodal point check
      return (d1 < d2) ? d1 : d2;
    }

    T Dot(const Quat& q) const {
      return x * q.x + y * q.y + z * q.z + w * q.w;
    }

    // Angle between two quaternions in radians
    T Angle(const Quat& q) const {
      return T(2) * Acos(Abs(Dot(q)));
    }

    // Angle of quaternion
    T Angle() const {
      return T(2) * Acos(Abs(w));
    }

    // Normalize
    bool IsNormalized() const {
      return fabs(LengthSq() - T(1)) < Math<T>::Tolerance();
    }

    void Normalize() {
      T s = Length();
      if (s != T(0))
        s = T(1) / s;
      *this *= s;
    }

    Quat Normalized() const {
      T s = Length();
      if (s != T(0))
        s = T(1) / s;
      return *this * s;
    }

    inline void EnsureSameHemisphere(const Quat& o) {
      if (Dot(o) < T(0)) {
        x = -x;
        y = -y;
        z = -z;
        w = -w;
      }
    }

    // Returns conjugate of the quaternion. Produces inverse rotation if quaternion is normalized.
    Quat Conj() const {
      return Quat(-x, -y, -z, w);
    }

    // Quaternion multiplication. Combines quaternion rotations, performing the one on the
    // right hand side first.
    Quat operator*(const Quat& b) const {
      return Quat(
        w * b.x + x * b.w + y * b.z - z * b.y,
        w * b.y - x * b.z + y * b.w + z * b.x,
        w * b.z + x * b.y - y * b.x + z * b.w,
        w * b.w - x * b.x - y * b.y - z * b.z);
    }
    const Quat& operator*=(const Quat& b) {
      *this = *this * b;
      return *this;
    }

    // MERGE_MOBILE_SDK
    Vector3<T> operator*(const Vector3<T>& v) const {
      return Rotate(v);
    }
    // MERGE_MOBILE_SDK

    //
    // this^p normalized; same as rotating by this p times.
    Quat PowNormalized(T p) const {
      Vector3<T> v;
      T a;
      GetAxisAngle(&v, &a);
      return Quat(v, a * p);
    }

    // Compute quaternion that rotates v into alignTo: alignTo = Quat::Align(alignTo, v).Rotate(v).
    // NOTE: alignTo and v must be normalized.
    static Quat Align(const Vector3<T>& alignTo, const Vector3<T>& v) {
      OVR_MATH_ASSERT(alignTo.IsNormalized() && v.IsNormalized());
      Vector3<T> bisector = (v + alignTo);
      bisector.Normalize();
      T cosHalfAngle = v.Dot(bisector); // 0..1
      if (cosHalfAngle > T(0)) {
        Vector3<T> imag = v.Cross(bisector);
        return Quat(imag.x, imag.y, imag.z, cosHalfAngle);
      }
      else {
        // cosHalfAngle == 0: a 180 degree rotation.
        // sinHalfAngle == 1, rotation axis is any axis perpendicular
        // to alignTo.  Choose axis to include largest magnitude components
        if (fabs(v.x) > fabs(v.y)) {
          // x or z is max magnitude component
          // = Cross(v, (0,1,0)).Normalized();
          T invLen = sqrt(v.x * v.x + v.z * v.z);
          if (invLen > T(0))
            invLen = T(1) / invLen;
          return Quat(-v.z * invLen, 0, v.x * invLen, 0);
        }
        else {
          // y or z is max magnitude component
          // = Cross(v, (1,0,0)).Normalized();
          T invLen = sqrt(v.y * v.y + v.z * v.z);
          if (invLen > T(0))
            invLen = T(1) / invLen;
          return Quat(0, v.z * invLen, -v.y * invLen, 0);
        }
      }
    }

    // Normalized linear interpolation of quaternions
    // NOTE: This function is a bad approximation of Slerp()
    // when the angle between the *this and b is large.
    // Use FastSlerp() or Slerp() instead.
    Quat Lerp(const Quat& b, T s) const {
      return (*this * (T(1) - s) + b * (Dot(b) < 0 ? -s : s)).Normalized();
    }

    // Spherical linear interpolation between rotations
    Quat Slerp(const Quat& b, T s) const {
      Vector3<T> delta = (b * this->Inverted()).ToRotationVector();
      return FromRotationVector(delta * s) * *this;
    }

    // Spherical linear interpolation: much faster for small rotations, accurate for large
    // rotations. See FastTo/FromRotationVector
    Quat FastSlerp(const Quat& b, T s) const {
      Vector3<T> delta = (b * this->Inverted()).FastToRotationVector();
      return (FastFromRotationVector(delta * s, false) * *this).Normalized();
    }

    // MERGE_MOBILE_SDK
    // FIXME: This is opposite of Lerp for some reason.  It goes from 1 to 0 instead of 0 to 1.
    // Leaving it as a gift for future generations to deal with.
    Quat Nlerp(const Quat& other, T a) const {
      T sign = (Dot(other) >= 0.0f) ? 1.0f : -1.0f;
      return (*this * sign * a + other * (1 - a)).Normalized();
    }
    // MERGE_MOBILE_SDK

    // Rotate transforms vector in a manner that matches Matrix rotations (counter-clockwise,
    // assuming negative direction of the axis). Standard formula: q(t) * V * q(t)^-1.
    Vector3<T> Rotate(const Vector3<T>& v) const {
      return ((*this * Quat<T>(v.x, v.y, v.z, T(0))) * Inverted()).Imag();
    }

    // Rotation by inverse of *this
    Vector3<T> InverseRotate(const Vector3<T>& v) const {
      return Inverted().Rotate(v);
    }

    // Inversed quaternion rotates in the opposite direction.
    Quat Inverted() const {
      return Quat(-x, -y, -z, w);
    }

    Quat Inverse() const {
      return Quat(-x, -y, -z, w);
    }

    // Sets this quaternion to the one rotates in the opposite direction.
    void Invert() {
      *this = Quat(-x, -y, -z, w);
    }

    // Time integration of constant angular velocity over dt
    Quat TimeIntegrate(const Vector3<T>& angularVelocity, T dt) const {
      // solution is: this * exp( omega*dt/2 ); FromRotationVector(v) gives exp(v*.5).
      return (*this * FastFromRotationVector(angularVelocity * dt, false)).Normalized();
    }

    // Time integration of constant angular acceleration and velocity over dt
    // These are the first two terms of the "Magnus expansion" of the solution
    //
    //   o = o * exp( W=(W1 + W2 + W3+...) * 0.5 );
    //
    //  omega1 = (omega + omegaDot*dt)
    //  W1 = (omega + omega1)*dt/2
    //  W2 = cross(omega, omega1)/12*dt^2 % (= -cross(omega_dot, omega)/12*dt^3)
    // Terms 3 and beyond are vanishingly small:
    //  W3 = cross(omega_dot, cross(omega_dot, omega))/240*dt^5
    //
    Quat TimeIntegrate(
      const Vector3<T>& angularVelocity,
      const Vector3<T>& angularAcceleration,
      T dt) const {
      const Vector3<T>& omega = angularVelocity;
      const Vector3<T>& omegaDot = angularAcceleration;

      Vector3<T> omega1 = (omega + omegaDot * dt);
      Vector3<T> W = ((omega + omega1) + omega.Cross(omega1) * (dt / T(6))) * (dt / T(2));

      // FromRotationVector(v) is exp(v*.5)
      return (*this * FastFromRotationVector(W, false)).Normalized();
    }

    // Decompose rotation into three rotations:
    // roll radians about Z axis, then pitch radians about X axis, then yaw radians about Y axis.
    // Call with nullptr if a return value is not needed.
    void GetYawPitchRoll(T* yaw, T* pitch, T* roll) const {
      return GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(yaw, pitch, roll);
    }

    // GetEulerAngles extracts Euler angles from the quaternion, in the specified order of
    // axis rotations and the specified coordinate system. Right-handed coordinate system
    // is the default, with CCW rotations while looking in the negative axis direction.
    // Here a,b,c, are the Yaw/Pitch/Roll angles to be returned.
    // Rotation order is c, b, a:
    // rotation c around axis A3
    // is followed by rotation b around axis A2
    // is followed by rotation a around axis A1
    // rotations are CCW or CW (D) in LH or RH coordinate system (S)
    //
    template <Axis A1, Axis A2, Axis A3, RotateDirection Denum, HandedSystem Senum>
    void GetEulerAngles(T* a, T* b, T* c) const {
      OVR_MATH_ASSERT(IsNormalized());
      OVR_MATH_STATIC_ASSERT(
        (A1 != A2) && (A2 != A3) && (A1 != A3), "(A1 != A2) && (A2 != A3) && (A1 != A3)");
      const T D = static_cast<T>(Denum);
      const T S = static_cast<T>(Senum);

      T Q[3] = { x, y, z }; // Quaternion components x,y,z

      T ww = w * w;
      T Q11 = Q[A1] * Q[A1];
      T Q22 = Q[A2] * Q[A2];
      T Q33 = Q[A3] * Q[A3];

      T psign = T(-1);
      // Determine whether even permutation
      if (((A1 + 1) % 3 == A2) && ((A2 + 1) % 3 == A3))
        psign = T(1);

      T s2 = psign * T(2) * (psign * w * Q[A2] + Q[A1] * Q[A3]);

      T singularityRadius = Math<T>::SingularityRadius();
      if (s2 < T(-1) + singularityRadius) { // South pole singularity
        if (a)
          *a = T(0);
        if (b)
          *b = -S * D * ((T)MATH_DOUBLE_PIOVER2);
        if (c)
          *c = S * D *
          static_cast<T>(
            atan2(T(2) * (psign * Q[A1] * Q[A2] + w * Q[A3]), ww + Q22 - Q11 - Q33));
      }
      else if (s2 > T(1) - singularityRadius) { // North pole singularity
        if (a)
          *a = T(0);
        if (b)
          *b = S * D * ((T)MATH_DOUBLE_PIOVER2);
        if (c)
          *c = S * D *
          static_cast<T>(
            atan2(T(2) * (psign * Q[A1] * Q[A2] + w * Q[A3]), ww + Q22 - Q11 - Q33));
      }
      else {
        if (a)
          *a = -S * D *
          static_cast<T>(
            atan2(T(-2) * (w * Q[A1] - psign * Q[A2] * Q[A3]), ww + Q33 - Q11 - Q22));
        if (b)
          *b = S * D * static_cast<T>(asin(s2));
        if (c)
          *c = S * D *
          static_cast<T>(
            atan2(T(2) * (w * Q[A3] - psign * Q[A1] * Q[A2]), ww + Q11 - Q22 - Q33));
      }
    }

    template <Axis A1, Axis A2, Axis A3, RotateDirection D>
    void GetEulerAngles(T* a, T* b, T* c) const {
      GetEulerAngles<A1, A2, A3, D, Handed_R>(a, b, c);
    }

    template <Axis A1, Axis A2, Axis A3>
    void GetEulerAngles(T* a, T* b, T* c) const {
      GetEulerAngles<A1, A2, A3, Rotate_CCW, Handed_R>(a, b, c);
    }

    // GetEulerAnglesABA extracts Euler angles from the quaternion, in the specified order of
    // axis rotations and the specified coordinate system. Right-handed coordinate system
    // is the default, with CCW rotations while looking in the negative axis direction.
    // Here a,b,c, are the Yaw/Pitch/Roll angles to be returned.
    // rotation a around axis A1
    // is followed by rotation b around axis A2
    // is followed by rotation c around axis A1
    // Rotations are CCW or CW (D) in LH or RH coordinate system (S)
    template <Axis A1, Axis A2, RotateDirection Denum, HandedSystem Senum>
    void GetEulerAnglesABA(T* a, T* b, T* c) const {
      OVR_MATH_ASSERT(IsNormalized());
      OVR_MATH_STATIC_ASSERT(A1 != A2, "A1 != A2");
      const T D = static_cast<T>(Denum);
      const T S = static_cast<T>(Senum);

      T Q[3] = { x, y, z }; // Quaternion components

      // Determine the missing axis that was not supplied
      int m = 3 - A1 - A2;

      T ww = w * w;
      T Q11 = Q[A1] * Q[A1];
      T Q22 = Q[A2] * Q[A2];
      T Qmm = Q[m] * Q[m];

      T psign = T(-1);
      if ((A1 + 1) % 3 == A2) // Determine whether even permutation
      {
        psign = T(1);
      }

      T c2 = ww + Q11 - Q22 - Qmm;
      T singularityRadius = Math<T>::SingularityRadius();
      if (c2 < T(-1) + singularityRadius) { // South pole singularity
        if (a)
          *a = T(0);
        if (b)
          *b = S * D * ((T)MATH_DOUBLE_PI);
        if (c)
          *c = S * D * atan2(T(2) * (w * Q[A1] - psign * Q[A2] * Q[m]), ww + Q22 - Q11 - Qmm);
      }
      else if (c2 > T(1) - singularityRadius) { // North pole singularity
        if (a)
          *a = T(0);
        if (b)
          *b = T(0);
        if (c)
          *c = S * D * atan2(T(2) * (w * Q[A1] - psign * Q[A2] * Q[m]), ww + Q22 - Q11 - Qmm);
      }
      else {
        if (a)
          *a = S * D *
          atan2(psign * w * Q[m] + Q[A1] * Q[A2], w * Q[A2] - psign * Q[A1] * Q[m]);
        if (b)
          *b = S * D * acos(c2);
        if (c)
          *c = S * D *
          atan2(-psign * w * Q[m] + Q[A1] * Q[A2], w * Q[A2] + psign * Q[A1] * Q[m]);
      }
    }

    bool IsNan() const {
      return isnan(x) || isnan(y) || isnan(z) || isnan(w);
    }
  };

  // MERGE_MOBILE_SDK
  // allow multiplication in order vector * quat (member operator handles quat * vector)
  template <class T>
  Vector3<T> operator*(const Vector3<T>& v, const Quat<T>& q) {
    return Vector3<T>(q * v);
  }
  // MERGE_MOBILE_SDK

  typedef Quat<float> Quatf;
  typedef Quat<double> Quatd;

  OVR_MATH_STATIC_ASSERT((sizeof(Quatf) == 4 * sizeof(float)), "sizeof(Quatf) failure");
  OVR_MATH_STATIC_ASSERT((sizeof(Quatd) == 4 * sizeof(double)), "sizeof(Quatd) failure");

  //-------------------------------------------------------------------------------------
  // ***** Pose
  //
  // Position and orientation combined.
  //
  // This structure needs to be the same size and layout on 32-bit and 64-bit arch.
  // Update OVR_PadCheck.cpp when updating this object.
  template <class T>
  class Pose {
  public:
    typedef typename CompatibleTypes<Pose<T>>::Type CompatibleType;

    Pose() {}

    Pose(const Quat<T>& orientation, const Vector3<T>& pos)
      : Rotation(orientation), Translation(pos) {}

    Pose(const Pose& s) : Rotation(s.Rotation), Translation(s.Translation) {}

    Pose(const Matrix3<T>& R, const Vector3<T>& t) : Rotation((Quat<T>)R), Translation(t) {}

    Pose(const CompatibleType& s) : Rotation(s.Orientation), Translation(s.Position) {}

    Pose(const CompatibleType&& s) : Rotation(s.Orientation), Translation(s.Position) {}

    explicit Pose(const Pose<typename Math<T>::OtherFloatType>& s)
      : Rotation(s.Rotation), Translation(s.Translation) {
      // Ensure normalized rotation if converting from float to double
      if (sizeof(T) > sizeof(typename Math<T>::OtherFloatType))
        Rotation.Normalize();
    }

    Pose& operator=(Pose const& rhs) {
      if (&rhs != this) {
        this->Rotation = rhs.Rotation;
        this->Translation = rhs.Translation;
      }
      return *this;
    }

    explicit Pose(const Matrix4<T>& m) : Rotation(m), Translation(m.GetTranslation()) {}

    static Pose Identity() {
      return Pose(Quat<T>(0, 0, 0, 1), Vector3<T>(0, 0, 0));
    }

    void SetIdentity() {
      Rotation = Quat<T>(0, 0, 0, 1);
      Translation = Vector3<T>(0, 0, 0);
    }

    // used to make things obviously broken if someone tries to use the value
    void SetInvalid() {
      Rotation = Quat<T>(NAN, NAN, NAN, NAN);
      Translation = Vector3<T>(NAN, NAN, NAN);
    }

    bool IsEqual(const Pose& b, T tolerance = Math<T>::Tolerance()) const {
      return Translation.IsEqual(b.Translation, tolerance) &&
        Rotation.IsEqual(b.Rotation, tolerance);
    }

    bool IsEqualMatchHemisphere(const Pose& b, T tolerance = Math<T>::Tolerance()) const {
      return Translation.IsEqual(b.Translation, tolerance) &&
        Rotation.IsEqualMatchHemisphere(b.Rotation, tolerance);
    }

    operator typename CompatibleTypes<Pose<T>>::Type() const {
      typename CompatibleTypes<Pose<T>>::Type result;
      result.Orientation = Rotation;
      result.Position = Translation;
      return result;
    }

    Quat<T> Rotation;
    Vector3<T> Translation;

    OVR_MATH_STATIC_ASSERT(
      (sizeof(T) == sizeof(double) || sizeof(T) == sizeof(float)),
      "(sizeof(T) == sizeof(double) || sizeof(T) == sizeof(float))");

    void ToArray(T* arr) const {
      T temp[7] = {
              Rotation.x,
              Rotation.y,
              Rotation.z,
              Rotation.w,
              Translation.x,
              Translation.y,
              Translation.z };
      for (int i = 0; i < 7; i++)
        arr[i] = temp[i];
    }

    static Pose<T> FromArray(const T* v) {
      Quat<T> rotation(v[0], v[1], v[2], v[3]);
      Vector3<T> translation(v[4], v[5], v[6]);
      // Ensure rotation is normalized, in case it was originally a float, stored in a .json file,
      // etc.
      return Pose<T>(rotation.Normalized(), translation);
    }

    Vector3<T> Rotate(const Vector3<T>& v) const {
      return Rotation.Rotate(v);
    }

    Vector3<T> InverseRotate(const Vector3<T>& v) const {
      return Rotation.InverseRotate(v);
    }

    Vector3<T> Translate(const Vector3<T>& v) const {
      return v + Translation;
    }

    Vector3<T> Transform(const Vector3<T>& v) const {
      return Rotate(v) + Translation;
    }

    Vector3<T> InverseTransform(const Vector3<T>& v) const {
      return InverseRotate(v - Translation);
    }

    Vector3<T> Apply(const Vector3<T>& v) const {
      return Transform(v);
    }

    Pose operator*(const Pose& other) const {
      return Pose(Rotation * other.Rotation, Apply(other.Translation));
    }

    Pose Inverted() const {
      Quat<T> inv = Rotation.Inverted();
      return Pose(inv, inv.Rotate(-Translation));
    }

    // Interpolation between two poses: translation is interpolated with Lerp(),
    // and rotations are interpolated with Slerp().
    Pose Lerp(const Pose& b, T s) const {
      return Pose(Rotation.Slerp(b.Rotation, s), Translation.Lerp(b.Translation, s));
    }

    // Similar to Lerp above, except faster in case of small rotation differences.  See
    // Quat<T>::FastSlerp.
    Pose FastLerp(const Pose& b, T s) const {
      return Pose(Rotation.FastSlerp(b.Rotation, s), Translation.Lerp(b.Translation, s));
    }

    Pose TimeIntegrate(const Vector3<T>& linearVelocity, const Vector3<T>& angularVelocity, T dt)
      const {
      return Pose(
        (Rotation * Quat<T>::FastFromRotationVector(angularVelocity * dt, false)).Normalized(),
        Translation + linearVelocity * dt);
    }

    Pose TimeIntegrate(
      const Vector3<T>& linearVelocity,
      const Vector3<T>& linearAcceleration,
      const Vector3<T>& angularVelocity,
      const Vector3<T>& angularAcceleration,
      T dt) const {
      return Pose(
        Rotation.TimeIntegrate(angularVelocity, angularAcceleration, dt),
        Translation + linearVelocity * dt + linearAcceleration * dt * dt * T(0.5));
    }

    bool IsNan() const {
      return Translation.IsNan() || Rotation.IsNan();
    }
  };

  typedef Pose<float> Posef;
  typedef Pose<double> Posed;

  OVR_MATH_STATIC_ASSERT(
    (sizeof(Posed) == sizeof(Quatd) + sizeof(Vector3d)),
    "sizeof(Posed) failure");
  OVR_MATH_STATIC_ASSERT(
    (sizeof(Posef) == sizeof(Quatf) + sizeof(Vector3f)),
    "sizeof(Posef) failure");

  //-------------------------------------------------------------------------------------
  // ***** Matrix4
  //
  // Matrix4 is a 4x4 matrix used for 3d transformations and projections.
  // Translation stored in the last column.
  // The matrix is stored in row-major order in memory, meaning that values
  // of the first row are stored before the next one.
  //
  // The arrangement of the matrix is chosen to be in Right-Handed
  // coordinate system and counterclockwise rotations when looking down
  // the axis
  //
  // Transformation Order:
  //   - Transformations are applied from right to left, so the expression
  //     M1 * M2 * M3 * V means that the vector V is transformed by M3 first,
  //     followed by M2 and M1.
  //
  // Coordinate system: Right Handed
  //
  // Rotations: Counterclockwise when looking down the axis. All angles are in radians.
  //
  //  | sx   01   02   tx |    // First column  (sx, 10, 20): Axis X basis vector.
  //  | 10   sy   12   ty |    // Second column (01, sy, 21): Axis Y basis vector.
  //  | 20   21   sz   tz |    // Third columnt (02, 12, sz): Axis Z basis vector.
  //  | 30   31   32   33 |
  //
  //  The basis vectors are first three columns.

  template <class T>
  class Matrix4 {
    static const Matrix4 IdentityValue;

  public:
    typedef T ElementType;
    static const size_t Dimension = 4;

    T M[4][4];

    enum NoInitType { NoInit };

    // Construct with no memory initialization.
    Matrix4(NoInitType) {}

    // By default, we construct identity matrix.
    Matrix4() {
      M[0][0] = M[1][1] = M[2][2] = M[3][3] = T(1);
      M[0][1] = M[1][0] = M[2][3] = M[3][1] = T(0);
      M[0][2] = M[1][2] = M[2][0] = M[3][2] = T(0);
      M[0][3] = M[1][3] = M[2][1] = M[3][0] = T(0);
    }

    Matrix4(
      T m11,
      T m12,
      T m13,
      T m14,
      T m21,
      T m22,
      T m23,
      T m24,
      T m31,
      T m32,
      T m33,
      T m34,
      T m41,
      T m42,
      T m43,
      T m44) {
      M[0][0] = m11;
      M[0][1] = m12;
      M[0][2] = m13;
      M[0][3] = m14;
      M[1][0] = m21;
      M[1][1] = m22;
      M[1][2] = m23;
      M[1][3] = m24;
      M[2][0] = m31;
      M[2][1] = m32;
      M[2][2] = m33;
      M[2][3] = m34;
      M[3][0] = m41;
      M[3][1] = m42;
      M[3][2] = m43;
      M[3][3] = m44;
    }

    Matrix4(T m11, T m12, T m13, T m21, T m22, T m23, T m31, T m32, T m33) {
      M[0][0] = m11;
      M[0][1] = m12;
      M[0][2] = m13;
      M[0][3] = T(0);
      M[1][0] = m21;
      M[1][1] = m22;
      M[1][2] = m23;
      M[1][3] = T(0);
      M[2][0] = m31;
      M[2][1] = m32;
      M[2][2] = m33;
      M[2][3] = T(0);
      M[3][0] = T(0);
      M[3][1] = T(0);
      M[3][2] = T(0);
      M[3][3] = T(1);
    }

    explicit Matrix4(const Matrix3<T>& m) {
      M[0][0] = m.M[0][0];
      M[0][1] = m.M[0][1];
      M[0][2] = m.M[0][2];
      M[0][3] = T(0);
      M[1][0] = m.M[1][0];
      M[1][1] = m.M[1][1];
      M[1][2] = m.M[1][2];
      M[1][3] = T(0);
      M[2][0] = m.M[2][0];
      M[2][1] = m.M[2][1];
      M[2][2] = m.M[2][2];
      M[2][3] = T(0);
      M[3][0] = T(0);
      M[3][1] = T(0);
      M[3][2] = T(0);
      M[3][3] = T(1);
    }

    explicit Matrix4(const Quat<T>& q) {
      OVR_MATH_ASSERT(q.IsNormalized());
      T ww = q.w * q.w;
      T xx = q.x * q.x;
      T yy = q.y * q.y;
      T zz = q.z * q.z;

      M[0][0] = ww + xx - yy - zz;
      M[0][1] = 2 * (q.x * q.y - q.w * q.z);
      M[0][2] = 2 * (q.x * q.z + q.w * q.y);
      M[0][3] = T(0);
      M[1][0] = 2 * (q.x * q.y + q.w * q.z);
      M[1][1] = ww - xx + yy - zz;
      M[1][2] = 2 * (q.y * q.z - q.w * q.x);
      M[1][3] = T(0);
      M[2][0] = 2 * (q.x * q.z - q.w * q.y);
      M[2][1] = 2 * (q.y * q.z + q.w * q.x);
      M[2][2] = ww - xx - yy + zz;
      M[2][3] = T(0);
      M[3][0] = T(0);
      M[3][1] = T(0);
      M[3][2] = T(0);
      M[3][3] = T(1);
    }

    explicit Matrix4(const Pose<T>& p) {
      Matrix4 result(p.Rotation);
      result.SetTranslation(p.Translation);
      *this = result;
    }

    // C-interop support
    explicit Matrix4(const Matrix4<typename Math<T>::OtherFloatType>& src) {
      for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
          M[i][j] = (T)src.M[i][j];
    }

    // C-interop support.
    Matrix4(const typename CompatibleTypes<Matrix4<T>>::Type& s) {
      OVR_MATH_STATIC_ASSERT(sizeof(s) == sizeof(Matrix4), "sizeof(s) == sizeof(Matrix4)");
      memcpy(M, s.M, sizeof(M));
    }

    operator typename CompatibleTypes<Matrix4<T>>::Type() const {
      typename CompatibleTypes<Matrix4<T>>::Type result;
      OVR_MATH_STATIC_ASSERT(
        sizeof(result) == sizeof(Matrix4), "sizeof(result) == sizeof(Matrix4)");
      memcpy(result.M, M, sizeof(M));
      return result;
    }

    void ToString(char* dest, size_t destsize) const {
      size_t pos = 0;
      for (int r = 0; r < 4; r++) {
        for (int c = 0; c < 4; c++) {
          pos += OVRMath_sprintf(dest + pos, destsize - pos, "%g ", M[r][c]);
        }
      }
    }

    static Matrix4 FromString(const char* src) {
      Matrix4 result;
      if (src) {
        for (int r = 0; r < 4; r++) {
          for (int c = 0; c < 4; c++) {
            result.M[r][c] = (T)atof(src);
            while (*src && *src != ' ') {
              src++;
            }
            while (*src && *src == ' ') {
              src++;
            }
          }
        }
      }
      return result;
    }

    static const Matrix4& Identity() {
      return IdentityValue;
    }

    void SetIdentity() {
      M[0][0] = M[1][1] = M[2][2] = M[3][3] = T(1);
      M[0][1] = M[1][0] = M[2][3] = M[3][1] = T(0);
      M[0][2] = M[1][2] = M[2][0] = M[3][2] = T(0);
      M[0][3] = M[1][3] = M[2][1] = M[3][0] = T(0);
    }

    void SetXBasis(const Vector3<T>& v) {
      M[0][0] = v.x;
      M[1][0] = v.y;
      M[2][0] = v.z;
    }
    Vector3<T> GetXBasis() const {
      return Vector3<T>(M[0][0], M[1][0], M[2][0]);
    }

    void SetYBasis(const Vector3<T>& v) {
      M[0][1] = v.x;
      M[1][1] = v.y;
      M[2][1] = v.z;
    }
    Vector3<T> GetYBasis() const {
      return Vector3<T>(M[0][1], M[1][1], M[2][1]);
    }

    void SetZBasis(const Vector3<T>& v) {
      M[0][2] = v.x;
      M[1][2] = v.y;
      M[2][2] = v.z;
    }
    Vector3<T> GetZBasis() const {
      return Vector3<T>(M[0][2], M[1][2], M[2][2]);
    }

    T operator()(int i, int j) const {
      return M[i][j];
    }
    T& operator()(int i, int j) {
      return M[i][j];
    }

    bool operator==(const Matrix4& b) const {
      bool isEqual = true;
      for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
          isEqual &= (M[i][j] == b.M[i][j]);

      return isEqual;
    }

    Matrix4 operator+(const Matrix4& b) const {
      Matrix4 result(*this);
      result += b;
      return result;
    }

    Matrix4& operator+=(const Matrix4& b) {
      for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
          M[i][j] += b.M[i][j];
      return *this;
    }

    Matrix4 operator-(const Matrix4& b) const {
      Matrix4 result(*this);
      result -= b;
      return result;
    }

    Matrix4& operator-=(const Matrix4& b) {
      for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
          M[i][j] -= b.M[i][j];
      return *this;
    }

    // Multiplies two matrices into destination with minimum copying.
    static Matrix4& Multiply(Matrix4* d, const Matrix4& a, const Matrix4& b) {
      OVR_MATH_ASSERT((d != &a) && (d != &b));
      int i = 0;
      do {
        d->M[i][0] = a.M[i][0] * b.M[0][0] + a.M[i][1] * b.M[1][0] + a.M[i][2] * b.M[2][0] +
          a.M[i][3] * b.M[3][0];
        d->M[i][1] = a.M[i][0] * b.M[0][1] + a.M[i][1] * b.M[1][1] + a.M[i][2] * b.M[2][1] +
          a.M[i][3] * b.M[3][1];
        d->M[i][2] = a.M[i][0] * b.M[0][2] + a.M[i][1] * b.M[1][2] + a.M[i][2] * b.M[2][2] +
          a.M[i][3] * b.M[3][2];
        d->M[i][3] = a.M[i][0] * b.M[0][3] + a.M[i][1] * b.M[1][3] + a.M[i][2] * b.M[2][3] +
          a.M[i][3] * b.M[3][3];
      } while ((++i) < 4);

      return *d;
    }

    Matrix4 operator*(const Matrix4& b) const {
      Matrix4 result(Matrix4::NoInit);
      Multiply(&result, *this, b);
      return result;
    }

    Matrix4& operator*=(const Matrix4& b) {
      return Multiply(this, Matrix4(*this), b);
    }

    Vector4<T> operator*(const Vector4<T>& b) const {
      return Transform(b);
    }

    Matrix4 operator*(T s) const {
      Matrix4 result(*this);
      result *= s;
      return result;
    }

    Matrix4& operator*=(T s) {
      for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
          M[i][j] *= s;
      return *this;
    }

    Matrix4 operator/(T s) const {
      Matrix4 result(*this);
      result /= s;
      return result;
    }

    Matrix4& operator/=(T s) {
      for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
          M[i][j] /= s;
      return *this;
    }

    Vector3<T> Transform(const Vector3<T>& v) const {
      const T w = (M[3][0] * v.x + M[3][1] * v.y + M[3][2] * v.z + M[3][3]);
      OVR_MATH_ASSERT(fabs(w) >= Math<T>::SmallestNonDenormal());
      const T rcpW = T(1) / w;
      return Vector3<T>(
        (M[0][0] * v.x + M[0][1] * v.y + M[0][2] * v.z + M[0][3]) * rcpW,
        (M[1][0] * v.x + M[1][1] * v.y + M[1][2] * v.z + M[1][3]) * rcpW,
        (M[2][0] * v.x + M[2][1] * v.y + M[2][2] * v.z + M[2][3]) * rcpW);
    }

    Vector4<T> Transform(const Vector4<T>& v) const {
      return Vector4<T>(
        M[0][0] * v.x + M[0][1] * v.y + M[0][2] * v.z + M[0][3] * v.w,
        M[1][0] * v.x + M[1][1] * v.y + M[1][2] * v.z + M[1][3] * v.w,
        M[2][0] * v.x + M[2][1] * v.y + M[2][2] * v.z + M[2][3] * v.w,
        M[3][0] * v.x + M[3][1] * v.y + M[3][2] * v.z + M[3][3] * v.w);
    }

    Matrix4 Transposed() const {
      return Matrix4(
        M[0][0],
        M[1][0],
        M[2][0],
        M[3][0],
        M[0][1],
        M[1][1],
        M[2][1],
        M[3][1],
        M[0][2],
        M[1][2],
        M[2][2],
        M[3][2],
        M[0][3],
        M[1][3],
        M[2][3],
        M[3][3]);
    }

    void Transpose() {
      *this = Transposed();
    }

    T SubDet(const size_t* rows, const size_t* cols) const {
      return M[rows[0]][cols[0]] *
        (M[rows[1]][cols[1]] * M[rows[2]][cols[2]] -
          M[rows[1]][cols[2]] * M[rows[2]][cols[1]]) -
        M[rows[0]][cols[1]] *
        (M[rows[1]][cols[0]] * M[rows[2]][cols[2]] -
          M[rows[1]][cols[2]] * M[rows[2]][cols[0]]) +
        M[rows[0]][cols[2]] *
        (M[rows[1]][cols[0]] * M[rows[2]][cols[1]] - M[rows[1]][cols[1]] * M[rows[2]][cols[0]]);
    }

    T Cofactor(size_t I, size_t J) const {
      const size_t indices[4][3] = { {1, 2, 3}, {0, 2, 3}, {0, 1, 3}, {0, 1, 2} };
      return ((I + J) & 1) ? -SubDet(indices[I], indices[J]) : SubDet(indices[I], indices[J]);
    }

    T Determinant() const {
      return M[0][0] * Cofactor(0, 0) + M[0][1] * Cofactor(0, 1) + M[0][2] * Cofactor(0, 2) +
        M[0][3] * Cofactor(0, 3);
    }

    Matrix4 Adjugated() const {
      return Matrix4(
        Cofactor(0, 0),
        Cofactor(1, 0),
        Cofactor(2, 0),
        Cofactor(3, 0),
        Cofactor(0, 1),
        Cofactor(1, 1),
        Cofactor(2, 1),
        Cofactor(3, 1),
        Cofactor(0, 2),
        Cofactor(1, 2),
        Cofactor(2, 2),
        Cofactor(3, 2),
        Cofactor(0, 3),
        Cofactor(1, 3),
        Cofactor(2, 3),
        Cofactor(3, 3));
    }

    Matrix4 Inverted() const {
      T det = Determinant();
      OVR_MATH_ASSERT(fabs(det) >= Math<T>::SmallestNonDenormal());
      return Adjugated() * (T(1) / det);
    }

    void Invert() {
      *this = Inverted();
    }

    // This is more efficient than general inverse, but ONLY works
    // correctly if it is a homogeneous transform matrix (rot + trans)
    Matrix4 InvertedHomogeneousTransform() const {
      // Make the inverse rotation matrix
      Matrix4 rinv = this->Transposed();
      rinv.M[3][0] = rinv.M[3][1] = rinv.M[3][2] = T(0);
      // Make the inverse translation matrix
      Vector3<T> tvinv(-M[0][3], -M[1][3], -M[2][3]);
      Matrix4 tinv = Matrix4::Translation(tvinv);
      return rinv * tinv; // "untranslate", then "unrotate"
    }

    // This is more efficient than general inverse, but ONLY works
    // correctly if it is a homogeneous transform matrix (rot + trans)
    void InvertHomogeneousTransform() {
      *this = InvertedHomogeneousTransform();
    }

    // Matrix to Euler Angles conversion
    // a,b,c, are the YawPitchRoll angles to be returned
    // rotation a around axis A1
    // is followed by rotation b around axis A2
    // is followed by rotation c around axis A3
    // rotations are CCW or CW (D) in LH or RH coordinate system (S)
    template <Axis A1, Axis A2, Axis A3, RotateDirection Denum, HandedSystem Senum>
    void ToEulerAngles(T* a, T* b, T* c) const {
      OVR_MATH_STATIC_ASSERT(
        (A1 != A2) && (A2 != A3) && (A1 != A3), "(A1 != A2) && (A2 != A3) && (A1 != A3)");
      const T D = static_cast<T>(Denum);
      const T S = static_cast<T>(Senum);

      T psign = T(-1);
      if (((A1 + 1) % 3 == A2) && ((A2 + 1) % 3 == A3)) // Determine whether even permutation
        psign = T(1);

      T pm = psign * M[A1][A3];
      T singularityRadius = Math<T>::SingularityRadius();
      if (pm < T(-1) + singularityRadius) { // South pole singularity
        *a = T(0);
        *b = -S * D * ((T)MATH_DOUBLE_PIOVER2);
        *c = S * D * static_cast<T>(atan2(psign * M[A2][A1], M[A2][A2]));
      }
      else if (pm > T(1) - singularityRadius) { // North pole singularity
        *a = T(0);
        *b = S * D * ((T)MATH_DOUBLE_PIOVER2);
        *c = S * D * static_cast<T>(atan2(psign * M[A2][A1], M[A2][A2]));
      }
      else { // Normal case (nonsingular)
        *a = S * D * static_cast<T>(atan2(-psign * M[A2][A3], M[A3][A3]));
        *b = S * D * static_cast<T>(asin(pm));
        *c = S * D * static_cast<T>(atan2(-psign * M[A1][A2], M[A1][A1]));
      }
    }

    // Matrix to Euler Angles conversion
    // a,b,c, are the YawPitchRoll angles to be returned
    // rotation a around axis A1
    // is followed by rotation b around axis A2
    // is followed by rotation c around axis A1
    // rotations are CCW or CW (D) in LH or RH coordinate system (S)
    template <Axis A1, Axis A2, RotateDirection Denum, HandedSystem Senum>
    void ToEulerAnglesABA(T* a, T* b, T* c) const {
      OVR_MATH_STATIC_ASSERT(A1 != A2, "A1 != A2");
      const T D = static_cast<T>(Denum);
      const T S = static_cast<T>(Senum);

      // Determine the axis that was not supplied
      int m = 3 - A1 - A2;

      T psign = T(-1);
      if ((A1 + 1) % 3 == A2) // Determine whether even permutation
        psign = T(1);

      T c2 = M[A1][A1];
      T singularityRadius = Math<T>::SingularityRadius();
      if (c2 < T(-1) + singularityRadius) { // South pole singularity
        *a = T(0);
        *b = S * D * ((T)MATH_DOUBLE_PI);
        *c = S * D * atan2(-psign * M[A2][m], M[A2][A2]);
      }
      else if (c2 > T(1) - singularityRadius) { // North pole singularity
        *a = T(0);
        *b = T(0);
        *c = S * D * atan2(-psign * M[A2][m], M[A2][A2]);
      }
      else { // Normal case (nonsingular)
        *a = S * D * atan2(M[A2][A1], -psign * M[m][A1]);
        *b = S * D * acos(c2);
        *c = S * D * atan2(M[A1][A2], psign * M[A1][m]);
      }
    }

    // Creates a matrix that converts the vertices from one coordinate system
    // to another.
    static Matrix4 AxisConversion(const WorldAxes& to, const WorldAxes& from) {
      // Holds axis values from the 'to' structure
      int toArray[3] = { to.XAxis, to.YAxis, to.ZAxis };

      // The inverse of the toArray
      int inv[4];
      inv[0] = inv[abs(to.XAxis)] = 0;
      inv[abs(to.YAxis)] = 1;
      inv[abs(to.ZAxis)] = 2;

      Matrix4 m(0, 0, 0, 0, 0, 0, 0, 0, 0);

      // Only three values in the matrix need to be changed to 1 or -1.
      m.M[inv[abs(from.XAxis)]][0] = T(from.XAxis / toArray[inv[abs(from.XAxis)]]);
      m.M[inv[abs(from.YAxis)]][1] = T(from.YAxis / toArray[inv[abs(from.YAxis)]]);
      m.M[inv[abs(from.ZAxis)]][2] = T(from.ZAxis / toArray[inv[abs(from.ZAxis)]]);
      return m;
    }

    // Creates a matrix for translation by vector
    static Matrix4 Translation(const Vector3<T>& v) {
      Matrix4 t;
      t.M[0][3] = v.x;
      t.M[1][3] = v.y;
      t.M[2][3] = v.z;
      return t;
    }

    // Creates a matrix for translation by vector
    static Matrix4 Translation(T x, T y, T z = T(0)) {
      Matrix4 t;
      t.M[0][3] = x;
      t.M[1][3] = y;
      t.M[2][3] = z;
      return t;
    }

    // Sets the translation part
    void SetTranslation(const Vector3<T>& v) {
      M[0][3] = v.x;
      M[1][3] = v.y;
      M[2][3] = v.z;
    }

    Vector3<T> GetTranslation() const {
      return Vector3<T>(M[0][3], M[1][3], M[2][3]);
    }

    // Creates a matrix for scaling by vector
    static Matrix4 Scaling(const Vector3<T>& v) {
      Matrix4 t;
      t.M[0][0] = v.x;
      t.M[1][1] = v.y;
      t.M[2][2] = v.z;
      return t;
    }

    // Creates a matrix for scaling by vector
    static Matrix4 Scaling(T x, T y, T z) {
      Matrix4 t;
      t.M[0][0] = x;
      t.M[1][1] = y;
      t.M[2][2] = z;
      return t;
    }

    // Creates a matrix for scaling by constant
    static Matrix4 Scaling(T s) {
      Matrix4 t;
      t.M[0][0] = s;
      t.M[1][1] = s;
      t.M[2][2] = s;
      return t;
    }

    // Simple L1 distance in R^12
    T Distance(const Matrix4& m2) const {
      T d = fabs(M[0][0] - m2.M[0][0]) + fabs(M[0][1] - m2.M[0][1]);
      d += fabs(M[0][2] - m2.M[0][2]) + fabs(M[0][3] - m2.M[0][3]);
      d += fabs(M[1][0] - m2.M[1][0]) + fabs(M[1][1] - m2.M[1][1]);
      d += fabs(M[1][2] - m2.M[1][2]) + fabs(M[1][3] - m2.M[1][3]);
      d += fabs(M[2][0] - m2.M[2][0]) + fabs(M[2][1] - m2.M[2][1]);
      d += fabs(M[2][2] - m2.M[2][2]) + fabs(M[2][3] - m2.M[2][3]);
      d += fabs(M[3][0] - m2.M[3][0]) + fabs(M[3][1] - m2.M[3][1]);
      d += fabs(M[3][2] - m2.M[3][2]) + fabs(M[3][3] - m2.M[3][3]);
      return d;
    }

    // Creates a rotation matrix rotating around the X axis by 'angle' radians.
    // Just for quick testing.  Not for final API.  Need to remove case.
    static Matrix4 RotationAxis(Axis A, T angle, RotateDirection dEnum, HandedSystem sEnum) {
      const T d = static_cast<T>(dEnum);
      const T s = static_cast<T>(sEnum);
      T sina = s * d * sin(angle);
      T cosa = cos(angle);

      switch (A) {
      case Axis_X:
        return Matrix4(1, 0, 0, 0, cosa, -sina, 0, sina, cosa);
      case Axis_Y:
        return Matrix4(cosa, 0, sina, 0, 1, 0, -sina, 0, cosa);
      case Axis_Z:
        return Matrix4(cosa, -sina, 0, sina, cosa, 0, 0, 0, 1);
      default:
        return Matrix4();
      }
    }

    // Creates a rotation matrix rotating around the X axis by 'angle' radians.
    // Rotation direction is depends on the coordinate system:
    // RHS (Oculus default): Positive angle values rotate Counter-clockwise (CCW),
    //                        while looking in the negative axis direction. This is the
    //                        same as looking down from positive axis values towards origin.
    // LHS: Positive angle values rotate clock-wise (CW), while looking in the
    //       negative axis direction.
    static Matrix4 RotationX(T angle) {
      T sina = (T)sin(angle);
      T cosa = (T)cos(angle);
      return Matrix4(1, 0, 0, 0, cosa, -sina, 0, sina, cosa);
    }

    // Creates a rotation matrix rotating around the Y axis by 'angle' radians.
    // Rotation direction is depends on the coordinate system:
    //  RHS (Oculus default): Positive angle values rotate Counter-clockwise (CCW),
    //                        while looking in the negative axis direction. This is the
    //                        same as looking down from positive axis values towards origin.
    //  LHS: Positive angle values rotate clock-wise (CW), while looking in the
    //       negative axis direction.
    static Matrix4 RotationY(T angle) {
      T sina = (T)sin(angle);
      T cosa = (T)cos(angle);
      return Matrix4(cosa, 0, sina, 0, 1, 0, -sina, 0, cosa);
    }

    // Creates a rotation matrix rotating around the Z axis by 'angle' radians.
    // Rotation direction is depends on the coordinate system:
    //  RHS (Oculus default): Positive angle values rotate Counter-clockwise (CCW),
    //                        while looking in the negative axis direction. This is the
    //                        same as looking down from positive axis values towards origin.
    //  LHS: Positive angle values rotate clock-wise (CW), while looking in the
    //       negative axis direction.
    static Matrix4 RotationZ(T angle) {
      T sina = (T)sin(angle);
      T cosa = (T)cos(angle);
      return Matrix4(cosa, -sina, 0, sina, cosa, 0, 0, 0, 1);
    }

    // MERGE_MOBILE_SDK
    static Matrix4 RotationAxisAngle(const Vector3<T>& axis, T angle) {
      T x = axis.x;
      T y = axis.y;
      T z = axis.z;
      T c = cos(angle);
      T s = sin(angle);
      T t = 1 - c;
      Matrix4 m(
        t * x * x + c,
        t * x * y - z * s,
        t * x * z + y * s,
        t * x * y + z * s,
        t * y * y + c,
        t * y * z - x * s,
        t * x * z - y * s,
        t * y * z + x * s,
        t * z * z + c);
      return m;
    }
    // MERGE_MOBILE_SDK

    // LookAtRH creates a View transformation matrix for right-handed coordinate system.
    // The resulting matrix points camera from 'eye' towards 'at' direction, with 'up'
    // specifying the up vector. The resulting matrix should be used with PerspectiveRH
    // projection.
    static Matrix4 LookAtRH(const Vector3<T>& eye, const Vector3<T>& at, const Vector3<T>& up) {
      // FIXME: this fails when looking straight up, should probably at least assert
      Vector3<T> z = (eye - at).Normalized(); // Forward
      Vector3<T> x = up.Cross(z).Normalized(); // Right
      Vector3<T> y = z.Cross(x);

      Matrix4 m(
        x.x,
        x.y,
        x.z,
        -(x.Dot(eye)),
        y.x,
        y.y,
        y.z,
        -(y.Dot(eye)),
        z.x,
        z.y,
        z.z,
        -(z.Dot(eye)),
        0,
        0,
        0,
        1);
      return m;
    }

    // LookAtLH creates a View transformation matrix for left-handed coordinate system.
    // The resulting matrix points camera from 'eye' towards 'at' direction, with 'up'
    // specifying the up vector.
    static Matrix4 LookAtLH(const Vector3<T>& eye, const Vector3<T>& at, const Vector3<T>& up) {
      // FIXME: this fails when looking straight up, should probably at least assert
      Vector3<T> z = (at - eye).Normalized(); // Forward
      Vector3<T> x = up.Cross(z).Normalized(); // Right
      Vector3<T> y = z.Cross(x);

      Matrix4 m(
        x.x,
        x.y,
        x.z,
        -(x.Dot(eye)),
        y.x,
        y.y,
        y.z,
        -(y.Dot(eye)),
        z.x,
        z.y,
        z.z,
        -(z.Dot(eye)),
        0,
        0,
        0,
        1);
      return m;
    }

    // MERGE_MOBILE_SDK
    static Matrix4 CreateFromBasisVectors(Vector3<T> const& zBasis, Vector3<T> const& up) {
      OVR_MATH_ASSERT(zBasis.IsNormalized());
      OVR_MATH_ASSERT(up.IsNormalized());
      T dot = zBasis.Dot(up);
      if (dot < (T)-0.9999 || dot >(T)0.9999) {
        // z basis cannot be parallel to the specified up
        OVR_MATH_ASSERT(dot >= (T)-0.9999 || dot <= (T)0.9999);
        return Matrix4<T>();
      }

      Vector3<T> xBasis = up.Cross(zBasis);
      xBasis.Normalize();

      Vector3<T> yBasis = zBasis.Cross(xBasis);
      // no need to normalize yBasis because xBasis and zBasis must already be orthogonal

      return Matrix4<T>(
        xBasis.x,
        yBasis.x,
        zBasis.x,
        (T)0,
        xBasis.y,
        yBasis.y,
        zBasis.y,
        (T)0,
        xBasis.z,
        yBasis.z,
        zBasis.z,
        (T)0,
        (T)0,
        (T)0,
        (T)0,
        (T)1);
    }
    // MERGE_MOBILE_SDK

    // PerspectiveRH creates a right-handed perspective projection matrix that can be
    // used with the Oculus sample renderer.
    //  yfov   - Specifies vertical field of view in radians.
    //  aspect - Screen aspect ration, which is usually width/height for square pixels.
    //           Note that xfov = yfov * aspect.
    //  znear  - Absolute value of near Z clipping clipping range.
    //  zfar   - Absolute value of far  Z clipping clipping range (larger then near).
    // Even though RHS usually looks in the direction of negative Z, positive values
    // are expected for znear and zfar.
    static Matrix4 PerspectiveRH(T yfov, T aspect, T znear, T zfar) {
      Matrix4 m;
      T tanHalfFov = static_cast<T>(tan(yfov * T(0.5)));

      m.M[0][0] = T(1) / (aspect * tanHalfFov);
      m.M[1][1] = T(1) / tanHalfFov;
      m.M[2][2] = zfar / (znear - zfar);
      m.M[3][2] = T(-1);
      m.M[2][3] = (zfar * znear) / (znear - zfar);
      m.M[3][3] = T(0);

      // Note: Post-projection matrix result assumes Left-Handed coordinate system,
      //       with Y up, X right and Z forward. This supports positive z-buffer values.
      // This is the case even for RHS coordinate input.
      return m;
    }

    // PerspectiveLH creates a left-handed perspective projection matrix that can be
    // used with the Oculus sample renderer.
    //  yfov   - Specifies vertical field of view in radians.
    //  aspect - Screen aspect ration, which is usually width/height for square pixels.
    //           Note that xfov = yfov * aspect.
    //  znear  - Absolute value of near Z clipping clipping range.
    //  zfar   - Absolute value of far  Z clipping clipping range (larger then near).
    static Matrix4 PerspectiveLH(T yfov, T aspect, T znear, T zfar) {
      Matrix4 m;
      T tanHalfFov = static_cast<T>(tan(yfov * T(0.5)));

      m.M[0][0] = T(1) / (aspect * tanHalfFov);
      m.M[1][1] = T(1) / tanHalfFov;
      // m.M[2][2] = zfar / (znear - zfar);
      m.M[2][2] = zfar / (zfar - znear);
      m.M[3][2] = T(-1);
      m.M[2][3] = (zfar * znear) / (znear - zfar);
      m.M[3][3] = T(0);

      // Note: Post-projection matrix result assumes Left-Handed coordinate system,
      //       with Y up, X right and Z forward. This supports positive z-buffer values.
      // This is the case even for RHS coordinate input.
      return m;
    }

    static Matrix4 Ortho2D(T w, T h) {
      Matrix4 m;
      m.M[0][0] = T(2.0) / w;
      m.M[1][1] = T(-2.0) / h;
      m.M[0][3] = T(-1.0);
      m.M[1][3] = T(1.0);
      m.M[2][2] = T(0);
      return m;
    }
  };

  // Implicit instantiation
  template <typename T>
  const Matrix4<T> Matrix4<T>::IdentityValue;

  typedef Matrix4<float> Matrix4f;
  typedef Matrix4<double> Matrix4d;

  //-------------------------------------------------------------------------------------
  // ***** Matrix3
  //
  // Matrix3 is a 3x3 matrix used for representing a rotation matrix.
  // The matrix is stored in row-major order in memory, meaning that values
  // of the first row are stored before the next one.
  //
  // The arrangement of the matrix is chosen to be in Right-Handed
  // coordinate system and counterclockwise rotations when looking down
  // the axis
  //
  // Transformation Order:
  //   - Transformations are applied from right to left, so the expression
  //     M1 * M2 * M3 * V means that the vector V is transformed by M3 first,
  //     followed by M2 and M1.
  //
  // Coordinate system: Right Handed
  //
  // Rotations: Counterclockwise when looking down the axis. All angles are in radians.

  template <class T>
  class Matrix3 {
    static const Matrix3 IdentityValue;

  public:
    typedef T ElementType;
    static const size_t Dimension = 3;

    T M[3][3];

    enum NoInitType { NoInit };

    // Construct with no memory initialization.
    Matrix3(NoInitType) {}

    // By default, we construct identity matrix.
    Matrix3() {
      M[0][0] = M[1][1] = M[2][2] = T(1);
      M[0][1] = M[1][0] = M[2][0] = T(0);
      M[0][2] = M[1][2] = M[2][1] = T(0);
    }

    Matrix3(const Matrix3& b) = default;

    Matrix3(T m11, T m12, T m13, T m21, T m22, T m23, T m31, T m32, T m33) {
      M[0][0] = m11;
      M[0][1] = m12;
      M[0][2] = m13;
      M[1][0] = m21;
      M[1][1] = m22;
      M[1][2] = m23;
      M[2][0] = m31;
      M[2][1] = m32;
      M[2][2] = m33;
    }

    // Construction from X, Y, Z basis vectors
    Matrix3(const Vector3<T>& xBasis, const Vector3<T>& yBasis, const Vector3<T>& zBasis) {
      M[0][0] = xBasis.x;
      M[0][1] = yBasis.x;
      M[0][2] = zBasis.x;
      M[1][0] = xBasis.y;
      M[1][1] = yBasis.y;
      M[1][2] = zBasis.y;
      M[2][0] = xBasis.z;
      M[2][1] = yBasis.z;
      M[2][2] = zBasis.z;
    }

    explicit Matrix3(const Quat<T>& q) {
      OVR_MATH_ASSERT(q.IsNormalized());
      const T tx = q.x + q.x, ty = q.y + q.y, tz = q.z + q.z;
      const T twx = q.w * tx, twy = q.w * ty, twz = q.w * tz;
      const T txx = q.x * tx, txy = q.x * ty, txz = q.x * tz;
      const T tyy = q.y * ty, tyz = q.y * tz, tzz = q.z * tz;
      M[0][0] = T(1) - (tyy + tzz);
      M[0][1] = txy - twz;
      M[0][2] = txz + twy;
      M[1][0] = txy + twz;
      M[1][1] = T(1) - (txx + tzz);
      M[1][2] = tyz - twx;
      M[2][0] = txz - twy;
      M[2][1] = tyz + twx;
      M[2][2] = T(1) - (txx + tyy);
    }

    inline explicit Matrix3(T s) {
      M[0][0] = M[1][1] = M[2][2] = s;
      M[0][1] = M[0][2] = M[1][0] = M[1][2] = M[2][0] = M[2][1] = T(0);
    }

    Matrix3(T m11, T m22, T m33) {
      M[0][0] = m11;
      M[0][1] = T(0);
      M[0][2] = T(0);
      M[1][0] = T(0);
      M[1][1] = m22;
      M[1][2] = T(0);
      M[2][0] = T(0);
      M[2][1] = T(0);
      M[2][2] = m33;
    }

    explicit Matrix3(const Matrix3<typename Math<T>::OtherFloatType>& src) {
      for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
          M[i][j] = (T)src.M[i][j];
    }

    // C-interop support.
    Matrix3(const typename CompatibleTypes<Matrix3<T>>::Type& s) {
      OVR_MATH_STATIC_ASSERT(sizeof(s) == sizeof(Matrix3), "sizeof(s) == sizeof(Matrix3)");
      memcpy(M, s.M, sizeof(M));
    }

    operator const typename CompatibleTypes<Matrix3<T>>::Type() const {
      typename CompatibleTypes<Matrix3<T>>::Type result;
      OVR_MATH_STATIC_ASSERT(
        sizeof(result) == sizeof(Matrix3), "sizeof(result) == sizeof(Matrix3)");
      memcpy(result.M, M, sizeof(M));
      return result;
    }

    T operator()(int i, int j) const {
      return M[i][j];
    }
    T& operator()(int i, int j) {
      return M[i][j];
    }

    void ToString(char* dest, size_t destsize) const {
      size_t pos = 0;
      for (int r = 0; r < 3; r++) {
        for (int c = 0; c < 3; c++)
          pos += OVRMath_sprintf(dest + pos, destsize - pos, "%g ", M[r][c]);
      }
    }

    static Matrix3 FromString(const char* src) {
      Matrix3 result;
      if (src) {
        for (int r = 0; r < 3; r++) {
          for (int c = 0; c < 3; c++) {
            result.M[r][c] = (T)atof(src);
            while (*src && *src != ' ')
              src++;
            while (*src && *src == ' ')
              src++;
          }
        }
      }
      return result;
    }

    static const Matrix3& Identity() {
      return IdentityValue;
    }

    void SetIdentity() {
      M[0][0] = M[1][1] = M[2][2] = T(1);
      M[0][1] = M[1][0] = M[2][0] = T(0);
      M[0][2] = M[1][2] = M[2][1] = T(0);
    }

    static Matrix3 Diagonal(T m00, T m11, T m22) {
      return Matrix3(m00, 0, 0, 0, m11, 0, 0, 0, m22);
    }
    static Matrix3 Diagonal(const Vector3<T>& v) {
      return Diagonal(v.x, v.y, v.z);
    }

    T Trace() const {
      return M[0][0] + M[1][1] + M[2][2];
    }

    bool operator==(const Matrix3& b) const {
      bool isEqual = true;
      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++)
          isEqual &= (M[i][j] == b.M[i][j]);
      }

      return isEqual;
    }

    Matrix3 operator+(const Matrix3& b) const {
      Matrix3<T> result(*this);
      result += b;
      return result;
    }

    Matrix3& operator+=(const Matrix3& b) {
      for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
          M[i][j] += b.M[i][j];
      return *this;
    }

    void operator=(const Matrix3& b) {
      for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
          M[i][j] = b.M[i][j];
    }

    Matrix3 operator-(const Matrix3& b) const {
      Matrix3 result(*this);
      result -= b;
      return result;
    }

    Matrix3& operator-=(const Matrix3& b) {
      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++)
          M[i][j] -= b.M[i][j];
      }

      return *this;
    }

    // Multiplies two matrices into destination with minimum copying.
    static Matrix3& Multiply(Matrix3* d, const Matrix3& a, const Matrix3& b) {
      OVR_MATH_ASSERT((d != &a) && (d != &b));
      int i = 0;
      do {
        d->M[i][0] = a.M[i][0] * b.M[0][0] + a.M[i][1] * b.M[1][0] + a.M[i][2] * b.M[2][0];
        d->M[i][1] = a.M[i][0] * b.M[0][1] + a.M[i][1] * b.M[1][1] + a.M[i][2] * b.M[2][1];
        d->M[i][2] = a.M[i][0] * b.M[0][2] + a.M[i][1] * b.M[1][2] + a.M[i][2] * b.M[2][2];
      } while ((++i) < 3);

      return *d;
    }

    Matrix3 operator*(const Matrix3& b) const {
      Matrix3 result(Matrix3::NoInit);
      Multiply(&result, *this, b);
      return result;
    }

    Matrix3& operator*=(const Matrix3& b) {
      return Multiply(this, Matrix3(*this), b);
    }

    Matrix3 operator*(T s) const {
      Matrix3 result(*this);
      result *= s;
      return result;
    }

    Matrix3& operator*=(T s) {
      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++)
          M[i][j] *= s;
      }

      return *this;
    }

    Vector3<T> operator*(const Vector3<T>& b) const {
      Vector3<T> result;
      result.x = M[0][0] * b.x + M[0][1] * b.y + M[0][2] * b.z;
      result.y = M[1][0] * b.x + M[1][1] * b.y + M[1][2] * b.z;
      result.z = M[2][0] * b.x + M[2][1] * b.y + M[2][2] * b.z;

      return result;
    }

    Matrix3 operator/(T s) const {
      Matrix3 result(*this);
      result /= s;
      return result;
    }

    Matrix3& operator/=(T s) {
      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++)
          M[i][j] /= s;
      }

      return *this;
    }

    Vector2<T> Transform(const Vector2<T>& v) const {
      const T rcpZ = T(1) / (M[2][0] * v.x + M[2][1] * v.y + M[2][2]);
      return Vector2<T>(
        (M[0][0] * v.x + M[0][1] * v.y + M[0][2]) * rcpZ,
        (M[1][0] * v.x + M[1][1] * v.y + M[1][2]) * rcpZ);
    }

    Vector3<T> Transform(const Vector3<T>& v) const {
      return Vector3<T>(
        M[0][0] * v.x + M[0][1] * v.y + M[0][2] * v.z,
        M[1][0] * v.x + M[1][1] * v.y + M[1][2] * v.z,
        M[2][0] * v.x + M[2][1] * v.y + M[2][2] * v.z);
    }

    Matrix3 Transposed() const {
      return Matrix3(
        M[0][0], M[1][0], M[2][0], M[0][1], M[1][1], M[2][1], M[0][2], M[1][2], M[2][2]);
    }

    void Transpose() {
      *this = Transposed();
    }

    T SubDet(const size_t* rows, const size_t* cols) const {
      return M[rows[0]][cols[0]] *
        (M[rows[1]][cols[1]] * M[rows[2]][cols[2]] -
          M[rows[1]][cols[2]] * M[rows[2]][cols[1]]) -
        M[rows[0]][cols[1]] *
        (M[rows[1]][cols[0]] * M[rows[2]][cols[2]] -
          M[rows[1]][cols[2]] * M[rows[2]][cols[0]]) +
        M[rows[0]][cols[2]] *
        (M[rows[1]][cols[0]] * M[rows[2]][cols[1]] - M[rows[1]][cols[1]] * M[rows[2]][cols[0]]);
    }

    // M += a*b.t()
    inline void Rank1Add(const Vector3<T>& a, const Vector3<T>& b) {
      M[0][0] += a.x * b.x;
      M[0][1] += a.x * b.y;
      M[0][2] += a.x * b.z;
      M[1][0] += a.y * b.x;
      M[1][1] += a.y * b.y;
      M[1][2] += a.y * b.z;
      M[2][0] += a.z * b.x;
      M[2][1] += a.z * b.y;
      M[2][2] += a.z * b.z;
    }

    // M -= a*b.t()
    inline void Rank1Sub(const Vector3<T>& a, const Vector3<T>& b) {
      M[0][0] -= a.x * b.x;
      M[0][1] -= a.x * b.y;
      M[0][2] -= a.x * b.z;
      M[1][0] -= a.y * b.x;
      M[1][1] -= a.y * b.y;
      M[1][2] -= a.y * b.z;
      M[2][0] -= a.z * b.x;
      M[2][1] -= a.z * b.y;
      M[2][2] -= a.z * b.z;
    }

    inline Vector3<T> Col(int c) const {
      return Vector3<T>(M[0][c], M[1][c], M[2][c]);
    }

    inline Vector3<T> Row(int r) const {
      return Vector3<T>(M[r][0], M[r][1], M[r][2]);
    }

    inline Vector3<T> GetColumn(int c) const {
      return Vector3<T>(M[0][c], M[1][c], M[2][c]);
    }

    inline Vector3<T> GetRow(int r) const {
      return Vector3<T>(M[r][0], M[r][1], M[r][2]);
    }

    inline void SetColumn(int c, const Vector3<T>& v) {
      M[0][c] = v.x;
      M[1][c] = v.y;
      M[2][c] = v.z;
    }

    inline void SetRow(int r, const Vector3<T>& v) {
      M[r][0] = v.x;
      M[r][1] = v.y;
      M[r][2] = v.z;
    }

    inline T Determinant() const {
      const Matrix3<T>& m = *this;
      T d;

      d = m.M[0][0] * (m.M[1][1] * m.M[2][2] - m.M[1][2] * m.M[2][1]);
      d -= m.M[0][1] * (m.M[1][0] * m.M[2][2] - m.M[1][2] * m.M[2][0]);
      d += m.M[0][2] * (m.M[1][0] * m.M[2][1] - m.M[1][1] * m.M[2][0]);

      return d;
    }

    inline Matrix3<T> Inverse() const {
      Matrix3<T> a;
      const Matrix3<T>& m = *this;
      T d = Determinant();

      OVR_MATH_ASSERT(fabs(d) >= Math<T>::SmallestNonDenormal());
      T s = T(1) / d;

      a.M[0][0] = s * (m.M[1][1] * m.M[2][2] - m.M[1][2] * m.M[2][1]);
      a.M[1][0] = s * (m.M[1][2] * m.M[2][0] - m.M[1][0] * m.M[2][2]);
      a.M[2][0] = s * (m.M[1][0] * m.M[2][1] - m.M[1][1] * m.M[2][0]);

      a.M[0][1] = s * (m.M[0][2] * m.M[2][1] - m.M[0][1] * m.M[2][2]);
      a.M[1][1] = s * (m.M[0][0] * m.M[2][2] - m.M[0][2] * m.M[2][0]);
      a.M[2][1] = s * (m.M[0][1] * m.M[2][0] - m.M[0][0] * m.M[2][1]);

      a.M[0][2] = s * (m.M[0][1] * m.M[1][2] - m.M[0][2] * m.M[1][1]);
      a.M[1][2] = s * (m.M[0][2] * m.M[1][0] - m.M[0][0] * m.M[1][2]);
      a.M[2][2] = s * (m.M[0][0] * m.M[1][1] - m.M[0][1] * m.M[1][0]);

      return a;
    }

    // Outer Product of two column vectors: a * b.Transpose()
    static Matrix3 OuterProduct(const Vector3<T>& a, const Vector3<T>& b) {
      return Matrix3(
        a.x * b.x,
        a.x * b.y,
        a.x * b.z,
        a.y * b.x,
        a.y * b.y,
        a.y * b.z,
        a.z * b.x,
        a.z * b.y,
        a.z * b.z);
    }

    // Vector cross product as a premultiply matrix:
    // L.Cross(R) = LeftCrossAsMatrix(L) * R
    static Matrix3 LeftCrossAsMatrix(const Vector3<T>& L) {
      return Matrix3(T(0), -L.z, +L.y, +L.z, T(0), -L.x, -L.y, +L.x, T(0));
    }

    // Vector cross product as a premultiply matrix:
    // L.Cross(R) = RightCrossAsMatrix(R) * L
    static Matrix3 RightCrossAsMatrix(const Vector3<T>& R) {
      return Matrix3(T(0), +R.z, -R.y, -R.z, T(0), +R.x, +R.y, -R.x, T(0));
    }

    // Angle in radians of a rotation matrix
    // Uses identity trace(a) = 2*cos(theta) + 1
    T Angle() const {
      return Acos((Trace() - T(1)) * T(0.5));
    }

    // Angle in radians between two rotation matrices
    T Angle(const Matrix3& b) const {
      // Compute trace of (this->Transposed() * b)
      // This works out to sum of products of elements.
      T trace = T(0);
      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
          trace += M[i][j] * b.M[i][j];
        }
      }
      return Acos((trace - T(1)) * T(0.5));
    }
  };

  // Implicit instantiation
  template <typename T>
  const Matrix3<T> Matrix3<T>::IdentityValue;

  typedef Matrix3<float> Matrix3f;
  typedef Matrix3<double> Matrix3d;

  //-------------------------------------------------------------------------------------
  // ***** Matrix2

  template <class T>
  class Matrix2 {
  public:
    typedef T ElementType;
    static const size_t Dimension = 2;

    T M[2][2];

    enum NoInitType { NoInit };

    // Construct with no memory initialization.
    Matrix2(NoInitType) {}

    // By default, we construct identity matrix.
    Matrix2() {
      M[0][0] = M[1][1] = T(1);
      M[0][1] = M[1][0] = T(0);
    }

    Matrix2(T m11, T m12, T m21, T m22) {
      M[0][0] = m11;
      M[0][1] = m12;
      M[1][0] = m21;
      M[1][1] = m22;
    }

    // Construction from X, Y basis vectors
    Matrix2(const Vector2<T>& xBasis, const Vector2<T>& yBasis) {
      M[0][0] = xBasis.x;
      M[0][1] = yBasis.x;
      M[1][0] = xBasis.y;
      M[1][1] = yBasis.y;
    }

    explicit Matrix2(T s) {
      M[0][0] = M[1][1] = s;
      M[0][1] = M[1][0] = T(0);
    }

    Matrix2(T m11, T m22) {
      M[0][0] = m11;
      M[0][1] = T(0);
      M[1][0] = T(0);
      M[1][1] = m22;
    }

    explicit Matrix2(const Matrix2<typename Math<T>::OtherFloatType>& src) {
      M[0][0] = T(src.M[0][0]);
      M[0][1] = T(src.M[0][1]);
      M[1][0] = T(src.M[1][0]);
      M[1][1] = T(src.M[1][1]);
    }

    // C-interop support
    Matrix2(const typename CompatibleTypes<Matrix2<T>>::Type& s) {
      OVR_MATH_STATIC_ASSERT(sizeof(s) == sizeof(Matrix2), "sizeof(s) == sizeof(Matrix2)");
      memcpy(M, s.M, sizeof(M));
    }

    operator const typename CompatibleTypes<Matrix2<T>>::Type() const {
      typename CompatibleTypes<Matrix2<T>>::Type result;
      OVR_MATH_STATIC_ASSERT(
        sizeof(result) == sizeof(Matrix2), "sizeof(result) == sizeof(Matrix2)");
      memcpy(result.M, M, sizeof(M));
      return result;
    }

    T operator()(int i, int j) const {
      return M[i][j];
    }
    T& operator()(int i, int j) {
      return M[i][j];
    }
    const T* operator[](int i) const {
      return M[i];
    }
    T* operator[](int i) {
      return M[i];
    }

    static Matrix2 Identity() {
      return Matrix2();
    }

    void SetIdentity() {
      M[0][0] = M[1][1] = T(1);
      M[0][1] = M[1][0] = T(0);
    }

    static Matrix2 Diagonal(T m00, T m11) {
      return Matrix2(m00, m11);
    }
    static Matrix2 Diagonal(const Vector2<T>& v) {
      return Matrix2(v.x, v.y);
    }

    T Trace() const {
      return M[0][0] + M[1][1];
    }

    bool operator==(const Matrix2& b) const {
      return M[0][0] == b.M[0][0] && M[0][1] == b.M[0][1] && M[1][0] == b.M[1][0] &&
        M[1][1] == b.M[1][1];
    }

    Matrix2 operator+(const Matrix2& b) const {
      return Matrix2(
        M[0][0] + b.M[0][0], M[0][1] + b.M[0][1], M[1][0] + b.M[1][0], M[1][1] + b.M[1][1]);
    }

    Matrix2& operator+=(const Matrix2& b) {
      M[0][0] += b.M[0][0];
      M[0][1] += b.M[0][1];
      M[1][0] += b.M[1][0];
      M[1][1] += b.M[1][1];
      return *this;
    }

    void operator=(const Matrix2& b) {
      M[0][0] = b.M[0][0];
      M[0][1] = b.M[0][1];
      M[1][0] = b.M[1][0];
      M[1][1] = b.M[1][1];
    }

    Matrix2 operator-(const Matrix2& b) const {
      return Matrix2(
        M[0][0] - b.M[0][0], M[0][1] - b.M[0][1], M[1][0] - b.M[1][0], M[1][1] - b.M[1][1]);
    }

    Matrix2& operator-=(const Matrix2& b) {
      M[0][0] -= b.M[0][0];
      M[0][1] -= b.M[0][1];
      M[1][0] -= b.M[1][0];
      M[1][1] -= b.M[1][1];
      return *this;
    }

    Matrix2 operator*(const Matrix2& b) const {
      return Matrix2(
        M[0][0] * b.M[0][0] + M[0][1] * b.M[1][0],
        M[0][0] * b.M[0][1] + M[0][1] * b.M[1][1],
        M[1][0] * b.M[0][0] + M[1][1] * b.M[1][0],
        M[1][0] * b.M[0][1] + M[1][1] * b.M[1][1]);
    }

    Matrix2& operator*=(const Matrix2& b) {
      *this = *this * b;
      return *this;
    }

    Matrix2 operator*(T s) const {
      return Matrix2(M[0][0] * s, M[0][1] * s, M[1][0] * s, M[1][1] * s);
    }

    Matrix2& operator*=(T s) {
      M[0][0] *= s;
      M[0][1] *= s;
      M[1][0] *= s;
      M[1][1] *= s;
      return *this;
    }

    Matrix2 operator/(T s) const {
      return *this * (T(1) / s);
    }

    Matrix2& operator/=(T s) {
      return *this *= (T(1) / s);
    }

    Vector2<T> operator*(const Vector2<T>& b) const {
      return Vector2<T>(M[0][0] * b.x + M[0][1] * b.y, M[1][0] * b.x + M[1][1] * b.y);
    }

    Vector2<T> Transform(const Vector2<T>& v) const {
      return Vector2<T>(M[0][0] * v.x + M[0][1] * v.y, M[1][0] * v.x + M[1][1] * v.y);
    }

    Matrix2 Transposed() const {
      return Matrix2(M[0][0], M[1][0], M[0][1], M[1][1]);
    }

    void Transpose() {
      OVRMath_Swap(M[1][0], M[0][1]);
    }

    Vector2<T> GetColumn(int c) const {
      return Vector2<T>(M[0][c], M[1][c]);
    }

    Vector2<T> GetRow(int r) const {
      return Vector2<T>(M[r][0], M[r][1]);
    }

    void SetColumn(int c, const Vector2<T>& v) {
      M[0][c] = v.x;
      M[1][c] = v.y;
    }

    void SetRow(int r, const Vector2<T>& v) {
      M[r][0] = v.x;
      M[r][1] = v.y;
    }

    T Determinant() const {
      return M[0][0] * M[1][1] - M[0][1] * M[1][0];
    }

    Matrix2 Inverse() const {
      T rcpDet = T(1) / Determinant();
      return Matrix2(M[1][1] * rcpDet, -M[0][1] * rcpDet, -M[1][0] * rcpDet, M[0][0] * rcpDet);
    }

    // Outer Product of two column vectors: a * b.Transpose()
    static Matrix2 OuterProduct(const Vector2<T>& a, const Vector2<T>& b) {
      return Matrix2(a.x * b.x, a.x * b.y, a.y * b.x, a.y * b.y);
    }

    // Angle in radians between two rotation matrices
    T Angle(const Matrix2& b) const {
      const Matrix2& a = *this;
      return Acos(a(0, 0) * b(0, 0) + a(1, 0) * b(1, 0));
    }
  };

  typedef Matrix2<float> Matrix2f;
  typedef Matrix2<double> Matrix2d;

  //-------------------------------------------------------------------------------------

  template <class T>
  class SymMat3 {
  private:
    typedef SymMat3<T> this_type;

  public:
    typedef T Value_t;
    // Upper symmetric
    T v[6]; // _00 _01 _02 _11 _12 _22

    inline SymMat3() {}

    inline explicit SymMat3(T s) {
      v[0] = v[3] = v[5] = s;
      v[1] = v[2] = v[4] = T(0);
    }

    inline explicit SymMat3(T a00, T a01, T a02, T a11, T a12, T a22) {
      v[0] = a00;
      v[1] = a01;
      v[2] = a02;
      v[3] = a11;
      v[4] = a12;
      v[5] = a22;
    }

    // Cast to symmetric Matrix3
    operator Matrix3<T>() const {
      return Matrix3<T>(v[0], v[1], v[2], v[1], v[3], v[4], v[2], v[4], v[5]);
    }

    static inline int Index(unsigned int i, unsigned int j) {
      return static_cast<int>(
        (i <= j) ? (3 * i - i * (i + 1) / 2 + j) : (3 * j - j * (j + 1) / 2 + i));
    }

    inline T operator()(int i, int j) const {
      return v[Index(i, j)];
    }

    inline T& operator()(int i, int j) {
      return v[Index(i, j)];
    }

    inline this_type& operator+=(const this_type& b) {
      v[0] += b.v[0];
      v[1] += b.v[1];
      v[2] += b.v[2];
      v[3] += b.v[3];
      v[4] += b.v[4];
      v[5] += b.v[5];
      return *this;
    }

    inline this_type& operator-=(const this_type& b) {
      v[0] -= b.v[0];
      v[1] -= b.v[1];
      v[2] -= b.v[2];
      v[3] -= b.v[3];
      v[4] -= b.v[4];
      v[5] -= b.v[5];

      return *this;
    }

    inline this_type& operator*=(T s) {
      v[0] *= s;
      v[1] *= s;
      v[2] *= s;
      v[3] *= s;
      v[4] *= s;
      v[5] *= s;

      return *this;
    }

    inline SymMat3 operator*(T s) const {
      SymMat3 d;
      d.v[0] = v[0] * s;
      d.v[1] = v[1] * s;
      d.v[2] = v[2] * s;
      d.v[3] = v[3] * s;
      d.v[4] = v[4] * s;
      d.v[5] = v[5] * s;

      return d;
    }

    // Multiplies two matrices into destination with minimum copying.
    static SymMat3& Multiply(SymMat3* d, const SymMat3& a, const SymMat3& b) {
      // _00 _01 _02 _11 _12 _22

      d->v[0] = a.v[0] * b.v[0];
      d->v[1] = a.v[0] * b.v[1] + a.v[1] * b.v[3];
      d->v[2] = a.v[0] * b.v[2] + a.v[1] * b.v[4];

      d->v[3] = a.v[3] * b.v[3];
      d->v[4] = a.v[3] * b.v[4] + a.v[4] * b.v[5];

      d->v[5] = a.v[5] * b.v[5];

      return *d;
    }

    inline T Determinant() const {
      const this_type& m = *this;
      T d;

      d = m(0, 0) * (m(1, 1) * m(2, 2) - m(1, 2) * m(2, 1));
      d -= m(0, 1) * (m(1, 0) * m(2, 2) - m(1, 2) * m(2, 0));
      d += m(0, 2) * (m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0));

      return d;
    }

    inline this_type Inverse() const {
      this_type a;
      const this_type& m = *this;
      T d = Determinant();

      OVR_MATH_ASSERT(fabs(d) >= Math<T>::SmallestNonDenormal());
      T s = T(1) / d;

      a(0, 0) = s * (m(1, 1) * m(2, 2) - m(1, 2) * m(2, 1));

      a(0, 1) = s * (m(0, 2) * m(2, 1) - m(0, 1) * m(2, 2));
      a(1, 1) = s * (m(0, 0) * m(2, 2) - m(0, 2) * m(2, 0));

      a(0, 2) = s * (m(0, 1) * m(1, 2) - m(0, 2) * m(1, 1));
      a(1, 2) = s * (m(0, 2) * m(1, 0) - m(0, 0) * m(1, 2));
      a(2, 2) = s * (m(0, 0) * m(1, 1) - m(0, 1) * m(1, 0));

      return a;
    }

    inline T Trace() const {
      return v[0] + v[3] + v[5];
    }

    // M = a*a.t()
    inline void Rank1(const Vector3<T>& a) {
      v[0] = a.x * a.x;
      v[1] = a.x * a.y;
      v[2] = a.x * a.z;
      v[3] = a.y * a.y;
      v[4] = a.y * a.z;
      v[5] = a.z * a.z;
    }

    // M += a*a.t()
    inline void Rank1Add(const Vector3<T>& a) {
      v[0] += a.x * a.x;
      v[1] += a.x * a.y;
      v[2] += a.x * a.z;
      v[3] += a.y * a.y;
      v[4] += a.y * a.z;
      v[5] += a.z * a.z;
    }

    // M -= a*a.t()
    inline void Rank1Sub(const Vector3<T>& a) {
      v[0] -= a.x * a.x;
      v[1] -= a.x * a.y;
      v[2] -= a.x * a.z;
      v[3] -= a.y * a.y;
      v[4] -= a.y * a.z;
      v[5] -= a.z * a.z;
    }
  };

  typedef SymMat3<float> SymMat3f;
  typedef SymMat3<double> SymMat3d;

  template <class T>
  inline Matrix3<T> operator*(const SymMat3<T>& a, const SymMat3<T>& b) {
#define AJB_ARBC(r, c) (a(r, 0) * b(0, c) + a(r, 1) * b(1, c) + a(r, 2) * b(2, c))
    return Matrix3<T>(
      AJB_ARBC(0, 0),
      AJB_ARBC(0, 1),
      AJB_ARBC(0, 2),
      AJB_ARBC(1, 0),
      AJB_ARBC(1, 1),
      AJB_ARBC(1, 2),
      AJB_ARBC(2, 0),
      AJB_ARBC(2, 1),
      AJB_ARBC(2, 2));
#undef AJB_ARBC
  }

  template <class T>
  inline Matrix3<T> operator*(const Matrix3<T>& a, const SymMat3<T>& b) {
#define AJB_ARBC(r, c) (a(r, 0) * b(0, c) + a(r, 1) * b(1, c) + a(r, 2) * b(2, c))
    return Matrix3<T>(
      AJB_ARBC(0, 0),
      AJB_ARBC(0, 1),
      AJB_ARBC(0, 2),
      AJB_ARBC(1, 0),
      AJB_ARBC(1, 1),
      AJB_ARBC(1, 2),
      AJB_ARBC(2, 0),
      AJB_ARBC(2, 1),
      AJB_ARBC(2, 2));
#undef AJB_ARBC
  }

  //-------------------------------------------------------------------------------------
  // ***** Angle

  // Cleanly representing the algebra of 2D rotations.
  // The operations maintain the angle between -Pi and Pi, the same range as atan2.

  template <class T>
  class Angle {
  public:
    enum AngularUnits { Radians = 0, Degrees = 1 };

    Angle() : a(0) {}

    // Fix the range to be between -Pi and Pi
    Angle(T a_, AngularUnits u = Radians)
      : a((u == Radians) ? a_ : a_ * ((T)MATH_DOUBLE_DEGREETORADFACTOR)) {
      FixRange();
    }

    Angle(T adjacent, T opposite) : Angle(Atan2(opposite, adjacent)) {}

    static Angle FromOpposite(T opposite) {
      return Angle(Asin(opposite));
    }
    static Angle FromAdjacent(T adjacent) {
      return Angle(Acos(adjacent));
    }

    T Get(AngularUnits u = Radians) const {
      return (u == Radians) ? a : a * ((T)MATH_DOUBLE_RADTODEGREEFACTOR);
    }
    void Set(const T& x, AngularUnits u = Radians) {
      a = (u == Radians) ? x : x * ((T)MATH_DOUBLE_DEGREETORADFACTOR);
      FixRange();
    }
    int Sign() const {
      if (a == 0)
        return 0;
      else
        return (a > 0) ? 1 : -1;
    }
    T Abs() const {
      return (a >= 0) ? a : -a;
    }
    Angle AbsAngle() const {
      Angle res;
      res.a = Abs();
      return res;
    }

    bool operator==(const Angle& b) const {
      return a == b.a;
    }
    bool operator!=(const Angle& b) const {
      return a != b.a;
    }
    //    bool operator<  (const Angle& b) const    { return a < a.b; }
    //    bool operator>  (const Angle& b) const    { return a > a.b; }
    //    bool operator<= (const Angle& b) const    { return a <= a.b; }
    //    bool operator>= (const Angle& b) const    { return a >= a.b; }
    //    bool operator= (const T& x)               { a = x; FixRange(); }

    // These operations assume a is already between -Pi and Pi.
    Angle& operator+=(const Angle& b) {
      a = a + b.a;
      FastFixRange();
      return *this;
    }
    Angle& operator+=(const T& x) {
      a = a + x;
      FixRange();
      return *this;
    }
    Angle operator+(const Angle& b) const {
      Angle res = *this;
      res += b;
      return res;
    }
    Angle operator+(const T& x) const {
      Angle res = *this;
      res += x;
      return res;
    }
    Angle& operator-=(const Angle& b) {
      a = a - b.a;
      FastFixRange();
      return *this;
    }
    Angle& operator-=(const T& x) {
      a = a - x;
      FixRange();
      return *this;
    }
    Angle operator-(const Angle& b) const {
      Angle res = *this;
      res -= b;
      return res;
    }
    Angle operator-(const T& x) const {
      Angle res = *this;
      res -= x;
      return res;
    }
    Angle& operator*=(const T& x) {
      a = a * x;
      FixRange();
      return *this;
    }
    Angle operator*(const T& x) const {
      Angle res = *this;
      res *= x;
      return res;
    }

    T Distance(const Angle& b) const {
      T c = fabs(a - b.a);
      return (c <= ((T)MATH_DOUBLE_PI)) ? c : ((T)MATH_DOUBLE_TWOPI) - c;
    }

    Angle Lerp(const Angle& b, T f) const {
      return *this + (b - *this) * f;
    }

  private:
    // The stored angle, which should be maintained between -Pi and Pi
    T a;

    // Fixes the angle range to [-Pi,Pi], but assumes no more than 2Pi away on either side
    inline void FastFixRange() {
      if (a < -((T)MATH_DOUBLE_PI))
        a += ((T)MATH_DOUBLE_TWOPI);
      else if (a > ((T)MATH_DOUBLE_PI))
        a -= ((T)MATH_DOUBLE_TWOPI);
    }

    // Fixes the angle range to [-Pi,Pi] for any given range, but slower then the fast method
    inline void FixRange() {
      // do nothing if the value is already in the correct range, since fmod call is expensive
      if (a >= -((T)MATH_DOUBLE_PI) && a <= ((T)MATH_DOUBLE_PI))
        return;
      a = fmod(a, ((T)MATH_DOUBLE_TWOPI));
      if (a < -((T)MATH_DOUBLE_PI))
        a += ((T)MATH_DOUBLE_TWOPI);
      else if (a > ((T)MATH_DOUBLE_PI))
        a -= ((T)MATH_DOUBLE_TWOPI);
    }
  };

  typedef Angle<float> Anglef;
  typedef Angle<double> Angled;

  //-------------------------------------------------------------------------------------
  // ***** Plane

  // Consists of a normal vector and distance from the origin where the plane is located.

  template <class T>
  class Plane {
  public:
    Vector3<T> N;
    T D;

    Plane() : D(0) {}

    // Normals must already be normalized
    Plane(const Vector3<T>& n, T d) : N(n), D(d) {}
    Plane(T x, T y, T z, T d) : N(x, y, z), D(d) {}

    // construct from a point on the plane and the normal
    Plane(const Vector3<T>& p, const Vector3<T>& n) : N(n), D(-(p.Dot(n))) {}

    // Find the point to plane distance. The sign indicates what side of the plane the point is on
    // (0 = point on plane).
    T TestSide(const Vector3<T>& p) const {
      return (N.Dot(p)) + D;
    }

    Plane<T> Flipped() const {
      return Plane(-N, -D);
    }

    void Flip() {
      N = -N;
      D = -D;
    }

    bool operator==(const Plane<T>& rhs) const {
      return (this->D == rhs.D && this->N == rhs.N);
    }
  };

  typedef Plane<float> Planef;
  typedef Plane<double> Planed;

  //-----------------------------------------------------------------------------------
  // ***** ScaleAndOffset2D

  struct ScaleAndOffset2D {
    Vector2f Scale;
    Vector2f Offset;

    ScaleAndOffset2D(float sx = 0.0f, float sy = 0.0f, float ox = 0.0f, float oy = 0.0f)
      : Scale(sx, sy), Offset(ox, oy) {}
  };

  //-----------------------------------------------------------------------------------
  // ***** FovPort

  // FovPort describes Field Of View (FOV) of a viewport.
  // This class has values for up, down, left and right, stored in
  // tangent of the angle units to simplify calculations.
  //
  // As an example, for a standard 90 degree vertical FOV, we would
  // have: { UpTan = tan(90 degrees / 2), DownTan = tan(90 degrees / 2) }.
  //
  // CreateFromRadians/Degrees helper functions can be used to
  // access FOV in different units.

  // ***** FovPort

  struct FovPort {
    float UpTan;
    float DownTan;
    float LeftTan;
    float RightTan;

    FovPort(float sideTan = 0.0f)
      : UpTan(sideTan), DownTan(sideTan), LeftTan(sideTan), RightTan(sideTan) {}
    FovPort(float u, float d, float l, float r) : UpTan(u), DownTan(d), LeftTan(l), RightTan(r) {}

#if 0
    // C-interop support.
    typedef CompatibleTypes<FovPort >::Type CompatibleType;

    FovPort(const CompatibleType& s)
      : UpTan(s.UpTan)
      , DownTan(s.DownTan)
      , LeftTan(s.LeftTan)
      , RightTan(s.RightTan)
    {  }

    operator const CompatibleType& () const
    {
      OVR_MATH_STATIC_ASSERT(sizeof(FovPort) == sizeof(CompatibleType), "sizeof(FovPort) failure");
      return reinterpret_cast<const CompatibleType&>(*this);
    }
#endif

    static FovPort CreateFromRadians(float horizontalFov, float verticalFov) {
      FovPort result;
      result.UpTan = tanf(verticalFov * 0.5f);
      result.DownTan = tanf(verticalFov * 0.5f);
      result.LeftTan = tanf(horizontalFov * 0.5f);
      result.RightTan = tanf(horizontalFov * 0.5f);
      return result;
    }

    static FovPort CreateFromDegrees(float horizontalFovDegrees, float verticalFovDegrees) {
      return CreateFromRadians(
        DegreeToRad(horizontalFovDegrees), DegreeToRad(verticalFovDegrees));
    }

    //  Get Horizontal/Vertical components of Fov in radians.
    float GetVerticalFovRadians() const {
      return atanf(UpTan) + atanf(DownTan);
    }
    float GetHorizontalFovRadians() const {
      return atanf(LeftTan) + atanf(RightTan);
    }
    //  Get Horizontal/Vertical components of Fov in degrees.
    float GetVerticalFovDegrees() const {
      return RadToDegree(GetVerticalFovRadians());
    }
    float GetHorizontalFovDegrees() const {
      return RadToDegree(GetHorizontalFovRadians());
    }

    // Compute maximum tangent value among all four sides.
    float GetMaxSideTan() const {
      return OVRMath_Max(OVRMath_Max(UpTan, DownTan), OVRMath_Max(LeftTan, RightTan));
    }

    static ScaleAndOffset2D CreateNDCScaleAndOffsetFromFov(FovPort tanHalfFov) {
      float projXScale = 2.0f / (tanHalfFov.LeftTan + tanHalfFov.RightTan);
      float projXOffset = (tanHalfFov.LeftTan - tanHalfFov.RightTan) * projXScale * 0.5f;
      float projYScale = 2.0f / (tanHalfFov.UpTan + tanHalfFov.DownTan);
      float projYOffset = (tanHalfFov.UpTan - tanHalfFov.DownTan) * projYScale * 0.5f;

      ScaleAndOffset2D result;
      result.Scale = Vector2f(projXScale, projYScale);
      result.Offset = Vector2f(projXOffset, projYOffset);
      // Hey - why is that Y.Offset negated?
      // It's because a projection matrix transforms from world coords with Y=up,
      // whereas this is from NDC which is Y=down.

      return result;
    }

    // Converts Fov Tan angle units to [-1,1] render target NDC space
    Vector2f TanAngleToRendertargetNDC(Vector2f const& tanEyeAngle) {
      ScaleAndOffset2D eyeToSourceNDC = CreateNDCScaleAndOffsetFromFov(*this);
      return tanEyeAngle * eyeToSourceNDC.Scale + eyeToSourceNDC.Offset;
    }

    // Compute per-channel minimum and maximum of Fov.
    static FovPort Min(const FovPort& a, const FovPort& b) {
      FovPort fov(
        OVRMath_Min(a.UpTan, b.UpTan),
        OVRMath_Min(a.DownTan, b.DownTan),
        OVRMath_Min(a.LeftTan, b.LeftTan),
        OVRMath_Min(a.RightTan, b.RightTan));
      return fov;
    }

    static FovPort Max(const FovPort& a, const FovPort& b) {
      FovPort fov(
        OVRMath_Max(a.UpTan, b.UpTan),
        OVRMath_Max(a.DownTan, b.DownTan),
        OVRMath_Max(a.LeftTan, b.LeftTan),
        OVRMath_Max(a.RightTan, b.RightTan));
      return fov;
    }
  };

} // Namespace OVR

#if defined(_MSC_VER)
#pragma warning(pop)
#endif

#define NUM_EYES 2

struct Framebuffer {
  void Clear();
  bool Create(
    const GLenum colorFormat,
    const int width,
    const int height,
    const int multisamples,
    const int swapChainLength,
    GLuint* colorTextures);
  void Destroy();
  void Bind(int element);
  void Resolve();
  int Width;
  int Height;
  int Multisamples;
  int SwapChainLength;
  struct Element {
    GLuint ColorTexture;
    GLuint DepthTexture;
    GLuint FrameBufferObject;
  };
  Element* Elements;
};

struct Scene {
  void Clear();
  void Create();
  bool IsCreated();
  bool CreatedScene;
  GLuint SceneMatrices;
};

struct AppRenderer {
  void Clear();
  void Create(
    GLenum format,
    int width,
    int height,
    int numMultiSamples,
    int swapChainLength,
    GLuint* colorTextures);
  void Destroy();

  struct FrameIn {
    int SwapChainIndex;
    OVR::Matrix4f View[NUM_EYES];
    OVR::Matrix4f Proj[NUM_EYES];
    OVR::Posef StagePose;
    OVR::Vector3f StageScale;
  };

  void RenderFrame(FrameIn frameIn);

  Framebuffer framebuffer;
  Scene scene;
};



void OXR_CheckErrors(XrResult result, const char* function, bool failOnError);
#define OXR(func) OXR_CheckErrors(func, #func, true);

inline OVR::Matrix4f OvrFromXr(const XrMatrix4x4f& x) {
  return OVR::Matrix4f(
    x.m[0x0],
    x.m[0x1],
    x.m[0x2],
    x.m[0x3],
    x.m[0x4],
    x.m[0x5],
    x.m[0x6],
    x.m[0x7],
    x.m[0x8],
    x.m[0x9],
    x.m[0xa],
    x.m[0xb],
    x.m[0xc],
    x.m[0xd],
    x.m[0xe],
    x.m[0xf]);
}

inline OVR::Quatf OvrFromXr(const XrQuaternionf& q) {
  return OVR::Quatf(q.x, q.y, q.z, q.w);
}

inline OVR::Vector3f OvrFromXr(const XrVector3f& v) {
  return OVR::Vector3f(v.x, v.y, v.z);
}

inline OVR::Posef OvrFromXr(const XrPosef& p) {
  return OVR::Posef(OvrFromXr(p.orientation), OvrFromXr(p.position));
}

/*
================================================================================

Egl

================================================================================
*/

struct Egl {
  void Clear();
  void CreateContext(const Egl* shareEgl);
  void DestroyContext();
#if defined(PLATFORM_OPENXR_ANDROID)
  EGLint MajorVersion;
  EGLint MinorVersion;
  EGLDisplay Display;
  EGLConfig Config;
  EGLSurface TinySurface;
  EGLSurface MainSurface;
  EGLContext Context;
#elif defined(XR_USE_GRAPHICS_API_OPENGL)
  HDC hDC;
  HGLRC hGLRC;
#endif
};

/*
================================================================================

App

================================================================================
*/


static XrInstance m_instance = XR_NULL_HANDLE;

union CompositionLayerUnion {
  XrCompositionLayerProjection Projection;
  XrCompositionLayerQuad Quad;
  XrCompositionLayerCylinderKHR Cylinder;
  XrCompositionLayerCubeKHR Cube;
  XrCompositionLayerEquirectKHR Equirect;
  // FB_passthrough sample begin
  XrCompositionLayerPassthroughFB Passthrough;
  // FB_passthrough sample end
};

enum { MaxLayerCount = 16 };

struct App {
  void Clear();
  void HandleSessionStateChanges(XrSessionState state);
  void HandleXrEvents();

  Egl egl;

#if defined(PLATFORM_OPENXR_ANDROID)
  bool Resumed;
#endif // defined(PLATFORM_OPENXR_ANDROID)
  bool ShouldExit;
  bool Focused;

  XrSession Session;
  XrViewConfigurationProperties ViewportConfig;
  XrViewConfigurationView ViewConfigurationView[NUM_EYES];
  XrSystemId SystemId;
  XrSpace HeadSpace;
  XrSpace LocalSpace;
  XrSpace StageSpace;
  bool SessionActive;

  int SwapInterval;
  int CpuLevel;
  int GpuLevel;
  // These threads will be marked as performance threads.
  int MainThreadTid;
  int RenderThreadTid;
  CompositionLayerUnion Layers[MaxLayerCount];
  int LayerCount;

  bool TouchPadDownLastFrame;

  XrSwapchain ColorSwapChain;
  uint32_t SwapChainLength;
  OVR::Vector3f StageBounds;
  // Provided by XrPassthroughGl, which is not aware of VrApi or OpenXR
  AppRenderer appRenderer;
};

class App;

void AppInput_init(App& app);
void AppInput_shutdown();
void AppInput_syncActions(App& app);

extern XrActionStateBoolean boolState;

//extern XrSpace leftControllerAimSpace;
//extern XrSpace rightControllerAimSpace;
//extern XrSpace leftControllerGripSpace;
//extern XrSpace rightControllerGripSpace;


using namespace OVR;

#if !defined(EGL_OPENGL_ES3_BIT_KHR)
#define EGL_OPENGL_ES3_BIT_KHR 0x0040
#endif

#define OVR_LOG_TAG "XrPassthrough"

#if !defined(PLATFORM_OPENXR_ANDROID) && !defined(XR_USE_GRAPHICS_API_OPENGL)
#error A graphics backend must be defined!
#elif defined(PLATFORM_OPENXR_ANDROID) && defined(XR_USE_GRAPHICS_API_OPENGL)
#error Only one graphics backend shall be defined!
#endif

#if defined(__ANDROID__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, OVR_LOG_TAG, __VA_ARGS__)
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, OVR_LOG_TAG, __VA_ARGS__)
#else
#define ALOGE(...)       \
    printf("ERROR: ");   \
    printf(__VA_ARGS__); \
    printf("\n")
#define ALOGV(...)       \
    printf("VERBOSE: "); \
    printf(__VA_ARGS__); \
    printf("\n")
#endif

static const int CPU_LEVEL = 2;
static const int GPU_LEVEL = 3;
static const int NUM_MULTI_SAMPLES = 4;

/*
================================================================================

OpenXR Utility Functions

================================================================================
*/

XrInstance instance;
void OXR_CheckErrors(XrResult result, const char* function, bool failOnError) {
  if (XR_FAILED(result)) {
    char errorBuffer[XR_MAX_RESULT_STRING_SIZE];
    xrResultToString(instance, result, errorBuffer);
    if (failOnError) {
      ALOGE("OpenXR error: %s: %s\n", function, errorBuffer);
    }
    else {
      ALOGV("OpenXR error: %s: %s\n", function, errorBuffer);
    }
  }
}

#define DECL_PFN(pfn) PFN_##pfn pfn = nullptr
#define INIT_PFN(pfn) OXR(xrGetInstanceProcAddr(instance, #pfn, (PFN_xrVoidFunction*)(&pfn)))

// FB_passthrough sample begin
DECL_PFN(xrCreatePassthroughFB);
DECL_PFN(xrDestroyPassthroughFB);
DECL_PFN(xrPassthroughStartFB);
DECL_PFN(xrPassthroughPauseFB);
DECL_PFN(xrCreatePassthroughLayerFB);
DECL_PFN(xrDestroyPassthroughLayerFB);
DECL_PFN(xrPassthroughLayerSetStyleFB);
DECL_PFN(xrPassthroughLayerPauseFB);
DECL_PFN(xrPassthroughLayerResumeFB);
DECL_PFN(xrCreateTriangleMeshFB);
DECL_PFN(xrDestroyTriangleMeshFB);
DECL_PFN(xrTriangleMeshGetVertexBufferFB);
DECL_PFN(xrTriangleMeshGetIndexBufferFB);
DECL_PFN(xrTriangleMeshBeginUpdateFB);
DECL_PFN(xrTriangleMeshEndUpdateFB);
DECL_PFN(xrCreateGeometryInstanceFB);
DECL_PFN(xrDestroyGeometryInstanceFB);
DECL_PFN(xrGeometryInstanceSetTransformFB);
// FB_passthrough sample end

/*
================================================================================

Egl Utility Functions

================================================================================
*/

#if defined(PLATFORM_OPENXR_ANDROID)
static const char* EglErrorString(const EGLint error) {
  switch (error) {
  case EGL_SUCCESS:
    return "EGL_SUCCESS";
  case EGL_NOT_INITIALIZED:
    return "EGL_NOT_INITIALIZED";
  case EGL_BAD_ACCESS:
    return "EGL_BAD_ACCESS";
  case EGL_BAD_ALLOC:
    return "EGL_BAD_ALLOC";
  case EGL_BAD_ATTRIBUTE:
    return "EGL_BAD_ATTRIBUTE";
  case EGL_BAD_CONTEXT:
    return "EGL_BAD_CONTEXT";
  case EGL_BAD_CONFIG:
    return "EGL_BAD_CONFIG";
  case EGL_BAD_CURRENT_SURFACE:
    return "EGL_BAD_CURRENT_SURFACE";
  case EGL_BAD_DISPLAY:
    return "EGL_BAD_DISPLAY";
  case EGL_BAD_SURFACE:
    return "EGL_BAD_SURFACE";
  case EGL_BAD_MATCH:
    return "EGL_BAD_MATCH";
  case EGL_BAD_PARAMETER:
    return "EGL_BAD_PARAMETER";
  case EGL_BAD_NATIVE_PIXMAP:
    return "EGL_BAD_NATIVE_PIXMAP";
  case EGL_BAD_NATIVE_WINDOW:
    return "EGL_BAD_NATIVE_WINDOW";
  case EGL_CONTEXT_LOST:
    return "EGL_CONTEXT_LOST";
  default:
    return "unknown";
  }
}

void Egl::Clear() {
  MajorVersion = 0;
  MinorVersion = 0;
  Display = 0;
  Config = 0;
  TinySurface = EGL_NO_SURFACE;
  MainSurface = EGL_NO_SURFACE;
  Context = EGL_NO_CONTEXT;
}

void Egl::CreateContext(const Egl* shareEgl) {
  if (Display != 0) {
    return;
  }

  Display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  ALOGV("        eglInitialize( Display, &MajorVersion, &MinorVersion )");
  eglInitialize(Display, &MajorVersion, &MinorVersion);
  // Do NOT use eglChooseConfig, because the Android EGL code pushes in multisample
  // flags in eglChooseConfig if the user has selected the "force 4x MSAA" option in
  // settings, and that is completely wasted for our warp target.
  const int MAX_CONFIGS = 1024;
  EGLConfig configs[MAX_CONFIGS];
  EGLint numConfigs = 0;
  if (eglGetConfigs(Display, configs, MAX_CONFIGS, &numConfigs) == EGL_FALSE) {
    ALOGE("        eglGetConfigs() failed: %s", EglErrorString(eglGetError()));
    return;
  }
  const EGLint configAttribs[] = {
      EGL_RED_SIZE,
      8,
      EGL_GREEN_SIZE,
      8,
      EGL_BLUE_SIZE,
      8,
      EGL_ALPHA_SIZE,
      8, // need alpha for the multi-pass timewarp compositor
      EGL_DEPTH_SIZE,
      0,
      EGL_STENCIL_SIZE,
      0,
      EGL_SAMPLES,
      0,
      EGL_NONE };
  Config = 0;
  for (int i = 0; i < numConfigs; i++) {
    EGLint value = 0;

    eglGetConfigAttrib(Display, configs[i], EGL_RENDERABLE_TYPE, &value);
    if ((value & EGL_OPENGL_ES3_BIT_KHR) != EGL_OPENGL_ES3_BIT_KHR) {
      continue;
    }

    // The pbuffer config also needs to be compatible with normal window rendering
    // so it can share textures with the window context.
    eglGetConfigAttrib(Display, configs[i], EGL_SURFACE_TYPE, &value);
    if ((value & (EGL_WINDOW_BIT | EGL_PBUFFER_BIT)) != (EGL_WINDOW_BIT | EGL_PBUFFER_BIT)) {
      continue;
    }

    int j = 0;
    for (; configAttribs[j] != EGL_NONE; j += 2) {
      eglGetConfigAttrib(Display, configs[i], configAttribs[j], &value);
      if (value != configAttribs[j + 1]) {
        break;
      }
    }
    if (configAttribs[j] == EGL_NONE) {
      Config = configs[i];
      break;
    }
  }
  if (Config == 0) {
    ALOGE("        eglChooseConfig() failed: %s", EglErrorString(eglGetError()));
    return;
  }
  EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
  ALOGV("        Context = eglCreateContext( Display, Config, EGL_NO_CONTEXT, contextAttribs )");
  Context = eglCreateContext(
    Display,
    Config,
    (shareEgl != nullptr) ? shareEgl->Context : EGL_NO_CONTEXT,
    contextAttribs);
  if (Context == EGL_NO_CONTEXT) {
    ALOGE("        eglCreateContext() failed: %s", EglErrorString(eglGetError()));
    return;
  }
  const EGLint surfaceAttribs[] = { EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE };
  ALOGV("        TinySurface = eglCreatePbufferSurface( Display, Config, surfaceAttribs )");
  TinySurface = eglCreatePbufferSurface(Display, Config, surfaceAttribs);
  if (TinySurface == EGL_NO_SURFACE) {
    ALOGE("        eglCreatePbufferSurface() failed: %s", EglErrorString(eglGetError()));
    eglDestroyContext(Display, Context);
    Context = EGL_NO_CONTEXT;
    return;
  }
  ALOGV("        eglMakeCurrent( Display, TinySurface, TinySurface, Context )");
  if (eglMakeCurrent(Display, TinySurface, TinySurface, Context) == EGL_FALSE) {
    ALOGE("        eglMakeCurrent() failed: %s", EglErrorString(eglGetError()));
    eglDestroySurface(Display, TinySurface);
    eglDestroyContext(Display, Context);
    Context = EGL_NO_CONTEXT;
    return;
  }
}

void Egl::DestroyContext() {
  if (Display != 0) {
    ALOGE("        eglMakeCurrent( Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT )");
    if (eglMakeCurrent(Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) == EGL_FALSE) {
      ALOGE("        eglMakeCurrent() failed: %s", EglErrorString(eglGetError()));
    }
  }
  if (Context != EGL_NO_CONTEXT) {
    ALOGE("        eglDestroyContext( Display, Context )");
    if (eglDestroyContext(Display, Context) == EGL_FALSE) {
      ALOGE("        eglDestroyContext() failed: %s", EglErrorString(eglGetError()));
    }
    Context = EGL_NO_CONTEXT;
  }
  if (TinySurface != EGL_NO_SURFACE) {
    ALOGE("        eglDestroySurface( Display, TinySurface )");
    if (eglDestroySurface(Display, TinySurface) == EGL_FALSE) {
      ALOGE("        eglDestroySurface() failed: %s", EglErrorString(eglGetError()));
    }
    TinySurface = EGL_NO_SURFACE;
  }
  if (Display != 0) {
    ALOGE("        eglTerminate( Display )");
    if (eglTerminate(Display) == EGL_FALSE) {
      ALOGE("        eglTerminate() failed: %s", EglErrorString(eglGetError()));
    }
    Display = 0;
  }
}

#elif defined(XR_USE_GRAPHICS_API_OPENGL)

#if defined(WIN32)
// Favor the high performance NVIDIA or AMD GPUs
extern "C" {
  // http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf
  __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
  // https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/
  __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
}
#endif //  defined(WIN32)

void Egl::Clear() {
  hDC = 0;
  hGLRC = 0;
}

void Egl::CreateContext(const Egl*) {
  ovrGl_CreateContext_Windows(&hDC, &hGLRC);
}

void Egl::DestroyContext() {
  ovrGl_DestroyContext_Windows();
}

#endif

void App::Clear() {
#if defined(PLATFORM_OPENXR_ANDROID)
  Resumed = false;
#endif // defined(PLATFORM_OPENXR_ANDROID)
  Focused = false;
  Session = XR_NULL_HANDLE;
  ViewportConfig = {};
  for (int i = 0; i < NUM_EYES; i++) {
    ViewConfigurationView[i] = {};
  }
  SystemId = XR_NULL_SYSTEM_ID;
  HeadSpace = XR_NULL_HANDLE;
  LocalSpace = XR_NULL_HANDLE;
  StageSpace = XR_NULL_HANDLE;
  SessionActive = false;
  SwapInterval = 1;
  for (int i = 0; i < MaxLayerCount; i++) {
    Layers[i] = {};
  }
  LayerCount = 0;
  CpuLevel = 2;
  GpuLevel = 2;
  MainThreadTid = 0;
  RenderThreadTid = 0;
  TouchPadDownLastFrame = false;

  egl.Clear();
  appRenderer.Clear();
}

void App::HandleSessionStateChanges(XrSessionState state) {
  if (state == XR_SESSION_STATE_READY) {
#if defined(PLATFORM_OPENXR_ANDROID)
    assert(Resumed);
#endif // defined(PLATFORM_OPENXR_ANDROID)
    assert(SessionActive == false);

    XrSessionBeginInfo sessionBeginInfo = { XR_TYPE_SESSION_BEGIN_INFO };
    sessionBeginInfo.primaryViewConfigurationType = ViewportConfig.viewConfigurationType;

    XrResult result;
    OXR(result = xrBeginSession(Session, &sessionBeginInfo));

    SessionActive = (result == XR_SUCCESS);

#if defined(PLATFORM_OPENXR_ANDROID)
    // Set session state once we have entered VR mode and have a valid session object.
    if (SessionActive) {
      XrPerfSettingsLevelEXT cpuPerfLevel = XR_PERF_SETTINGS_LEVEL_SUSTAINED_HIGH_EXT;
      switch (CpuLevel) {
      case 0:
        cpuPerfLevel = XR_PERF_SETTINGS_LEVEL_POWER_SAVINGS_EXT;
        break;
      case 1:
        cpuPerfLevel = XR_PERF_SETTINGS_LEVEL_SUSTAINED_LOW_EXT;
        break;
      case 2:
        cpuPerfLevel = XR_PERF_SETTINGS_LEVEL_SUSTAINED_HIGH_EXT;
        break;
      case 3:
        cpuPerfLevel = XR_PERF_SETTINGS_LEVEL_BOOST_EXT;
        break;
      default:
        ALOGE("Invalid CPU level %d", CpuLevel);
        break;
      }

      XrPerfSettingsLevelEXT gpuPerfLevel = XR_PERF_SETTINGS_LEVEL_SUSTAINED_HIGH_EXT;
      switch (GpuLevel) {
      case 0:
        gpuPerfLevel = XR_PERF_SETTINGS_LEVEL_POWER_SAVINGS_EXT;
        break;
      case 1:
        gpuPerfLevel = XR_PERF_SETTINGS_LEVEL_SUSTAINED_LOW_EXT;
        break;
      case 2:
        gpuPerfLevel = XR_PERF_SETTINGS_LEVEL_SUSTAINED_HIGH_EXT;
        break;
      case 3:
        gpuPerfLevel = XR_PERF_SETTINGS_LEVEL_BOOST_EXT;
        break;
      default:
        ALOGE("Invalid GPU level %d", GpuLevel);
        break;
      }

      PFN_xrPerfSettingsSetPerformanceLevelEXT pfnPerfSettingsSetPerformanceLevelEXT = NULL;
      OXR(xrGetInstanceProcAddr(
        m_instance,
        "xrPerfSettingsSetPerformanceLevelEXT",
        (PFN_xrVoidFunction*)(&pfnPerfSettingsSetPerformanceLevelEXT)));

      OXR(pfnPerfSettingsSetPerformanceLevelEXT(
        Session, XR_PERF_SETTINGS_DOMAIN_CPU_EXT, cpuPerfLevel));
      OXR(pfnPerfSettingsSetPerformanceLevelEXT(
        Session, XR_PERF_SETTINGS_DOMAIN_GPU_EXT, gpuPerfLevel));

      PFN_xrSetAndroidApplicationThreadKHR pfnSetAndroidApplicationThreadKHR = NULL;
      OXR(xrGetInstanceProcAddr(
              m_instance,
        "xrSetAndroidApplicationThreadKHR",
        (PFN_xrVoidFunction*)(&pfnSetAndroidApplicationThreadKHR)));

      OXR(pfnSetAndroidApplicationThreadKHR(
        Session, XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR, MainThreadTid));
      OXR(pfnSetAndroidApplicationThreadKHR(
        Session, XR_ANDROID_THREAD_TYPE_RENDERER_MAIN_KHR, RenderThreadTid));
    }
#endif // defined(PLATFORM_OPENXR_ANDROID)
  }
  else if (state == XR_SESSION_STATE_STOPPING) {
#if defined(PLATFORM_OPENXR_ANDROID)
    //assert(Resumed == false);
#endif // defined(PLATFORM_OPENXR_ANDROID)
    assert(SessionActive);
    OXR(xrEndSession(Session));
    SessionActive = false;
  }
}

void App::HandleXrEvents() {
  XrEventDataBuffer eventDataBuffer = {};

  // Poll for events
  for (;;) {
    XrEventDataBaseHeader* baseEventHeader = (XrEventDataBaseHeader*)(&eventDataBuffer);
    baseEventHeader->type = XR_TYPE_EVENT_DATA_BUFFER;
    baseEventHeader->next = NULL;
    XrResult r;
    OXR(r = xrPollEvent(m_instance, &eventDataBuffer));
    if (r != XR_SUCCESS) {
      break;
    }

    switch (baseEventHeader->type) {
    case XR_TYPE_EVENT_DATA_EVENTS_LOST:
      ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_EVENTS_LOST event");
      break;
    case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
      ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING event");
      break;
    case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
      ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED event");
      break;
    case XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT: {
#if defined(PLATFORM_OPENXR_ANDROID)
      const XrEventDataPerfSettingsEXT* perf_settings_event =
        (XrEventDataPerfSettingsEXT*)(baseEventHeader);
      ALOGV(
        "xrPollEvent: received XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT event: type %d subdomain %d : level %d -> level %d",
        perf_settings_event->type,
        perf_settings_event->subDomain,
        perf_settings_event->fromLevel,
        perf_settings_event->toLevel);
#endif // defined(PLATFORM_OPENXR_ANDROID)
    } break;
    case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
      ALOGV(
        "xrPollEvent: received XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING event");
      break;
    case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
      const XrEventDataSessionStateChanged* session_state_changed_event =
        (XrEventDataSessionStateChanged*)(baseEventHeader);
      //                ALOGV(
      //                    "xrPollEvent: received XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: %d for session %p at time %f",
      //                    session_state_changed_event->state,
      //                    (void*)session_state_changed_event->session,
      //                    FromXrTime(session_state_changed_event->time));

      switch (session_state_changed_event->state) {
      case XR_SESSION_STATE_FOCUSED:
        Focused = true;
        break;
      case XR_SESSION_STATE_VISIBLE:
        Focused = false;
        break;
      case XR_SESSION_STATE_READY:
      case XR_SESSION_STATE_STOPPING:
        HandleSessionStateChanges(session_state_changed_event->state);
        break;
      case XR_SESSION_STATE_EXITING:
        Global::appQuit = true;
        break;
      default:
        break;
      }
    } break;
    default:
      ALOGV("xrPollEvent: Unknown event");
      break;
    }
  }
}

#if defined(PLATFORM_OPENXR_ANDROID)
/*
================================================================================

Native Activity

================================================================================
*/

/**
 * Process the next main command.
 */
static void app_handle_cmd(struct android_app* androidApp, int32_t cmd) {
  App& app = *(App*)androidApp->userData;

  switch (cmd) {
    // There is no APP_CMD_CREATE. The ANativeActivity creates the
    // application thread from onCreate(). The application thread
    // then calls android_main().
  case APP_CMD_START: {
    ALOGV("onStart()");
    ALOGV("    APP_CMD_START");
    break;
  }
  case APP_CMD_RESUME: {
    ALOGV("onResume()");
    ALOGV("    APP_CMD_RESUME");
    app.Resumed = true;
    break;
  }
  case APP_CMD_PAUSE: {
    ALOGV("onPause()");
    ALOGV("    APP_CMD_PAUSE");
    app.Resumed = false;
    break;
  }
  case APP_CMD_STOP: {
    ALOGV("onStop()");
    ALOGV("    APP_CMD_STOP");
    break;
  }
  case APP_CMD_DESTROY: {
    ALOGV("onDestroy()");
    ALOGV("    APP_CMD_DESTROY");
    app.Clear();
    break;
  }
  case APP_CMD_INIT_WINDOW: {
    ALOGV("surfaceCreated()");
    ALOGV("    APP_CMD_INIT_WINDOW");
    break;
  }
  case APP_CMD_TERM_WINDOW: {
    ALOGV("surfaceDestroyed()");
    ALOGV("    APP_CMD_TERM_WINDOW");
    break;
  }
  }
}
#endif // defined(PLATFORM_OPENXR_ANDROID)

void UpdateStageBounds(App& app) {
  XrExtent2Df stageBounds = {};

  XrResult result;
  OXR(result = xrGetReferenceSpaceBoundsRect(
    app.Session, XR_REFERENCE_SPACE_TYPE_STAGE, &stageBounds));
  if (result != XR_SUCCESS) {
    ALOGV("Stage bounds query failed: using small defaults");
    stageBounds.width = 1.0f;
    stageBounds.height = 1.0f;
  }

  app.StageBounds = Vector3f(stageBounds.width * 0.5f, 1.0f, stageBounds.height * 0.5f);
}

static XrPath handSubactionPath[] = {XR_NULL_PATH, XR_NULL_PATH};
static XrSpace gripSpace[] = {XR_NULL_PATH, XR_NULL_PATH};
static XrSpace aimSpace[] = {XR_NULL_PATH, XR_NULL_PATH};

static XrActionSet actionSet = XR_NULL_HANDLE;
static XrAction gripPoseAction = XR_NULL_HANDLE;
static XrAction aimPoseAction = XR_NULL_HANDLE;
static XrAction hapticAction{ XR_NULL_HANDLE };

static XrAction thumbstickValueAction{ XR_NULL_HANDLE };
static XrAction thumbstickClickAction{ XR_NULL_HANDLE };
static XrAction thumbstickTouchAction{ XR_NULL_HANDLE };
static XrAction triggerValueAction{ XR_NULL_HANDLE };
static XrAction triggerClickAction{ XR_NULL_HANDLE };
static XrAction triggerTouchAction{ XR_NULL_HANDLE };
static XrAction squeezeValueAction{ XR_NULL_HANDLE };
static XrAction squeezeClickAction{ XR_NULL_HANDLE };

static XrAction AAction{ XR_NULL_HANDLE };
static XrAction BAction{ XR_NULL_HANDLE };
static XrAction XAction{ XR_NULL_HANDLE };
static XrAction YAction{ XR_NULL_HANDLE };
static XrAction ATouchAction{ XR_NULL_HANDLE };
static XrAction BTouchAction{ XR_NULL_HANDLE };
static XrAction XTouchAction{ XR_NULL_HANDLE };
static XrAction YTouchAction{ XR_NULL_HANDLE };
static XrAction menuClickAction{ XR_NULL_HANDLE };
static XrAction systemClickAction{ XR_NULL_HANDLE };


extern void init();
extern void tick();
extern void render(const mat4& viewProjectionMat, const mat4& highwayViewProjectionMat);
extern void fini();

/**
 * This is the main entry point of a native application that is using
 * android_native_app_glue.  It runs in its own thread, with its own
 * event loop for receiving input events and doing other things.
 */
void android_main(struct android_app* androidApp)
{
#if defined(PLATFORM_OPENXR_ANDROID)
  ALOGV("----------------------------------------------------------------");
  ALOGV("android_app_entry()");
  ALOGV("    android_main()");

  JNIEnv* Env;
  (*androidApp->activity->vm).AttachCurrentThread(&Env, nullptr);

  // Note that AttachCurrentThread will reset the thread name.
  prctl(PR_SET_NAME, (long)"OVR::Main", 0, 0, 0);
#endif // defined(PLATFORM_OPENXR_ANDROID)

  App app;
  app.Clear();

  Global::g_JVM = androidApp->activity->vm;
  Global::androidActivity = androidApp->activity->clazz;

#if defined(PLATFORM_OPENXR_ANDROID)
  PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
  xrGetInstanceProcAddr(
    XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
  if (xrInitializeLoaderKHR != NULL) {
    XrLoaderInitInfoAndroidKHR loaderInitializeInfoAndroid = { XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR };
    loaderInitializeInfoAndroid.applicationVM = androidApp->activity->vm;
    loaderInitializeInfoAndroid.applicationContext = androidApp->activity->clazz;
    xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&loaderInitializeInfoAndroid);
  }
#endif // defined(PLATFORM_OPENXR_ANDROID)

  // Log available layers.
  {
    XrResult result;

    PFN_xrEnumerateApiLayerProperties xrEnumerateApiLayerProperties;
    OXR(result = xrGetInstanceProcAddr(
      XR_NULL_HANDLE,
      "xrEnumerateApiLayerProperties",
      (PFN_xrVoidFunction*)&xrEnumerateApiLayerProperties));
    if (result != XR_SUCCESS) {
      ALOGE("Failed to get xrEnumerateApiLayerProperties function pointer.");
      exit(1);
    }

    uint32_t layerCount = 0;
    OXR(xrEnumerateApiLayerProperties(0, &layerCount, NULL));
    std::vector<XrApiLayerProperties> layerProperties(layerCount, { XR_TYPE_API_LAYER_PROPERTIES });
    OXR(xrEnumerateApiLayerProperties(layerCount, &layerCount, layerProperties.data()));

    for (const auto& layer : layerProperties) {
      ALOGV("Found layer %s", layer.layerName);
    }
  }

  // Check that the extensions required are present.
  const char* const requiredExtensionNames[] = {
    XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME,
    XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME,
    XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME,
    XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME,
    XR_FB_PASSTHROUGH_EXTENSION_NAME,
    XR_FB_TRIANGLE_MESH_EXTENSION_NAME
  };
  const uint32_t numRequiredExtensions =
    sizeof(requiredExtensionNames) / sizeof(requiredExtensionNames[0]);

  // Check the list of required extensions against what is supported by the runtime.
  {
    uint32_t numOutputExtensions = 0;
    OXR(xrEnumerateInstanceExtensionProperties(nullptr, 0, &numOutputExtensions, nullptr));
    ALOGV("xrEnumerateInstanceExtensionProperties found %u extension(s).", numOutputExtensions);

    auto extensionProperties =
      std::vector<XrExtensionProperties>(numOutputExtensions, { XR_TYPE_EXTENSION_PROPERTIES });

    OXR(xrEnumerateInstanceExtensionProperties(
      NULL, numOutputExtensions, &numOutputExtensions, extensionProperties.data()));
    for (uint32_t i = 0; i < numOutputExtensions; i++) {
      ALOGV("Extension #%d = '%s'.", i, extensionProperties[i].extensionName);
    }

    for (uint32_t i = 0; i < numRequiredExtensions; i++) {
      bool found = false;
      for (uint32_t j = 0; j < numOutputExtensions; j++) {
        if (!strcmp(requiredExtensionNames[i], extensionProperties[j].extensionName)) {
          ALOGV("Found required extension %s", requiredExtensionNames[i]);
          found = true;
          break;
        }
      }
      if (!found) {
        ALOGE("Failed to find required extension %s", requiredExtensionNames[i]);
        exit(1);
      }
    }
  }

  // Create the OpenXR instance.
  XrApplicationInfo appInfo = {};
  strcpy(appInfo.applicationName, "XrPassthrough");
  appInfo.applicationVersion = 0;
  strcpy(appInfo.engineName, "Oculus Mobile Sample");
  appInfo.engineVersion = 0;
  appInfo.apiVersion = XR_CURRENT_API_VERSION;

  XrInstanceCreateInfo instanceCreateInfo = { XR_TYPE_INSTANCE_CREATE_INFO };
  instanceCreateInfo.createFlags = 0;
  instanceCreateInfo.applicationInfo = appInfo;
  instanceCreateInfo.enabledApiLayerCount = 0;
  instanceCreateInfo.enabledApiLayerNames = NULL;
  instanceCreateInfo.enabledExtensionCount = numRequiredExtensions;
  instanceCreateInfo.enabledExtensionNames = requiredExtensionNames;

  XrResult initResult;
  OXR(initResult = xrCreateInstance(&instanceCreateInfo, &m_instance));
  if (initResult != XR_SUCCESS) {
    ALOGE("Failed to create XR m_instance: %d.", initResult);
    exit(1);
  }
  // Set the global used in macros
  instance = m_instance;

  XrInstanceProperties instanceInfo = { XR_TYPE_INSTANCE_PROPERTIES };
  OXR(xrGetInstanceProperties(m_instance, &instanceInfo));
  ALOGV(
    "Runtime %s: Version : %u.%u.%u",
    instanceInfo.runtimeName,
    XR_VERSION_MAJOR(instanceInfo.runtimeVersion),
    XR_VERSION_MINOR(instanceInfo.runtimeVersion),
    XR_VERSION_PATCH(instanceInfo.runtimeVersion));

  XrSystemGetInfo systemGetInfo = { XR_TYPE_SYSTEM_GET_INFO };
  systemGetInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;

  XrSystemId systemId;
  OXR(initResult = xrGetSystem(m_instance, &systemGetInfo, &systemId));
  if (initResult != XR_SUCCESS) {
    ALOGE("Failed to get system.");
    exit(1);
  }

  XrSystemProperties systemProperties = { XR_TYPE_SYSTEM_PROPERTIES };
  OXR(xrGetSystemProperties(m_instance, systemId, &systemProperties));

  ALOGV(
    "System Properties: Name=%s VendorId=%x",
    systemProperties.systemName,
    systemProperties.vendorId);
  ALOGV(
    "System Graphics Properties: MaxWidth=%d MaxHeight=%d MaxLayers=%d",
    systemProperties.graphicsProperties.maxSwapchainImageWidth,
    systemProperties.graphicsProperties.maxSwapchainImageHeight,
    systemProperties.graphicsProperties.maxLayerCount);
  ALOGV(
    "System Tracking Properties: OrientationTracking=%s PositionTracking=%s",
    systemProperties.trackingProperties.orientationTracking ? "True" : "False",
    systemProperties.trackingProperties.positionTracking ? "True" : "False");

  assert(MaxLayerCount <= systemProperties.graphicsProperties.maxLayerCount);

  // Get the graphics requirements.
#if defined(PLATFORM_OPENXR_ANDROID)
  PFN_xrGetOpenGLESGraphicsRequirementsKHR pfnGetOpenGLESGraphicsRequirementsKHR = NULL;
  OXR(xrGetInstanceProcAddr(
    m_instance,
    "xrGetOpenGLESGraphicsRequirementsKHR",
    (PFN_xrVoidFunction*)(&pfnGetOpenGLESGraphicsRequirementsKHR)));

  XrGraphicsRequirementsOpenGLESKHR graphicsRequirements = { XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR };
  OXR(pfnGetOpenGLESGraphicsRequirementsKHR(m_instance, systemId, &graphicsRequirements));
#elif defined(XR_USE_GRAPHICS_API_OPENGL)
  PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR = NULL;
  OXR(xrGetInstanceProcAddr(
    m_instance,
    "xrGetOpenGLGraphicsRequirementsKHR",
    (PFN_xrVoidFunction*)(&pfnGetOpenGLGraphicsRequirementsKHR)));

  XrGraphicsRequirementsOpenGLKHR graphicsRequirements = { XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR };
  OXR(pfnGetOpenGLGraphicsRequirementsKHR(m_instance, systemId, &graphicsRequirements));
#endif

  // Create the EGL Context
  app.egl.CreateContext(nullptr);

  // Check the graphics requirements.
  int eglMajor = 0;
  int eglMinor = 0;
  glGetIntegerv(GL_MAJOR_VERSION, &eglMajor);
  glGetIntegerv(GL_MINOR_VERSION, &eglMinor);
  const XrVersion eglVersion = XR_MAKE_VERSION(eglMajor, eglMinor, 0);
  if (eglVersion < graphicsRequirements.minApiVersionSupported ||
    eglVersion > graphicsRequirements.maxApiVersionSupported) {
    ALOGE("GLES version %d.%d not supported", eglMajor, eglMinor);
    exit(0);
  }

  app.CpuLevel = CPU_LEVEL;
  app.GpuLevel = GPU_LEVEL;
#if defined(__ANDROID__)
  app.MainThreadTid = gettid();
#else
  app.MainThreadTid = (int)std::hash<std::thread::id>{}(std::this_thread::get_id());
#endif

  app.SystemId = systemId;

  // FB_passthrough sample begin
  INIT_PFN(xrCreatePassthroughFB);
  INIT_PFN(xrDestroyPassthroughFB);
  INIT_PFN(xrPassthroughStartFB);
  INIT_PFN(xrPassthroughPauseFB);
  INIT_PFN(xrCreatePassthroughLayerFB);
  INIT_PFN(xrDestroyPassthroughLayerFB);
  INIT_PFN(xrPassthroughLayerSetStyleFB);
  INIT_PFN(xrPassthroughLayerPauseFB);
  INIT_PFN(xrPassthroughLayerResumeFB);
  INIT_PFN(xrCreateTriangleMeshFB);
  INIT_PFN(xrDestroyTriangleMeshFB);
  INIT_PFN(xrTriangleMeshGetVertexBufferFB);
  INIT_PFN(xrTriangleMeshGetIndexBufferFB);
  INIT_PFN(xrTriangleMeshBeginUpdateFB);
  INIT_PFN(xrTriangleMeshEndUpdateFB);
  INIT_PFN(xrCreateGeometryInstanceFB);
  INIT_PFN(xrDestroyGeometryInstanceFB);
  INIT_PFN(xrGeometryInstanceSetTransformFB);
  // FB_passthrough sample end

  // Create the OpenXR Session.
#if defined(PLATFORM_OPENXR_ANDROID)
  XrGraphicsBindingOpenGLESAndroidKHR graphicsBinding = { XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR };
  graphicsBinding.display = app.egl.Display;
  graphicsBinding.config = app.egl.Config;
  graphicsBinding.context = app.egl.Context;
#elif defined(XR_USE_GRAPHICS_API_OPENGL)
  XrGraphicsBindingOpenGLWin32KHR graphicsBinding = { XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR };
  graphicsBinding.hDC = app.egl.hDC;
  graphicsBinding.hGLRC = app.egl.hGLRC;
#endif

  XrSessionCreateInfo sessionCreateInfo = { XR_TYPE_SESSION_CREATE_INFO };
  sessionCreateInfo.next = &graphicsBinding;
  sessionCreateInfo.createFlags = 0;
  sessionCreateInfo.systemId = app.SystemId;

  OXR(initResult = xrCreateSession(m_instance, &sessionCreateInfo, &app.Session));
  if (initResult != XR_SUCCESS) {
    ALOGE("Failed to create XR session: %d.", initResult);
    exit(1);
  }

  // App only supports the primary stereo view config.
  const XrViewConfigurationType supportedViewConfigType =
    XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;

  // Enumerate the viewport configurations.
  uint32_t viewportConfigTypeCount = 0;
  OXR(xrEnumerateViewConfigurations(
    m_instance, app.SystemId, 0, &viewportConfigTypeCount, NULL));

  auto viewportConfigurationTypes = new XrViewConfigurationType[viewportConfigTypeCount];

  OXR(xrEnumerateViewConfigurations(
    m_instance,
    app.SystemId,
    viewportConfigTypeCount,
    &viewportConfigTypeCount,
    viewportConfigurationTypes));

  ALOGV("Available Viewport Configuration Types: %d", viewportConfigTypeCount);

  for (uint32_t i = 0; i < viewportConfigTypeCount; i++) {
    const XrViewConfigurationType viewportConfigType = viewportConfigurationTypes[i];

    ALOGV(
      "Viewport configuration type %d : %s",
      viewportConfigType,
      viewportConfigType == supportedViewConfigType ? "Selected" : "");

    XrViewConfigurationProperties viewportConfig = { XR_TYPE_VIEW_CONFIGURATION_PROPERTIES };
    OXR(xrGetViewConfigurationProperties(
      m_instance, app.SystemId, viewportConfigType, &viewportConfig));
    ALOGV(
      "FovMutable=%s ConfigurationType %d",
      viewportConfig.fovMutable ? "true" : "false",
      viewportConfig.viewConfigurationType);

    uint32_t viewCount;
    OXR(xrEnumerateViewConfigurationViews(
      m_instance, app.SystemId, viewportConfigType, 0, &viewCount, NULL));

    if (viewCount > 0) {
      auto elements = new XrViewConfigurationView[viewCount];

      for (uint32_t e = 0; e < viewCount; e++) {
        elements[e].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
        elements[e].next = NULL;
      }

      OXR(xrEnumerateViewConfigurationViews(
        m_instance, app.SystemId, viewportConfigType, viewCount, &viewCount, elements));

      // Log the view config info for each view type for debugging purposes.
      for (uint32_t e = 0; e < viewCount; e++) {
        const XrViewConfigurationView* element = &elements[e];
        (void)element;

        ALOGV(
          "Viewport [%d]: Recommended Width=%d Height=%d SampleCount=%d",
          e,
          element->recommendedImageRectWidth,
          element->recommendedImageRectHeight,
          element->recommendedSwapchainSampleCount);

        ALOGV(
          "Viewport [%d]: Max Width=%d Height=%d SampleCount=%d",
          e,
          element->maxImageRectWidth,
          element->maxImageRectHeight,
          element->maxSwapchainSampleCount);
      }

      // Cache the view config properties for the selected config type.
      if (viewportConfigType == supportedViewConfigType) {
        assert(viewCount == NUM_EYES);
        for (uint32_t e = 0; e < viewCount; e++) {
          app.ViewConfigurationView[e] = elements[e];
        }
      }

      delete[] elements;
    }
    else {
      ALOGE("Empty viewport configuration type: %d", viewCount);
    }
  }

  delete[] viewportConfigurationTypes;

  // Get the viewport configuration info for the chosen viewport configuration type.
  app.ViewportConfig.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES;

  OXR(xrGetViewConfigurationProperties(
    m_instance, app.SystemId, supportedViewConfigType, &app.ViewportConfig));

  bool stageSupported = false;

  uint32_t numOutputSpaces = 0;
  OXR(xrEnumerateReferenceSpaces(app.Session, 0, &numOutputSpaces, NULL));

  auto referenceSpaces = new XrReferenceSpaceType[numOutputSpaces];

  OXR(xrEnumerateReferenceSpaces(
    app.Session, numOutputSpaces, &numOutputSpaces, referenceSpaces));

  for (uint32_t i = 0; i < numOutputSpaces; i++) {
    if (referenceSpaces[i] == XR_REFERENCE_SPACE_TYPE_STAGE) {
      stageSupported = true;
      break;
    }
  }

  delete[] referenceSpaces;

  // Create a space to the first path
  XrReferenceSpaceCreateInfo spaceCreateInfo = { XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
  spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
  spaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f;
  OXR(xrCreateReferenceSpace(app.Session, &spaceCreateInfo, &app.HeadSpace));

  spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
  OXR(xrCreateReferenceSpace(app.Session, &spaceCreateInfo, &app.LocalSpace));

  if (stageSupported) {
    spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
    spaceCreateInfo.poseInReferenceSpace.position.y = 0.0f;
    OXR(xrCreateReferenceSpace(app.Session, &spaceCreateInfo, &app.StageSpace));
    ALOGV("Created stage space");
  }

  XrView projections[NUM_EYES];
  for (int eye = 0; eye < NUM_EYES; eye++) {
    projections[eye] = XrView{ XR_TYPE_VIEW };
  }

  GLenum format = GL_SRGB8_ALPHA8;
  Global::xrResolutionWidth = app.ViewConfigurationView[0].recommendedImageRectWidth;
  Global::xrResolutionHeight = app.ViewConfigurationView[0].recommendedImageRectHeight;

  XrSwapchainCreateInfo swapChainCreateInfo = { XR_TYPE_SWAPCHAIN_CREATE_INFO };
  swapChainCreateInfo.usageFlags =
    XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
  swapChainCreateInfo.format = format;
  swapChainCreateInfo.sampleCount = 1;
  swapChainCreateInfo.width = Global::xrResolutionWidth;
  swapChainCreateInfo.height = Global::xrResolutionHeight;
  swapChainCreateInfo.faceCount = 1;
  swapChainCreateInfo.arraySize = 2;
  swapChainCreateInfo.mipCount = 1;

  // Create the swapchain.
  OXR(xrCreateSwapchain(app.Session, &swapChainCreateInfo, &app.ColorSwapChain));
  OXR(xrEnumerateSwapchainImages(app.ColorSwapChain, 0, &app.SwapChainLength, nullptr));
#if defined(PLATFORM_OPENXR_ANDROID)
  auto images = new XrSwapchainImageOpenGLESKHR[app.SwapChainLength];
  // Populate the swapchain image array.
  for (uint32_t i = 0; i < app.SwapChainLength; i++) {
    images[i] = { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR };
  }
#elif defined(XR_USE_GRAPHICS_API_OPENGL)
  auto images = new XrSwapchainImageOpenGLKHR[app.SwapChainLength];
  // Populate the swapchain image array.
  for (uint32_t i = 0; i < app.SwapChainLength; i++) {
    images[i] = { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR };
  }
#endif

  OXR(xrEnumerateSwapchainImages(
    app.ColorSwapChain,
    app.SwapChainLength,
    &app.SwapChainLength,
    (XrSwapchainImageBaseHeader*)images));

  auto colorTextures = new GLuint[app.SwapChainLength];
  for (uint32_t i = 0; i < app.SwapChainLength; i++) {
    colorTextures[i] = GLuint(images[i].image);
  }

  app.appRenderer.Create(
    format, Global::xrResolutionWidth, Global::xrResolutionHeight, NUM_MULTI_SAMPLES, app.SwapChainLength, colorTextures);

  delete[] images;
  delete[] colorTextures;

  AppInput_init(app);

  // FB_passthrough sample begin
  // Create passthrough objects
  XrPassthroughFB passthrough = XR_NULL_HANDLE;
  //XrPassthroughLayerFB passthroughLayer = XR_NULL_HANDLE;
  XrPassthroughLayerFB reconPassthroughLayer = XR_NULL_HANDLE;
  //XrPassthroughLayerFB geomPassthroughLayer = XR_NULL_HANDLE;
  //XrGeometryInstanceFB geomInstance = XR_NULL_HANDLE;
  {
    XrPassthroughCreateInfoFB ptci = { XR_TYPE_PASSTHROUGH_CREATE_INFO_FB };
    XrResult result;
    OXR(result = xrCreatePassthroughFB(app.Session, &ptci, &passthrough));

    if (XR_SUCCEEDED(result)) {
      XrPassthroughLayerCreateInfoFB plci = { XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB };
      plci.passthrough = passthrough;
      plci.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB;
      OXR(xrCreatePassthroughLayerFB(app.Session, &plci, &reconPassthroughLayer));
    }

    if (XR_SUCCEEDED(result)) {
      //XrPassthroughLayerCreateInfoFB plci = {XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB};
      //plci.passthrough = passthrough;
      //plci.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB;
      //OXR(xrCreatePassthroughLayerFB(app.Session, &plci, &geomPassthroughLayer));

      OXR(xrPassthroughStartFB(passthrough));
      //passthroughLayer = reconPassthroughLayer;
      OXR(xrPassthroughLayerResumeFB(reconPassthroughLayer));

      const XrVector3f verts[] = { {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0} };
      const uint32_t indexes[] = { 0, 1, 2, 2, 1, 3 };
      XrTriangleMeshCreateInfoFB tmci = { XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB };
      tmci.vertexCount = 4;
      tmci.vertexBuffer = &verts[0];
      tmci.triangleCount = 2;
      tmci.indexBuffer = &indexes[0];

      XrTriangleMeshFB mesh = XR_NULL_HANDLE;
      OXR(xrCreateTriangleMeshFB(app.Session, &tmci, &mesh));

      //            XrGeometryInstanceCreateInfoFB gici = {XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB};
      //            gici.layer = geomPassthroughLayer;
      //            gici.mesh = mesh;
      //            gici.baseSpace = app.LocalSpace;
      //            gici.pose.orientation.w = 1.0f;
      //            gici.scale = {1.0f, 1.0f, 1.0f};
      //            OXR(xrCreateGeometryInstanceFB(app.Session, &gici, &geomInstance));
    }
  }
  // FB_passthrough sample end

#if defined(PLATFORM_OPENXR_ANDROID)
  androidApp->userData = &app;
  androidApp->onAppCmd = app_handle_cmd;
#endif // defined(PLATFORM_OPENXR_ANDROID)

  bool stageBoundsDirty = true;

  //double startTimeInSeconds = -1.0;

  //int frameCount = -1;
  //int framesCyclePaused = 0;
  //bool cyclePaused = false;

  constexpr int framesPerMode = 400;

  enum Mode {
    Mode_Passthrough_Basic = 0,
    Mode_NumModes = 1
  };

  Settings::init();
  init();

  // Get the supported display refresh rates for the system.
  {
    {
      PFN_xrEnumerateDisplayRefreshRatesFB pfnxrEnumerateDisplayRefreshRatesFB = nullptr;
      OXR(xrGetInstanceProcAddr(m_instance, "xrEnumerateDisplayRefreshRatesFB", (PFN_xrVoidFunction*)(&pfnxrEnumerateDisplayRefreshRatesFB)));

      u32 NumSupportedDisplayRefreshRates;
      OXR(pfnxrEnumerateDisplayRefreshRatesFB(app.Session, 0, &NumSupportedDisplayRefreshRates, nullptr));
      Global::graphicsRefreshRates.resize(NumSupportedDisplayRefreshRates);
      OXR(pfnxrEnumerateDisplayRefreshRatesFB(app.Session, NumSupportedDisplayRefreshRates, &NumSupportedDisplayRefreshRates, Global::graphicsRefreshRates.data()));
    }
    if (Settings::graphicsRefreshRate < Global::graphicsRefreshRates.size())
    {
      PFN_xrRequestDisplayRefreshRateFB pfnRequestDisplayRefreshRate = nullptr;
      OXR(xrGetInstanceProcAddr(m_instance, "xrRequestDisplayRefreshRateFB", (PFN_xrVoidFunction*)(&pfnRequestDisplayRefreshRate)));
      OXR(pfnRequestDisplayRefreshRate(app.Session, Global::graphicsRefreshRates[Settings::graphicsRefreshRate]));
    }
  }

#if defined(PLATFORM_OPENXR_ANDROID)
  while (androidApp->destroyRequested == 0)
#else
  while (true)
#endif
  {
    //frameCount++;

#if defined(PLATFORM_OPENXR_ANDROID)
    // Read all pending events.
    for (;;) {
      int events;
      struct android_poll_source* source;
      // If the timeout is zero, returns immediately without blocking.
      // If the timeout is negative, waits indefinitely until an event appears.
      const int timeoutMilliseconds = (app.Resumed == false && app.SessionActive == false &&
        androidApp->destroyRequested == 0)
        ? -1
        : 0;
      if (ALooper_pollOnce(timeoutMilliseconds, NULL, &events, (void**)&source) < 0) {
        break;
      }

      // Process this event.
      if (source != NULL) {
        source->process(androidApp, source);
      }
    }
#elif defined(XR_USE_PLATFORM_WIN32)
    MSG msg;
    while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) {
      if (msg.message == WM_QUIT) {
        app.ShouldExit = true;
      }
      else {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
      }
    }
#endif // defined(PLATFORM_OPENXR_ANDROID)

    app.HandleXrEvents();

    if (Global::appQuit) {
      fini();
      unreachable();
    }

    if (!app.SessionActive)
      continue;

    AppInput_syncActions(app);

    // Create the scene if not yet created.
    // The scene is created here to be able to show a loading icon.
    if (!app.appRenderer.scene.IsCreated()) {
      // Create the scene.
      app.appRenderer.scene.Create();
    }

    if (stageBoundsDirty) {
      UpdateStageBounds(app);
      stageBoundsDirty = false;
    }

    // NOTE: OpenXR does not use the concept of frame indices. Instead,
    // XrWaitFrame returns the predicted display time.
    XrFrameWaitInfo waitFrameInfo = { XR_TYPE_FRAME_WAIT_INFO };

    XrFrameState frameState = { XR_TYPE_FRAME_STATE };

    OXR(xrWaitFrame(app.Session, &waitFrameInfo, &frameState));

    // Get the HMD pose, predicted for the middle of the time period during which
    // the new eye images will be displayed. The number of frames predicted ahead
    // depends on the pipeline depth of the engine and the synthesis rate.
    // The better the prediction, the less black will be pulled in at the edges.
    XrFrameBeginInfo beginFrameDesc = { XR_TYPE_FRAME_BEGIN_INFO };
    OXR(xrBeginFrame(app.Session, &beginFrameDesc));

    XrPosef xfLocalFromHead;
    {
      XrSpaceLocation loc = { XR_TYPE_SPACE_LOCATION };
      OXR(xrLocateSpace(
        app.HeadSpace, app.LocalSpace, frameState.predictedDisplayTime, &loc));
      xfLocalFromHead = loc.pose;
    }

    XrViewState viewState = { XR_TYPE_VIEW_STATE };

    XrViewLocateInfo projectionInfo = { XR_TYPE_VIEW_LOCATE_INFO };
    projectionInfo.viewConfigurationType = app.ViewportConfig.viewConfigurationType;
    projectionInfo.displayTime = frameState.predictedDisplayTime;
    projectionInfo.space = app.HeadSpace;

    uint32_t projectionCapacityInput = NUM_EYES;
    uint32_t projectionCountOutput = projectionCapacityInput;

    OXR(xrLocateViews(
      app.Session,
      &projectionInfo,
      &viewState,
      projectionCapacityInput,
      &projectionCountOutput,
      projections));

    // update input information
    XrSpace controllerSpace[] = {
        aimSpace[0],
        gripSpace[0],
        aimSpace[1],
        gripSpace[1]
    };

    for (int i = 0; i < 4; i++)
    {
      if (Global::mControllerPoseState[i <= 1 ? 0 : 1].isActive != XR_FALSE) {
        XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION };
        OXR(xrLocateSpace(controllerSpace[i], app.LocalSpace, frameState.predictedDisplayTime, &spaceLocation));
        if (i == 1 || i == 3)
          Global::mControllerModel[i == 1 ? 0 : 1] = Xr::mat4_CreateTranslationRotation(spaceLocation.pose.position, spaceLocation.pose.orientation);
        else
        {
          Global::mAimModel[i == 0 ? 0 : 1] = Xr::mat4_CreateTranslationRotation(spaceLocation.pose.position, spaceLocation.pose.orientation);
          Global::mAimPose[i == 0 ? 0 : 1] = spaceLocation.pose;
        }
      }
    }

    // FB_passthrough sample begin

    // FB_passthrough sample end

    AppRenderer::FrameIn frameIn;
    uint32_t chainIndex = 0;
    XrSwapchainImageAcquireInfo acquireInfo = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, NULL };
    OXR(xrAcquireSwapchainImage(app.ColorSwapChain, &acquireInfo, &chainIndex));
    frameIn.SwapChainIndex = int(chainIndex);

    XrPosef xfLocalFromEye[NUM_EYES];

    for (int eye = 0; eye < NUM_EYES; eye++) {
      XrPosef xfHeadFromEye = projections[eye].pose;
      XrPosef_Multiply(&xfLocalFromEye[eye], &xfLocalFromHead, &xfHeadFromEye);

      XrPosef xfEyeFromLocal;
      XrPosef_Invert(&xfEyeFromLocal, &xfLocalFromEye[eye]);

      XrMatrix4x4f viewMat{};
      XrMatrix4x4f_CreateFromRigidTransform(&viewMat, &xfEyeFromLocal);

      const XrFovf fov = projections[eye].fov;
      XrMatrix4x4f projMat;
      XrMatrix4x4f_CreateProjectionFov(&projMat, fov, 0.1f, 0.0f);

      frameIn.View[eye] = OvrFromXr(viewMat);
      frameIn.Proj[eye] = OvrFromXr(projMat);
    }

    if (app.StageSpace != XR_NULL_HANDLE) {
      XrSpaceLocation loc = { XR_TYPE_SPACE_LOCATION };
      OXR(xrLocateSpace(
        app.StageSpace, app.LocalSpace, frameState.predictedDisplayTime, &loc));
      XrPosef xfLocalFromStage = loc.pose;

      frameIn.StagePose = OvrFromXr(xfLocalFromStage);
      frameIn.StageScale = app.StageBounds;
    }

    XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
    waitInfo.timeout = 1000000000; /* timeout in nanoseconds */
    XrResult res = xrWaitSwapchainImage(app.ColorSwapChain, &waitInfo);
    int retry = 0;
    while (res == XR_TIMEOUT_EXPIRED) {
      res = xrWaitSwapchainImage(app.ColorSwapChain, &waitInfo);
      retry++;
      ALOGV(
        " Retry xrWaitSwapchainImage %d times due to XR_TIMEOUT_EXPIRED (duration %f seconds)",
        retry,
        waitInfo.timeout * (1E-9));
    }

    app.appRenderer.RenderFrame(frameIn);

    XrSwapchainImageReleaseInfo releaseInfo = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, NULL };
    OXR(xrReleaseSwapchainImage(app.ColorSwapChain, &releaseInfo));

    // Set-up the compositor layers for this frame.
    // NOTE: Multiple independent layers are allowed, but they need to be added
    // in a depth consistent order.

    XrCompositionLayerProjectionView proj_views[2] = {};

    app.LayerCount = 0;
    memset(app.Layers, 0, sizeof(CompositionLayerUnion) * MaxLayerCount);

    // FB_passthrough sample begin
    // passthrough layer is backmost layer (if available)
    if (reconPassthroughLayer != XR_NULL_HANDLE) {
      XrCompositionLayerPassthroughFB passthrough_layer = { XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB };
      passthrough_layer.layerHandle = reconPassthroughLayer;
      passthrough_layer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
      passthrough_layer.space = XR_NULL_HANDLE;
      app.Layers[app.LayerCount++].Passthrough = passthrough_layer;
    }
    // FB_passthrough sample end

    XrCompositionLayerProjection proj_layer = { XR_TYPE_COMPOSITION_LAYER_PROJECTION };
    proj_layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
    proj_layer.layerFlags |= XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
    proj_layer.layerFlags |= XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
    proj_layer.space = app.LocalSpace;
    proj_layer.viewCount = NUM_EYES;
    proj_layer.views = proj_views;

    for (int eye = 0; eye < NUM_EYES; eye++) {
      XrCompositionLayerProjectionView& proj_view = proj_views[eye];
      proj_view = { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW };
      proj_view.pose = xfLocalFromEye[eye];
      proj_view.fov = projections[eye].fov;

      proj_view.subImage.swapchain = app.ColorSwapChain;
      proj_view.subImage.imageRect.offset.x = 0;
      proj_view.subImage.imageRect.offset.y = 0;
      proj_view.subImage.imageRect.extent.width = Global::xrResolutionWidth;
      proj_view.subImage.imageRect.extent.height = Global::xrResolutionHeight;
      proj_view.subImage.imageArrayIndex = eye;
    }

    app.Layers[app.LayerCount++].Projection = proj_layer;

    // Compose the layers for this frame.
    const XrCompositionLayerBaseHeader* layers[MaxLayerCount] = {};
    for (int i = 0; i < app.LayerCount; i++) {
      layers[i] = (const XrCompositionLayerBaseHeader*)&app.Layers[i];
    }

    XrFrameEndInfo endFrameInfo = { XR_TYPE_FRAME_END_INFO };
    endFrameInfo.displayTime = frameState.predictedDisplayTime;
    endFrameInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
    endFrameInfo.layerCount = app.LayerCount;
    endFrameInfo.layers = layers;

    OXR(xrEndFrame(app.Session, &endFrameInfo));
  }

//  app.appRenderer.Destroy();
//
//  AppInput_shutdown();
//
//  OXR(xrDestroySwapchain(app.ColorSwapChain));
//  OXR(xrDestroySpace(app.HeadSpace));
//  OXR(xrDestroySpace(app.LocalSpace));
//  // StageSpace is optional.
//  if (app.StageSpace != XR_NULL_HANDLE) {
//    OXR(xrDestroySpace(app.StageSpace));
//  }
//  OXR(xrDestroySession(app.Session));
//
//  app.egl.DestroyContext();
//
//  OXR(xrDestroyInstance(m_instance));
//
//#if defined(PLATFORM_OPENXR_ANDROID)
//  (*androidApp->activity->vm).DetachCurrentThread();
//#endif // defined(PLATFORM_OPENXR_ANDROID)
}


using namespace OVR;

#if !defined(EGL_OPENGL_ES3_BIT_KHR)
#define EGL_OPENGL_ES3_BIT_KHR 0x0040
#endif

#define DEBUG 1
#define LOG_TAG "XrPassthrough"

#if defined(__ANDROID__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#else
#include <cinttypes>
#define ALOGE(...)       \
    printf("ERROR: ");   \
    printf(__VA_ARGS__); \
    printf("\n")
#define ALOGV(...)       \
    printf("VERBOSE: "); \
    printf(__VA_ARGS__); \
    printf("\n")
#endif

static XrActionSet CreateActionSet(XrInstance instance, int priority, const char* name, const char* localizedName)
{
  XrActionSetCreateInfo asci = { XR_TYPE_ACTION_SET_CREATE_INFO };
  asci.priority = priority;
  strcpy(asci.actionSetName, name);
  strcpy(asci.localizedActionSetName, localizedName);
  XrActionSet actionSet = XR_NULL_HANDLE;
  OXR(xrCreateActionSet(instance, &asci, &actionSet));
  return actionSet;
}



//static XrSpace CreateActionSpace(App& app, XrAction poseAction, XrPath subactionPath)
//{
//  XrActionSpaceCreateInfo asci = { XR_TYPE_ACTION_SPACE_CREATE_INFO };
//  asci.action = poseAction;
//  asci.poseInActionSpace.orientation.w = 1.0f;
//  asci.subactionPath = subactionPath;
//  XrSpace actionSpace = XR_NULL_HANDLE;
//  OXR(xrCreateActionSpace(app.Session, &asci, &actionSpace));
//  return actionSpace;
//}

static XrActionStateBoolean GetActionStateBoolean(App& app, XrAction action)
{
  XrActionStateGetInfo getInfo = { XR_TYPE_ACTION_STATE_GET_INFO };
  getInfo.action = action;

  XrActionStateBoolean state = { XR_TYPE_ACTION_STATE_BOOLEAN };

  OXR(xrGetActionStateBoolean(app.Session, &getInfo, &state));
  return state;
}






//XrAction boolAction = XR_NULL_HANDLE;

//XrActionStateBoolean boolState;

//XrSpace leftControllerAimSpace = XR_NULL_HANDLE;
//XrSpace rightControllerAimSpace = XR_NULL_HANDLE;
//XrSpace leftControllerGripSpace = XR_NULL_HANDLE;
//XrSpace rightControllerGripSpace = XR_NULL_HANDLE;

void AppInput_init(App& app)
{
  // Actions
  actionSet = CreateActionSet(m_instance, 1, "running_action_set", "Action Set used on main loop");
  //boolAction = CreateAction(actionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "toggle", "Toggle");

  OXR(xrStringToPath(m_instance, "/user/hand/left", &handSubactionPath[0]));
  OXR(xrStringToPath(m_instance, "/user/hand/right", &handSubactionPath[1]));

  //aimPoseAction = CreateAction(actionSet, XR_ACTION_TYPE_POSE_INPUT, "aim_pose", nullptr, 2, handSubactionPath);
  //gripPoseAction = CreateAction(actionSet, XR_ACTION_TYPE_POSE_INPUT, "grip_pose", nullptr, 2, handSubactionPath);


  // Create actions.
  {
    // Create an input action getting the left and right hand poses.
    XrActionCreateInfo actionInfo{ XR_TYPE_ACTION_CREATE_INFO };
    actionInfo.actionType = XR_ACTION_TYPE_POSE_INPUT;
    strcpy_s(actionInfo.actionName, "grip_pose");
    strcpy_s(actionInfo.localizedActionName, "Grip_pose");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &gripPoseAction));

    actionInfo.actionType = XR_ACTION_TYPE_POSE_INPUT;
    strcpy_s(actionInfo.actionName, "aim_pose");
    strcpy_s(actionInfo.localizedActionName, "Aim_pose");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &aimPoseAction));

    // Create output actions for vibrating the left and right controller.
    actionInfo.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT;
    strcpy_s(actionInfo.actionName, "haptic");
    strcpy_s(actionInfo.localizedActionName, "Haptic");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &hapticAction));

    actionInfo.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT;
    strcpy_s(actionInfo.actionName, "thumbstick_value");
    strcpy_s(actionInfo.localizedActionName, "Thumbstick_value");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &thumbstickValueAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "thumbstick_click");
    strcpy_s(actionInfo.localizedActionName, "Thumbstick_click");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &thumbstickClickAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "thumbstick_touch");
    strcpy_s(actionInfo.localizedActionName, "Thumbstick_touch");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &thumbstickTouchAction));

    actionInfo.actionType = XR_ACTION_TYPE_FLOAT_INPUT;
    strcpy_s(actionInfo.actionName, "trigger_value");
    strcpy_s(actionInfo.localizedActionName, "Trigger_value");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &triggerValueAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "trigger_click");
    strcpy_s(actionInfo.localizedActionName, "Trigger_click");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &triggerClickAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "trigger_touch");
    strcpy_s(actionInfo.localizedActionName, "Trigger_touch");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &triggerTouchAction));

    actionInfo.actionType = XR_ACTION_TYPE_FLOAT_INPUT;
    strcpy_s(actionInfo.actionName, "squeeze_value");
    strcpy_s(actionInfo.localizedActionName, "Squeeze_value");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &squeezeValueAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "squeeze_click");
    strcpy_s(actionInfo.localizedActionName, "Squeeze_click");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &squeezeClickAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "akey");
    strcpy_s(actionInfo.localizedActionName, "Akey");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &AAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "bkey");
    strcpy_s(actionInfo.localizedActionName, "Bkey");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &BAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "xkey");
    strcpy_s(actionInfo.localizedActionName, "Xkey");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &XAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "ykey");
    strcpy_s(actionInfo.localizedActionName, "Ykey");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &YAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "akey_touch");
    strcpy_s(actionInfo.localizedActionName, "Akey_touch");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &ATouchAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "bkey_touch");
    strcpy_s(actionInfo.localizedActionName, "Bkey_touch");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &BTouchAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "xkey_touch");
    strcpy_s(actionInfo.localizedActionName, "Xkey_touch");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &XTouchAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "ykey_touch");
    strcpy_s(actionInfo.localizedActionName, "Ykey_touch");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &YTouchAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "menu_click");
    strcpy_s(actionInfo.localizedActionName, "Menu Click");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &menuClickAction));

    actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
    strcpy_s(actionInfo.actionName, "system_click");
    strcpy_s(actionInfo.localizedActionName, "System Click");
    actionInfo.countSubactionPaths = ARRAY_SIZE(handSubactionPath);
    actionInfo.subactionPaths = handSubactionPath;
    OXR(xrCreateAction(actionSet, &actionInfo, &systemClickAction));
  }

  XrPath gripPosePath[2];
  XrPath aimPosePath[2];
  XrPath hapticPath[2];
  XrPath thumbstickValuePath[2];
  XrPath thumbstickClickPath[2];
  XrPath thumbstickTouchPath[2];
  XrPath squeezeValuePath[2];
  XrPath squeezeClickPath[2];
  XrPath triggerClickPath[2];
  XrPath triggerValuePath[2];
  XrPath triggerTouchPath[2];
  XrPath AClickPath[2];
  XrPath BClickPath[2];
  XrPath XClickPath[2];
  XrPath YClickPath[2];
  XrPath ATouchPath[2];
  XrPath BTouchPath[2];
  XrPath XTouchPath[2];
  XrPath YTouchPath[2];
  XrPath menuClickPath;
  XrPath systemClickPath;


  OXR(xrStringToPath(m_instance, "/user/hand/left/input/grip/pose", &gripPosePath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/grip/pose", &gripPosePath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/aim/pose", &aimPosePath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/aim/pose", &aimPosePath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/output/haptic", &hapticPath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/output/haptic", &hapticPath[Side::RIGHT]));

  OXR(xrStringToPath(m_instance, "/user/hand/left/input/thumbstick", &thumbstickValuePath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/thumbstick", &thumbstickValuePath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/thumbstick/click", &thumbstickClickPath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/thumbstick/click", &thumbstickClickPath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/thumbstick/touch", &thumbstickTouchPath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/thumbstick/touch", &thumbstickTouchPath[Side::RIGHT]));

  OXR(xrStringToPath(m_instance, "/user/hand/left/input/trigger/value", &triggerValuePath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/trigger/value", &triggerValuePath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/trigger/click", &triggerClickPath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/trigger/click", &triggerClickPath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/trigger/touch", &triggerTouchPath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/trigger/touch", &triggerTouchPath[Side::RIGHT]));

  OXR(xrStringToPath(m_instance, "/user/hand/left/input/squeeze/value", &squeezeValuePath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/squeeze/value", &squeezeValuePath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/squeeze/click", &squeezeClickPath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/squeeze/click", &squeezeClickPath[Side::RIGHT]));

  OXR(xrStringToPath(m_instance, "/user/hand/right/input/a/click", &AClickPath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/b/click", &BClickPath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/x/click", &XClickPath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/y/click", &YClickPath[Side::LEFT]));

  OXR(xrStringToPath(m_instance, "/user/hand/right/input/a/touch", &ATouchPath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/b/touch", &BTouchPath[Side::RIGHT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/x/touch", &XTouchPath[Side::LEFT]));
  OXR(xrStringToPath(m_instance, "/user/hand/left/input/y/touch", &YTouchPath[Side::LEFT]));

  OXR(xrStringToPath(m_instance, "/user/hand/left/input/menu/click", &menuClickPath));
  OXR(xrStringToPath(m_instance, "/user/hand/right/input/system/click", &systemClickPath));

  { // Suggest bindings for the Quest 3 Controller.
    XrPath interactionProfilePath = XR_NULL_PATH;
    OXR(xrStringToPath(m_instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePath));
    std::vector<XrActionSuggestedBinding> bindings{{{gripPoseAction, gripPosePath[Side::LEFT]},
                                                    {gripPoseAction, gripPosePath[Side::RIGHT]},
                                                    {aimPoseAction, aimPosePath[Side::LEFT]},
                                                    {aimPoseAction, aimPosePath[Side::RIGHT]},
                                                    {hapticAction, hapticPath[Side::LEFT]},
                                                    {hapticAction, hapticPath[Side::RIGHT]},

                                                    {thumbstickValueAction, thumbstickValuePath[Side::LEFT]},
                                                    {thumbstickValueAction, thumbstickValuePath[Side::RIGHT]},
                                                    {thumbstickClickAction, thumbstickClickPath[Side::LEFT]},
                                                    {thumbstickClickAction, thumbstickClickPath[Side::RIGHT]},
                                                    {thumbstickTouchAction, thumbstickTouchPath[Side::LEFT]},
                                                    {thumbstickTouchAction, thumbstickTouchPath[Side::RIGHT]},

                                                    {triggerValueAction, triggerValuePath[Side::LEFT]},
                                                    {triggerValueAction, triggerValuePath[Side::RIGHT]},
                                                    //{triggerClickAction, triggerClickPath[Side::LEFT]},
                                                    //{triggerClickAction, triggerClickPath[Side::RIGHT]},
                                                    {triggerTouchAction, triggerTouchPath[Side::LEFT]},
                                                    {triggerTouchAction, triggerTouchPath[Side::RIGHT]},

                                                    //{squeezeClickAction, squeezeClickPath[Side::LEFT]},
                                                    //{squeezeClickAction, squeezeClickPath[Side::RIGHT]},
                                                    {squeezeValueAction, squeezeValuePath[Side::LEFT]},
                                                    {squeezeValueAction, squeezeValuePath[Side::RIGHT]},

                                                    {AAction, AClickPath[Side::RIGHT]},
                                                    {BAction, BClickPath[Side::RIGHT]},
                                                    {XAction, XClickPath[Side::LEFT]},
                                                    {YAction, YClickPath[Side::LEFT]},

                                                    {ATouchAction, ATouchPath[Side::RIGHT]},
                                                    {BTouchAction, BTouchPath[Side::RIGHT]},
                                                    {XTouchAction, XTouchPath[Side::LEFT]},
                                                    {YTouchAction, YTouchPath[Side::LEFT]},

                                                    {menuClickAction, menuClickPath},
                                                    {systemClickAction, systemClickPath},
    }};

    XrInteractionProfileSuggestedBinding suggestedBindings = { XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
    suggestedBindings.interactionProfile = interactionProfilePath;
    suggestedBindings.suggestedBindings = bindings.data();
    suggestedBindings.countSuggestedBindings = bindings.size();
    OXR(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings));

    XrActionSpaceCreateInfo actionSpaceInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO };
    actionSpaceInfo.action = gripPoseAction;
    actionSpaceInfo.poseInActionSpace.orientation.w = 1.0f;
    actionSpaceInfo.subactionPath = handSubactionPath[Side::LEFT];
    OXR(xrCreateActionSpace(app.Session, &actionSpaceInfo, &gripSpace[Side::LEFT]));
    actionSpaceInfo.subactionPath = handSubactionPath[Side::RIGHT];
    OXR(xrCreateActionSpace(app.Session, &actionSpaceInfo, &gripSpace[Side::RIGHT]));

    actionSpaceInfo.action = aimPoseAction;
    actionSpaceInfo.poseInActionSpace.orientation.w = 1.0f;
    actionSpaceInfo.subactionPath = handSubactionPath[Side::LEFT];
    OXR(xrCreateActionSpace(app.Session, &actionSpaceInfo, &aimSpace[Side::LEFT]));
    actionSpaceInfo.subactionPath = handSubactionPath[Side::RIGHT];
    OXR(xrCreateActionSpace(app.Session, &actionSpaceInfo, &aimSpace[Side::RIGHT]));

    // Attach to session
    XrSessionActionSetsAttachInfo attachInfo = { XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
    attachInfo.countActionSets = 1;
    attachInfo.actionSets = &actionSet;
    OXR(xrAttachSessionActionSets(app.Session, &attachInfo));
  }
}

void AppInput_shutdown() {
//  if (leftControllerAimSpace != XR_NULL_HANDLE) {
//    OXR(xrDestroySpace(leftControllerAimSpace));
//    OXR(xrDestroySpace(rightControllerAimSpace));
//    OXR(xrDestroySpace(leftControllerGripSpace));
//    OXR(xrDestroySpace(rightControllerGripSpace));
//    leftControllerAimSpace = XR_NULL_HANDLE;
//    rightControllerAimSpace = XR_NULL_HANDLE;
//    leftControllerGripSpace = XR_NULL_HANDLE;
//    rightControllerGripSpace = XR_NULL_HANDLE;
//  }
}

void AppInput_syncActions(App& app) {
  // sync action data
  XrActiveActionSet activeActionSet = {};
  activeActionSet.actionSet = actionSet;
  activeActionSet.subactionPath = XR_NULL_PATH;

  XrActionsSyncInfo syncInfo = { XR_TYPE_ACTIONS_SYNC_INFO };
  syncInfo.countActiveActionSets = 1;
  syncInfo.activeActionSets = &activeActionSet;
  OXR(xrSyncActions(app.Session, &syncInfo));

  // query input action states
  XrActionStateGetInfo getInfo = { XR_TYPE_ACTION_STATE_GET_INFO };
  getInfo.subactionPath = XR_NULL_PATH;

  //boolState = GetActionStateBoolean(app, boolAction);

//  if (leftControllerAimSpace == XR_NULL_HANDLE && app.SessionActive)
//  {
//    leftControllerAimSpace = CreateActionSpace(app, aimPoseAction, handSubactionPath[0]);
//    rightControllerAimSpace = CreateActionSpace(app, aimPoseAction, handSubactionPath[1]);
//    leftControllerGripSpace = CreateActionSpace(app, gripPoseAction, handSubactionPath[0]);
//    rightControllerGripSpace = CreateActionSpace(app, gripPoseAction, handSubactionPath[1]);
//  }

  {
    XrActionStateGetInfo getInfo = { XR_TYPE_ACTION_STATE_GET_INFO };
    getInfo.action = aimPoseAction;
    getInfo.subactionPath = handSubactionPath[0];

    OXR(xrGetActionStatePose(app.Session, &getInfo, &Global::mControllerPoseState[0]));
  }
  {
    XrActionStateGetInfo getInfo = { XR_TYPE_ACTION_STATE_GET_INFO };
    getInfo.action = aimPoseAction;
    getInfo.subactionPath = handSubactionPath[1];

    OXR(xrGetActionStatePose(app.Session, &getInfo, &Global::mControllerPoseState[1]));
  }

  static float joystick_x[Side::COUNT] = { 0 };
  static float joystick_y[Side::COUNT] = { 0 };
  static float trigger[Side::COUNT] = { 0 };
  static float squeeze[Side::COUNT] = { 0 };

  for (auto hand : { Side::LEFT, Side::RIGHT })
  {
    XrControllerEvent& applicationEvent = Global::inputXRControllerEvent[hand];
    applicationEvent.controllerEventBit = 0x00;

    XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
    getInfo.subactionPath = handSubactionPath[hand];

    //thumbstick value x/y
    getInfo.action = thumbstickValueAction;
    XrActionStateVector2f thumbstickValue{ XR_TYPE_ACTION_STATE_VECTOR2F };
    OXR(xrGetActionStateVector2f(app.Session, &getInfo, &thumbstickValue));
    if (thumbstickValue.isActive == XR_TRUE) {
      if (thumbstickValue.currentState.x == 0 && thumbstickValue.currentState.y == 0 && joystick_x[hand] == 0 && joystick_y[hand] == 0) {
      }
      else {
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_value_thumbstick;
        applicationEvent.thumbstick_x = thumbstickValue.currentState.x;
        applicationEvent.thumbstick_y = thumbstickValue.currentState.y;
      }
      joystick_x[hand] = thumbstickValue.currentState.x;
      joystick_y[hand] = thumbstickValue.currentState.y;
    }
    // thumbstick click
    getInfo.action = thumbstickClickAction;
    XrActionStateBoolean thumbstickClick{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &thumbstickClick));
    if ((thumbstickClick.isActive == XR_TRUE) && (thumbstickClick.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_thumbstick;
      if (thumbstickClick.currentState == XR_TRUE) {
        applicationEvent.click_thumbstick = true;
      }
      else {
        applicationEvent.click_thumbstick = false;
      }
    }
    // thumbstick touch
    getInfo.action = thumbstickTouchAction;
    XrActionStateBoolean thumbstickTouch{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &thumbstickTouch));
    if (thumbstickTouch.isActive == XR_TRUE) {
      if (thumbstickTouch.changedSinceLastSync == XR_TRUE) {
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_touch_thumbstick;
        if (thumbstickTouch.currentState == XR_TRUE) {
          applicationEvent.touch_thumbstick = true;
        }
        else {
          applicationEvent.touch_thumbstick = false;
        }
      }
    }

    //trigger value
    getInfo.action = triggerValueAction;
    XrActionStateFloat triggerValue{ XR_TYPE_ACTION_STATE_FLOAT };
    OXR(xrGetActionStateFloat(app.Session, &getInfo, &triggerValue));
    if (triggerValue.isActive == XR_TRUE) {
      if (triggerValue.currentState == 0 && trigger[hand] == 0) {
      }
      else {
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_value_trigger;
        applicationEvent.trigger = triggerValue.currentState;

#if 1
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_trigger;
        applicationEvent.click_trigger = triggerValue.currentState > 0.2;
#endif

      }
      trigger[hand] = triggerValue.currentState;
    }
    // trigger click
    getInfo.action = triggerClickAction;
    XrActionStateBoolean TriggerClick{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &TriggerClick));
    if (TriggerClick.isActive == XR_TRUE) {
      if (TriggerClick.changedSinceLastSync == XR_TRUE) {
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_trigger;
        if (TriggerClick.currentState == XR_TRUE) {
          applicationEvent.click_trigger = true;
        }
        else {
          applicationEvent.click_trigger = false;
        }
      }
    }
    // trigger touch
    getInfo.action = triggerTouchAction;
    XrActionStateBoolean TriggerTouch{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &TriggerTouch));
    if (TriggerTouch.isActive == XR_TRUE) {
      if (TriggerTouch.changedSinceLastSync == XR_TRUE) {
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_touch_trigger;
        if (TriggerTouch.currentState == XR_TRUE) {
          applicationEvent.touch_trigger = true;
        }
        else {
          applicationEvent.touch_trigger = false;
        }
      }
    }

    // squeeze value
    getInfo.action = squeezeValueAction;
    XrActionStateFloat squeezeValue{ XR_TYPE_ACTION_STATE_FLOAT };
    OXR(xrGetActionStateFloat(app.Session, &getInfo, &squeezeValue));
    if (squeezeValue.isActive == XR_TRUE) {
      if (squeezeValue.currentState == 0 && squeeze[hand] == 0) {
      }
      else {
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_value_squeeze;
        applicationEvent.squeeze = squeezeValue.currentState;
      }
      squeeze[hand] = squeezeValue.currentState;
    }
    // squeeze click
    getInfo.action = squeezeClickAction;
    XrActionStateBoolean squeezeClick{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &squeezeClick));
    if ((squeezeClick.isActive == XR_TRUE) && (squeezeClick.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_squeeze;
      if (squeezeClick.currentState == XR_TRUE) {
        applicationEvent.click_squeeze = true;
      }
      else {
        applicationEvent.click_squeeze = false;
      }
    }

    // A button
    getInfo.action = AAction;
    XrActionStateBoolean AValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &AValue));
    if ((AValue.isActive == XR_TRUE) && (AValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_a;
      if (AValue.currentState == XR_TRUE) {
        applicationEvent.click_a = true;
      }
      else {
        applicationEvent.click_a = false;
      }
    }
    // B button
    getInfo.action = BAction;
    XrActionStateBoolean BValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &BValue));
    if ((BValue.isActive == XR_TRUE) && (BValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_b;
      if (BValue.currentState == XR_TRUE) {
        applicationEvent.click_b = true;
      }
      else {
        applicationEvent.click_b = false;
      }
    }
    // X button
    getInfo.action = XAction;
    XrActionStateBoolean XValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &XValue));
    if ((XValue.isActive == XR_TRUE) && (XValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_x;
      if (XValue.currentState == XR_TRUE) {
        applicationEvent.click_x = true;
      }
      else {
        applicationEvent.click_x = false;
      }
    }
    // Y button
    getInfo.action = YAction;
    XrActionStateBoolean YValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &YValue));
    if ((YValue.isActive == XR_TRUE) && (YValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_y;
      if (YValue.currentState == XR_TRUE) {
        applicationEvent.click_y = true;
      }
      else {
        applicationEvent.click_y = false;
      }
    }

    // A button touch
    getInfo.action = ATouchAction;
    XrActionStateBoolean ATouchValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &ATouchValue));
    if ((ATouchValue.isActive == XR_TRUE) && (ATouchValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_touch_a;
      if (ATouchValue.currentState == XR_TRUE) {
        applicationEvent.touch_a = true;
      }
      else {
        applicationEvent.touch_a = false;
      }
    }
    // B button touch
    getInfo.action = BTouchAction;
    XrActionStateBoolean BTouchValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &BTouchValue));
    if ((BTouchValue.isActive == XR_TRUE) && (BTouchValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_touch_b;
      if (BTouchValue.currentState == XR_TRUE) {
        applicationEvent.touch_b = true;
      }
      else {
        applicationEvent.touch_b = false;
      }
    }
    // X button touch
    getInfo.action = XTouchAction;
    XrActionStateBoolean XTouchValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &XTouchValue));
    if ((XTouchValue.isActive == XR_TRUE) && (XTouchValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_touch_x;
      if (XTouchValue.currentState == XR_TRUE) {
        applicationEvent.touch_x = true;
      }
      else {
        applicationEvent.touch_x = false;
      }
    }
    // Y button touch
    getInfo.action = YTouchAction;
    XrActionStateBoolean YTouchValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &YTouchValue));
    if ((YTouchValue.isActive == XR_TRUE) && (YTouchValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_touch_y;
      if (YTouchValue.currentState == XR_TRUE) {
        applicationEvent.touch_y = true;
      }
      else {
        applicationEvent.touch_y = false;
      }
    }

    //menu click
    getInfo.action = menuClickAction;
    XrActionStateBoolean menuClickValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &menuClickValue));
    if (menuClickValue.isActive == XR_TRUE) {
      if (menuClickValue.changedSinceLastSync == XR_TRUE) {
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_menu;
        if (menuClickValue.currentState == XR_TRUE) {
          applicationEvent.click_menu = true;
        }
        else {
          applicationEvent.click_menu = false;
        }
      }
    }

    //system click
    getInfo.action = systemClickAction;
    XrActionStateBoolean systemClickValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(app.Session, &getInfo, &systemClickValue));
    if (systemClickValue.isActive == XR_TRUE) {
      if (systemClickValue.changedSinceLastSync == XR_TRUE) {
        applicationEvent.controllerEventBit |= CONTROLLER_EVENT_BIT_click_menu;
        if (systemClickValue.currentState == XR_TRUE) {
          applicationEvent.click_menu = true;
        }
        else {
          applicationEvent.click_menu = false;
        }
      }
    }
  }
}

using namespace OVR;

// EXT_texture_border_clamp
#ifndef GL_CLAMP_TO_BORDER
#define GL_CLAMP_TO_BORDER 0x812D
#endif

#ifndef GL_TEXTURE_BORDER_COLOR
#define GL_TEXTURE_BORDER_COLOR 0x1004
#endif

#ifndef GL_FRAMEBUFFER_SRGB_EXT
#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9
#endif

#if !defined(GL_EXT_multisampled_render_to_texture)
typedef void(GL_APIENTRY* PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)(
  GLenum target,
  GLsizei samples,
  GLenum internalformat,
  GLsizei width,
  GLsizei height);
typedef void(GL_APIENTRY* PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)(
  GLenum target,
  GLenum attachment,
  GLenum textarget,
  GLuint texture,
  GLint level,
  GLsizei samples);
#endif

#if !defined(GL_OVR_multiview)
typedef void(GL_APIENTRY* PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)(
  GLenum target,
  GLenum attachment,
  GLuint texture,
  GLint level,
  GLint baseViewIndex,
  GLsizei numViews);
#endif

#if !defined(GL_OVR_multiview_multisampled_render_to_texture)
typedef void(GL_APIENTRY* PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC)(
  GLenum target,
  GLenum attachment,
  GLuint texture,
  GLint level,
  GLsizei samples,
  GLint baseViewIndex,
  GLsizei numViews);
#endif

#define DEBUG 1
#define OVR_LOG_TAG "XrPassthroughGl"

#if defined(__ANDROID__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, OVR_LOG_TAG, __VA_ARGS__)
#if DEBUG
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, OVR_LOG_TAG, __VA_ARGS__)
#else
#define ALOGV(...)
#endif
#else
#define ALOGE(...)       \
    printf("ERROR: ");   \
    printf(__VA_ARGS__); \
    printf("\n")
#define ALOGV(...)       \
    printf("VERBOSE: "); \
    printf(__VA_ARGS__); \
    printf("\n")
#endif

/*
================================================================================

OpenGL-ES Utility Functions

================================================================================
*/

namespace {
  struct OpenGLExtensions_t {
    bool multi_view; // GL_OVR_multiview, GL_OVR_multiview2
    bool EXT_texture_border_clamp; // GL_EXT_texture_border_clamp, GL_OES_texture_border_clamp
    bool EXT_sRGB_write_control;
  };

  OpenGLExtensions_t glExtensions;
} // namespace

static void EglInitExtensions() {
  glExtensions = {};
  const char* allExtensions = (const char*)glGetString(GL_EXTENSIONS);
  if (allExtensions != nullptr) {
    glExtensions.multi_view = strstr(allExtensions, "GL_OVR_multiview2") &&
      strstr(allExtensions, "GL_OVR_multiview_multisampled_render_to_texture");

    glExtensions.EXT_texture_border_clamp =
      strstr(allExtensions, "GL_EXT_texture_border_clamp") ||
      strstr(allExtensions, "GL_OES_texture_border_clamp");
    glExtensions.EXT_sRGB_write_control = strstr(allExtensions, "GL_EXT_sRGB_write_control");
  }
}

static const char* GlFrameBufferStatusString(GLenum status) {
  switch (status) {
  case GL_FRAMEBUFFER_UNDEFINED:
    return "GL_FRAMEBUFFER_UNDEFINED";
  case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
    return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
  case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
    return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
  case GL_FRAMEBUFFER_UNSUPPORTED:
    return "GL_FRAMEBUFFER_UNSUPPORTED";
  case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
    return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
  default:
    return "unknown";
  }
}

/*
================================================================================

Framebuffer

================================================================================
*/

void Framebuffer::Clear() {
  Width = 0;
  Height = 0;
  Multisamples = 0;
  SwapChainLength = 0;
  Elements = nullptr;
}

static void* GlGetExtensionProc(const char* functionName) {
#if defined(__ANDROID__)
  return (void*)eglGetProcAddress(functionName);
#elif defined(WIN32)
  return (void*)wglGetProcAddress(functionName);
#else
  static_assert(false);
#endif
}

bool Framebuffer::Create(
  const GLenum colorFormat,
  const int width,
  const int height,
  const int multisamples,
  const int swapChainLength,
  GLuint* colorTextures) {
  PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR =
    (PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)GlGetExtensionProc(
      "glFramebufferTextureMultiviewOVR");
  PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC glFramebufferTextureMultisampleMultiviewOVR =
    (PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC)GlGetExtensionProc(
      "glFramebufferTextureMultisampleMultiviewOVR");

  Width = width;
  Height = height;
  Multisamples = multisamples;
  SwapChainLength = swapChainLength;

  Elements = new Element[SwapChainLength];

  for (int i = 0; i < SwapChainLength; i++) {
    Element& el = Elements[i];
    // Create the color buffer texture.
    el.ColorTexture = colorTextures[i];
    GLenum colorTextureTarget = GL_TEXTURE_2D_ARRAY;
    GL(glBindTexture(colorTextureTarget, el.ColorTexture));
    GL(glTexParameteri(colorTextureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER));
    GL(glTexParameteri(colorTextureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER));
    GLfloat borderColor[] = { 0.0f, 0.0f, 0.0f, 0.0f };
    GL(glTexParameterfv(colorTextureTarget, GL_TEXTURE_BORDER_COLOR, borderColor));
    GL(glTexParameteri(colorTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
    GL(glTexParameteri(colorTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
    GL(glBindTexture(colorTextureTarget, 0));

    // Create the depth buffer texture.
    GL(glGenTextures(1, &el.DepthTexture));
    GL(glBindTexture(GL_TEXTURE_2D_ARRAY, el.DepthTexture));
    GL(glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH_COMPONENT24, width, height, 2));
    GL(glBindTexture(GL_TEXTURE_2D_ARRAY, 0));

    // Create the frame buffer.
    GL(glGenFramebuffers(1, &el.FrameBufferObject));
    GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, el.FrameBufferObject));
    if (multisamples > 1 && (glFramebufferTextureMultisampleMultiviewOVR != nullptr)) {
      GL(glFramebufferTextureMultisampleMultiviewOVR(
        GL_DRAW_FRAMEBUFFER,
        GL_DEPTH_ATTACHMENT,
        el.DepthTexture,
        0 /* level */,
        multisamples /* samples */,
        0 /* baseViewIndex */,
        2 /* numViews */));
      GL(glFramebufferTextureMultisampleMultiviewOVR(
        GL_DRAW_FRAMEBUFFER,
        GL_COLOR_ATTACHMENT0,
        el.ColorTexture,
        0 /* level */,
        multisamples /* samples */,
        0 /* baseViewIndex */,
        2 /* numViews */));
    }
    else if (glFramebufferTextureMultiviewOVR != nullptr) {
      GL(glFramebufferTextureMultiviewOVR(
        GL_DRAW_FRAMEBUFFER,
        GL_DEPTH_ATTACHMENT,
        el.DepthTexture,
        0 /* level */,
        0 /* baseViewIndex */,
        2 /* numViews */));
      GL(glFramebufferTextureMultiviewOVR(
        GL_DRAW_FRAMEBUFFER,
        GL_COLOR_ATTACHMENT0,
        el.ColorTexture,
        0 /* level */,
        0 /* baseViewIndex */,
        2 /* numViews */));
    }

    GLenum renderFramebufferStatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
    GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
    if (renderFramebufferStatus != GL_FRAMEBUFFER_COMPLETE) {
      ALOGE(
        "Incomplete frame buffer object: %s",
        GlFrameBufferStatusString(renderFramebufferStatus));
      return false;
    }
  }

  return true;
}

void Framebuffer::Destroy() {
  for (int i = 0; i < SwapChainLength; i++) {
    Element& el = Elements[i];
    GL(glDeleteFramebuffers(1, &el.FrameBufferObject));
    GL(glDeleteTextures(1, &el.DepthTexture));
  }
  delete[] Elements;
  Clear();
}

void Framebuffer::Bind(int element) {
  GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, Elements[element].FrameBufferObject));
}

/*
================================================================================

Scene

================================================================================
*/

void Scene::Clear() {
  CreatedScene = false;
  SceneMatrices = 0;
}

bool Scene::IsCreated() {
  return CreatedScene;
}

void Scene::Create() {
  // Setup the scene matrices.
  GL(glGenBuffers(1, &SceneMatrices));
  GL(glBindBuffer(GL_UNIFORM_BUFFER, SceneMatrices));
  GL(glBufferData(
    GL_UNIFORM_BUFFER,
    2 * sizeof(Matrix4f) /* 2 view matrices */ +
    2 * sizeof(Matrix4f) /* 2 projection matrices */,
    nullptr,
    GL_STATIC_DRAW));
  GL(glBindBuffer(GL_UNIFORM_BUFFER, 0));

  CreatedScene = true;
}

/*
================================================================================

AppRenderer

================================================================================
*/

void AppRenderer::Clear() {
  framebuffer.Clear();
  scene.Clear();
}

void AppRenderer::Create(
  GLenum format,
  int width,
  int height,
  int numMultiSamples,
  int swapChainLength,
  GLuint* colorTextures) {
  EglInitExtensions();
  framebuffer.Create(format, width, height, numMultiSamples, swapChainLength, colorTextures);
  if (glExtensions.EXT_sRGB_write_control) {
    // This app was originally written with the presumption that
    // its swapchains and compositor front buffer were RGB.
    // In order to have the colors the same now that its compositing
    // to an sRGB front buffer, we have to write to an sRGB swapchain
    // but with the linear->sRGB conversion disabled on write.
    GL(glDisable(GL_FRAMEBUFFER_SRGB_EXT));
  }
}

void AppRenderer::Destroy() {
  framebuffer.Destroy();
}

void AppRenderer::RenderFrame(AppRenderer::FrameIn frameIn)
{
  // Update the scene matrices.
  GL(glBindBuffer(GL_UNIFORM_BUFFER, scene.SceneMatrices));
  void* sceneMatrices = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 4 * sizeof(mat4) /* 2 view + 2 proj matrices */, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
  ASSERT(sceneMatrices != nullptr);
  memcpy(sceneMatrices, &frameIn.View, 4 * sizeof(mat4));

  GL(glUnmapBuffer(GL_UNIFORM_BUFFER));

  // Render the eye images.
  framebuffer.Bind(frameIn.SwapChainIndex);

  GL(glEnable(GL_CULL_FACE));
  GL(glEnable(GL_DEPTH_TEST));
  GL(glDepthFunc(GL_LEQUAL));
  GL(glEnable(GL_BLEND));
  GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
  GL(glDepthMask(GL_TRUE));
  GL(glCullFace(GL_BACK));
  GL(glViewport(0, 0, framebuffer.Width, framebuffer.Height));
  //GL(glScissor(0, 0, framebuffer.Width, framebuffer.Height));
  GL(glClearColor(Settings::environmentClearColor.r, Settings::environmentClearColor.g, Settings::environmentClearColor.b, Settings::environmentClearColor.a));
  GL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));

  GL(glBindBuffer(GL_ARRAY_BUFFER, Global::vbo));

  GL(glBindBufferBase(GL_UNIFORM_BUFFER, 0, scene.SceneMatrices));

  {
    if (Global::inputXRControllerEvent[0].click_trigger)
      Global::inputXRActiveController = XrActiveController::left;
    else if (Global::inputXRControllerEvent[1].click_trigger)
      Global::inputXRActiveController = XrActiveController::right;
    {
      Input::handleInputBegin();
      Ui::handleInputBegin();
      Xr::handleXrControllerInput();
      Ui::handleInputXr();
      Ui::handleInputEnd();
      Input::handleInputEnd();
      Input::proccessInputEvents();
    }
  }

  tick();

  GL(glBindVertexArray(Global::dynamicDrawVao));
  GL(glBindTexture(GL_TEXTURE_2D, Global::texture));
  
  {
    static vec3 cameraTargetPosition;
    static vec3 cameraCurrentPosition;
    const mat4 viewMat = Camera::calculateViewMat(Settings::cameraMode, Global::selectedArrangementIndex, cameraTargetPosition, cameraCurrentPosition);
    static vec3 highwayTargetPosition;
    static vec3 highwayCurrentPosition;
    const mat4 highwayViewProjectionMat = Camera::calculateHighwayProjectionViewMat(Settings::cameraMode, viewMat, highwayTargetPosition, highwayCurrentPosition);

    render(viewMat, highwayViewProjectionMat);
  }

  mat4 quest3fix{}; // TODO: on quest3 the view matrix is handled in the shader.
  Ui::render(framebuffer.Width, framebuffer.Height, &quest3fix);
  Ui::renderClear();

  glBindVertexArray(Global::staticDrawVao);

  if (Global::mControllerPoseState[0].isActive || Global::mControllerPoseState[1].isActive)
  {
    if (Global::mControllerPoseState[0].isActive)
      Xr::calulateControllerUiIntersection(Global::mAimPose[0], Global::inputXrControllerLeftPosX, Global::inputXrControllerLeftPosY);
    if (Global::mControllerPoseState[1].isActive)
      Xr::calulateControllerUiIntersection(Global::mAimPose[1], Global::inputXrControllerRightPosX, Global::inputXrControllerRightPosY);

    {
      glUseProgram(Shader::dotInlay);

      glUniform4f(Shader::dotInlayUniformColor, Settings::xrCursorColor[0].r, Settings::xrCursorColor[0].g, Settings::xrCursorColor[0].b, Settings::xrCursorColor[0].a);
      glUniform4f(Shader::dotInlayUniformColor2, Settings::xrCursorColor[1].r, Settings::xrCursorColor[1].g, Settings::xrCursorColor[1].b, Settings::xrCursorColor[1].a);

      glBindTexture(GL_TEXTURE_2D, Global::texture);

      if (Global::mControllerPoseState[0].isActive)
        Xr::drawXrCursor({}, Global::inputXrControllerLeftPosX, Global::inputXrControllerLeftPosY, Settings::uiXrZ);
      if (Global::mControllerPoseState[1].isActive)
        Xr::drawXrCursor({}, Global::inputXrControllerRightPosX, Global::inputXrControllerRightPosY, Settings::uiXrZ);
    }

    {
      Xr::drawXrControllers({}, Global::mControllerPoseState, Global::mControllerModel, Global::mAimModel);
    }
  }

  // Discard the depth buffer, so the tiler won't need to write it back out to memory.
  const GLenum depthAttachment[1] = { GL_DEPTH_ATTACHMENT };
  glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, depthAttachment);
  // We now let the resolve happen implicitly.

}

#endif // PLATFORM_QUEST_3
