/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include #include #include "Math/Math.hpp" #include "Scene/Ray.hpp" #include "Scene/GeometryFactory.hpp" using namespace OpenVulkano::Math; using namespace OpenVulkano::Math::Utils; using namespace OpenVulkano::Scene; namespace { void CompareVec3Approx(const Vector3f& f, const Vector3f& s) { REQUIRE_THAT(f.x, Catch::Matchers::WithinRel(s.x)); REQUIRE_THAT(f.x, Catch::Matchers::WithinRel(s.x)); REQUIRE_THAT(f.x, Catch::Matchers::WithinRel(s.x)); } }; TEST_CASE("RaySphereIntersection") { auto sphere = GeometryFactory::MakeSphere(1, 32, 16); // 2 intersections { RayHit h1, h2; Ray ray(Vector3f(0, 0, -5), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 2); REQUIRE((h1.point == Vector3f(0, 0, -1) && h2.point == Vector3f(0, 0, 1))); REQUIRE((h1.normal == Vector3f(0, 0, -1) && h2.normal == Vector3f(0, 0, 1))); REQUIRE(h1.distance2 < h2.distance2); REQUIRE((h1.GetDistance() == distance(ray.GetOrigin(), h1.point) && h2.GetDistance() == distance(ray.GetOrigin(), h2.point))); REQUIRE_THAT(h1.GetDistance(), Catch::Matchers::WithinRel(std::sqrt(h1.distance2))); // this returns just closest point if (auto opt = ray.IntersectSphere(Vector3f(0), 1)) { REQUIRE(opt.value() == h1); } } // 1 intersection { RayHit h1, h2; Ray ray(Vector3f(1, 0, -1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 1); REQUIRE(h1 == h2); REQUIRE(h1.point == Vector3f(1, 0, 0)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1) == h1); } // 0 intersections { RayHit h1, h2; Ray ray(Vector3f(2, 0, -1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 0); REQUIRE(!ray.IntersectSphere(Vector3f(0), 1).has_value()); } // ray is inside sphere { RayHit h1, h2; Ray ray(Vector3f(0, 0, 0), Vector3f(0.5, 0.5, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 1); REQUIRE(h1 == h2); ::CompareVec3Approx(h1.normal, h1.point); auto value = ray.IntersectSphere(Vector3f(0), 1); REQUIRE(value->GetDistance() == h1.GetDistance()); REQUIRE_THAT(value->distance2, Catch::Matchers::WithinRel(h1.distance2)); ::CompareVec3Approx(value->normal, h1.normal); ::CompareVec3Approx(value->point, h1.point); } // ray intersects sphere behind the origin { RayHit h1, h2; Ray ray(Vector3f(0, 0, 3), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 0); REQUIRE(!ray.IntersectSphere(Vector3f(0), 1).has_value()); } } TEST_CASE("RayTriangleIntersection") { auto tri = GeometryFactory::MakeTriangle(Vector3f(0), Vector3f(3, 0, 0), Vector3f(1.5, 2, 0)); // intersects { Ray ray(Vector3f(1.5, 2, -5), Vector3f(0, 0, 1)); std::optional hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); REQUIRE(hit.has_value()); REQUIRE(hit->GetDistance() == distance(ray.GetOrigin(), hit->point)); REQUIRE(hit->point == Vector3f(1.5, 2, 0)); } { Ray ray(Vector3f(1.5, 1, -1), Vector3f(0, 0, 1)); std::optional hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); REQUIRE(hit.has_value()); REQUIRE(hit->GetDistance() == distance(ray.GetOrigin(), hit->point)); REQUIRE(hit->point == Vector3f(1.5, 1, 0)); } // no intersections { Ray ray(Vector3f(5, 0, 0), Vector3f(0, 0, 1)); std::optional hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); REQUIRE(!hit.has_value()); } { Ray ray(Vector3f(1.5, 1, 0.5), Vector3f(0, 0, 1)); std::optional hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); REQUIRE(!hit.has_value()); } } TEST_CASE("RayQuadIntersection") { auto cube = GeometryFactory::MakeCube(); std::optional hit; // intersects { Ray ray(Vector3f(0), Vector3f(0, 0, 1)); // front face hit = ray.IntersectQuad(cube.vertices[0].position, cube.vertices[1].position, cube.vertices[2].position, cube.vertices[3].position); REQUIRE(hit.has_value()); REQUIRE(hit->point == Vector3f(0, 0, 0.5)); } // no intersections { Ray ray(Vector3f(0), Vector3f(0, 0, -1)); hit = ray.IntersectQuad(cube.vertices[0].position, cube.vertices[1].position, cube.vertices[2].position, cube.vertices[3].position); REQUIRE(!hit.has_value()); } { Ray ray(Vector3f(1, 1, 0), Vector3f(0, 0, 1)); hit = ray.IntersectQuad(cube.vertices[0].position, cube.vertices[1].position, cube.vertices[2].position, cube.vertices[3].position); REQUIRE(!hit.has_value()); } } TEST_CASE("RayAABBIntersection") { auto sphere = GeometryFactory::MakeSphere(1, 32, 16); sphere.CalculateAABB(); std::optional hit; // intersects { RayHit h1, h2; Ray ray(Vector3f(0, 0, -2), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 2); REQUIRE(h1.distance2 < h2.distance2); REQUIRE(h1.point == Vector3f(0, 0, -1)); REQUIRE(h2.point == Vector3f(0, 0, 1)); auto p = ray.IntersectAABB(sphere.aabb); REQUIRE(p->point == h1.point); REQUIRE(p->distance2 == h1.distance2); REQUIRE(p->GetDistance() == h1.GetDistance()); } { RayHit h1, h2; Ray ray(Vector3f(0, 0, 1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 1); REQUIRE(h1.distance2 == h2.distance2); CompareVec3Approx(h1.point, h2.point); REQUIRE(h1.point == Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb)->distance2 == h1.distance2); } { RayHit h1, h2; // inside sphere Ray ray(Vector3f(0), Vector3f(0.3)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 1); REQUIRE(h1 == h2); auto val = ray.IntersectAABB(sphere.aabb); REQUIRE(val.has_value()); REQUIRE_THAT(val->distance2, Catch::Matchers::WithinRel(h1.distance2)); CompareVec3Approx(val->point, h1.point); } { RayHit h1, h2; Ray ray(Vector3f(2, -0.5, 1.5), Vector3f(-2, 0.5, -1.5)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 2); REQUIRE(h1.distance2 < h2.distance2); auto val = ray.IntersectAABB(sphere.aabb); REQUIRE(val.has_value()); REQUIRE(val->distance2 == h1.distance2); REQUIRE_THAT(val->GetDistance(), Catch::Matchers::WithinRel(h1.GetDistance())); CompareVec3Approx(val->point, h1.point); } // no intersections { RayHit h1, h2; Ray ray(Vector3f(3, 0, 1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 0); REQUIRE(!ray.IntersectAABB(sphere.aabb).has_value()); } { RayHit h1, h2; Ray ray(Vector3f(0, 0, 1.1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 0); REQUIRE(!ray.IntersectAABB(sphere.aabb).has_value()); } } TEST_CASE("RayPlaneIntersection") { Vector3f pOrigin = Vector3f(0); Vector3f pNorm = Vector3f(0, 1, 0); { Ray ray(Vector3f(2, -2, 2), Vector3f(0, 1, 0)); auto hit = ray.IntersectPlane(pOrigin, pNorm); REQUIRE(hit.has_value()); REQUIRE(hit->normal == pNorm); REQUIRE(hit->GetDistance() == 2.f); REQUIRE(hit->point == Vector3f(2, 0, 2)); } { Ray ray(Vector3f(2, -2, 2), Vector3f(1, 0, 0)); REQUIRE(!ray.IntersectPlane(pOrigin, pNorm)); } { Ray ray(Vector3f(0), Vector3f(1, 0, 0)); REQUIRE(!ray.IntersectPlane(pOrigin, pNorm)); } }