---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by HUAFEI2.
--- DateTime: 2020/6/28 17:30
---

local bCheckMatrix = false;

---@class LMatrix
LMatrix = LMatrix or class("LMatrix");

function LMatrix:ctor()
    local data = {};
    for i = 1, 16 do
        data[i] = 0;
    end
    data[1] = 1;
    data[6] = 1;
    data[11] = 1;
    data[16] = 1;
    self.f = data;
end

function LMatrix:GetData()
    return KArray:new(self.f);
end

---@param i number
---@param val number
function LMatrix:Set(i, val)
    self.f[i] = val;
end

---@returns number
function LMatrix:Get(i)
    return self.f[i];
end

function LMatrix:Identity()
    local f = self.f;
    for i = 1, 16 do
        f[i] = 0;
    end
    f[1] = 1;
    f[6] = 1;
    f[11] = 1;
    f[16] = 1;
end

---@return boolean
function LMatrix:IsIdentity()
    local f = self.f;

    for i = 1, 16 do
        local shouldVal = (i % 5 == 1) and 1 or 0;
        if shouldVal ~= f[i] then
            return false;
        end
    end
    return true;
end

---@param i number
---@param rowVec LVec4
---@return LMatrix
function LMatrix:SetRow(i, rowVec)
    local f = self.f;

    local nStartIdx = (i - 1) * 4 + 1;
    if not math.isNaN(rowVec.x) then
        f[nStartIdx] = rowVec.x;
    else
        LOG_E("x value is nan");
    end
    if not math.isNaN(rowVec.y) then
        f[nStartIdx + 1] = rowVec.y;
    else
        LOG_E("y value is nan");
    end
    if not math.isNaN(rowVec.z) then
        f[nStartIdx + 2] = rowVec.z;
    else
        LOG_E("z value is nan");
    end
    if not math.isNaN(rowVec.w) then
        f[nStartIdx + 3] = rowVec.w;
    else
        LOG_E("w value is nan");
    end
    return self;
end

---@param i number
---@param rowVec LVec4
---@return LMatrix
function LMatrix:GetRow(i, rowVec)
    local f = self.f;
    local nStartIdx = (i - 1) * 4 + 1;
    rowVec.x = f[nStartIdx];
    rowVec.y = f[nStartIdx + 1];
    rowVec.z = f[nStartIdx + 2];
    rowVec.w = f[nStartIdx + 3];
    return self;
end

---@param x number
---@param y number
---@param z number
function LMatrix:SetPos(x, y, z)
    local f = self.f;
    if not math.isNaN(x) then
        f[13] = x;
    else
        LOG_E("x value is nan");
    end

    if not math.isNaN(y) then
        f[14] = y;
    else
        LOG_E("y value is nan");
    end

    if not math.isNaN(z) then
        f[15] = z;
    else
        LOG_E("z value is nan");
    end
end

---@param rowVec LVec3
function LMatrix:GetPos(rowVec)
    local f = self.f;
    rowVec.x = f[13];
    rowVec.y = f[14];
    rowVec.z = f[15];
end

---@param mat LMatrix
function LMatrix:CopyFrom(mat)
    local f = self.f;
    local mf = mat.f;
    for i = 1, 16 do
        f[i] = mf[i];
    end
end

---return LMatrix
function LMatrix:Clone()
    local mat = LMatrix:new();
    mat:CopyFrom(self);
    return mat;
end

---@param array KArray
---@param offset number
function LMatrix:FromArray(array, offset)
    local f = self.f;
    for i = 1, 16 do
        f[i] = array:Get(offset + i - 1);
    end
end

---@param array KArray
---@param offset number
function LMatrix:ToArray(array, offset)
    local f = self.f;
    for i = 1, 16 do
        array:Set(offset + i - 1, f[i]);
    end
end

---@returns boolean
function LMatrix:Check()
    if not bCheckMatrix then
        return true;
    end
    local bValid = true;
    local f = self.f;
    for i = 1, 16 do
        if math.isNan(f[i]) then
            LOG_E("matrix "..i.." is nan");
            bValid = false;
        end
    end
    return bValid;
end

---@param mat LMatrix
function LMatrix:Multiply(mat)
    MatrixMultiply(self, self, mat);
    return self;
end

---@returns boolean
function LMatrix:IsScaled()
    local f = self.f;
    local x0 = f[1];
    local y0 = f[2];
    local z0 = f[3];
    local x1 = f[4];
    local y1 = f[5];
    local z1 = f[6];
    local x2 = f[9];
    local y2 = f[10];
    local z2 = f[11];

    local bRet = false;
    --Retrieving the scale is done by gathering the legnth of the vectors for each column of the 3x3 matrix.
    if (math.abs(x0 * x0 + y0 * y0 + z0 * z0 - 1.0) < 0.01 and
        math.abs(x1 * x1 + y1 * y1 + z1 * z1 - 1.0) < 0.01 and
        math.abs(x2 * x2 + y2 * y2 + z2 * z2 - 1.0) < 0.01) then
        bRet = true;
    end
    return bRet;
end

---@class LMatrix34
LMatrix34 = LMatrix34 or class("LMatrix34");

---@param { undefined | Number[] array, Number idStart}
function LMatrix34:ctor()
    local data = {};
    for i = 1, 12 do
        data[i] = 0;
    end
    data[1] = 1;
    data[6] = 1;
    data[11] = 1;
    self.f = data;
end

---@param mat44 LMatrix
function LMatrix34:Set(mat44)
    local f = self.f;
    local mf = mat44.f;
    f[1] = mf[1];
    f[2] = mf[5];
    f[3] = mf[9];
    f[4] = mf[13];

    f[5] = mf[2];
    f[6] = mf[6];
    f[7] = mf[10];
    f[8] = mf[14];

    f[9] = mf[3];
    f[10] = mf[7];
    f[11] = mf[11];
    f[12] = mf[15];
end

---@returns KArray
function LMatrix34:GetData()
    local f = self.f;
    return KArray:new(f);
end

---@param mat44 LMatrix
---@param array KArray
---@param offset number
function CopyMat44ToArray34(mat44, array, offset)
    local f = mat44.f;
    for i = 1, 16 do
        array:Set(offset + i - 1, f[i]);
    end
end

---@param mOut LMatrix
---@param mA LMatrix
---@param mB LMatrix
---@return LMatrix
function MatrixMultiply(mOut, mA, mB)
    local a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15;
    local b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15;

    local f = mA.f;
    a0 = f[1];
    a1 = f[2];
    a2 = f[3];
    a3 = f[4];
    a4 = f[5];
    a5 = f[6];
    a6 = f[7];
    a7 = f[8];
    a8 = f[9];
    a9 = f[10];
    a10 = f[11];
    a11 = f[12];
    a12 = f[13];
    a13 = f[14];
    a14 = f[15];
    a15 = f[16];

    f = mB.f;
    b0 = f[1];
    b1 = f[2];
    b2 = f[3];
    b3 = f[4];
    b4 = f[5];
    b5 = f[6];
    b6 = f[7];
    b7 = f[8];
    b8 = f[9];
    b9 = f[10];
    b10 = f[11];
    b11 = f[12];
    b12 = f[13];
    b13 = f[14];
    b14 = f[15];
    b15 = f[16];

    f = mOut.f;
    f[1] = a0 * b0 + a1 * b4 + a2 * b8 + a3 * b12;
    f[2] = a0 * b1 + a1 * b5 + a2 * b9 + a3 * b13;
    f[3] = a0 * b2 + a1 * b6 + a2 * b10 + a3 * b14;
    f[4] = a0 * b3 + a1 * b7 + a2 * b11 + a3 * b15;

    f[5] = a4 * b0 + a5 * b4 + a6 * b8 + a7 * b12;
    f[6] = a4 * b1 + a5 * b5 + a6 * b9 + a7 * b13;
    f[7] = a4 * b2 + a5 * b6 + a6 * b10 + a7 * b14;
    f[8] = a4 * b3 + a5 * b7 + a6 * b11 + a7 * b15;

    f[9] = a8 * b0 + a9 * b4 + a10 * b8 + a11 * b12;
    f[10] = a8 * b1 + a9 * b5 + a10 * b9 + a11 * b13;
    f[11] = a8 * b2 + a9 * b6 + a10 * b10 + a11 * b14;
    f[12] = a8 * b3 + a9 * b7 + a10 * b11 + a11 * b15;

    f[13] = a12 * b0 + a13 * b4 + a14 * b8 + a15 * b12;
    f[14] = a12 * b1 + a13 * b5 + a14 * b9 + a15 * b13;
    f[15] = a12 * b2 + a13 * b6 + a14 * b10 + a15 * b14;
    f[16] = a12 * b3 + a13 * b7 + a14 * b11 + a15 * b15;
    return mOut;
end

---@param mOut LMatrix
---@param x number
---@param y number
---@param z number
---@return LMatrix
function MatrixTranslation(mOut, x, y, z)
    local f = mOut.f;
    f[1] = 1.0; f[2] = 0.0; f[3] = 0.0;  f[4] = 0.0;
    f[5] = 0.0; f[6] = 1.0; f[7] = 0.0;  f[8] = 0.0;
    f[9] = 0.0; f[10] = 0.0; f[11] = 1.0; f[12] = 0.0;
    f[13] = x;  f[14] = y;  f[15] = z;   f[16] = 1.0;
    return mOut;
end

---@param mOut LMatrix
---@param x number
---@param y number
---@param z number
---@return LMatrix
function MatrixScaling(mOut, x, y, z)
    local f = mOut.f;
    f[1] = x;    f[2] = 0.0;  f[3] = 0.0;  f[4] = 0.0;
    f[5] = 0.0;  f[6] = y;    f[7] = 0.0;  f[8] = 0.0;
    f[9] = 0.0;  f[10] = 0.0; f[11] = z;   f[12] = 0.0;
    f[13] = 0.0; f[14] = 0.0; f[15] = 0.0; f[16] = 1.0;
    return mOut;
end

---@param mOut LMatrix
---@param fAngle number
---@param x number
---@param y number
---@param z number
---@param bNormalizeXYZ boolean
---@return LMatrix
function MatrixRotationAxis(mOut, fAngle, x, y, z, bNormalizeXYZ)
    local s = math.sin(fAngle);
    local c = math.cos(fAngle);
    local f = mOut.f;

    if bNormalizeXYZ then
        local axis = LVec3:new(x, y, z);
        axis:Normalize();
        x = axis.x;
        y = axis.y;
        z = axis.z;
    end
    f[1] = x * x * (1 - c) + c;
    f[5] = x * y * (1 - c) - (z * s);
    f[9] = x * z * (1 - c) + (y * s);
    f[13] = 0;

    f[2] = y * x * (1 - c) + (z * s);
    f[6] = y * y * (1 - c) + c;
    f[10] = y * z * (1 - c) - (x * s);
    f[14] = 0;

    f[3] = z * x * (1 - c) - (y * s);
    f[7] = z * y * (1 - c) + (x * s);
    f[11] = z * z * (1 - c) + c;
    f[15] = 0.0;

    f[4] = 0.0;
    f[8] = 0.0;
    f[12] = 0.0;
    f[16] = 1.0;
    return mOut;
end

---@param mOut LMatrix
---@param fAngle number
function MatrixRotationX(mOut, fAngle)
    local fCosine = math.cos(fAngle);
    local fSine = math.sin(fAngle);
    local f = mOut.f;

    f[1] = 1.0;  f[2] = 0.0;     f[3] = 0.0;      f[4] = 0.0;
    f[5] = 0.0;  f[6] = fCosine; f[7] = fSine;    f[8] = 0.0;
    f[9] = 0.0;  f[10] = -fSine; f[11] = fCosine; f[12] = 0.0;
    f[13] = 0.0; f[14] = 0.0;    f[15] = 0.0;     f[16] = 1.0;
end

---@param mOut LMatrix
---@param fAngle number
function MatrixRotationY(mOut, fAngle)
    local fCosine = math.cos(fAngle);
    local fSine = math.sin(fAngle);
    local f = mOut.f;
    f[1] = fCosine;  f[2] = 0.0;   f[3] = -fSine;   f[4] = 0.0;
    f[5] = 0.0;      f[6] = 1.0;   f[7] = 0.0;      f[8] = 0.0;
    f[9] = fSine;    f[10] = 0.0;  f[11] = fCosine; f[12] = 0.0;
    f[13] = 0.0;     f[14] = 0.0;  f[15] = 0.0;     f[16] = 1.0;
end

---@param mOut LMatrix
---@param fAngle number
function MatrixRotationZ(mOut, fAngle)
    local fCosine = math.cos(fAngle);
    local fSine = math.sin(fAngle);
    local f = mOut.f;
    f[1] = fCosine; f[2] = fSine;    f[3] = 0.0;    f[4] = 0.0;
    f[5] = -fSine;  f[6] = fCosine;  f[7] = 0.0;    f[8] = 0.0;
    f[9] = 0.0;     f[10] = 0.0;     f[11] = 1.0;   f[12] = 0.0;
    f[13] = 0.0;    f[14] = 0.0;     f[15] = 0.0;   f[16] = 1.0;
end

---@param mOut LMatrix
---@param mIn LMatrix
function MatrixTranspose(mOut, mIn)
    mOut:CopyFrom(mIn);
end

---@param mb LMatrix
---@return number
function MatrixfDeterminant(mb)
    local minor = LVec4:new();
    local v1 = LVec4:new();
    local v2 = LVec4:new();
    local v3 = LVec4:new();

    local f = mb.f;
    v1.x = f[1]; v1.y = f[5]; v1.z = f[9]; v1.w = f[13];
    v2.x = f[2]; v2.y = f[6]; v2.z = f[10]; v2.w = f[14];
    v3.x = f[3]; v3.y = f[7]; v3.z = f[11]; v3.w = f[15];
    Vec4Cross(minor, v1, v2, v3);
    local det = -(f[4] * minor.x + f[8] * minor.y + f[12] * minor.z + f[16] * minor.w);
    return det;
end

---@param outMat LMatrix
---@param refMat LMatrix
function MatrixInverse(outMat, refMat)
    local mb = LMatrix:new();
    mb:CopyFrom(refMat);

    local v = LVec4:new();
    local vec_0 = LVec4:new();
    local vec_1 = LVec4:new();
    local vec_2 = LVec4:new();

    local f = mb.f;
    local of = outMat.f;
    det = MatrixfDeterminant(mb);

    if det == nil then
        return
    end
    local a;
    for i = 1, 4 do
        for j = 1, 4 do
            if j ~= i then
                a = j;
                if j > i then
                    a = a - 1;
                end
                local vec;
                if a == 1 then
                    vec = vec_0;
                elseif a == 2 then
                    vec = vec_1;
                elseif a == 3 then
                    vec = vec_2;
                end
                vec.x = f[j * 4 + 1];
                vec.y = f[j * 4 + 2];
                vec.z = f[j * 4 + 3];
                vec.w = f[j * 4 + 4];
            end
        end
        Vec4Cross(v, vec_0, vec_1, vec_2);
        of[0 * 4 + i] = math.pow(-1.0, i) * v.x / det;
        of[1 * 4 + i] = math.pow(-1.0, i) * v.y / det;
        of[2 * 4 + i] = math.pow(-1.0, i) * v.z / det;
        of[3 * 4 + i] = math.pow(-1.0, i) * v.w / det;
    end
end

---@param mOut LMatrix
---@param vEye LVec3
---@param vAt LVec3
---@param vUp LVec3
function MatrixLookAtLH(mOut, vEye, vAt, vUp)
    local f = LVec3:new();
    local vUpActual = LVec3:new();
    local s = LVec3:new();
    local u = LVec3:new();
    local t = LMatrix:new();

    local mf = mOut.f;
    f.x = vEye.x - vAt.x;
    f.y = vEye.y - vAt.y;
    f.z = vEye.z - vAt.z;
    f:Normalize();

    vUpActual:CopyFrom(vUp);
    vUpActual:Normalize();
    Vec3Cross(s, f, vUpActual);
    Vec3Cross(u, s, f);
    mf[1] = s.x;
    mf[2] = u.x;
    mf[3] = -f.x;
    mf[4] = 0.0;

    mf[5] = s.y;
    mf[6] = u.y;
    mf[7] = -f.y;
    mf[8] = 0.0;

    mf[9] = s.z;
    mf[10] = u.z;
    mf[11] = -f.z;
    mf[12] = 0.0;

    mf[13] = 0.0;
    mf[14] = 0.0;
    mf[15] = 0.0;
    mf[16] = 1.0;
    MatrixTranslation(t, -vEye.x, -vEye.y, -vEye.z);
    MatrixMultiply(mOut, t, mOut);
end

---@param mOut LMatrix
---@param vEye LVec3
---@param vAt LVec3
---@param vUp LVec3
function MatrixLookAtRH(mOut, vEye, vAt, vUp)
    local f = LVec3:new();
    local vUpActual = LVec3:new();
    local s = LVec3:new();
    local u = LVec3:new();
    local t = LMatrix:new();

    local mf = mOut.f;
    local fForwardDotUp = 0.0;
    f.x = vAt.x - vEye.x;
    f.y = vAt.y - vEye.y;
    f.z = vAt.z - vEye.z;
    f:Normalize();
    vUpActual:CopyFrom(vUp);
    vUpActual:Normalize();
    fForwardDotUp = vUpActual:Dot(f);

    if (math.abs(math.abs(fForwardDotUp) - 1.0) < 0.005) then
        vUpActual.x = 0.0;
        vUpActual.y = 0.0;
        vUpActual.z = -1.0;
    end
    Vec3Cross(s, f, vUpActual);
    Vec3Cross(u, s, f);

    mf[1] = s.x;
    mf[2] = u.x;
    mf[3] = -f.x;
    mf[4] = 0.0;

    mf[5] = s.y;
    mf[6] = u.y;
    mf[7] = -f.y;
    mf[8] = 0.0;

    mf[9] = s.z;
    mf[10] = u.z;
    mf[11] = -f.z;
    mf[12] = 0.0;

    mf[13] = 0.0;
    mf[14] = 0.0;
    mf[15] = 0.0;
    mf[16] = 1.0;

    MatrixTranslation(t, -vEye.x, -vEye.y, -vEye.z);
    MatrixMultiply(mOut, t, mOut);
end

---@param mOut LMatrix
---@param fFOVy number
---@param fAspect number
---@param fNear number
---@param fFar number
function MatrixPerspectiveFovLH(mOut, fFOVy, fAspect, fNear, fFar)
    local fRealAspect = fAspect;
    local f = 1.0 / math.tan(fFOVy * 0.5);
    local n = 1.0 / (fFar - fNear);

    local mf = mOut.f;
    mf[1] = f / fRealAspect;
    mf[2] = 0.0;
    mf[3] = 0.0;
    mf[4] = 0.0;

    mf[5] = 0.0;
    mf[6] = f;
    mf[7] = 0.0;
    mf[8] = 0.0;

    mf[9] = 0.0;
    mf[10] = 0.0;
    mf[11] = fFar * n;
    mf[12] = 1.0;

    mf[13] = 0.0;
    mf[14] = 0.0;
    mf[15] = -fFar * fNear * n;
    mf[16] = 0.0;
end

---@param mOut LMatrix
---@param fFOVy number
---@param fAspect number
---@param fNear number
---@param fFar number
function MatrixPerspectiveFovRH(mOut, fFOVy, fAspect, fNear, fFar)
    local fRealAspect = fAspect;
    local f = 1.0 / math.tan(fFOVy * 0.5);
    local n = 1.0 / (fNear - fFar);
    local mf = mOut.f;
    mf[1] = f / fRealAspect;
    mf[2] = 0;
    mf[3] = 0;
    mf[4] = 0;

    mf[5] = 0;
    mf[6] = f;
    mf[7] = 0;
    mf[8] = 0;

    mf[9] = 0;
    mf[10] = 0;
    mf[11] = (fFar + fNear) * n;
    mf[12] = -1;

    mf[13] = 0;
    mf[14] = 0;
    mf[15] = (2 * fFar * fNear) * n;
    mf[16] = 0;
end

---@param mOut LMatrix
---@param w number
---@param h number
---@param zn number
---@param zf number
function MatrixOrthoLH(mOut, w, h, zn, zf)
    local f = mOut.f;
    f[1] = 2.0 / w; f[2] = 0.0;     f[3] = 0.0;               f[4] = 0.0;
    f[5] = 0.0;     f[6] = 2.0 / h; f[7] = 0.0;               f[8] = 0.0;
    f[9] = 0.0;     f[10] = 0.0;    f[11] = 1.0 / (zf - zn);  f[12] = 0.0;
    f[13] = 0.0;    f[14] = 0.0;    f[15] = zn / (zn - zf);   f[16] = 1.0;
end

---@param mOut LMatrix
---@param w number
---@param h number
---@param zn number
---@param zf number
function MatrixOrthoRH(mOut, w, h, zn, zf)
    local f = mOut.f;
    f[1] = 2.0 / w;   f[2] = 0.0;       f[3] = 0.0;               f[4] = 0.0;
    f[5] = 0.0;       f[6] = 2.0 / h;   f[7] = 0.0;               f[8] = 0.0;
    f[9] = 0.0;       f[10] = 0.0;      f[11] = 1.0 / (zn - zf);  f[12] = 0.0;
    f[13] = 0.0;      f[14] = 0.0;      f[15] = zn / (zn - zf);   f[16] = 1.0;
end

---@param qOut LQuaternion
---@param vAxis LVec3
---@param fAngle number
function MatrixQuaternionRotationAxis(qOut, vAxis, fAngle)
    local temp = LVec3:new(vAxis.x, vAxis.y, vAxis.z);
    local fSin = math.sin(fAngle * 0.5);
    local fCos = math.cos(fAngle * 0.5);
    temp:Normalize();
    qOut.x = fSin * temp.x;
    qOut.y = fSin * temp.y;
    qOut.z = fSin * temp.z;
    qOut.w = fCos;
end

---@param qIn LQuaternion
---@param vAxis LVec3
function MatrixQuaternionToAxisAngle(qIn, vAxis)
    local fCosAngle, fSinAngle, temp, fAngle;
    fCosAngle = qIn.w;
    temp = 1.0 - fCosAngle * fCosAngle;
    fAngle = math.acos(fCosAngle) * 2.0;
    fSinAngle = math.sqrt(temp);

    if (math.abs(fSinAngle) < 0.0005) then
        fSinAngle = 1.0;
    end
    vAxis.x = qIn.x / fSinAngle;
    vAxis.y = qIn.y / fSinAngle;
    vAxis.z = qIn.z / fSinAngle;
end

---@param qOut LQuaternion
---@param qA LQuaternion
---@param qB LQuaternion
---@param t number
function MatrixQuaternionSlerp(qOut, qA, qB, t)
    local fCosine, fAngle, A, B;
    if (t < 0.0 or t > 1.0) then
        LOG_E("MatrixQuaternionSlerp : Bad parameters");
        qOut.x = 0;
        qOut.y = 0;
        qOut.z = 0;
        qOut.w = 1;
        return;
    end

    fCosine = qA.w * qB.w + qA.x * qB.x + qA.y * qB.y + qA.z * qB.z;
    if (fCosine < 0.0) then
        --[[<http://www.magic-software.com/Documentation/Quaternions.pdf>
        "It is important to note that the quaternions q and -q represent
        the same rotation... while either quaternion will do, the
        interpolation methods require choosing one over the other.

        Although q1 and -q1 represent the same rotation, the values of
        Slerp(t; q0, q1) and Slerp(t; q0,-q1) are not the same. It is
        customary to choose the sign... on q1 so that... the angle
        between q0 and q1 is acute. This choice avoids extra
        spinning caused by the interpolated rotations."--]]
        local qi = LQuaternion:new(-qB.x, -qB.y, -qB.z, -qB.w);
        --local qi = AllocQuaternion(-qB.x, -qB.y, -qB.z, -qB.w);
        MatrixQuaternionSlerp(qOut, qA, qi, t);
        FreeQuaternion(qi);
        return;
    end

    fCosine = (fCosine < 1.0 and fCosine or 1.0);
    fAngle = math.acos(fCosine);
    if(fAngle == 0.0) then
        qOut:CopyFrom(qA);
        return;
    end

    A = math.sin((1.0 - t) * fAngle) / math.sin(fAngle);
    B = math.sin(t * fAngle) / math.sin(fAngle);
    qOut.x = A * qA.x + B * qB.x;
    qOut.y = A * qA.y + B * qB.y;
    qOut.z = A * qA.z + B * qB.z;
    qOut.w = A * qA.w + B * qB.w;
    qOut:Normalize();
end

---@param vOut LVec3
---@param v1 LVec3
---@param v2 LVec3
function MatrixVec3Lerp(vOut, v1, v2, s)
    vOut.x = v1.x + s * (v2.x - v1.x);
    vOut.y = v1.y + s * (v2.y - v1.y);
    vOut.z = v1.z + s * (v2.z - v1.z);
end

---@param matOut LMatrix
---@param m1 LMatrix
---@param m2 LMatrix
---@param t number
---@return LMatrix
function MatrixSlerp(matOut, m1, m2, t)
    local t1 = 1.0 - t;
    local t2 = t;

    local f1 = m1.f;
    local f2 = m2.f;
    local f = matOut.f;
    f[1] = f1[1] * t1 + f2[1] * t2;
    f[2] = f1[2] * t1 + f2[2] * t2;
    f[3] = f1[3] * t1 + f2[3] * t2;
    f[4] = f1[4] * t1 + f2[4] * t2;

    f[6] = f1[6] * t1 + f2[6] * t2;
    f[7] = f1[7] * t1 + f2[7] * t2;
    f[8] = f1[8] * t1 + f2[8] * t2;
    f[9] = f1[9] * t1 + f2[9] * t2;

    f[9] = f1[9] * t1 + f2[9] * t2;
    f[10] = f1[10] * t1 + f2[10] * t2;
    f[11] = f1[11] * t1 + f2[11] * t2;
    f[12] = f1[12] * t1 + f2[12] * t2;

    f[13] = f1[13] * t1 + f2[13] * t2;
    f[14] = f1[14] * t1 + f2[14] * t2;
    f[15] = f1[15] * t1 + f2[15] * t2;
    f[16] = f1[16] * t1 + f2[16] * t2;
    return matOut;
end

---@param mOut LMatrix
---@param quat LQuaternion
function MatrixRotationQuaternion(mOut, quat)
    mOut:Identity();
    local pqx = quat.x;
    local pqy = quat.y;
    local pqz = quat.z;
    local pqw = quat.w;
    local f = mOut.f;
    f[1] = 1.0 - 2.0 * (pqy * pqy + pqz * pqz);
    f[2] = 2.0 * (pqx * pqy + pqz * pqw);
    f[3] = 2.0 * (pqx * pqz - pqy * pqw);
    f[4] = 0.0;

    f[5] = 2.0 * (pqx * pqy - pqz * pqw);
    f[6] = 1.0 - 2.0 * (pqx * pqx + pqz * pqz);
    f[7] = 2.0 * (pqy * pqz + pqx * pqw);
    f[8] = 0.0;

    f[9] = 2.0 * (pqx * pqz + pqy * pqw);
    f[10] = 2.0 * (pqy * pqz - pqx * pqw);
    f[11] = 1.0 - 2.0 * (pqx * pqx + pqy * pqy);
    f[12] = 0.0;

    f[13] = 0.0;
    f[14] = 0.0;
    f[15] = 0.0;
    f[16] = 1.0;
    mOut:Check();
end

---@param quatOut LQuaternion
---@param mMat LMatrix
function MatrixQuaternionFromMatrix(quatOut, mMat)
    local f = mMat.f;
    local fTrace = f[1] + f[6] + f[11];
    local v = {0.0, 0.0, 0.0, 0.0}; -- x, y, z, w;
    local fRoot;
    if ( fTrace > 0.0 ) then
        -- |w| > 1/2, may as well choose w > 1/2
        fRoot = math.sqrt(fTrace + 1.0);  -- 2w
        v[4] = 0.5 * fRoot;
        fRoot = 0.5 / fRoot; -- 1/(4w)
        v[1] = (f[6] - f[9]) * fRoot;
        v[2] = (f[8] - f[2]) * fRoot;
        v[3] = (f[1] - f[4]) * fRoot;
    else
        -- |w| <= 1/2
        local s_iNext = { 1, 2, 0 };
        local i = 1;
        if (f[6] > f[1]) then
            i = 2;
        end
        if (f[11] > f[i * 4 + i]) then
            i = 3;
        end
        local j = s_iNext[i];
        local k = s_iNext[j];
        fRoot = math.sqrt(f[i * 4 + i] - f[j * 4 + j] - f[k * 4 + k] + 1.0);
        v[i] = 0.5 * fRoot;
        fRoot = 0.5 / fRoot;
        v[3] = (f[j * 4 + k] - f[k * 4 + j]) * fRoot;
        v[j] = (f[i * 4 + j] + f[j * 4 + i]) * fRoot;
        v[k] = (f[i * 4 + k] + f[k * 4 + i]) * fRoot;
    end
    quatOut.x = v[1];
    quatOut.y = v[2];
    quatOut.z = v[3];
    quatOut.w = v[4];
end

---@param qOut LQuaternion
---@param qA LQuaternion
---@param qB LQuaternion
function MatrixQuaternionMultiply(qOut, qA, qB)
    local pq1_x = qA.x;
    local pq1_y = qA.y;
    local pq1_z = qA.z;
    local pq1_w = qA.w;
    local pq2_x = qB.x;
    local pq2_y = qB.y;
    local pq2_z = qB.z;
    local pq2_w = qB.w;
    qOut.x = pq2_w * pq1_x + pq2_x * pq1_w + pq2_y * pq1_z - pq2_z * pq1_y;
    qOut.y = pq2_w * pq1_y - pq2_x * pq1_z + pq2_y * pq1_w + pq2_z * pq1_x;
    qOut.z = pq2_w * pq1_z + pq2_x * pq1_y - pq2_y * pq1_x + pq2_z * pq1_w;
    qOut.w = pq2_w * pq1_w - pq2_x * pq1_x - pq2_y * pq1_y - pq2_z * pq1_z;
end

---@param matUpRotation LMatrix
---@param vNormal LVec3
---@return number
function MatrixByNormal(matUpRotation, vNormal)
    local vec3DefaultUp = LVec3(0.0, 1.0, 0.0);
    local vec3Axis = LVec3:new(vNormal.x, vNormal.y, vNormal.z);
    local K = vNormal:Dot(vec3DefaultUp);
    --assert(K>=-1 && K<= 1);
    local fAngle = math.acos(K);
    if math.abs(K) >= 0.999 then
        matUpRotation:Identity();
    else
        Vec3Cross(vec3Axis, vec3Axis, vec3DefaultUp);
        MatrixRotationAxis(matUpRotation, -fAngle, vec3Axis.x, vec3Axis.y, vec3Axis.z);
    end
    return K;
end

---@param qOut LQuaternion
---@param qIn LQuaternion
function QuaternionConjugate(qOut, qIn)
    qOut.x = -qIn.x;
    qOut.y = -qIn.y;
    qOut.z = -qIn.z;
    qOut.w = qIn.w;
end

---@param qOut LQuaternion
---@param qIn LQuaternion
function QuaternionInverse(qOut, qIn)
    QuaternionConjugate(qOut, qIn);
    qOut:Normalize();
end

---@param matOut LMatrix
---@param pScalingCenter LVec3
---@param pScalingRotation LVec3
---@param pScaling LVec3
---@param pRotationCenter LVec3
---@param pRotation LVec3
---@param pTranslation LVec3
function MatrixTransformation(matOut, pScalingCenter, pScalingRotation, pScaling, pRotationCenter, pRotation, pTranslation)
    --以下是从微软的D3DXMatrixTransformation源码改出来的,也是OK的但不够优化
    local _m1 = LMatrix:new();
    local _m2 = LMatrix:new();
    local _m3 = LMatrix:new();
    local _m4 = LMatrix:new();
    local _m5 = LMatrix:new();
    local _m6 = LMatrix:new();
    local _m7 = LMatrix:new();
    local _p1 = LMatrix:new();
    local _p2 = LMatrix:new();
    local _p3 = LMatrix:new();
    local _p4 = LMatrix:new();
    local _p5 = LMatrix:new();

    local _prc = LQuaternion:new();
    local _psc = LVec3:new();
    local _pt = LVec3:new();

    local m1 = _m1;
    local m2 = _m2;
    local m3 = _m3;
    local m4 = _m4;
    local m5 = _m5;
    local m6 = _m6;
    local m7 = _m7;
    local p1 = _p1;
    local p2 = _p2;
    local p3 = _p3;
    local p4 = _p4;
    local p5 = _p5;

    local prc = _prc;
    local psc = _psc;
    local pt = _pt;

    local m2_Identity = true;
    local m3_Identity = true;
    local m4_Identity = true;
    local m5_Identity = true;
    local m6_Identity = true;
    local m7_Identity = true;

    if not pScalingCenter then
        psc.x = 0.0;
        psc.y = 0.0;
        psc.z = 0.0;
    else
        psc.x = pScalingCenter.x;
        psc.y = pScalingCenter.y;
        psc.z = pScalingCenter.z;
    end
    if not pRotationCenter then
        prc.x = 0.0;
        prc.y = 0.0;
        prc.z = 0.0;
    else
        prc.x = pRotationCenter.x;
        prc.y = pRotationCenter.y;
        prc.z = pRotationCenter.z;
    end
    if not pTranslation then
        pt.x = 0.0;
        pt.y = 0.0;
        pt.z = 0.0;
    else
        pt.x = pTranslation.x;
        pt.y = pTranslation.y;
        pt.z = pTranslation.z;
    end
    MatrixTranslation(m1, -psc.x, -psc.y, -psc.z);
    if pScalingRotation then
        MatrixRotationQuaternion(m4, pScalingRotation);
        MatrixInverse(m2, m4);
        m2_Identity = false;
        m4_Identity = false;
    end
    if pScaling then
        MatrixScaling(m3, pScaling.x, pScaling.y, pScaling.z);
        m3_Identity = false;
    end
    if pRotation then
        MatrixRotationQuaternion(m6, pRotation);
        m6_Identity = false;
    end
    MatrixTranslation(m5, psc.x - prc.x, psc.y - prc.y, psc.z - prc.z);
    MatrixTranslation(m7, prc.x + pt.x, prc.y + pt.y, prc.z + pt.z);

    if (psc.x - prc.x ~= 0 or psc.y - prc.y ~= 0 or psc.z - prc.z ~= 0) then
        m5_Identity = false;
    end
    if (prc.x + pt.x ~= 0 or prc.y + pt.y ~= 0 or prc.z + pt.z ~= 0) then
        m7_Identity = false;
    end
    if not m2_Identity then
        MatrixMultiply(p1, m1, m2);
    else
        p1 = m1;
    end
    if not m3_Identity then
        MatrixMultiply(p2, p1, m3);
    else
        p2 = m1;
    end
    if not m4_Identity then
        MatrixMultiply(p3, p2, m4);
    else
        p3 = p2;
    end
    if not m5_Identity then
        MatrixMultiply(p4, p3, m5);
    else
        p4 = p3;
    end
    if not m6_Identity then
        MatrixMultiply(p5, p4, m6);
    else
        p5 = p4;
    end
    if not m7_Identity then
        MatrixMultiply(matOut, p5, m7);
    else
        matOut.copyFrom(p5);
    end
end

---@param qOut LQuaternion
---@param radians number
---@return LQuaternion
function MatrixQuaterionX(qOut, radians)
    local cosa = math.cosf(radians * 0.5);
    local sina = math.sinf(radians * 0.5);

    qOut.x = sina;
    qOut.y = 0.0;
    qOut.z = 0.0;
    qOut.w = cosa;
end

---@param qOut LQuaternion
---@param radians number
---@return LQuaternion
function MatrixQuaterionY(qOut, radians)
    local cosa = math.cosf(radians * 0.5);
    local sina = math.sinf(radians * 0.5);

    qOut.x = 0.0;
    qOut.y = sina;
    qOut.z = 0.0;
    qOut.w = cosa;
end

---@param qOut LQuaternion
---@param radians number
function MatrixQuaterionZ(qOut, radians)
    local cosa = math.cosf(radians * 0.5);
    local sina = math.sinf(radians * 0.5);

    qOut.x = 0.0;
    qOut.y = 0.0;
    qOut.z = sina;
    qOut.w = cosa;
end

---@param qOut LQuaternion
---@param heading_y number
---@param pitch_x number
---@param roll_z number
function MatrixQuaterionFromEuler(qOut, heading_y, pitch_x, roll_z)
    local h = LQuaternion:new();
    local p = LQuaternion:new();
    local ro = LQuaternion:new();

    MatrixQuaterionY(h, heading_y);
    MatrixQuaterionX(p, pitch_x);
    MatrixQuaterionZ(ro, roll_z);

    MatrixQuaternionMultiply(qOut, h, p);
    MatrixQuaternionMultiply(qOut, ro, Out);
end

---@param mOut LMatrix
---@param heading_y number
---@param pitch_x number
---@param roll_z number
function MatrixRotationYawPitchRoll(mOut, heading_y, pitch_x, roll_z)
    local q = LQuaternion:new();
    MatrixQuaterionFromEuler(q, heading_y, pitch_x, roll_z);
    MatrixRotationQuaternion(mOut, q);
end

---@param matOut LMatrix
---@param matIn LMatrix
function MatrixDXToOpengl(matOut, matIn)
    if (matOut ~= matIn) then
        matOut:CopyFrom(matIn);
    end
    matOut._13 = -matOut._13;
    matOut._23 = -matOut._23;
    matOut._31 = -matOut._31;
    matOut._32 = -matOut._32;
    matOut._43 = -matOut._43;
end

---@param qOut LQuaternion
---@param qIn LQuaternion
function QuaterionDxToGL2(qOut, qIn)
    local m = LMatrix:new();
    MatrixRotationQuaternion(m, qIn);
    MatrixDXToOpengl(m, m);
    MatrixQuaternionFromMatrix(qOut, m);
end

---@param mat LMatrix
function FixMaxOrgMatrix(mat)
    local fTemp = 0;
    fTemp = mat._12; mat._12 = mat._13; mat._13 = fTemp;
    fTemp = mat._22; mat._22 = mat._23; mat._23 = fTemp;
    fTemp = mat._32; mat._32 = mat._33; mat._33 = fTemp;
    fTemp = mat._42; mat._42 = mat._43; mat._43 = fTemp;

    fTemp = mat._21; mat._21 = mat._31; mat._31 = fTemp;
    fTemp = mat._22; mat._22 = mat._32; mat._32 = fTemp;
    fTemp = mat._23; mat._23 = mat._33; mat._33 = fTemp;
end

---@param mat LMatrix
---@param vTrans LVec3
---@param vScale LVec3
---@param mRot LMatrix
function MatrixDecompose(mat, vTrans, vScale, mRot)
    local f = mat.f;
    --Retrieving the translation is as easy as getting the x,y,z values from the thrid row:
    vTrans.x = f[12];
    vTrans.y = f[13];
    vTrans.z = f[14];

    --We create a temporary Vector3 array to store the 3x3 matrix that contains the scaling and rotation. We will then take self information and seperate it into its scale and rotation components.
    local vCols0 = LVec3:new(f[1], f[2], f[3]);
    local vCols1 = LVec3:new(f[5], f[6], f[7]);
    local vCols2 = LVec3:new(f[9], f[10], f[11]);

    --Retrieving the scale is done by gathering the legnth of the vectors for each column of the 3x3 matrix.
    vScale.x = vCols0:Length();
    vScale.y = vCols1:Length();
    vScale.z = vCols2:Length();

    --The 3x3 rotation matrix can be obtained by dividing each column of the 3x3 rotation/scale matrix by the retrieved scalar component:
    if vScale.x ~= 0 then
        vCols0.x = vCols0.x / vScale.x;
        vCols0.y = vCols0.y / vScale.x;
        vCols0.z = vCols0.z / vScale.x;
    end
    if vScale.y ~= 0 then
        vCols1.x = vCols1.x / vScale.y;
        vCols1.y = vCols1.x / vScale.y;
        vCols1.z = vCols1.x / vScale.y;
    end
    if vScale.z ~= 0 then
        vCols2.x = vCols2.x / vScale.z;
        vCols2.y = vCols2.y / vScale.z;
        vCols2.z = vCols2.z / vScale.z;
    end
    f = mRot.f;
    f[1] = vCols0.x; f[2] = vCols0.y;   f[3] = vCols0.z;    f[4] = 0.0;
    f[5] = vCols1.x; f[6] = vCols1.y;   f[7] = vCols1.z;    f[8] = 0.0;
    f[9] = vCols2.x; f[10] = vCols2.y;  f[11] = vCols2.z;   f[12] = 0.0;
    f[13] = 0.0;     f[14] = 0.0;       f[15] = 0.0;        f[16] = 1.0;
end

---@param matOut LMatrix
---@param matIn LMatrix
function MatrixRemoveScale(matOut, matIn)
    local mRot = LMatrix:new();
    local vTrans = LVec3:new();
    local vScale = LVec3:new();

    MatrixDecompose(matIn, vTrans, vScale, mRot);
    mRot:SetPos(vTrans);
    matOut:CopyFrom(mRot);
end

---@param matOut LMatrix
---@param Mat LMatrix
function MatrixLockAxisY(matOut, Mat)
    local _Mat = Mat:Clone();
    local vScanl = LVec3:new();
    local vTrans = LVec3:new();
    local matRot = LMatrix:new();

    MatrixDecompose(_Mat, vTrans, vScanl, matRot);

    local matScanl = LMatrix:new();
    local matTrans = LMatrix:new();
    local matRotat = LMatrix:new();

    MatrixTranslation(matTrans, vTrans.x, vTrans.y, vTrans.z);
    MatrixScaling(matScanl, vScanl.x, vScanl.y, vScanl.z);
    local vAlexY = LVec3:new(matRot._21, matRot._22, matRot._23);

    matRotat.copyFrom(matRot);

    if not vAlexY.AlmostEqual(0.0, 1.0, 0.0) then
        vAlexY:Normalize();
        local vWordY = LVec3:new(0.0, 1.0, 0.0);
        local vCross = LVec3:new();
        Vec3Cross(vCross, vAlexY, vWordY);
        vCross:Normalize();
        local fDot = vAlexY.dot(vWordY);
        local fAngle = math.acos(fDot);
        local Rot = LMatrix:new();
        MatrixRotationAxis(Rot, fAngle, vCross.x, vCross.y, vCross.z);
        MatrixMultiply(matRotat, matRot, Rot);
    end
    MatrixMultiply(matOut, matScanl, matRotat);
    MatrixMultiply(matOut, matOut, matTrans);
end

---@param vec1 LVec2
---@param vec2 LVec2
function GetRotateAngle(vec1, vec2)
    local fEpsilon = 1.0e-6;
    local fPI = math.acos(-1.0);
    local fDot, fAngle;
    fDot = vec1.dot(vec2);
    if (math.abs(fDot - 1.0) <= fEpsilon) then
        fAngle = 0.0;
    elseif (math.abs(fDot + 1.0) <= fEpsilon) then
        fAngle = fPI;
    else
        local fCross;
        fAngle = acosf(fDot);
        fCross = vec1.x * vec2.y - vec2.x * vec1.y;
        if (fCross < 0.0) then
            fAngle = 2.0 * fPI - fAngle;
        end
    end
    ---assert(fAngle == fAngle);
    return fAngle;
end

---@param mOut LMatrix
function MatrixIdentity(mOut)
    mOut:Identity();
end
