/* * Copyright (C) 2023 Christopher J. Howard * * This file is part of Antkeeper source code. * * Antkeeper source code is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Antkeeper source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Antkeeper source code. If not, see . */ #ifndef ANTKEEPER_GEOM_CLOSEST_POINT_HPP #define ANTKEEPER_GEOM_CLOSEST_POINT_HPP #include #include #include #include #include #include #include #include #include #include #include namespace geom { /** * Calculates the closest point on a ray to a point. * * @tparam T Real type. * @tparam N Number of dimensions. * * @param a Ray a. * @param b Point b. * * @return Closest point on ray a to point b. */ template [[nodiscard]] constexpr point closest_point(const ray& a, const point& b) noexcept { return a.extrapolate(std::max(T{0}, math::dot(b - a.origin, a.direction))); } /** * Calculates the closest point on a line segment to a point. * * @tparam T Real type. * @tparam N Number of dimensions. * * @param ab Line segment ab. * @param c Point c. * * @return Closest point on line segment ab to point c. */ template [[nodiscard]] constexpr point closest_point(const line_segment& ab, const point& c) noexcept { const auto direction_ab = ab.b - ab.a; const auto distance_ab = math::dot(c - ab.a, direction_ab); if (distance_ab <= T{0}) { return ab.a; } else { const auto sqr_length_ab = math::sqr_length(direction_ab); if (distance_ab >= sqr_length_ab) { return ab.b; } else { return ab.a + direction_ab * (distance_ab / sqr_length_ab); } } } /** * Calculates the closest points on two line segments. * * @tparam T Real type. * @tparam N Number of dimensions. * * @param ab Line segment ab. * @param cd Line segment cd. * * @return Tuple containing the closest point on segment ab to segment cd, followed by the closest point on segment cd to segment ab. */ template [[nodiscard]] constexpr std::tuple, point> closest_point(const line_segment& ab, const line_segment& cd) noexcept { const auto direction_ab = ab.b - ab.a; const auto direction_cd = cd.b - cd.a; const auto direction_ca = ab.a - cd.a; const auto sqr_length_ab = math::sqr_length(direction_ab); const auto sqr_length_cd = math::sqr_length(direction_cd); const auto cd_dot_ca = math::dot(direction_cd, direction_ca); if (sqr_length_ab <= T{0}) { if (sqr_length_cd <= T{0}) { // Both segments are points return {ab.a, cd.a}; } else { // Segment ab is a point return { ab.a, cd.a + direction_cd * std::min(std::max(cd_dot_ca / sqr_length_cd, T{0}), T{1}) }; } } else { const auto ab_dot_ca = math::dot(direction_ab, direction_ca); if (sqr_length_cd <= T{0}) { // Segment cd is a point return { ab.a + direction_ab * std::min(std::max(-ab_dot_ca / sqr_length_ab, T{0}), T{1}), cd.a }; } else { const auto ab_dot_cd = math::dot(direction_ab, direction_cd); const auto den = sqr_length_ab * sqr_length_cd - ab_dot_cd * ab_dot_cd; auto distance_ab = (den) ? std::min(std::max((ab_dot_cd * cd_dot_ca - ab_dot_ca * sqr_length_cd) / den, T{0}), T{1}) : T{0}; auto distance_cd = (ab_dot_cd * distance_ab + cd_dot_ca) / sqr_length_cd; if (distance_cd < T{0}) { return { ab.a + direction_ab * std::min(std::max(-ab_dot_ca / sqr_length_ab, T{0}), T{1}), cd.a }; } else if (distance_cd > T{1}) { return { ab.a + direction_ab * std::min(std::max((ab_dot_cd - ab_dot_ca) / sqr_length_ab, T{0}), T{1}), cd.b }; } return { ab.a + direction_ab * distance_ab, cd.a + direction_cd * distance_cd }; } } } /** * Calculates the closest point on a hyperplane to a point. * * @tparam T Real type. * @tparam N Number of dimensions. * * @param a Hyperplane a. * @param b Point b. * * @return Closest point on hyperplane a to point b. */ template [[nodiscard]] constexpr point closest_point(const hyperplane& a, const point& b) noexcept { return b - a.normal * (math::dot(a.normal, b) + a.constant); } /** * Calculates the closest point on a triangle to a point. * * @tparam T Real type. * * @param tri Triangle. * @param a First point of triangle. * @param b Second point of triangle. * @param c Third point of triangle. * @param p Point. * * @return Closest point on the triangle to point @p p, followed by the index of the edge on which the point lies (`-1` if not on an edge). */ /// @{ template [[nodiscard]] constexpr point closest_point(const point& a, const point& b, const point& c, const point& p) noexcept { const auto ab = b - a; const auto ca = a - c; const auto ap = p - a; const auto n = math::cross(ab, ca); const auto d = math::sqr_length(n); const auto q = math::cross(n, ap); T u, v, w; if ((w = math::dot(q, ab) / d) < T{0}) { v = std::min(std::max(math::dot(ab, ap) / math::sqr_length(ab), T{0}), T{1}); return {a * (T{1} - v) + b * v}; } else if ((v = math::dot(q, ca) / d) < T{0}) { u = std::min(std::max(math::dot(ca, p - c) / math::sqr_length(ca), T{0}), T{1}); return {a * u + c * (T{1} - u)}; } else if ((u = T{1} - v - w) < T{0}) { const auto bc = c - b; w = std::min(std::max(math::dot(bc, p - b) / math::sqr_length(bc), T{0}), T{1}); return {b * (T{1} - w) + c * w}; } return {a * u + b * v + c * w}; } template [[nodiscard]] inline constexpr point closest_point(const triangle& tri, const point& p) noexcept { return closest_point(tri.a, tri.b, tri.c, p); } /// @} /** * Calculates the closest point on or in a hypersphere to a point. * * @tparam T Real type. * @tparam N Number of dimensions. * * @param a Hypersphere a. * @param b Point b. * * @return Closest point on or in hypersphere a to point b. */ template [[nodiscard]] point closest_point(const hypersphere& a, const point& b) { const auto ab = b - a.center; const auto d = math::sqr_length(ab); return d > a.radius * a.radius ? a.center + ab * (a.radius / std::sqrt(d)) : b; } /** * Calculates the closest point on or in a hypercapsule to a point. * * @tparam T Real type. * @tparam N Number of dimensions. * * @param a Hypercapsule a. * @param b Point b. * * @return Closest point on or in hypercapsule a to point b. */ template [[nodiscard]] point closest_point(const hypercapsule& a, const point& b) { const auto c = closest_point(a.segment, b); const auto cb = b - c; const auto d = math::sqr_length(cb); return d > a.radius * a.radius ? c + cb * (a.radius / std::sqrt(d)) : b; } /** * Calculates the closest point on or in a hyperrectangle to a point. * * @tparam T Real type. * @tparam N Number of dimensions. * * @param a Hyperrectangle a. * @param b Point b. * * @return Closest point on or in hyperrectangle a to point b. */ template [[nodiscard]] constexpr point closest_point(const hyperrectangle& a, const point& b) noexcept { return math::min(math::max(b, a.min), a.max); } } // namespace geom #endif // ANTKEEPER_GEOM_CLOSEST_POINT_HPP