Loading files
3d097a032This file is pinned to the selected release. Download the original to open it in your design tool.
// Copyright (c) 2025 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.
#include <gtest/gtest.h>
#include <BVH_Box.hxx>
#include <BVH_Tools.hxx>
#include <Precision.hxx>
TEST(BVH_ToolsTest, PointBoxSquareDistance)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
// Point inside box
double aDist1 = BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(0.5, 0.5, 0.5), aBox);
EXPECT_NEAR(aDist1, 0.0, Precision::Confusion());
// Point on face
double aDist2 = BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(0.5, 0.5, 1.0), aBox);
EXPECT_NEAR(aDist2, 0.0, Precision::Confusion());
// Point outside box (distance = 1)
double aDist3 = BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(2.0, 0.5, 0.5), aBox);
EXPECT_NEAR(aDist3, 1.0, Precision::Confusion());
// Point at corner outside (distance^2 = 1 + 1 + 1 = 3)
double aDist4 = BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(2.0, 2.0, 2.0), aBox);
EXPECT_NEAR(aDist4, 3.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, BoxBoxSquareDistance)
{
BVH_Box<double, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
BVH_Box<double, 3> aBox2(BVH_Vec3d(2.0, 0.0, 0.0), BVH_Vec3d(3.0, 1.0, 1.0));
BVH_Box<double, 3> aBox3(BVH_Vec3d(0.5, 0.5, 0.5), BVH_Vec3d(1.5, 1.5, 1.5));
// Separated boxes
double aDist1 = BVH_Tools<double, 3>::BoxBoxSquareDistance(aBox1, aBox2);
EXPECT_NEAR(aDist1, 1.0, Precision::Confusion());
// Overlapping boxes
double aDist2 = BVH_Tools<double, 3>::BoxBoxSquareDistance(aBox1, aBox3);
EXPECT_NEAR(aDist2, 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointBoxProjection)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
// Point inside - should return same point
BVH_Vec3d aProj1 = BVH_Tools<double, 3>::PointBoxProjection(BVH_Vec3d(0.5, 0.5, 0.5), aBox);
EXPECT_NEAR(aProj1.x(), 0.5, Precision::Confusion());
EXPECT_NEAR(aProj1.y(), 0.5, Precision::Confusion());
EXPECT_NEAR(aProj1.z(), 0.5, Precision::Confusion());
// Point outside - should clamp to box
BVH_Vec3d aProj2 = BVH_Tools<double, 3>::PointBoxProjection(BVH_Vec3d(2.0, 0.5, 0.5), aBox);
EXPECT_NEAR(aProj2.x(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj2.y(), 0.5, Precision::Confusion());
EXPECT_NEAR(aProj2.z(), 0.5, Precision::Confusion());
}
TEST(BVH_ToolsTest, RayBoxIntersection)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray hitting the box
bool aHit1 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 0.5, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit1);
EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
// Ray missing the box
bool aHit2 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 5.0, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit2);
// Ray starting inside box
bool aHit3 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(0.5, 0.5, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit3);
EXPECT_LE(aTimeEnter, 0.0);
EXPECT_NEAR(aTimeLeave, 0.5, Precision::Confusion());
}
TEST(BVH_ToolsTest, RayBoxIntersectionParallelRay)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray parallel to X-axis, passing through box
bool aHit1 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 0.5, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit1);
// Ray parallel to X-axis, missing box (Y out of range)
bool aHit2 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 2.0, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit2);
}
TEST(BVH_ToolsTest, PointTriangleProjection)
{
BVH_Vec3d aNode0(0.0, 0.0, 0.0);
BVH_Vec3d aNode1(1.0, 0.0, 0.0);
BVH_Vec3d aNode2(0.0, 1.0, 0.0);
// Point projects to vertex
BVH_Vec3d aProj1 = BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(-1.0, -1.0, 0.0),
aNode0,
aNode1,
aNode2);
EXPECT_NEAR(aProj1.x(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj1.y(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj1.z(), 0.0, Precision::Confusion());
// Point projects to edge
BVH_Vec3d aProj2 = BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(0.5, -1.0, 0.0),
aNode0,
aNode1,
aNode2);
EXPECT_NEAR(aProj2.x(), 0.5, Precision::Confusion());
EXPECT_NEAR(aProj2.y(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj2.z(), 0.0, Precision::Confusion());
// Point projects inside triangle
BVH_Vec3d aProj3 = BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(0.25, 0.25, 1.0),
aNode0,
aNode1,
aNode2);
EXPECT_NEAR(aProj3.x(), 0.25, Precision::Confusion());
EXPECT_NEAR(aProj3.y(), 0.25, Precision::Confusion());
EXPECT_NEAR(aProj3.z(), 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointBoxSquareDistance2D)
{
BVH_Box<double, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
// Point inside box
double aDist1 = BVH_Tools<double, 2>::PointBoxSquareDistance(BVH_Vec2d(0.5, 0.5), aBox);
EXPECT_NEAR(aDist1, 0.0, Precision::Confusion());
// Point outside box
double aDist2 = BVH_Tools<double, 2>::PointBoxSquareDistance(BVH_Vec2d(2.0, 0.5), aBox);
EXPECT_NEAR(aDist2, 1.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, RayBoxIntersectionDiagonalRay)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Diagonal ray through box center
BVH_Vec3d aDir(1.0, 1.0, 1.0);
double aNorm = std::sqrt(3.0);
aDir = BVH_Vec3d(aDir.x() / aNorm, aDir.y() / aNorm, aDir.z() / aNorm);
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, -1.0, -1.0),
aDir,
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit);
EXPECT_GT(aTimeEnter, 0.0);
EXPECT_GT(aTimeLeave, aTimeEnter);
}
TEST(BVH_ToolsTest, RayBoxIntersectionNegativeDirection)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray going in negative X direction
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(2.0, 0.5, 0.5),
BVH_Vec3d(-1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit);
EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, RayBoxIntersectionTouchingEdge)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray touching edge of box
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 0.0, 0.0),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit);
}
TEST(BVH_ToolsTest, RayBoxIntersectionTouchingCorner)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray through corner
BVH_Vec3d aDir(1.0, 1.0, 1.0);
double aNorm = std::sqrt(3.0);
aDir = BVH_Vec3d(aDir.x() / aNorm, aDir.y() / aNorm, aDir.z() / aNorm);
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, -1.0, -1.0),
aDir,
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit);
}
TEST(BVH_ToolsTest, BoxBoxSquareDistanceTouching)
{
BVH_Box<double, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
BVH_Box<double, 3> aBox2(BVH_Vec3d(1.0, 0.0, 0.0), BVH_Vec3d(2.0, 1.0, 1.0));
// Touching boxes (sharing a face)
double aDist = BVH_Tools<double, 3>::BoxBoxSquareDistance(aBox1, aBox2);
EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, BoxBoxSquareDistanceOneInsideOther)
{
BVH_Box<double, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
BVH_Box<double, 3> aBox2(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(3.0, 3.0, 3.0));
// Small box inside large box
double aDist = BVH_Tools<double, 3>::BoxBoxSquareDistance(aBox1, aBox2);
EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, BoxBoxSquareDistanceCornerToCorner)
{
BVH_Box<double, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
BVH_Box<double, 3> aBox2(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(3.0, 3.0, 3.0));
// Distance from corner to corner: sqrt(1^2 + 1^2 + 1^2) = sqrt(3), squared = 3
double aDist = BVH_Tools<double, 3>::BoxBoxSquareDistance(aBox1, aBox2);
EXPECT_NEAR(aDist, 3.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointBoxProjectionNegativeCoords)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(-1.0, -1.0, -1.0), BVH_Vec3d(1.0, 1.0, 1.0));
// Point far outside in negative direction
BVH_Vec3d aProj = BVH_Tools<double, 3>::PointBoxProjection(BVH_Vec3d(-5.0, -5.0, -5.0), aBox);
EXPECT_NEAR(aProj.x(), -1.0, Precision::Confusion());
EXPECT_NEAR(aProj.y(), -1.0, Precision::Confusion());
EXPECT_NEAR(aProj.z(), -1.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointTriangleProjectionOnEdge01)
{
BVH_Vec3d aNode0(0.0, 0.0, 0.0);
BVH_Vec3d aNode1(2.0, 0.0, 0.0);
BVH_Vec3d aNode2(0.0, 2.0, 0.0);
// Point projects onto edge between Node0 and Node1
BVH_Vec3d aProj = BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(1.0, -1.0, 0.0),
aNode0,
aNode1,
aNode2);
EXPECT_NEAR(aProj.x(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj.y(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointTriangleProjectionOnEdge12)
{
BVH_Vec3d aNode0(0.0, 0.0, 0.0);
BVH_Vec3d aNode1(2.0, 0.0, 0.0);
BVH_Vec3d aNode2(0.0, 2.0, 0.0);
// Point projects onto edge between Node1 and Node2
BVH_Vec3d aProj =
BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(2.0, 2.0, 0.0), aNode0, aNode1, aNode2);
EXPECT_NEAR(aProj.x(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj.y(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointTriangleProjectionOnEdge20)
{
BVH_Vec3d aNode0(0.0, 0.0, 0.0);
BVH_Vec3d aNode1(2.0, 0.0, 0.0);
BVH_Vec3d aNode2(0.0, 2.0, 0.0);
// Point projects onto edge between Node2 and Node0
BVH_Vec3d aProj = BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(-1.0, 1.0, 0.0),
aNode0,
aNode1,
aNode2);
EXPECT_NEAR(aProj.x(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj.y(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointTriangleProjectionVertex1)
{
BVH_Vec3d aNode0(0.0, 0.0, 0.0);
BVH_Vec3d aNode1(2.0, 0.0, 0.0);
BVH_Vec3d aNode2(0.0, 2.0, 0.0);
// Point projects to Node1
BVH_Vec3d aProj = BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(3.0, -1.0, 0.0),
aNode0,
aNode1,
aNode2);
EXPECT_NEAR(aProj.x(), 2.0, Precision::Confusion());
EXPECT_NEAR(aProj.y(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointTriangleProjectionVertex2)
{
BVH_Vec3d aNode0(0.0, 0.0, 0.0);
BVH_Vec3d aNode1(2.0, 0.0, 0.0);
BVH_Vec3d aNode2(0.0, 2.0, 0.0);
// Point projects to Node2
BVH_Vec3d aProj = BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(-1.0, 3.0, 0.0),
aNode0,
aNode1,
aNode2);
EXPECT_NEAR(aProj.x(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj.y(), 2.0, Precision::Confusion());
EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointTriangleProjection3D)
{
// Triangle in 3D space (not in XY plane)
BVH_Vec3d aNode0(0.0, 0.0, 0.0);
BVH_Vec3d aNode1(1.0, 0.0, 0.0);
BVH_Vec3d aNode2(0.0, 1.0, 1.0);
// Point above triangle center
BVH_Vec3d aProj =
BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(0.3, 0.3, 0.8), aNode0, aNode1, aNode2);
// Should project inside triangle
EXPECT_GE(aProj.x(), 0.0);
EXPECT_GE(aProj.y(), 0.0);
EXPECT_LE(aProj.x() + aProj.y(), 1.0 + Precision::Confusion());
}
TEST(BVH_ToolsTest, BoxBoxSquareDistance2D)
{
BVH_Box<double, 2> aBox1(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
BVH_Box<double, 2> aBox2(BVH_Vec2d(3.0, 0.0), BVH_Vec2d(4.0, 1.0));
// Distance = 2
double aDist = BVH_Tools<double, 2>::BoxBoxSquareDistance(aBox1, aBox2);
EXPECT_NEAR(aDist, 4.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointBoxProjection2D)
{
BVH_Box<double, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
// Point outside
BVH_Vec2d aProj = BVH_Tools<double, 2>::PointBoxProjection(BVH_Vec2d(2.0, 2.0), aBox);
EXPECT_NEAR(aProj.x(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj.y(), 1.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointBoxSquareDistanceAtVertex)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
// Point on vertex
double aDist = BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(0.0, 0.0, 0.0), aBox);
EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointBoxSquareDistanceAtEdge)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
// Point on edge
double aDist = BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(0.5, 0.0, 0.0), aBox);
EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointBoxSquareDistanceFloat)
{
BVH_Box<float, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
// Point inside box
float aDist1 = BVH_Tools<float, 3>::PointBoxSquareDistance(BVH_Vec3f(0.5f, 0.5f, 0.5f), aBox);
EXPECT_NEAR(aDist1, 0.0f, 1e-5f);
// Point outside box
float aDist2 = BVH_Tools<float, 3>::PointBoxSquareDistance(BVH_Vec3f(2.0f, 0.5f, 0.5f), aBox);
EXPECT_NEAR(aDist2, 1.0f, 1e-5f);
}
TEST(BVH_ToolsTest, RayBoxIntersectionFloat)
{
BVH_Box<float, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
float aTimeEnter, aTimeLeave;
// Ray hitting the box
bool aHit = BVH_Tools<float, 3>::RayBoxIntersection(BVH_Vec3f(-1.0f, 0.5f, 0.5f),
BVH_Vec3f(1.0f, 0.0f, 0.0f),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit);
EXPECT_NEAR(aTimeEnter, 1.0f, 1e-5f);
EXPECT_NEAR(aTimeLeave, 2.0f, 1e-5f);
}
TEST(BVH_ToolsTest, RayBoxIntersectionBehindRay)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray pointing away from box
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(2.0, 0.5, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
// The box is behind the ray origin
EXPECT_TRUE(aTimeLeave < 0.0 || !aHit);
}
TEST(BVH_ToolsTest, RayBoxIntersectionYAxis)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray along Y axis
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(0.5, -1.0, 0.5),
BVH_Vec3d(0.0, 1.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit);
EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, RayBoxIntersectionZAxis)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray along Z axis
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(0.5, 0.5, -1.0),
BVH_Vec3d(0.0, 0.0, 1.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit);
EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, BoxBoxSquareDistanceFloat)
{
BVH_Box<float, 3> aBox1(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
BVH_Box<float, 3> aBox2(BVH_Vec3f(2.0f, 0.0f, 0.0f), BVH_Vec3f(3.0f, 1.0f, 1.0f));
float aDist = BVH_Tools<float, 3>::BoxBoxSquareDistance(aBox1, aBox2);
EXPECT_NEAR(aDist, 1.0f, 1e-5f);
}
TEST(BVH_ToolsTest, PointBoxProjectionFloat)
{
BVH_Box<float, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
BVH_Vec3f aProj = BVH_Tools<float, 3>::PointBoxProjection(BVH_Vec3f(2.0f, 0.5f, 0.5f), aBox);
EXPECT_NEAR(aProj.x(), 1.0f, 1e-5f);
EXPECT_NEAR(aProj.y(), 0.5f, 1e-5f);
EXPECT_NEAR(aProj.z(), 0.5f, 1e-5f);
}
TEST(BVH_ToolsTest, PointBoxSquareDistanceNegativeBox)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(-2.0, -2.0, -2.0), BVH_Vec3d(-1.0, -1.0, -1.0));
// Point at origin
double aDist = BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(0.0, 0.0, 0.0), aBox);
// Distance = sqrt(1^2 + 1^2 + 1^2) = sqrt(3), squared = 3
EXPECT_NEAR(aDist, 3.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, BoxBoxSquareDistanceEdgeToEdge)
{
BVH_Box<double, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
BVH_Box<double, 3> aBox2(BVH_Vec3d(2.0, 2.0, 0.0), BVH_Vec3d(3.0, 3.0, 1.0));
// Closest points are on edges, distance = sqrt(1^2 + 1^2) = sqrt(2), squared = 2
double aDist = BVH_Tools<double, 3>::BoxBoxSquareDistance(aBox1, aBox2);
EXPECT_NEAR(aDist, 2.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointTriangleProjectionFloat)
{
BVH_Vec3f aNode0(0.0f, 0.0f, 0.0f);
BVH_Vec3f aNode1(1.0f, 0.0f, 0.0f);
BVH_Vec3f aNode2(0.0f, 1.0f, 0.0f);
// Point projects inside triangle
BVH_Vec3f aProj = BVH_Tools<float, 3>::PointTriangleProjection(BVH_Vec3f(0.25f, 0.25f, 1.0f),
aNode0,
aNode1,
aNode2);
EXPECT_NEAR(aProj.x(), 0.25f, 1e-5f);
EXPECT_NEAR(aProj.y(), 0.25f, 1e-5f);
EXPECT_NEAR(aProj.z(), 0.0f, 1e-5f);
}
TEST(BVH_ToolsTest, PointBoxSquareDistanceLargeBox)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1000.0, 1000.0, 1000.0));
// Point inside
double aDist1 =
BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(500.0, 500.0, 500.0), aBox);
EXPECT_NEAR(aDist1, 0.0, Precision::Confusion());
// Point outside
double aDist2 =
BVH_Tools<double, 3>::PointBoxSquareDistance(BVH_Vec3d(1001.0, 500.0, 500.0), aBox);
EXPECT_NEAR(aDist2, 1.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, RayBoxIntersectionLargeBox)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1000.0, 1000.0, 1000.0));
double aTimeEnter, aTimeLeave;
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-100.0, 500.0, 500.0),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit);
EXPECT_NEAR(aTimeEnter, 100.0, Precision::Confusion());
EXPECT_NEAR(aTimeLeave, 1100.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointTriangleProjectionCentroid)
{
BVH_Vec3d aNode0(0.0, 0.0, 0.0);
BVH_Vec3d aNode1(3.0, 0.0, 0.0);
BVH_Vec3d aNode2(0.0, 3.0, 0.0);
// Point directly above centroid
BVH_Vec3d aProj =
BVH_Tools<double, 3>::PointTriangleProjection(BVH_Vec3d(1.0, 1.0, 5.0), aNode0, aNode1, aNode2);
EXPECT_NEAR(aProj.x(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj.y(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, BoxBoxSquareDistanceSameBox)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
// Distance to itself should be 0
double aDist = BVH_Tools<double, 3>::BoxBoxSquareDistance(aBox, aBox);
EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, PointBoxProjectionAllCorners)
{
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
// Test projection from each octant
BVH_Vec3d aProj1 = BVH_Tools<double, 3>::PointBoxProjection(BVH_Vec3d(-1.0, -1.0, -1.0), aBox);
EXPECT_NEAR(aProj1.x(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj1.y(), 0.0, Precision::Confusion());
EXPECT_NEAR(aProj1.z(), 0.0, Precision::Confusion());
BVH_Vec3d aProj2 = BVH_Tools<double, 3>::PointBoxProjection(BVH_Vec3d(2.0, 2.0, 2.0), aBox);
EXPECT_NEAR(aProj2.x(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj2.y(), 1.0, Precision::Confusion());
EXPECT_NEAR(aProj2.z(), 1.0, Precision::Confusion());
}
// =======================================================================================
// Tests for improved RayBoxIntersection (dimension-independent, early exit)
// =======================================================================================
TEST(BVH_ToolsTest, RayBoxIntersection2D)
{
// Test 2D ray-box intersection (old code was hardcoded for 3D)
BVH_Box<double, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray hitting the box
bool aHit1 = BVH_Tools<double, 2>::RayBoxIntersection(BVH_Vec2d(-1.0, 0.5),
BVH_Vec2d(1.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit1);
EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
// Ray missing the box
bool aHit2 = BVH_Tools<double, 2>::RayBoxIntersection(BVH_Vec2d(-1.0, 2.0),
BVH_Vec2d(1.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit2);
// Ray parallel to X axis, inside Y bounds
bool aHit3 = BVH_Tools<double, 2>::RayBoxIntersection(BVH_Vec2d(-1.0, 0.5),
BVH_Vec2d(1.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit3);
// Ray parallel to Y axis, inside X bounds
bool aHit4 = BVH_Tools<double, 2>::RayBoxIntersection(BVH_Vec2d(0.5, -1.0),
BVH_Vec2d(0.0, 1.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit4);
EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
}
TEST(BVH_ToolsTest, RayBoxIntersectionEarlyExit)
{
// Test early exit optimization when ray misses on first axis
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray clearly misses in X direction - should exit immediately
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-2.0, 0.5, 0.5),
BVH_Vec3d(-1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit);
// Ray with early mismatch in Y direction
bool aHit2 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(0.5, -2.0, 0.5),
BVH_Vec3d(0.0, -1.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit2);
}
TEST(BVH_ToolsTest, RayBoxIntersectionParallelRayEarlyExit)
{
// Test parallel ray that misses - should exit immediately
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray parallel to X axis but Y coordinate outside box - should reject immediately
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 2.0, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit);
// Ray parallel to Y axis but X coordinate outside box
bool aHit2 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(2.0, -1.0, 0.5),
BVH_Vec3d(0.0, 1.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit2);
// Ray parallel to Z axis but X and Y outside
bool aHit3 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, -1.0, 0.5),
BVH_Vec3d(0.0, 0.0, 1.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit3);
}
TEST(BVH_ToolsTest, RayBoxIntersection2DParallelBothAxes)
{
// 2D test with ray parallel to both axes (direction = 0,0)
BVH_Box<double, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray origin inside box, direction = 0
bool aHit1 = BVH_Tools<double, 2>::RayBoxIntersection(BVH_Vec2d(0.5, 0.5),
BVH_Vec2d(0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit1); // Should still hit because origin is inside
// Ray origin outside box, direction = 0
bool aHit2 = BVH_Tools<double, 2>::RayBoxIntersection(BVH_Vec2d(2.0, 2.0),
BVH_Vec2d(0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit2); // Should miss because origin is outside
}
TEST(BVH_ToolsTest, RayBoxIntersectionNegativeTime)
{
// Test that ray doesn't report intersection behind the origin
BVH_Box<double, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
double aTimeEnter, aTimeLeave;
// Ray origin is past the box, pointing away
bool aHit = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(2.0, 0.5, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_FALSE(aHit);
// Ray origin inside box, pointing away - should still hit (leave point is in front)
bool aHit2 = BVH_Tools<double, 3>::RayBoxIntersection(BVH_Vec3d(0.5, 0.5, 0.5),
BVH_Vec3d(1.0, 0.0, 0.0),
aBox,
aTimeEnter,
aTimeLeave);
EXPECT_TRUE(aHit2);
EXPECT_TRUE(aTimeLeave >= 0.0);
}