// ==============================================================================
//
// Collection of useful functions building on top of, and extending, core zmath.
// https://github.com/michal-z/zig-gamedev/tree/main/libs/zmath
//
// ------------------------------------------------------------------------------
// 1. Matrix functions
// ------------------------------------------------------------------------------
//
// As an example, in a left handed Y-up system:
//   getAxisX is equivalent to the right vector
//   getAxisY is equivalent to the up vector
//   getAxisZ is equivalent to the forward vector
//
// getTranslationVec(m: Mat) Vec
// getAxisX(m: Mat) Vec
// getAxisY(m: Mat) Vec
// getAxisZ(m: Mat) Vec
//
// ==============================================================================

const zm = @import("zmath.zig");
const std = @import("std");
const math = std.math;
const expect = std.testing.expect;

pub fn getTranslationVec(m: zm.Mat) zm.Vec {
    var translation = m[3];
    translation[3] = 0;
    return translation;
}

pub fn setTranslationVec(m: *zm.Mat, translation: zm.Vec) void {
    const w = m[3][3];
    m[3] = translation;
    m[3][3] = w;
}

pub fn getScaleVec(m: zm.Mat) zm.Vec {
    const scale_x = zm.length3(zm.f32x4(m[0][0], m[1][0], m[2][0], 0))[0];
    const scale_y = zm.length3(zm.f32x4(m[0][1], m[1][1], m[2][1], 0))[0];
    const scale_z = zm.length3(zm.f32x4(m[0][2], m[1][2], m[2][2], 0))[0];
    return zm.f32x4(scale_x, scale_y, scale_z, 0);
}

pub fn getRotationQuat(_m: zm.Mat) zm.Quat {
    // Ortho normalize given matrix.
    const c1 = zm.normalize3(zm.f32x4(_m[0][0], _m[1][0], _m[2][0], 0));
    const c2 = zm.normalize3(zm.f32x4(_m[0][1], _m[1][1], _m[2][1], 0));
    const c3 = zm.normalize3(zm.f32x4(_m[0][2], _m[1][2], _m[2][2], 0));
    var m = _m;
    m[0][0] = c1[0];
    m[1][0] = c1[1];
    m[2][0] = c1[2];
    m[0][1] = c2[0];
    m[1][1] = c2[1];
    m[2][1] = c2[2];
    m[0][2] = c3[0];
    m[1][2] = c3[1];
    m[2][2] = c3[2];

    // Extract rotation
    return zm.quatFromMat(m);
}

pub fn getAxisX(m: zm.Mat) zm.Vec {
    return zm.normalize3(zm.f32x4(m[0][0], m[0][1], m[0][2], 0.0));
}

pub fn getAxisY(m: zm.Mat) zm.Vec {
    return zm.normalize3(zm.f32x4(m[1][0], m[1][1], m[1][2], 0.0));
}

pub fn getAxisZ(m: zm.Mat) zm.Vec {
    return zm.normalize3(zm.f32x4(m[2][0], m[2][1], m[2][2], 0.0));
}

test "zmath.util.mat.translation" {
    // zig fmt: off
    const mat_data = [18]f32{
        1.0,
        2.0, 3.0, 4.0, 5.0,
        6.0, 7.0, 8.0, 9.0,
        10.0,11.0, 12.0,13.0,
        14.0, 15.0, 16.0, 17.0,
        18.0,
    };
    // zig fmt: on
    const mat = zm.loadMat(mat_data[1..]);
    const translation = getTranslationVec(mat);
    try zm.expectVecApproxEqAbs(translation, zm.f32x4(14.0, 15.0, 16.0, 0.0), 0.0001);
}

test "zmath.util.mat.scale" {
    const mat = zm.mul(zm.scaling(3, 4, 5), zm.translation(6, 7, 8));
    const scale = getScaleVec(mat);
    try zm.expectVecApproxEqAbs(scale, zm.f32x4(3.0, 4.0, 5.0, 0.0), 0.0001);
}

test "zmath.util.mat.rotation" {
    const rotate_origin = zm.matFromRollPitchYaw(0.1, 1.2, 2.3);
    const mat = zm.mul(zm.mul(rotate_origin, zm.scaling(3, 4, 5)), zm.translation(6, 7, 8));
    const rotate_get = getRotationQuat(mat);
    const v0 = zm.mul(zm.f32x4s(1), rotate_origin);
    const v1 = zm.mul(zm.f32x4s(1), zm.quatToMat(rotate_get));
    try zm.expectVecApproxEqAbs(v0, v1, 0.0001);
}

test "zmath.util.mat.z_vec" {
    const degToRad = std.math.degreesToRadians;
    var identity = zm.identity();
    var z_vec = getAxisZ(identity);
    try zm.expectVecApproxEqAbs(z_vec, zm.f32x4(0.0, 0.0, 1.0, 0), 0.0001);
    const rot_yaw = zm.rotationY(degToRad(90));
    identity = zm.mul(identity, rot_yaw);
    z_vec = getAxisZ(identity);
    try zm.expectVecApproxEqAbs(z_vec, zm.f32x4(1.0, 0.0, 0.0, 0), 0.0001);
}

test "zmath.util.mat.y_vec" {
    const degToRad = std.math.degreesToRadians;
    var identity = zm.identity();
    var y_vec = getAxisY(identity);
    try zm.expectVecApproxEqAbs(y_vec, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01);
    const rot_yaw = zm.rotationY(degToRad(90));
    identity = zm.mul(identity, rot_yaw);
    y_vec = getAxisY(identity);
    try zm.expectVecApproxEqAbs(y_vec, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01);
    const rot_pitch = zm.rotationX(degToRad(90));
    identity = zm.mul(identity, rot_pitch);
    y_vec = getAxisY(identity);
    try zm.expectVecApproxEqAbs(y_vec, zm.f32x4(0.0, 0.0, 1.0, 0), 0.01);
}

test "zmath.util.mat.right" {
    const degToRad = std.math.degreesToRadians;
    var identity = zm.identity();
    var right = getAxisX(identity);
    try zm.expectVecApproxEqAbs(right, zm.f32x4(1.0, 0.0, 0.0, 0), 0.01);
    const rot_yaw = zm.rotationY(degToRad(90));
    identity = zm.mul(identity, rot_yaw);
    right = getAxisX(identity);
    try zm.expectVecApproxEqAbs(right, zm.f32x4(0.0, 0.0, -1.0, 0), 0.01);
    const rot_pitch = zm.rotationX(degToRad(90));
    identity = zm.mul(identity, rot_pitch);
    right = getAxisX(identity);
    try zm.expectVecApproxEqAbs(right, zm.f32x4(0.0, 1.0, 0.0, 0), 0.01);
}

// ------------------------------------------------------------------------------
// This software is available under 2 licenses -- choose whichever you prefer.
// ------------------------------------------------------------------------------
// ALTERNATIVE A - MIT License
// Copyright (c) 2022 Michal Ziulek and Contributors
// Permission is hereby granted, free of charge, to any person obtaining identity copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ------------------------------------------------------------------------------
// ALTERNATIVE B - Public Domain (www.unlicense.org)
// This is free and unencumbered software released into the public domain.
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
// software, either in source code form or as identity compiled binary, for any purpose,
// commercial or non-commercial, and by any means.
// In jurisdictions that recognize copyright laws, the author or authors of this
// software dedicate any and all copyright interest in the software to the public
// domain. We make this dedication for the benefit of the public at large and to
// the detriment of our heirs and successors. We intend this dedication to be an
// overt act of relinquishment in perpetuity of all present and future rights to
// this software under copyright law.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ------------------------------------------------------------------------------
