/* * 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) { 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; if (x0 > x1) { std::swap(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.distance = distance(m_origin, hitRes.point); return hitRes; } return {}; } std::optional Ray::IntersectTriangle(const Math::Vector3f& v0, const Math::Vector3f& v1, const Math::Vector3f& v2) const { RayHit hitRes; vec2 baryPos; if (intersectRayTriangle(m_origin, m_dir, v0, v1, v2, baryPos, hitRes.distance) && hitRes.distance >= 0) { hitRes.point = (1.f - baryPos.x - baryPos.y) * v0 + baryPos.x * v1 + baryPos.y * v2; // calculate like cross product or leave empty ? what if current triangle is smoothly shaded ? hitRes.normal = Math::Vector3f(0); return hitRes; } return {}; } std::optional Ray::IntersectQuad(const Math::Vector3f& v0, const Math::Vector3f& v1, const Math::Vector3f& v2, const Math::Vector3f& v3) const { if (auto hitRes = IntersectTriangle(v0, v1, v2)) { return hitRes; } if (auto hitRes = IntersectTriangle(v0, v2, v3)) { return hitRes; } return {}; } std::optional Ray::IntersectAABB(const Math::AABB& bbox) const { RayHit h1, h2; const int intersections = this->IntersectAABB(bbox, h1, h2); switch (intersections) { case 1: return h1; case 2: return (h1.distance < h2.distance) ? h1 : h2; } return {}; } int Ray::IntersectAABB(const Math::AABB& bbox, RayHit& p1, RayHit& p2) const { const auto bMax = bbox.max; const auto bMin = bbox.min; float tmin = (bMin.x - m_origin.x) / m_dir.x; float tmax = (bMax.x - m_origin.x) / m_dir.x; if (tmin > tmax) { std::swap(tmin, tmax); } float tymin = (bMin.y - m_origin.y) / m_dir.y; float tymax = (bMax.y - m_origin.y) / m_dir.y; if (tymin > tymax) { std::swap(tymin, tymax); } if ((tmin > tymax) || (tymin > tmax)) { return 0; } if (tymin > tmin) { tmin = tymin; } if (tymax < tmax) { tmax = tymax; } float tzmin = (bMin.z - m_origin.z) / m_dir.z; float tzmax = (bMax.z - m_origin.z) / m_dir.z; if (tzmin > tzmax) { std::swap(tzmin, tzmax); } if ((tmin > tzmax) || (tzmin > tmax)) { return 0; } if (tzmin > tmin) { tmin = tzmin; } if (tzmax < tmax) { tmax = tzmax; } int intersections = 2; if (tmin < 0) { if (tmax < 0) { return 0; } intersections--; tmin = tmax; } p1.point = m_origin + tmin * m_dir; p2.point = m_origin + tmax * m_dir; p1.distance = distance(m_origin, p1.point); p2.distance = distance(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 { RayHit hit; Math::Vector3f norm = normalize(pNorm); if (intersectRayPlane(m_origin, m_dir, pOrigin, pNorm, hit.distance)) { hit.point = m_origin + m_dir * hit.distance; hit.normal = norm; return hit; } 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 = dot(m_dir, m_dir); // equals to length^2 const float b = 2 * dot(m_dir, L); const float c = dot(L, L) - radius * radius; float x1, x2; int roots = ::SolveQuadraticEquation(a, b, c, x1, x2); if (roots == 0) { return 0; } if (x1 > x2) { std::swap(x1, x2); } if (roots == 1) { // ray intersects sphere behind the origin if (x1 < 0) { return 0; } p1.point = m_origin + x1 * m_dir; p1.distance = distance(m_origin, p1.point); p1.normal = normalize(p1.point - center); p2 = p1; } else if (roots == 2) { // ray intersects sphere behind the origin if (x1 < 0 && x2 < 0) { return 0; } if (x1 >= 0 && x2 >= 0) { p1.point = m_origin + x1 * m_dir; p1.distance = distance(m_origin, p1.point); p1.normal = normalize(p1.point - center); p2.point = m_origin + x2 * m_dir; p2.distance = distance(m_origin, p2.point); p2.normal = normalize(p2.point - center); } else { --roots; if (x1 < 0) { x1 = x2; } p1.point = m_origin + x1 * m_dir; p1.distance = distance(m_origin, p1.point); p1.normal = normalize(p1.point - center); p2 = p1; } } return roots; } }