From d737aee1c536676557a4b4c9829d38f2bd79592a Mon Sep 17 00:00:00 2001 From: Orange Date: Thu, 19 Mar 2026 19:29:01 +0300 Subject: [PATCH] added by distance targeting --- include/omath/algorithm/targeting.hpp | 40 ++++++++++ tests/general/unit_test_targeting.cpp | 106 ++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/include/omath/algorithm/targeting.hpp b/include/omath/algorithm/targeting.hpp index dee152e..6d4dbea 100644 --- a/include/omath/algorithm/targeting.hpp +++ b/include/omath/algorithm/targeting.hpp @@ -60,4 +60,44 @@ namespace omath::algorithm std::ranges::begin(range), std::ranges::end(range), camera, get_position, filter_func); } + // ── By world-space distance ─────────────────────────────────────────────── + + template + requires std::is_invocable_r_v, std::iter_reference_t> + [[nodiscard]] + IteratorType get_closest_target_by_distance(const IteratorType& begin, const IteratorType& end, + const Vector3& origin, auto get_position, + const std::optional>& filter_func = std::nullopt) + { + auto best_target = end; + + for (auto current = begin; current != end; current = std::next(current)) + { + if (filter_func && !filter_func.value()(*current)) + continue; + + if (best_target == end) + { + best_target = current; + continue; + } + + if (origin.distance_to(get_position(*current)) < origin.distance_to(get_position(*best_target))) + best_target = current; + } + return best_target; + } + + template + requires std::is_invocable_r_v, + std::ranges::range_reference_t> + [[nodiscard]] + auto get_closest_target_by_distance(const RangeType& range, const Vector3& origin, + auto get_position, + const std::optional>& filter_func = std::nullopt) + { + return get_closest_target_by_distance( + std::ranges::begin(range), std::ranges::end(range), origin, get_position, filter_func); + } + } // namespace omath::algorithm \ No newline at end of file diff --git a/tests/general/unit_test_targeting.cpp b/tests/general/unit_test_targeting.cpp index 8386290..f169726 100644 --- a/tests/general/unit_test_targeting.cpp +++ b/tests/general/unit_test_targeting.cpp @@ -33,6 +33,12 @@ namespace return omath::algorithm::get_closest_target_by_fov( begin, end, camera, get_pos); } + + Iter find_nearest(const Iter begin, const Iter end, const omath::Vector3& origin) + { + return omath::algorithm::get_closest_target_by_distance( + begin, end, origin, get_pos); + } } TEST(unit_test_targeting, returns_end_for_empty_range) @@ -152,3 +158,103 @@ TEST(unit_test_targeting, many_targets_picks_best) ASSERT_NE(result, targets.cend()); EXPECT_EQ(result, targets.cbegin() + 4); } + +// ── get_closest_target_by_distance tests ──────────────────────────────────── + +TEST(unit_test_targeting, distance_returns_end_for_empty_range) +{ + Targets targets; + + EXPECT_EQ(find_nearest(targets.cbegin(), targets.cend(), {0, 0, 0}), targets.cend()); +} + +TEST(unit_test_targeting, distance_single_target) +{ + Targets targets = {{50.f, 0.f, 0.f}}; + + EXPECT_EQ(find_nearest(targets.cbegin(), targets.cend(), {0, 0, 0}), targets.cbegin()); +} + +TEST(unit_test_targeting, distance_picks_nearest) +{ + const omath::Vector3 origin{0.f, 0.f, 0.f}; + + Targets targets = { + {100.f, 0.f, 0.f}, // distance = 100 + {10.f, 0.f, 0.f}, // distance = 10 (closest) + {50.f, 0.f, 0.f}, // distance = 50 + }; + + const auto result = find_nearest(targets.cbegin(), targets.cend(), origin); + + ASSERT_NE(result, targets.cend()); + EXPECT_EQ(result, targets.cbegin() + 1); +} + +TEST(unit_test_targeting, distance_considers_all_axes) +{ + const omath::Vector3 origin{0.f, 0.f, 0.f}; + + Targets targets = { + {30.f, 30.f, 30.f}, // distance = sqrt(2700) ~ 51.96 + {50.f, 0.f, 0.f}, // distance = 50 + {0.f, 0.f, 10.f}, // distance = 10 (closest) + }; + + const auto result = find_nearest(targets.cbegin(), targets.cend(), origin); + + ASSERT_NE(result, targets.cend()); + EXPECT_EQ(result, targets.cbegin() + 2); +} + +TEST(unit_test_targeting, distance_from_nonzero_origin) +{ + const omath::Vector3 origin{100.f, 100.f, 100.f}; + + Targets targets = { + {0.f, 0.f, 0.f}, // distance = sqrt(30000) ~ 173 + {105.f, 100.f, 100.f}, // distance = 5 (closest) + {200.f, 200.f, 200.f}, // distance = sqrt(30000) ~ 173 + }; + + const auto result = find_nearest(targets.cbegin(), targets.cend(), origin); + + ASSERT_NE(result, targets.cend()); + EXPECT_EQ(result, targets.cbegin() + 1); +} + +TEST(unit_test_targeting, distance_equidistant_returns_first) +{ + const omath::Vector3 origin{0.f, 0.f, 0.f}; + + // Both targets at distance 100, symmetric + Targets targets = { + {100.f, 0.f, 0.f}, + {-100.f, 0.f, 0.f}, + }; + + const auto result = find_nearest(targets.cbegin(), targets.cend(), origin); + + ASSERT_NE(result, targets.cend()); + EXPECT_EQ(result, targets.cbegin()); +} + +TEST(unit_test_targeting, distance_many_targets) +{ + const omath::Vector3 origin{0.f, 0.f, 0.f}; + + Targets targets = { + {500.f, 0.f, 0.f}, + {200.f, 200.f, 0.f}, + {100.f, 100.f, 100.f}, + {50.f, 50.f, 50.f}, + {1.f, 1.f, 1.f}, // distance = sqrt(3) ~ 1.73 (closest) + {10.f, 10.f, 10.f}, + {80.f, 0.f, 0.f}, + }; + + const auto result = find_nearest(targets.cbegin(), targets.cend(), origin); + + ASSERT_NE(result, targets.cend()); + EXPECT_EQ(result, targets.cbegin() + 4); +}