#include "Runtime/Collision/CCollidableOBBTree.hpp"

#include <array>

#include "Runtime/Collision/CCollisionInfoList.hpp"
#include "Runtime/Collision/CInternalRayCastStructure.hpp"
#include "Runtime/Collision/CMaterialFilter.hpp"
#include "Runtime/Collision/CollisionUtil.hpp"

namespace metaforce {

CCollidableOBBTree::CCollidableOBBTree(const COBBTree* tree, const metaforce::CMaterialList& material)
: CCollisionPrimitive(material), x10_tree(tree) {}

bool CCollidableOBBTree::LineIntersectsLeaf(const COBBTree::CLeafData& leaf, CRayCastInfo& info) const {
  bool ret = false;
  u16 intersectIdx = 0;
  for (size_t i = 0; i < leaf.GetSurfaceVector().size(); ++i) {
    u16 surfIdx = leaf.GetSurfaceVector()[i];
    CCollisionSurface surface = x10_tree->GetSurface(surfIdx);
    CMaterialList matList = GetMaterial();
    matList.Add(surface.GetSurfaceFlags());
    if (info.GetMaterialFilter().Passes(matList)) {
      if (CollisionUtil::RayTriangleIntersection(info.GetRay().start, info.GetRay().dir, surface.GetVerts(),
                                                 info.Magnitude())) {
        intersectIdx = surfIdx;
        ret = true;
      }
    }
  }

  if (ret) {
    CCollisionSurface surf = x10_tree->GetSurface(intersectIdx);
    info.Plane() = surf.GetPlane();
    info.Material() = CMaterialList(surf.GetSurfaceFlags());
  }

  return ret;
}

bool CCollidableOBBTree::LineIntersectsOBBTree(const COBBTree::CNode& n0, const COBBTree::CNode& n1,
                                               CRayCastInfo& info) const {
  bool ret = false;
  float t0, t1;
  bool intersects0 = false;
  bool intersects1 = false;
  if (CollisionUtil::LineIntersectsOBBox(n0.GetOBB(), info.GetRay(), t0) && t0 < info.GetMagnitude())
    intersects0 = true;
  if (CollisionUtil::LineIntersectsOBBox(n1.GetOBB(), info.GetRay(), t1) && t1 < info.GetMagnitude())
    intersects1 = true;

  if (intersects0 && intersects1) {
    if (t0 < t1) {
      if (n0.IsLeaf() ? LineIntersectsLeaf(n0.GetLeafData(), info)
                      : LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info)) {
        if (info.GetMagnitude() < t1)
          return true;
        ret = true;
      }
      if (n1.IsLeaf()) {
        if (LineIntersectsLeaf(n1.GetLeafData(), info))
          return true;
      } else {
        if (LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info))
          return true;
      }
    } else {
      if (n1.IsLeaf() ? LineIntersectsLeaf(n1.GetLeafData(), info)
                      : LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info)) {
        if (info.GetMagnitude() < t0)
          return true;
        ret = true;
      }
      if (n0.IsLeaf()) {
        if (LineIntersectsLeaf(n0.GetLeafData(), info))
          return true;
      } else {
        if (LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info))
          return true;
      }
    }
  } else if (intersects0) {
    return n0.IsLeaf() ? LineIntersectsLeaf(n0.GetLeafData(), info)
                       : LineIntersectsOBBTree(n0.GetLeft(), n0.GetRight(), info);
  } else if (intersects1) {
    return n1.IsLeaf() ? LineIntersectsLeaf(n1.GetLeafData(), info)
                       : LineIntersectsOBBTree(n1.GetLeft(), n1.GetRight(), info);
  }

  return ret;
}

bool CCollidableOBBTree::LineIntersectsOBBTree(const COBBTree::CNode& node, CRayCastInfo& info) const {
  float t;
  bool ret = false;

  if (CollisionUtil::LineIntersectsOBBox(node.GetOBB(), info.GetRay(), t) && t < info.GetMagnitude()) {
    if (node.IsLeaf()) {
      if (LineIntersectsLeaf(node.GetLeafData(), info))
        ret = true;
    } else {
      if (LineIntersectsOBBTree(node.GetLeft(), node.GetRight(), info))
        ret = true;
    }
    const_cast<COBBTree::CNode&>(node).SetHit(true);
  } else {
    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;
  }

  return ret;
}

CRayCastResult CCollidableOBBTree::LineIntersectsTree(const zeus::CMRay& ray, const CMaterialFilter& filter,
                                                      float maxTime, const zeus::CTransform& xf) const {
  zeus::CMRay useRay = ray.getInvUnscaledTransformRay(xf);
  CRayCastInfo info(useRay, filter, maxTime);
  if (LineIntersectsOBBTree(x10_tree->GetRoot(), info)) {
    zeus::CPlane xfPlane = TransformPlane(info.GetPlane(), xf);
    return CRayCastResult(info.GetMagnitude(), ray.start + info.GetMagnitude() * ray.dir, xfPlane, info.GetMaterial());
  } else {
    return {};
  }
}

zeus::CPlane CCollidableOBBTree::TransformPlane(const zeus::CPlane& pl, const zeus::CTransform& xf) {
  zeus::CVector3f normal = xf.rotate(pl.normal());
  return zeus::CPlane(normal, (xf * (pl.normal() * pl.d())).dot(normal));
}

bool CCollidableOBBTree::SphereCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,
                                                     const zeus::CSphere& sphere, const CMaterialList& matList,
                                                     const CMaterialFilter& filter, const zeus::CVector3f& dir,
                                                     double& dOut, CCollisionInfo& infoOut) const {
  bool ret = false;

  zeus::CAABox aabb(sphere.position - sphere.radius, sphere.position + sphere.radius);
  zeus::CAABox moveAABB = aabb;
  zeus::CVector3f moveVec = float(dOut) * dir;
  moveAABB.accumulateBounds(aabb.max + moveVec);
  moveAABB.accumulateBounds(aabb.min + moveVec);

  zeus::CVector3f center = moveAABB.center();
  zeus::CVector3f extent = moveAABB.extents();

  for (u16 triIdx : leaf.GetSurfaceVector()) {
    CCollisionSurface surf = x10_tree->GetTransformedSurface(triIdx, xf);
    CMaterialList triMat = GetMaterial();
    triMat.Add(CMaterialList(surf.GetSurfaceFlags()));
    if (filter.Passes(triMat)) {
      if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) {
        const_cast<CCollidableOBBTree&>(*this).x1c_hits += 1;

        zeus::CVector3f surfNormal = surf.GetNormal();
        if ((sphere.position + moveVec - surf.GetVert(0)).dot(surfNormal) <= sphere.radius) {
          const float mag = (sphere.radius - (sphere.position - surf.GetVert(0)).dot(surfNormal)) / dir.dot(surfNormal);
          const zeus::CVector3f intersectPoint = sphere.position + mag * dir;

          const std::array<bool, 3> outsideEdges{
              (intersectPoint - surf.GetVert(0)).dot((surf.GetVert(1) - surf.GetVert(0)).cross(surfNormal)) < 0.f,
              (intersectPoint - surf.GetVert(1)).dot((surf.GetVert(2) - surf.GetVert(1)).cross(surfNormal)) < 0.f,
              (intersectPoint - surf.GetVert(2)).dot((surf.GetVert(0) - surf.GetVert(2)).cross(surfNormal)) < 0.f,
          };

          if (mag >= 0.f && !outsideEdges[0] && !outsideEdges[1] && !outsideEdges[2] && mag < dOut) {
            infoOut = CCollisionInfo(intersectPoint - sphere.radius * surfNormal, matList, triMat, surfNormal);
            dOut = mag;
            ret = true;
          }

          const bool intersects = (sphere.position - surf.GetVert(0)).dot(surfNormal) <= sphere.radius;
          std::array<bool, 3> testVert{true, true, true};
          const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx);
          for (int k = 0; k < 3; ++k) {
            if (intersects || outsideEdges[k]) {
              u16 edgeIdx = edgeIndices[k];
              if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupEdgeList[edgeIdx]) {
                CMetroidAreaCollider::g_DupEdgeList[edgeIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
                CMaterialList edgeMat(x10_tree->GetEdgeMaterial(edgeIdx));
                if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) {
                  int nextIdx = (k + 1) % 3;
                  zeus::CVector3f edgeVec = surf.GetVert(nextIdx) - surf.GetVert(k);
                  float edgeVecMag = edgeVec.magnitude();
                  edgeVec *= zeus::CVector3f(1.f / edgeVecMag);
                  float dirDotEdge = dir.dot(edgeVec);
                  zeus::CVector3f edgeRej = dir - dirDotEdge * edgeVec;
                  float edgeRejMagSq = edgeRej.magSquared();
                  zeus::CVector3f vertToSphere = sphere.position - surf.GetVert(k);
                  float vtsDotEdge = vertToSphere.dot(edgeVec);
                  zeus::CVector3f vtsRej = vertToSphere - vtsDotEdge * edgeVec;
                  if (edgeRejMagSq > 0.f) {
                    const float tmp = 2.f * vtsRej.dot(edgeRej);
                    const float tmp2 = 4.f * edgeRejMagSq * (vtsRej.magSquared() - sphere.radius * sphere.radius) - tmp * tmp;
                    if (tmp2 >= 0.f) {
                      const float mag2 = 0.5f / edgeRejMagSq * (-tmp - std::sqrt(tmp2));
                      if (mag2 >= 0.f) {
                        const float t = mag2 * dirDotEdge + vtsDotEdge;
                        if (t >= 0.f && t <= edgeVecMag && mag2 < dOut) {
                          zeus::CVector3f point = surf.GetVert(k) + t * edgeVec;
                          infoOut = CCollisionInfo(point, matList, edgeMat,
                                                   (sphere.position + mag2 * dir - point).normalized());
                          dOut = mag2;
                          ret = true;
                          testVert[k] = false;
                          testVert[nextIdx] = false;
                        } else if (t < -sphere.radius && dirDotEdge <= 0.f) {
                          testVert[k] = false;
                        } else if (t > edgeVecMag + sphere.radius && dirDotEdge >= 0.0) {
                          testVert[nextIdx] = false;
                        }
                      }
                    } else {
                      testVert[k] = false;
                      testVert[nextIdx] = false;
                    }
                  }
                }
              }
            }
          }

          const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx);
          for (int k = 0; k < 3; ++k) {
            const u16 vertIdx = vertIndices[k];
            if (testVert[k]) {
              if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupVertexList[vertIdx]) {
                CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
                double d = dOut;
                if (CollisionUtil::RaySphereIntersection_Double(zeus::CSphere(surf.GetVert(k), sphere.radius),
                                                                sphere.position, dir, d) &&
                    d >= 0.0) {
                  infoOut = CCollisionInfo(surf.GetVert(k), matList, x10_tree->GetVertMaterial(vertIdx),
                                           (sphere.position + dir * d - surf.GetVert(k)).normalized());
                  dOut = d;
                  ret = true;
                }
              }
            } else {
              CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
            }
          }
        }
      } else {
        const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx);
        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;

        const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx);
        CMetroidAreaCollider::g_DupVertexList[vertIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
        CMetroidAreaCollider::g_DupVertexList[vertIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
        CMetroidAreaCollider::g_DupVertexList[vertIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
      }
    }
  }

  return ret;
}

bool CCollidableOBBTree::SphereCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf,
                                               const zeus::CSphere& sphere, const zeus::COBBox& obb,
                                               const CMaterialList& material, const CMaterialFilter& filter,
                                               const zeus::CVector3f& dir, double& dOut, CCollisionInfo& info) const {
  bool ret = false;

  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;
  if (obb.OBBIntersectsBox(node.GetOBB())) {
    const_cast<COBBTree::CNode&>(node).SetHit(true);
    if (node.IsLeaf()) {
      if (SphereCollideWithLeafMoving(node.GetLeafData(), xf, sphere, material, filter, dir, dOut, info))
        ret = true;
    } else {
      if (SphereCollisionMoving(node.GetLeft(), xf, sphere, obb, material, filter, dir, dOut, info))
        ret = true;
      if (SphereCollisionMoving(node.GetRight(), xf, sphere, obb, material, filter, dir, dOut, info))
        ret = true;
    }
  } else {
    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;
  }

  return ret;
}

bool CCollidableOBBTree::AABoxCollideWithLeafMoving(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,
                                                    const zeus::CAABox& aabb, const CMaterialList& matList,
                                                    const CMaterialFilter& filter,
                                                    const CMovingAABoxComponents& components,
                                                    const zeus::CVector3f& dir, double& dOut,
                                                    CCollisionInfo& infoOut) const {
  bool ret = false;

  zeus::CAABox movedAABB = components.x6e8_aabb;
  zeus::CVector3f moveVec = float(dOut) * dir;
  movedAABB.accumulateBounds(aabb.min + moveVec);
  movedAABB.accumulateBounds(aabb.max + moveVec);

  zeus::CVector3f center = movedAABB.center();
  zeus::CVector3f extent = movedAABB.extents();

  zeus::CVector3f normal, point;

  for (u16 triIdx : leaf.GetSurfaceVector()) {
    CCollisionSurface surf = x10_tree->GetTransformedSurface(triIdx, xf);
    CMaterialList triMat = GetMaterial();
    triMat.Add(CMaterialList(surf.GetSurfaceFlags()));
    if (filter.Passes(triMat)) {
      if (CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) {
        const_cast<CCollidableOBBTree&>(*this).x1c_hits += 1;

        const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx);

        double d = dOut;
        if (CMetroidAreaCollider::MovingAABoxCollisionCheck_BoxVertexTri(surf, aabb, components.x6c4_vertIdxs, dir, d,
                                                                         normal, point) &&
            d < dOut) {
          ret = true;
          infoOut = CCollisionInfo(point, matList, triMat, normal);
          dOut = d;
        }

        for (int k = 0; k < 3; ++k) {
          u16 vertIdx = vertIndices[k];
          const zeus::CVector3f& vtx = surf.GetVert(k);
          if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupVertexList[vertIdx]) {
            CMetroidAreaCollider::g_DupVertexList[vertIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
            if (movedAABB.pointInside(vtx)) {
              d = dOut;
              if (CMetroidAreaCollider::MovingAABoxCollisionCheck_TriVertexBox(vtx, aabb, dir, d, normal, point) &&
                  d < dOut) {
                CMaterialList vertMat(x10_tree->GetVertMaterial(vertIdx));
                ret = true;
                infoOut = CCollisionInfo(point, matList, vertMat, normal);
                dOut = d;
              }
            }
          }
        }

        const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx);
        for (int k = 0; k < 3; ++k) {
          u16 edgeIdx = edgeIndices[k];
          if (CMetroidAreaCollider::g_DupPrimitiveCheckCount != CMetroidAreaCollider::g_DupEdgeList[edgeIdx]) {
            CMetroidAreaCollider::g_DupEdgeList[edgeIdx] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
            CMaterialList edgeMat(x10_tree->GetEdgeMaterial(edgeIdx));
            if (!edgeMat.HasMaterial(EMaterialTypes::NoEdgeCollision)) {
              d = dOut;
              if (CMetroidAreaCollider::MovingAABoxCollisionCheck_Edge(surf.GetVert(k), surf.GetVert((k + 1) % 3),
                                                                       components.x0_edges, dir, d, normal, point) &&
                  d < dOut) {
                ret = true;
                infoOut = CCollisionInfo(point, matList, edgeMat, normal);
                dOut = d;
              }
            }
          }
        }
      } else {
        const u16* edgeIndices = x10_tree->GetTriangleEdgeIndices(triIdx);
        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
        CMetroidAreaCollider::g_DupEdgeList[edgeIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;

        const auto vertIndices = x10_tree->GetTriangleVertexIndices(triIdx);
        CMetroidAreaCollider::g_DupVertexList[vertIndices[0]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
        CMetroidAreaCollider::g_DupVertexList[vertIndices[1]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
        CMetroidAreaCollider::g_DupVertexList[vertIndices[2]] = CMetroidAreaCollider::g_DupPrimitiveCheckCount;
      }
    }
  }

  return ret;
}

bool CCollidableOBBTree::AABoxCollisionMoving(const COBBTree::CNode& node, const zeus::CTransform& xf,
                                              const zeus::CAABox& aabb, const zeus::COBBox& obb,
                                              const CMaterialList& material, const CMaterialFilter& filter,
                                              const CMovingAABoxComponents& components, const zeus::CVector3f& dir,
                                              double& dOut, CCollisionInfo& info) const {
  bool ret = false;

  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;
  if (obb.OBBIntersectsBox(node.GetOBB())) {
    const_cast<COBBTree::CNode&>(node).SetHit(true);
    if (node.IsLeaf()) {
      if (AABoxCollideWithLeafMoving(node.GetLeafData(), xf, aabb, material, filter, components, dir, dOut, info))
        ret = true;
    } else {
      if (AABoxCollisionMoving(node.GetLeft(), xf, aabb, obb, material, filter, components, dir, dOut, info))
        ret = true;
      if (AABoxCollisionMoving(node.GetRight(), xf, aabb, obb, material, filter, components, dir, dOut, info))
        ret = true;
    }
  } else {
    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;
  }

  return ret;
}

bool CCollidableOBBTree::SphereCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf,
                                                const zeus::CSphere& sphere, const zeus::COBBox& obb,
                                                const CMaterialFilter& filter) const {
  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;
  if (obb.OBBIntersectsBox(node.GetOBB())) {
    const_cast<COBBTree::CNode&>(node).SetHit(true);
    if (node.IsLeaf()) {
      for (u16 surfIdx : node.GetLeafData().GetSurfaceVector()) {
        CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf);
        CMaterialList triMat = GetMaterial();
        triMat.Add(CMaterialList(surf.GetSurfaceFlags()));
        if (filter.Passes(triMat) &&
            CollisionUtil::TriSphereOverlap(sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2)))
          return true;
      }
    } else {
      if (SphereCollisionBoolean(node.GetLeft(), xf, sphere, obb, filter))
        return true;
      if (SphereCollisionBoolean(node.GetRight(), xf, sphere, obb, filter))
        return true;
    }
  } else {
    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;
  }

  return false;
}

bool CCollidableOBBTree::AABoxCollisionBoolean(const COBBTree::CNode& node, const zeus::CTransform& xf,
                                               const zeus::CAABox& aabb, const zeus::COBBox& obb,
                                               const CMaterialFilter& filter) const {
  zeus::CVector3f center = aabb.center();
  zeus::CVector3f extent = aabb.extents();

  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;
  if (obb.OBBIntersectsBox(node.GetOBB())) {
    const_cast<COBBTree::CNode&>(node).SetHit(true);
    if (node.IsLeaf()) {
      for (u16 surfIdx : node.GetLeafData().GetSurfaceVector()) {
        CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf);
        CMaterialList triMat = GetMaterial();
        triMat.Add(CMaterialList(surf.GetSurfaceFlags()));
        if (filter.Passes(triMat) &&
            CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2)))
          return true;
      }
    } else {
      if (AABoxCollisionBoolean(node.GetLeft(), xf, aabb, obb, filter))
        return true;
      if (AABoxCollisionBoolean(node.GetRight(), xf, aabb, obb, filter))
        return true;
    }
  } else {
    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;
  }

  return false;
}

bool CCollidableOBBTree::SphereCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,
                                               const zeus::CSphere& sphere, const CMaterialList& material,
                                               const CMaterialFilter& filter, CCollisionInfoList& infoList) const {
  bool ret = false;
  zeus::CVector3f point, normal;

  for (u16 surfIdx : leaf.GetSurfaceVector()) {
    CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf);
    CMaterialList triMat = GetMaterial();
    triMat.Add(CMaterialList(surf.GetSurfaceFlags()));
    if (filter.Passes(triMat)) {
      const_cast<CCollidableOBBTree&>(*this).x1c_hits += 1;
      if (CollisionUtil::TriSphereIntersection(sphere, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2), point,
                                               normal)) {
        CCollisionInfo collision(point, material, triMat, normal);
        infoList.Add(collision, false);
        ret = true;
      }
    }
  }

  return ret;
}

bool CCollidableOBBTree::SphereCollision(const COBBTree::CNode& node, const zeus::CTransform& xf,
                                         const zeus::CSphere& sphere, const zeus::COBBox& obb,
                                         const CMaterialList& material, const CMaterialFilter& filter,
                                         CCollisionInfoList& infoList) const {
  bool ret = false;

  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;
  if (obb.OBBIntersectsBox(node.GetOBB())) {
    const_cast<COBBTree::CNode&>(node).SetHit(true);
    if (node.IsLeaf()) {
      if (SphereCollideWithLeaf(node.GetLeafData(), xf, sphere, material, filter, infoList))
        ret = true;
    } else {
      if (SphereCollision(node.GetLeft(), xf, sphere, obb, material, filter, infoList))
        ret = true;
      if (SphereCollision(node.GetRight(), xf, sphere, obb, material, filter, infoList))
        ret = true;
    }
  } else {
    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;
  }

  return ret;
}

bool CCollidableOBBTree::AABoxCollideWithLeaf(const COBBTree::CLeafData& leaf, const zeus::CTransform& xf,
                                              const zeus::CAABox& aabb, const CMaterialList& material,
                                              const CMaterialFilter& filter, const std::array<zeus::CPlane, 6>& planes,
                                              CCollisionInfoList& infoList) const {
  bool ret = false;
  zeus::CVector3f center = aabb.center();
  zeus::CVector3f extent = aabb.extents();

  for (u16 surfIdx : leaf.GetSurfaceVector()) {
    CCollisionSurface surf = x10_tree->GetTransformedSurface(surfIdx, xf);
    CMaterialList triMat = GetMaterial();
    triMat.Add(CMaterialList(surf.GetSurfaceFlags()));
    if (filter.Passes(triMat) &&
        CollisionUtil::TriBoxOverlap(center, extent, surf.GetVert(0), surf.GetVert(1), surf.GetVert(2))) {
      zeus::CAABox newAABB = zeus::CAABox();
      const_cast<CCollidableOBBTree&>(*this).x1c_hits += 1;
      if (CMetroidAreaCollider::ConvexPolyCollision(planes, surf.GetVerts(), newAABB)) {
        zeus::CPlane plane = surf.GetPlane();
        CCollisionInfo collision(newAABB, triMat, material, plane.normal(), -plane.normal());
        infoList.Add(collision, false);
        ret = true;
      }
    }
  }

  return ret;
}

bool CCollidableOBBTree::AABoxCollision(const COBBTree::CNode& node, const zeus::CTransform& xf,
                                        const zeus::CAABox& aabb, const zeus::COBBox& obb,
                                        const CMaterialList& material, const CMaterialFilter& filter,
                                        const std::array<zeus::CPlane, 6>& planes, CCollisionInfoList& infoList) const {
  bool ret = false;

  const_cast<CCollidableOBBTree&>(*this).x14_tries += 1;
  if (obb.OBBIntersectsBox(node.GetOBB())) {
    const_cast<COBBTree::CNode&>(node).SetHit(true);
    if (node.IsLeaf()) {
      if (AABoxCollideWithLeaf(node.GetLeafData(), xf, aabb, material, filter, planes, infoList))
        ret = true;
    } else {
      if (AABoxCollision(node.GetLeft(), xf, aabb, obb, material, filter, planes, infoList))
        ret = true;
      if (AABoxCollision(node.GetRight(), xf, aabb, obb, material, filter, planes, infoList))
        ret = true;
    }
  } else {
    const_cast<CCollidableOBBTree&>(*this).x18_misses += 1;
  }

  return ret;
}

FourCC CCollidableOBBTree::GetPrimType() const { return SBIG('OBBT'); }

CRayCastResult CCollidableOBBTree::CastRayInternal(const CInternalRayCastStructure& rayCast) const {
  return LineIntersectsTree(rayCast.GetRay(), rayCast.GetFilter(), rayCast.GetMaxTime(), rayCast.GetTransform());
}

zeus::CAABox CCollidableOBBTree::CalculateAABox(const zeus::CTransform& xf) const {
  return zeus::COBBox::FromAABox(x10_tree->CalculateLocalAABox(), xf).calculateAABox();
}

zeus::CAABox CCollidableOBBTree::CalculateLocalAABox() const { return x10_tree->CalculateLocalAABox(); }

} // namespace metaforce
