/* * 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 "Ray.hpp" #include namespace { int SolveQuadraticEquation(float a, float b, float c, float& x0, float& x1) { const float discr = b * b - 4 * a * c; if (discr < 0) return 0; if (discr == 0) { x0 = x1 = (-b) / (2 * a); return 1; } float q = (b > 0) ? -0.5 * (b + std::sqrt(discr)) : -0.5 * (b - std::sqrt(discr)); x0 = q / a; x1 = c / q; OpenVulkano::Math::Utils::SortPair(x0, x1); return 2; } }; namespace OpenVulkano::Scene { using namespace Math::Utils; std::optional Ray::IntersectSphere(const Math::Vector3f& center, float radius) const { RayHit hitRes; if (intersectRaySphere(m_origin, m_dir, center, radius, hitRes.point, hitRes.normal)) { hitRes.distance2 = distance2(m_origin, hitRes.point); return hitRes; } return {}; } std::optional Ray::IntersectTriangle(const Math::Vector3f& v0, const Math::Vector3f& v1, const Math::Vector3f& v2) const { float d; if (intersectRayTriangle(m_origin, m_dir, v0, v1, v2, m_baryPos, d) && d >= 0) { RayHit hitRes; hitRes.point = (1.f - m_baryPos.x - m_baryPos.y) * v0 + m_baryPos.x * v1 + m_baryPos.y * v2; Math::Vector3f e = v1 - v0; Math::Vector3f e2 = v2 - v0; // triangle normal. won't work if triangle is smoothly shaded. use other overloaded method instead. hitRes.SetDistance(d); hitRes.normal = normalize(cross(e, e2)); return hitRes; } return {}; } std::optional Ray::IntersectTriangle(const Math::Vector3f& v0, const Math::Vector3f& v1, const Math::Vector3f& v2, const Math::Vector3f& n0, const Math::Vector3f& n1, const Math::Vector3f& n2) const { auto hit = IntersectTriangle(v0, v1, v2); if (hit && (n0 != n1 || n0 != n2 || n1 != n2)) { // TODO: NEED ACTUAL TESTING hit->normal = normalize((1.f - m_baryPos.x - m_baryPos.y) * n0 + m_baryPos.x * n1 + m_baryPos.y * n2); } return hit; } std::optional Ray::IntersectQuad(const Math::Vector3f& v0, const Math::Vector3f& v1, const Math::Vector3f& v2, const Math::Vector3f& v3) const { if (const auto hitRes = IntersectTriangle(v0, v1, v2)) { return hitRes; } if (const auto hitRes = IntersectTriangle(v0, v2, v3)) { return hitRes; } return {}; } std::optional Ray::IntersectAABB(const Math::AABB2f& bbox) const { //TODO impl that skips z checks return IntersectAABB(Math::AABB({bbox.GetMin(), 0}, {bbox.GetMax(), 0})); } std::optional Ray::IntersectAABB(const Math::AABB& bbox) const { RayHit h1, h2; const int intersections = this->IntersectAABB(bbox, h1, h2); if (intersections == 1) return h1; if (intersections == 2) return (h1.distance2 < h2.distance2) ? h1 : h2; return {}; } int Ray::IntersectAABB(const Math::AABB& bbox, RayHit& p1, RayHit& p2) const { auto tmin = (bbox.min - m_origin) / m_dir; auto tmax = (bbox.max - m_origin) / m_dir; SortPair(tmin.x, tmax.x); SortPair(tmin.y, tmax.y); SortPair(tmin.z, tmax.z); if ((tmin.x > tmax.y) || (tmin.y > tmax.x)) return 0; if (tmin.y > tmin.x) { tmin.x = tmin.y; } if (tmax.y < tmax.x) { tmax.x = tmax.y; } if ((tmin.x > tmax.z) || (tmin.z > tmax.x)) return 0; if (tmin.z > tmin.x) { tmin.x = tmin.z; } if (tmax.z < tmax.x) { tmax.x = tmax.z; } int intersections = 2; if (tmin.x < 0) { if (tmax.x < 0) return 0; intersections--; tmin.x = tmax.x; } p1.point = m_origin + tmin.x * m_dir; p2.point = m_origin + tmax.x * m_dir; p1.distance2 = distance2(m_origin, p1.point); p2.distance2 = distance2(m_origin, p2.point); p1.normal = p2.normal = Math::Vector3f(0); return intersections; } std::optional Ray::IntersectPlane(const Math::Vector3f& pOrigin, const Math::Vector3f& pNorm) const { Math::Vector3f norm = normalize(pNorm); float d; if (intersectRayPlane(m_origin, m_dir, pOrigin, pNorm, d)) { return {{ m_origin + m_dir * d, norm, d, d * d }}; } return {}; } int Ray::IntersectSphere(const Math::Vector3f& center, float radius, RayHit& p1, RayHit& p2) const { const Math::Vector3f L = m_origin - center; const float a = length2(m_dir); const float b = 2 * dot(m_dir, L); const float c = length2(L) - radius * radius; float x1, x2; int roots = ::SolveQuadraticEquation(a, b, c, x1, x2); if (roots == 0) return 0; SortPair(x1, x2); bool calcP2 = false; if (roots == 1) { if (x1 < 0) return 0; // ray intersects sphere behind the origin } else { if (x1 < 0 && x2 < 0) return 0; // ray intersects sphere behind the origin if (x1 >= 0 && x2 >= 0) { calcP2 = true; } else { --roots; if (x1 < 0) x1 = x2; } } p1.point = m_origin + x1 * m_dir; p1.distance2 = distance2(m_origin, p1.point); p1.normal = normalize(p1.point - center); if (calcP2) { p2.point = m_origin + x2 * m_dir; p2.distance2 = distance2(m_origin, p2.point); p2.normal = normalize(p2.point - center); } else p2 = p1; return roots; } bool RayHit::operator==(const RayHit& other) const { return distance2 == other.distance2 && point == other.point && normal == other.normal; } bool RayHit::operator!=(const RayHit& other) const { return !(*this == other); } }