---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by HUAFEI2.
--- DateTime: 2020/6/29 12:39
---

---@class LRay
LRay = LRay or class("LRay");
function LRay:ctor()
    self.vOrign = LVec3:new();
    self.vDir = LVec3:new();
end

---@class LAABBbox
LAABBbox = LAABBbox or class("LAABBbox");
function LAABBbox:ctor()
    self.vMax = LVec3:new(0.0, 0.0, 0.0);
    self.vMin = LVec3:new(0.0, 0.0, 0.0);
    self.bInit = false;
end

---@param point LVec3
---@return boolean
function LAABBbox:IsInAabbBox(point)
    return TestPointInAABBBox(point, self.vMax, self.vMin);
end

---@param x number
---@param z number
---@return boolean
function LAABBbox:IsXZInAabbBox(x, z)
    if (x >= self.vMin.x and z >= self.vMin.z and x <= self.vMax.x and z <= self.vMax.z) then
        return true;
    else
        return false;
    end
end

---@param other LAABBbox
---@return boolean
function LAABBbox:IsIntersect(other)
    if (other.vMax.x < self.vMin.x or other.vMax.y < self.vMin.y or other.vMax.z < self.vMin.z) then
        return false;
    elseif (other.vMin.x > self.vMax.x or other.vMin.y > self.vMax.y or other.vMin.z > self.vMax.z) then
        return false;
    end
    return true;
end

---@param other LAABBbox
---@return boolean
function LAABBbox:IsIntersectXZ(other)
    if (other.vMax.x < self.vMin.x or other.vMax.z < self.vMin.z) then
        return false;
    elseif (other.vMin.x > self.vMax.x or other.vMin.z > self.vMax.z) then
        return false;
    end
    return true;
end

---@param points KArray<LVec3>
---@return LAABBbox
function LAABBbox:Get8Points(points)
    if (points.length >= 8) then
        --下面四个顶点
        points[1].set(self.vMin.x, self.vMin.y, self.vMin.z);
        points[2].set(self.vMax.x, self.vMin.y, self.vMin.z);
        points[3].set(self.vMax.x, self.vMin.y, self.vMax.z);
        points[4].set(self.vMin.x, self.vMin.y, self.vMax.z);

        --上面四个顶点
        points[5].set(self.vMin.x, self.vMax.y, self.vMin.z);
        points[6].set(self.vMax.x, self.vMax.y, self.vMin.z);
        points[7].set(self.vMax.x, self.vMax.y, self.vMax.z);
        points[8].set(self.vMin.x, self.vMax.y, self.vMax.z);
    else
        LOG_E("points lens less then 8");
    end
    return self;
end

---@param {Array.<LVec3>} points
---@param {KMATRIX} mat
---@return {LAABBbox}
function LAABBbox:TransformCoord(points, mat)
    if points and mat then
        self:Get8Points(points);
        for i = 1, 8 do
            self:TransTransformCoord(points[i], points[i], mat);
        end
    end
    return self;
end

---@return LAABBbox
function LAABBbox:Clear()
    self.vMax.x = 0.0; self.vMax.y = 0.0; self.vMax.z = 0.0;
    self.vMin.x = 0.0; self.vMin.y = 0.0; self.vMin.z = 0.0;
    self.bInit = false;
    return self;
end

---@param {Number x, Number y, Number z | LVec3}
---@return LAABBbox
function LAABBbox:AddPosition(arguments)
    local x, y, z;
    if (arguments.length == 1) then
        x = arguments[1].x;
        y = arguments[1].y;
        z = arguments[1].z;
    elseif (arguments.length == 3) then
        x = arguments[1];
        y = arguments[2];
        z = arguments[3];
    end

    if not self.bInit then
        self.vMax.set(x, y, z);
        self.vMin.set(x, y, z);
        self.bInit = true;
    else
        if (self.vMin.x > x) then self.vMin.x = x; end
        if (self.vMin.y > y) then self.vMin.y = y; end
        if (self.vMin.z > z) then self.vMin.z = z; end

        if (self.vMax.x < x) then self.vMax.x = x; end
        if (self.vMax.y < y) then self.vMax.y = y; end
        if (self.vMax.z < z) then self.vMax.z = z; end
    end
    return self;
end

---@param points KArray<LVec3>
---@param nCount number
---@return LAABBbox
function LAABBbox:AddPositions(points, nCount)
    if (points) then
        for i = 1, nCount do
            self:AddPosition(points[i]);
        end
    end
    return self;
end

---@param pCenter LVec3
---@return LAABBbox
function LAABBbox:GetCenter(pCenter)
    pCenter.x = (vMax.x + vMin.x) * 0.5;
    pCenter.y = (vMax.y + vMin.y) * 0.5;
    pCenter.z = (vMax.z + vMin.z) * 0.5;
    return self;
end

---@return number
function LAABBbox:GeWidthAvg()
    return (self.vMax.x + self.vMax.y + self.vMax.z - self.vMin.x - self.vMin.y - self.vMin.z) * 0.33;
end

---@class LRayIntersection
LRayIntersection = LRayIntersection or class("LRayIntersection");
function LRayIntersection:ctor()
    --@type {Number} dwFace mesh face that was intersected           相交的面的编号*/
    self.dwFace = 0;
    --@type {Number} fBary1  barycentric coords of intersection      相交面的重心u v坐标*/
    self.fBary1 = 0;
    --@type {Number} fBary2*/
    self.fBary2 = 0;
    --@type {Number} fDist distance from ray origin to intersection  射线和相交面的距离*/
    self.fDist = 0;
    --@type {Number} tu texture coords of intersection               相交面的贴图坐标*/
    self.tu = 0;
    --@type {Number} tv*/
    self.tv = 0;
    --@type {LVec3} vIntersectionFaceNormal*/
    self.vIntersectionFaceNormal = LVec3:new();
    --@type {LVec3} IntersectPoint*/
    self.IntersectPoint = LVec3:new();
    --@type {LVec3} v0*/
    self.v0 = LVec3:new();
    --@type {LVec3} v1*/
    self.v1 = LVec3:new();
    --@type {LVec3} v2*/
    self.v2 = LVec3:new();
    --@type {Object} pInterSectObj*/
    self.pInterSectObj = nil;
end

---@class LSphere
LSphere = LSphere or class("LSphere");
function LSphere:ctor()
    self.vCenter = LVec3:new();
    self.fRadius = 0.0;
end

---@param other LSphere
---@return boolean
function LSphere:IsIntersect(other)
    if (self.vCenter.distanceTo(other.vCenter) > self.fRadius + other.fRadius) then
        return false;
    else
        return true;
    end
end

---@param pAabbBox LAABBbox
---@return LSphere
function LSphere:FitAabbBox(pAabbBox)
    pAabbBox:GetCenter(self.vCenter);
    self.fRadius = pAabbBox.vMin:distanceTo(pAabbBox.vMax) * 0.5;
    return self;
end

---@param point LVec3
---@param vMax LVec3
---@param vMin LVec3
---@return boolean
function TestPointInAABBBox(point, vMax, vMin)
    if point.x >= vMin.x and point.y >= vMin.y and point.z >= vMin.z and
            point.x <= vMax.x and point.y <= vMax.y and point.z <= vMax.z then
        return true;
    end
    return false;
end

---@param w number
---@param h number
---@param cursor_x number
---@param cursor_y number
---@param pView LMatrix
---@param pProj LMatrix
---@param pOutRay LRay
---@returns {LRay} pOutRay
function GetWorldPickRay(w, h, cursor_x, cursor_y, pView, pProj,  pOutRay)
    local px = ((( 2.0 * cursor_x) / w) - 1.0) / pProj._11;
    local py = (((-2.0 * cursor_y) / h) + 1.0) / pProj._22;
    pOutRay.vOrign:Set(0.0, 0.0, 0.0);
    pOutRay.vDir:Set(px, py, -1.0);

    -- transform the ray to world space
    local viewInverse = LMatrix:new();
    MatrixInverse(viewInverse, pView);
    -- transform the ray's origin, w = 1.
    TransTransformCoord(pOutRay.vOrign, pOutRay.vOrign, viewInverse);
    -- transform the ray's direction, w = 0.
    TransTransformNormal(pOutRay.vDir, pOutRay.vDir, viewInverse);
    pOutRay.vDir:Normalize();
    return pOutRay;
end

---@param pDistObjectWorldMatrix LMatrix
---@param pInRay LRay
---@param pOutRay LRay
---@returns LRay
function ToObjectLocalPickRay(pDistObjectWorldMatrix, pInRay, pOutRay)
    local invMat = LMatrix:new();
    --需要把射线变换到mesh的本地坐标系中去，那么需要去乘被拾取对象的世界变换的逆矩阵变换
    MatrixInverse(invMat, pDistObjectWorldMatrix);
    TransTransformCoord(pOutRay.vOrign, pInRay.vOrign, invMat);
    TransTransformNormal(pOutRay.vDir, pInRay.vDir, invMat);
    pOutRay.vDir:Normalize();
    return pOutRay;
end

---@param w number
---@param h number
---@param cursor_x number
---@param cursor_y number
---@param pDestObjectWordMatrix LMatrix
---@param pViewMat LMatrix
---@param pProj LMatrix
---@param pOutRay LRay
---@return LRay
function GetObjectLocalPickRay(w, h, cursor_x, cursor_y, pDestObjectWordMatrix, pViewMat, pProj,  pOutRay)
    GetWorldPickRay(w, h, cursor_x, cursor_y, pViewMat, pProj, pOutRay);
    ToObjectLocalPickRay(pDestObjectWordMatrix, pOutRay, pOutRay);
    return pOutRay;
end

---@param pRay LRay
---@param v0 LVec3
---@param v1 LVec3
---@param v2 LVec3
---@param out_distX_uY_vZ; LVec3
---@return boolean
function RayTriangleTest(pRay, v0, v1, v2,  out_distX_uY_vZ)
    local edge1 = LVec3:new();
    local edge2 = LVec3:new();
    local D = 0.0;
    local U = 0.0;
    local V = 0.0;

    Vec3Minus(edge1, v1, v0);
    Vec3Minus(edge2, v2, v0);

    local pvec = LVec3:new();
    Vec3Cross(pvec, pRay.vDir, edge2);

    -- 如果det为0，或接近于零则射线与三角面共面或平行，不相交
    -- 此处det就相当于上面的
    local det =  edge1.dot(pvec);
    local tvec = LVec3:new();

    if det > 0 then
        Vec3Minus(tvec, pRay.vOrign, v0);
    else
        Vec3Minus(tvec, v0, pRay.vOrign);
        det = -det;
    end

    if det < 0.0001 then --近似0
        return false;
    end
    -- 计算u并测试是否合法（在三角形内）
    U = tvec:Dot(pvec);
    if U < 0.0 or U > det then
        return false;
    end
    -- Prepare to test V parameter
    local qvec = LVec3:new();
    Vec3Cross(qvec, tvec, edge1);
    -- 计算u并测试是否合法（在三角形内）
    V = pRay.vDir.dot(qvec);
    if V < 0.0 or U + V > det then
        return false;
    end

    --[[计算fdist,并把t,u,v放缩为合法值（注意前面的t,v,u不同于算法描述中的相应量，乘了一个系数det）
    注意：由于该步运算需要使用除法，所以放到最后来进行，避免不必要的运算，提高算法效率--]]
    D =  edge2:Dot(qvec);
    local fInvDet = 1.0 / det;
    D = D * fInvDet;
    U = U * fInvDet;
    V = V * fInvDet;
    out_distX_uY_vZ.x = D;
    out_distX_uY_vZ.y = U;
    out_distX_uY_vZ.z = V;
    return true;
end

---@function 射线与平面相交
---@param pOutIntersectPos LVec3
---@param pRay LRay
---@param pPlane LPlane
---@return boolean
function RayPlaneTest(pOutIntersectPos, pRay, pPlane)
    local plane = LPlane:new();
    plane:Set(pOutIntersectPos);
    PlaneNormalize(plane);
    local d = pPlane.d;
    local f = plane.vNormal:Dot(pRay.vDir);
    local t = -(d + plane.vNormal:Dot(pRay.vOrign)) / f;
    if t > 0 then
        pOutIntersectPos.x = pRay.vOrign.x + t * pRay.vDir.x;
        pOutIntersectPos.y = pRay.vOrign.y + t * pRay.vDir.y;
        pOutIntersectPos.z = pRay.vOrign.z + t * pRay.vDir.z;
        return true;
    end
    return false;
end

---@function  射线与球相交
---@param pWorldRay LRay
---@param vCenter LVec3
---@param fRadius number
---@return boolean
function RaySphereIntTest(pWorldRay, vCenter, fRadius)
    local v = LVec3:new();
    Vec3Minus(v, pWorldRay.vOrign, vCenter);
    local b = 2.0 *  pWorldRay.vDir:Dot(v);
    local c = v:Dot(v) - (fRadius * fRadius);
    -- find the discriminant
    local discriminant = (b * b) - (4.0 * c);
    -- test for imaginary number
    if discriminant < 0.0 then
        return false;
    end
    discriminant = math.sqrt(discriminant);
    local s0 = (-b + discriminant) / 2.0;
    local s1 = (-b - discriminant) / 2.0;
    -- if a solution is >= 0, then we intersected the sphere
    if s0 >= 0.0 or s1 >= 0.0 then
        return true;
    end
    return false;
end

---@function  射线与BBox相交
---@param vRayOrig LVec3
---@param vRayDir LVec3
---@param pvMax LVec3
---@param pvMin LVec3
---@param pOutInterSection LRayIntersection
---@return boolean
function RayAaBBboxTest(vRayOrig, vRayDir, pvMax, pvMin, pOutInterSection)
    local ptOnPlane = LVec3:new();  --射线与包围盒某面的交点
    local min = pvMin;              --aabb包围盒最小点坐标
    local max = pvMax;              --aabb包围盒最大点坐标
    local origin = vRayOrig;        --射线起始点
    local dir = vRayDir;            --方向矢量
    local t = 0.0;
    -- 分别判断射线与各面的相交情况
    -- 判断射线与包围盒x轴方向的面是否有交点
    -- 射线x轴方向分量不为0 若射线方向矢量的x轴分量为0，射线不可能经过包围盒朝x轴方向的两个面
    if (dir.x ~= 0.0) then
        --使用射线与平面相交的公式求交点
        if (dir.x > 0) then  --若射线沿x轴正方向偏移
            t = (min.x - origin.x) / dir.x;
        else  --射线沿x轴负方向偏移
            t = (max.x - origin.x) / dir.x;
        end

        if (t > 0.0) then -- t>0时则射线与平面相交
            --计算交点坐标
            ptOnPlane.x = origin.x + t * dir.x;
            ptOnPlane.y = origin.y + t * dir.y;
            ptOnPlane.z = origin.z + t * dir.z;

            --判断交点是否在当前面内
            if (min.y < ptOnPlane.y and ptOnPlane.y < max.y and min.z < ptOnPlane.z and ptOnPlane.z < max.z) then
                --射线与包围盒有交点
                if pOutInterSection then
                    pOutInterSection.IntersectPoint:CopyFrom(ptOnPlane);
                    pOutInterSection.fDist = t;
                end
                return true;
            end
        end
    end

    --若射线沿y轴方向有分量 判断是否与包围盒y轴方向有交点
    if (dir.y ~= 0.0) then
        if (dir.y > 0.0) then
            t = (min.y - origin.y) / dir.y;
        else
            t = (max.y - origin.y) / dir.y;
        end
        if (t > 0.0) then
            --计算交点坐标
            ptOnPlane.x = origin.x + t * dir.x;
            ptOnPlane.y = origin.y + t * dir.y;
            ptOnPlane.z = origin.z + t * dir.z;

            if (min.z < ptOnPlane.z and ptOnPlane.z < max.z and min.x < ptOnPlane.x and ptOnPlane.x < max.x) then
                --射线与包围盒有交点
                if pOutInterSection then
                    pOutInterSection.IntersectPoint:CopyFrom(ptOnPlane);
                    pOutInterSection.fDist = t;
                end
                return true;
            end
        end
    end

    --若射线沿z轴方向有分量 判断是否与包围盒y轴方向有交点
    if (dir.z ~= 0.0) then
        if (dir.z > 0) then
            t = (min.z - origin.z) / dir.z;
        else
            t = (max.z - origin.z) / dir.z;
        end
        if (t > 0.0) then
            --计算交点坐标
            ptOnPlane.x = origin.x + t * dir.x;
            ptOnPlane.y = origin.y + t * dir.y;
            ptOnPlane.z = origin.z + t * dir.z;

            if (min.x < ptOnPlane.x and ptOnPlane.x < max.x and min.y < ptOnPlane.y and ptOnPlane.y < max.y) then
                --射线与包围盒有交点
                if pOutInterSection then
                    pOutInterSection.IntersectPoint:CopyFrom(ptOnPlane);
                    pOutInterSection.fDist = t;
                end
                return true;
            end
        end
    end
    return false;
end

---@function
---@param pWorldRay LRay
---@param bNearest boolean
---@param pVertices KArray<number>
---@param pIndices KArray<number> | nil
---@param dwNumFaces number
---@param dwVertStride number
---@param OutIntersections KArray<LRayIntersection>
---@param wMat LMatrix
---@param fMaxLen number
---@param bBackFace boolean
---@return boolean
function RayFacesTest(pWorldRay, bNearest, pVertices, pIndices, dwNumFaces, dwVertStride, OutIntersections, wMat, fMaxLen, bBackFace)
    local InterSectionnums = 0;
    local localRay = LRay:new();
    ToObjectLocalPickRay(wMat, pWorldRay, localRay);

    local fDist_fBary1_fBary2 = LVec3:new();
    local vInterSections = KArray:new();

    local pV0 = LVec3:new();
    local pV1 = LVec3:new();
    local pV2 = LVec3:new();

    local edge1 = LVec3:new();
    local edge2 = LVec3:new();
    local vLocalFaceNormal = LVec3:new();

    for i = 1, dwNumFaces do
        local id0 = 0;
        local id1 = 0;
        local id2 = 0;
        if pIndices then
            --有索引可用
            --三角形的三个索引
            id0 = pIndices[i * 3 + 0];
            id1 = pIndices[i * 3 + 1];
            id2 = pIndices[i * 3 + 2];
        else
            --无索引可用，那么直接从顶点类列表里面取面好了
            id0 = i * 3 + 0;
            id1 = i * 3 + 1;
            id2 = i * 3 + 2;
        end
        pV0.set(pVertices[id0 * dwVertStride], pVertices[id0 * dwVertStride + 1], pVertices[id0 * dwVertStride + 2]);
        pV1.set(pVertices[id1 * dwVertStride], pVertices[id1 * dwVertStride + 1], pVertices[id1 * dwVertStride + 2]);
        pV2.set(pVertices[id2 * dwVertStride], pVertices[id2 * dwVertStride + 1], pVertices[id2 * dwVertStride + 2]);

        Vec3Minus(edge1, pV1, pV0);
        Vec3Minus(edge2, pV2, pV0);
        Vec3Cross(vLocalFaceNormal, edge1, edge2);
        vLocalFaceNormal.normalize();

        local fThetha = -vLocalFaceNormal.dot(localRay.vDir);
        -- 夹角小于90但比90还要小一点，才认为有机会相交，这是为了迅速剔除背面的三角形
        if ((fThetha >= 0.0 or bBackFace) and RayTriangleTest(localRay, pV0, pV1, pV2, fDist_fBary1_fBary2)) then
            if (fDist < fMaxLen) then
                local intersection = LRayIntersection:new();
                intersection.dwFace = i;
                intersection.fBary1 = fBary1;
                intersection.fBary2 = fBary2;
                --intersection.fDist = fDist; //本地坐标系的距离，这样计算是有点问题的，后面换算到世界坐标系去算才是正确的

                intersection.v0.copyFrom(pV0);
                intersection.v1.copyFrom(pV1);
                intersection.v2.copyFrom(pV2);

                intersection.IntersectPoint.x = pV0.x + intersection.fBary1 * (pV1.x - pV0.x) + intersection.fBary2 * (pV2.x - pV0.x);
                intersection.IntersectPoint.y = pV0.y + intersection.fBary1 * (pV1.y - pV0.y) + intersection.fBary2 * (pV2.y - pV0.y);
                intersection.IntersectPoint.z = pV0.z + intersection.fBary1 * (pV1.z - pV0.z) + intersection.fBary2 * (pV2.z - pV0.z);

                --intersection.vIntersectionFaceNormal = (*pV1 - *pV0).cross(*pV2 - *pV0);
                intersection.vIntersectionFaceNormal.copyFrom(vLocalFaceNormal);

                --把被拾取的点从本地坐标系变换到世界坐标系中去
                TransTransformCoord(intersection.IntersectPoint, intersection.IntersectPoint, wMat);
                TransTransformCoord(intersection.v0, intersection.v0, wMat);
                TransTransformCoord(intersection.v1, intersection.v1, wMat);
                TransTransformCoord(intersection.v2, intersection.v2, wMat);

                -- 修正距离计算
                intersection.fDist = intersection.IntersectPoint.distanceTo(pWorldRay.vOrign);
                vInterSections:PushBack(intersection);

                if not bNearest then
                    if (numArrray == 1) then
                        --如果不需要找最近的交点，而且也只需要一个交点，直接就可以返回了
                        if (OutIntersections) then
                            OutIntersections[1] = intersection;
                        end
                        InterSectionnums = 1;
                        return true;
                    else
                        break;
                    end
                end
            end
        end
    end

    local n = vInterSections:Size();
    if (n) then
        local fdist = 0;
        for i = 1, n do
            local intersection = vInterSections:Get(i);
            if (bNearest) then
                if (InterSectionnums == 0) then
                    if (OutIntersections) then
                        OutIntersections[1] = intersection;
                    end
                    InterSectionnums = 1;
                    fdist = intersection.fDist;
                else
                    if (intersection.fDist < fdist) then
                        if (OutIntersections) then
                            OutIntersections[1] = intersection;
                        end
                        fdist = intersection.fDist;
                    end
                end
            elseif (InterSectionnums < numArrray) then
                if (OutIntersections) then
                    OutIntersections[InterSectionnums] = intersection;
                end
                InterSectionnums = InterSectionnums + 1;
            end
        end
    end

    if InterSectionnums > 0 then
        return true;
    else
        return false;
    end
end

---@function
---@param b LAABBbox
---@param p LPlane
---@return boolean
function TestAABBPlane(b, p)
    local c = LVec3();
    b:GetCenter(c);
    local e = LVec3();
    Vec3Minus(e, b.vMax, c);
    local r = e.x * math.abs(p.vNormal.x) + e.y * math.abs(p.vNormal.y) + e.z * math.abs(p.vNormal.z);
    local s = p.vNormal:Dot(c) - p.d;
    return math.abs(s) <= r;
end

---@function
---@param _v0 LVec3
---@param _v1 LVec3
---@param _v2 LVec3
---@param b LAABBbox
---@return boolean
function TestTriangleAABB(_v0, _v1, _v2, b)
    local v0 = LVec3:new(_v0.x, _v0.y, _v0.z);
    local v1 = LVec3:new(_v1.x, _v1.y, _v1.z);
    local v2 = LVec3:new(_v2.x, _v2.y, _v2.z);
    local p0 = 0.0;
    local p1 = 0.0;
    local p2 = 0.0;
    local r = 0.0;

    local c = LVec3:new();
    b:GetCenter(c);

    local e0 = (b.vMax.x - b.vMin.x) * 0.5;
    local e1 = (b.vMax.y - b.vMin.y) * 0.5;
    local e2 = (b.vMax.z - b.vMin.z) * 0.5;

    Vec3Minus(v0, v0, c);
    Vec3Minus(v1, v1, c);
    Vec3Minus(v2, v2, c);


    local f0 = LVec3:new();
    local f1 = LVec3:new();
    local f2 = LVec3:new();
    Vec3Minus(f0, v1, v0);
    Vec3Minus(f1, v2, v1);
    Vec3Minus(f2, v0, v2);

    local u0 = LVec3:new(1.0, 0.0, 0.0);
    local u1 = LVec3:new(0.0, 1.0, 0.0);
    local u2 = LVec3:new(0.0, 0.0, 1.0);

    local a00 = LVec3:new(0, -f0.z, f0.y);
    local a01 = LVec3:new(0, -f1.z, f1.y);
    local a02 = LVec3:new(0, -f2.z, f2.y);
    local a10 = LVec3:new(f0.z, 0, -f0.x);
    local a11 = LVec3:new(f1.z, 0, -f1.x);
    local a12 = LVec3:new(f2.z, 0, -f2.x);
    local a20 = LVec3:new(-f0.y, f0.x, 0);
    local a21 = LVec3:new(-f1.y, f1.x, 0);
    local a22 = LVec3:new(-f2.y, f2.x, 0);

    local n = {a00, a01, a02, a10, a11, a12, a20, a21, a22};
    -- Test axes a00..a22
    for i = 1, 9 do
        p0 = v0.dot(n[i]);
        p1 = v1.dot(n[i]);
        p2 = v2.dot(n[i]);

        r = e0 * Math.abs(u0.dot(n[i])) + e1 * Math.abs(u1.dot(n[i])) + e2 * Math.abs(u2.dot(n[i]));
        if (Math.max(Math.max(p0, p1), p2) < -r or Math.min(Math.min(p0, p1), p2) > r) then
            return false;
        end

    end
    if (Math.max(Math.max(v0.x, v1.x), v2.x) < -e0 or Math.min(Math.min(v0.x, v1.x), v2.x) > e0) then
        return false;
    end
    if (Math.max(Math.max(v0.y, v1.y), v2.y) < -e1 or Math.min(Math.min(v0.y, v1.y), v2.y) > e1) then
        return false;
    end
    if (Math.max(Math.max(v0.z, v1.z), v2.z) < -e2 or Math.min(Math.min(v0.z, v1.z), v2.z) > e2) then
        return false;
    end
    local p = LPlane:new();
    Vec3Cross(p.vNormal, f0, f1);
    p.d = p.vNormal:Dot(Vec3Add(v0, v0, c));
    return TestAABBPlane(b, p);
end

---@class KRectRay
KRectRay = KRectRay or class("KRectRay");
function KRectRay:ctor()
    self.m_plane = {};
    for i = 1, 6 do
        local plane = LPlane:new();
        table.insert(self.m_plane, plane);
    end
    --@type {LVec3} m_vPos
    self.m_vPos = LVec3:new();
end

---@param posA LVec2
---@param posB LVec2
---@param nScreenWidth number
---@param viewProject LMatrix
---@return KRectRay
function KRectRay:Update(posA, posB, nScreenWidth, nScreeHeight, viewProject)
    local vtx = {};
    for i = 1, 8 do
        local vec3 = LVec3:new();
        table.insert(vtx, vec3);
    end

    local top = LVec2:new(math.min(posA.x, posB.x), math.min(posA.y, posB.y));
    local down = LVec2:new(math.max(posA.x, posB.x), math.max(posA.y, posB.y));

    TransScreenPointToSurfacePoint(top, top, nScreenWidth,  nScreeHeight);
    TransScreenPointToSurfacePoint(down, down, nScreenWidth, nScreeHeight);

    --box down four verts
    vtx[1].x =  top.x;	    vtx[1].y = down.y;      vtx[1].z = 0.0;
    vtx[2].x =  down.x;     vtx[2].y = down.y; 	    vtx[2].z = 0.0;
    vtx[3].x =  vtx[2].x;	vtx[3].y = vtx[1].y;	vtx[3].z = 1.0;
    vtx[4].x =  vtx[1].x;	vtx[4].y = vtx[0].y;    vtx[4].z = 1.0;

    --box top four verts
    vtx[5].x =  top.x;	    vtx[5].y = top.y;	    vtx[5].z = 0.0;
    vtx[6].x =  down.x;	    vtx[6].y = top.y;	    vtx[6].z = 0.0;
    vtx[7].x =  vtx[6].x;	vtx[7].y = vtx[5].y;	vtx[7].z = 1.0;
    vtx[8].x =  vtx[5].x;   vtx[8].y = vtx[4].y;    vtx[8].z = 1.0;

    local worldMat = LMatrix:new();
    MatrixInverse(worldMat, viewProject);
    for i = 1, 8 do
        TransTransformCoord(vtx[i], vtx[i], worldMat);
    end

    --the center of near face  is 1 and 6 point's center
    tis.m_vpos.x = (vtx[1].x + vtx[6].x) * 0.5;
    tis.m_vpos.y = (vtx[1].y + vtx[6].y) * 0.5;
    tis.m_vpos.z = (vtx[1].z + vtx[6].z) * 0.5;

    --near
    ComputePlaneFromPoints(self.m_plane[1], vtx[1], vtx[5], vtx[5]);
    --top
    ComputePlaneFromPoints(self.m_plane[2], vtx[5], vtx[7], vtx[8]);
    --bottom
    ComputePlaneFromPoints(self.m_plane[3], vtx[1], vtx[3], vtx[2]);

    --far
    ComputePlaneFromPoints(self.m_plane[4], vtx[3], vtx[8], vtx[7]);
    --left
    ComputePlaneFromPoints(self.m_plane[5], vtx[1], vtx[8], vtx[4]);
    --right
    ComputePlaneFromPoints(self.m_plane[6], vtx[2], vtx[7], vtx[6]);
    return self;
end

---@param vCenter LVec3
---@param fRadius number
---@return boolean
function KRectRay:IsOutSphere(vCenter, fRadius)
    local PLANE_EPSILON = 0.5;
    local d = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    local ret =  false;
    for i = 1, 6 do
        d[i] = ComputeDistance(self.m_plane[i], vCenter);
        if (d[i] > fRadius + PLANE_EPSILON) then
            ret = true;
            break;
        end
    end
    return ret;
end

---@param vMax LVec3
---@param vMin number
---@return boolean
function KRectRay:IsOutAABBbox(vMax, vMin)
    --视点在包围盒内部
    if (m_vpos.x >= vMin.x and m_vpos.y >= vMin.y and m_vpos.z >= vMin.z and m_vpos.x <= vMax.x and m_vpos.y <= vMax.y and m_vpos.z <= vMax.z) then
        return false;
    end
    local point = LVec3:new();
    for i = 1, 6 do
        point.x = self.m_plane[i].vNormal.x < 0 and vMax.x or vMin.x;
        point.y = self.m_plane[i].vNormal.y < 0 and vMax.y or vMin.y;
        point.z = self.m_plane[i].vNormal.z < 0 and vMax.z or vMin.z;
        if (GetToPlaneSide(m_plane[i], point) == KPointSide.FRONT_PLANE) then
            return true;
        end
    end
    return false;
end

---@function
---@param aabbBox LAABBbox
---@param localAabbBox LAABBbox
---@param wMat LMatrix
---@return LAABBbox
function ComputeAABBboxFromLocal(aabbBox, localAabbBox, wMat)
    local vecLocalBoxPoint = {};
    for i = 1, 8 do
        local vec3 = LVec3:new();
        table.insert(vecLocalBoxPoint, vec3);
    end
    localAabbBox:Get8Points(vecLocalBoxPoint);
    aabbBox:Clear();
    for i = 0, 8 do
        TransTransformCoord(vecLocalBoxPoint[i], vecLocalBoxPoint[i], wMat);
        aabbBox:AddPositon(vecLocalBoxPoint[i]);
    end
end
