// Created by: Eugeny MALTCHIKOV // Created on: 2019-04-17 // Copyright (c) 2019 OPEN CASCADE SAS // // This file is part of Open CASCADE Technology software library. // // This library is free software; you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License version 2.1 as published // by the Free Software Foundation, with special exception defined in the file // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT // distribution for complete text of the license and disclaimer of any warranty. // // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. #ifndef _BVH_Tools_Header #define _BVH_Tools_Header #include "BVH_Box.hxx" #include "BVH_Ray.hxx" #include "BVH_Types.hxx" //! Defines a set of static methods operating with points and bounding boxes. //! \tparam T Numeric data type //! \tparam N Vector dimension template class BVH_Tools { public: //! @name public types typedef typename BVH::VectorType::Type BVH_VecNt; public: enum BVH_PrjStateInTriangle { BVH_PrjStateInTriangle_VERTEX, BVH_PrjStateInTriangle_EDGE, BVH_PrjStateInTriangle_INNER }; public: //! @name Box-Box Square distance //! Computes Square distance between Axis aligned bounding boxes static T BoxBoxSquareDistance(const BVH_Box& theBox1, const BVH_Box& theBox2) { if (!theBox1.IsValid() || !theBox2.IsValid()) { return static_cast(0); } return BoxBoxSquareDistance(theBox1.CornerMin(), theBox1.CornerMax(), theBox2.CornerMin(), theBox2.CornerMax()); } //! Computes Square distance between Axis aligned bounding boxes static T BoxBoxSquareDistance(const BVH_VecNt& theCMin1, const BVH_VecNt& theCMax1, const BVH_VecNt& theCMin2, const BVH_VecNt& theCMax2) { T aDist = T(0); for (int i = 0; i < N; ++i) { if (theCMin1[i] > theCMax2[i]) { T d = theCMin1[i] - theCMax2[i]; aDist += d * d; } else if (theCMax1[i] < theCMin2[i]) { T d = theCMin2[i] - theCMax1[i]; aDist += d * d; } } return aDist; } public: //! @name Point-Box Square distance //! Computes square distance between point and bounding box static T PointBoxSquareDistance(const BVH_VecNt& thePoint, const BVH_Box& theBox) { if (!theBox.IsValid()) { return static_cast(0); } return PointBoxSquareDistance(thePoint, theBox.CornerMin(), theBox.CornerMax()); } //! Computes square distance between point and bounding box static T PointBoxSquareDistance(const BVH_VecNt& thePoint, const BVH_VecNt& theCMin, const BVH_VecNt& theCMax) { T aDist = T(0); for (int i = 0; i < N; ++i) { if (thePoint[i] < theCMin[i]) { T d = theCMin[i] - thePoint[i]; aDist += d * d; } else if (thePoint[i] > theCMax[i]) { T d = thePoint[i] - theCMax[i]; aDist += d * d; } } return aDist; } public: //! @name Point-Box projection //! Computes projection of point on bounding box static BVH_VecNt PointBoxProjection(const BVH_VecNt& thePoint, const BVH_Box& theBox) { if (!theBox.IsValid()) { return thePoint; } return PointBoxProjection(thePoint, theBox.CornerMin(), theBox.CornerMax()); } //! Computes projection of point on bounding box static BVH_VecNt PointBoxProjection(const BVH_VecNt& thePoint, const BVH_VecNt& theCMin, const BVH_VecNt& theCMax) { return thePoint.cwiseMax(theCMin).cwiseMin(theCMax); } private: //! @name Internal helpers for point-triangle projection //! Helper to set projection state for vertex static void SetVertexState(BVH_PrjStateInTriangle* thePrjState, int* theFirstNode, int* theLastNode, int theVertexIndex) { if (thePrjState != nullptr) { *thePrjState = BVH_PrjStateInTriangle_VERTEX; *theFirstNode = theVertexIndex; *theLastNode = theVertexIndex; } } //! Helper to set projection state for edge static void SetEdgeState(BVH_PrjStateInTriangle* thePrjState, int* theFirstNode, int* theLastNode, int theStartVertex, int theEndVertex) { if (thePrjState != nullptr) { *thePrjState = BVH_PrjStateInTriangle_EDGE; *theFirstNode = theStartVertex; *theLastNode = theEndVertex; } } //! Helper to compute projection onto edge static BVH_VecNt ProjectToEdge(const BVH_VecNt& theEdgeStart, const BVH_VecNt& theEdge, T theDot1, T theDot2) { T aT = theDot1 / (theDot1 + theDot2); return theEdgeStart + theEdge * aT; } public: //! @name Point-Triangle Square distance //! Find nearest point on a triangle for the given point. //! Uses Voronoi region testing to determine closest feature (vertex, edge, or interior). static BVH_VecNt PointTriangleProjection(const BVH_VecNt& thePoint, const BVH_VecNt& theNode0, const BVH_VecNt& theNode1, const BVH_VecNt& theNode2, BVH_PrjStateInTriangle* thePrjState = nullptr, int* theNumberOfFirstNode = nullptr, int* theNumberOfLastNode = nullptr) { // Compute edge vectors const BVH_VecNt aAB = theNode1 - theNode0; const BVH_VecNt aAC = theNode2 - theNode0; const BVH_VecNt aBC = theNode2 - theNode1; // Compute point-to-vertex vectors const BVH_VecNt aAP = thePoint - theNode0; const BVH_VecNt aBP = thePoint - theNode1; const BVH_VecNt aCP = thePoint - theNode2; // Compute dot products for Voronoi region tests const T aABdotAP = aAB.Dot(aAP); const T aACdotAP = aAC.Dot(aAP); // Check if P is in vertex region outside A if (aABdotAP <= T(0) && aACdotAP <= T(0)) { SetVertexState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 0); return theNode0; } const T aBAdotBP = -aAB.Dot(aBP); const T aBCdotBP = aBC.Dot(aBP); // Check if P is in vertex region outside B if (aBAdotBP <= T(0) && aBCdotBP <= T(0)) { SetVertexState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 1); return theNode1; } const T aCBdotCP = -aBC.Dot(aCP); const T aCAdotCP = -aAC.Dot(aCP); // Check if P is in vertex region outside C if (aCAdotCP <= T(0) && aCBdotCP <= T(0)) { SetVertexState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 2); return theNode2; } // Compute barycentric coordinates for edge/interior tests const T aACdotBP = aAC.Dot(aBP); const T aVC = aABdotAP * aACdotBP + aBAdotBP * aACdotAP; // Check if P is in edge region of AB if (aVC <= T(0) && aABdotAP > T(0) && aBAdotBP > T(0)) { SetEdgeState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 0, 1); return ProjectToEdge(theNode0, aAB, aABdotAP, aBAdotBP); } const T aABdotCP = aAB.Dot(aCP); const T aVA = aBAdotBP * aCAdotCP - aABdotCP * aACdotBP; // Check if P is in edge region of BC if (aVA <= T(0) && aBCdotBP > T(0) && aCBdotCP > T(0)) { SetEdgeState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 1, 2); return ProjectToEdge(theNode1, aBC, aBCdotBP, aCBdotCP); } const T aVB = aABdotCP * aACdotAP + aABdotAP * aCAdotCP; // Check if P is in edge region of CA if (aVB <= T(0) && aACdotAP > T(0) && aCAdotCP > T(0)) { SetEdgeState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 2, 0); return ProjectToEdge(theNode0, aAC, aACdotAP, aCAdotCP); } // P is inside triangle - compute barycentric coordinates const T aNorm = aVA + aVB + aVC; // Handle degenerate triangle (zero or near-zero area) if (aNorm <= std::numeric_limits::epsilon() * (std::abs(aVA) + std::abs(aVB) + std::abs(aVC))) { SetVertexState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 0); return (theNode0 + theNode1 + theNode2) / T(3); } if (thePrjState != nullptr) { *thePrjState = BVH_PrjStateInTriangle_INNER; } return (theNode0 * aVA + theNode1 * aVB + theNode2 * aVC) / aNorm; } //! Computes square distance between point and triangle static T PointTriangleSquareDistance(const BVH_VecNt& thePoint, const BVH_VecNt& theNode0, const BVH_VecNt& theNode1, const BVH_VecNt& theNode2) { const BVH_VecNt aProj = PointTriangleProjection(thePoint, theNode0, theNode1, theNode2); const BVH_VecNt aPP = aProj - thePoint; return aPP.Dot(aPP); } public: //! @name Ray-Box Intersection //! Computes hit time of ray-box intersection. //! Uses precomputed reciprocal direction from BVH_Ray for optimal performance. static bool RayBoxIntersection(const BVH_Ray& theRay, const BVH_Box& theBox, T& theTimeEnter, T& theTimeLeave) { if (!theBox.IsValid()) { return false; } return RayBoxIntersection(theRay, theBox.CornerMin(), theBox.CornerMax(), theTimeEnter, theTimeLeave); } //! Computes hit time of ray-box intersection. //! Uses precomputed reciprocal direction from BVH_Ray for optimal performance. //! Handles parallel rays (infinite inverse direction) explicitly to avoid NaN from 0*inf //! when ray origin is exactly on a slab boundary. static bool RayBoxIntersection(const BVH_Ray& theRay, const BVH_VecNt& theBoxCMin, const BVH_VecNt& theBoxCMax, T& theTimeEnter, T& theTimeLeave) { T aTimeEnter = (std::numeric_limits::lowest)(); T aTimeLeave = (std::numeric_limits::max)(); for (int i = 0; i < N; ++i) { // Handle parallel rays (infinite inverse direction) to avoid NaN from 0*inf if (std::isinf(theRay.InvDirect[i])) { if (theRay.Origin[i] < theBoxCMin[i] || theRay.Origin[i] > theBoxCMax[i]) { return false; } continue; } T aT1 = (theBoxCMin[i] - theRay.Origin[i]) * theRay.InvDirect[i]; T aT2 = (theBoxCMax[i] - theRay.Origin[i]) * theRay.InvDirect[i]; aTimeEnter = (std::max)(aTimeEnter, (std::min)(aT1, aT2)); aTimeLeave = (std::min)(aTimeLeave, (std::max)(aT1, aT2)); if (aTimeEnter > aTimeLeave) { return false; } } // Check if intersection is behind the ray origin if (aTimeLeave < T(0)) { return false; } theTimeEnter = aTimeEnter; theTimeLeave = aTimeLeave; return true; } //! Computes hit time of ray-box intersection static bool RayBoxIntersection(const BVH_VecNt& theRayOrigin, const BVH_VecNt& theRayDirection, const BVH_Box& theBox, T& theTimeEnter, T& theTimeLeave) { if (!theBox.IsValid()) { return false; } return RayBoxIntersection(theRayOrigin, theRayDirection, theBox.CornerMin(), theBox.CornerMax(), theTimeEnter, theTimeLeave); } //! Computes hit time of ray-box intersection. //! Uses optimized single-pass algorithm with early exit. //! @param theRayOrigin ray origin point //! @param theRayDirection ray direction vector //! @param theBoxCMin minimum corner of the box //! @param theBoxCMax maximum corner of the box //! @param theTimeEnter time of ray entering the box //! @param theTimeLeave time of ray leaving the box //! @return true if ray intersects the box static bool RayBoxIntersection(const BVH_VecNt& theRayOrigin, const BVH_VecNt& theRayDirection, const BVH_VecNt& theBoxCMin, const BVH_VecNt& theBoxCMax, T& theTimeEnter, T& theTimeLeave) { T aTimeEnter = (std::numeric_limits::lowest)(); T aTimeLeave = (std::numeric_limits::max)(); for (int i = 0; i < N; ++i) { if (theRayDirection[i] == T(0)) { // Ray is parallel to this axis slab - check if origin is within bounds if (theRayOrigin[i] < theBoxCMin[i] || theRayOrigin[i] > theBoxCMax[i]) { return false; // Ray misses the slab entirely } // Ray is within the slab, doesn't constrain the intersection interval continue; } // Compute intersection distances for this axis T aT1 = (theBoxCMin[i] - theRayOrigin[i]) / theRayDirection[i]; T aT2 = (theBoxCMax[i] - theRayOrigin[i]) / theRayDirection[i]; // Ensure aT1 <= aT2 (handle negative direction) T aTMin = (std::min)(aT1, aT2); T aTMax = (std::max)(aT1, aT2); // Update intersection interval aTimeEnter = (std::max)(aTimeEnter, aTMin); aTimeLeave = (std::min)(aTimeLeave, aTMax); // Early exit if no intersection if (aTimeEnter > aTimeLeave) { return false; } } // Check if intersection is behind the ray origin if (aTimeLeave < T(0)) { return false; } theTimeEnter = aTimeEnter; theTimeLeave = aTimeLeave; return true; } }; #endif // _BVH_Tools_Header