diff --git a/CMakeLists.txt b/CMakeLists.txt index 65725c07b1731b4239a8d6189b0b066476f88746..de57f895c23c7d1238d16044cb28d716e29528bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,8 @@ target_include_directories(${CMAKE_PROJECT_NAME} target_sources(${CMAKE_PROJECT_NAME} PRIVATE + src/Component/Points.cpp + src/Object/CommonSign.cpp src/Object/MovingObject.cpp src/Object/RoadMarking.cpp @@ -83,16 +85,18 @@ target_sources(${CMAKE_PROJECT_NAME} src/Street/ReferenceLine.cpp src/Street/Road.cpp + src/Types/Enum/LaneDirection.cpp + src/Types/Enum/RoadDirection.cpp + src/Types/Enum/Side.cpp + src/Types/Area.cpp src/Types/Bounds.cpp + src/Types/Circle.cpp src/Types/Enum.cpp src/Types/Geometry.cpp - src/Types/LaneDirection.cpp src/Types/LocalBounds.cpp src/Types/Matrix.cpp - src/Types/RoadDirection.cpp src/Types/Shape.cpp - src/Types/Side.cpp src/Types/Value.cpp src/Utility/XYZ.cpp diff --git a/README.md b/README.md index 664709819640064d1f81990401a61fc1666a51c2..4d91ff0feaec5090805ec134d9c912be8c55cc0f 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,12 @@ All wrapper objects have public handles to their corresponding `osi3` entities. ### Dependencies -| Library | Version | Additional Information | -| ------- | ------- | ---- | -| boost Geometry | Tested with 1.72.0 | [boost doc](https://www.boost.org) and [boost Geometry](https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/index.html) | -| Open Simulation Interface | 3.5 and 3.6 | [OSI doc](https://opensimulationinterface.github.io/osi-antora-generator/asamosi/latest/interface/setup/installing_prerequisites.html) and [OpenPASS doc](https://www.eclipse.org/openpass/content/html/installation_guide/20_install_prerequisites.html) | -| - protobuf<br />- protobuf-shared | Tested with 3.20.0 | [OSI doc](https://opensimulationinterface.github.io/osi-antora-generator/asamosi/latest/interface/setup/installing_prerequisites.html) and [OpenPASS doc](https://www.eclipse.org/openpass/content/html/installation_guide/20_install_prerequisites.html) | -| googletest | Tested with 1.13.0 | https://github.com/google/googletest | +| Library | Version | Additional Information | +| --------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| boost Geometry | Tested with 1.72.0 | [boost doc](https://www.boost.org) and [boost Geometry](https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/index.html) | +| Open Simulation Interface | 3.5 and 3.6 | [OSI doc](https://opensimulationinterface.github.io/osi-antora-generator/asamosi/latest/interface/setup/installing_prerequisites.html) and [OpenPASS doc](https://www.eclipse.org/openpass/content/html/installation_guide/20_install_prerequisites.html) | +| - protobuf<br />- protobuf-shared | Tested with 3.20.0 | [OSI doc](https://opensimulationinterface.github.io/osi-antora-generator/asamosi/latest/interface/setup/installing_prerequisites.html) and [OpenPASS doc](https://www.eclipse.org/openpass/content/html/installation_guide/20_install_prerequisites.html) | +| googletest | Tested with 1.13.0 | https://github.com/google/googletest | ## Build Example @@ -80,7 +80,7 @@ if (route.empty()) // ... increment timestep here ... // After modifying the groundTruth, update the osiql query if there has // been a change in lane assignments or objects have been added/removed: -query.Update(alteredGroundTruth.moving_object()); +query.UpdateAll<MovingObject>(alteredGroundTruth.moving_object()); route = query.GetRoute(destination); if (route.empty()) diff --git a/changelog.md b/changelog.md index e2650165a094ac9095740f12cd6c979b26e23f49..748b0a52e08fb5d5ee2831f04a1dde8094908f8d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,23 @@ +### 2025-04-01 v2.2.0 +#### Breaking Changes +- `GetCurvature` now treats its chain of points as lying on the sampled curve. The old behavior (points define edges that are tangents of the curve) is moved to `GetCurvatureFromLocalTangents`. + +#### New +- Add `osiql::Interpolate` & `osiql::Average`, which return linear interpolations of their inputs +- Add `osiql::SetId(Type, Id)` for any `Type` that is an OSI type (`osi3::Identifier` or has the member `mutable_id()`) +- Add `World::UpdateAll<StationaryObject>` + +#### Fixes +- Comparison functors with transformers now correctly transform both arguments, not just the first +- Make Lane routes iterable just like road routes + +#### Changes +- `GetDistanceBetween(a, b)` and related methods now returns negative distances if `b` is behind `a`. + +#### Changes +- Deprecated `World::Update`. Prefer `World::UpdateAll<MovingObject>` +- Improve lookup performance for `StationaryObject`s and `LightBulb`s + ### 2025-03-11 v2.1.1 #### Breaking Changes - Rename `Traversal` to `LaneDirection` diff --git a/doc/source/Extensions/10_world.rst b/doc/source/Extensions/10_world.rst index 6853b3c536cfc80b55176846bd000b7e6b26eea5..4b45ee913f8ac975f107249439dc3c9f1597c49a 100644 --- a/doc/source/Extensions/10_world.rst +++ b/doc/source/Extensions/10_world.rst @@ -40,7 +40,7 @@ The ``osiql::World`` needs to be informed when its underlying handle has changed // The user is responsible for managing the handle's lifetime. World world{groundTruth}; // When the moving objects of the underlying groundTruth change, the world needs to be updated: - world.Update(groundTruth.moving_object()); + world.UpdateAll<MovingObject>(groundTruth.moving_object()); See the doxygen documentation for all methods that ``osiql::World`` provides. diff --git a/include/OsiQueryLibrary/Component/Identifiable.h b/include/OsiQueryLibrary/Component/Identifiable.h index 9751e8ff78ce8fe959d159e8797dc50b602baa39..16ee75addff79ab205e233b3c85e8fd6e5ebd207 100644 --- a/include/OsiQueryLibrary/Component/Identifiable.h +++ b/include/OsiQueryLibrary/Component/Identifiable.h @@ -68,4 +68,10 @@ struct Identifiable : Wrapper<Type> //! \return Id Id GetId() const; }; + +//! Assigns the id of an OSI object +//! +//! \tparam Handle OSI type with an assignable id +template <typename Handle> +void SetId(Handle &, Id); } // namespace osiql diff --git a/include/OsiQueryLibrary/Component/Identifiable.tpp b/include/OsiQueryLibrary/Component/Identifiable.tpp index 372a07a6a2073350512b30f6875e555f960fad44..20594e0fba110715065ea7ff20452d87a289c2f5 100644 --- a/include/OsiQueryLibrary/Component/Identifiable.tpp +++ b/include/OsiQueryLibrary/Component/Identifiable.tpp @@ -63,4 +63,21 @@ Id Identifiable<Type>::GetId() const return this->GetHandle().has_id() ? this->GetHandle().id().value() : UNDEFINED_ID; } } + +template <typename Handle> +void SetId(Handle &handle, Id id) +{ + if constexpr (std::is_same_v<Handle, osi3::Identifier>) + { + handle.set_value(id); + } + else if constexpr (OSIQL_HAS_MEMBER(Handle, mutable_id())) + { + handle.mutable_id()->set_value(id); + } + else + { + static_assert(always_false<Handle>, "Not supported"); + } +} } // namespace osiql diff --git a/include/OsiQueryLibrary/Component/Points.h b/include/OsiQueryLibrary/Component/Points.h index 924e2a2526656f689d6f17bf8f87d113705368c1..dad83747e87c9ec853145b6d9e1b270d1c80eea3 100644 --- a/include/OsiQueryLibrary/Component/Points.h +++ b/include/OsiQueryLibrary/Component/Points.h @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2023-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -61,15 +61,15 @@ struct Points : Iterable<Type, From> //! \return double double GetLength() const; - //! Returns the interpolated point on this chain of points with the given s-coordinate + //! Returns the interpolated point on this chain of points with the given s-coordinate. + //! If the s-coordinate falls outside the length of this chain, the nearest edge is extended infinitely to allow conversion. //! //! \param s s-coordinate representing the distance from the start of a reference line along that reference line //! \return XY xy-coordinate pair representing a global point in world coordinates XY GetXY(double s) const; - //! Returns the global point of any given s- & t-coordinate relative to this Points. This implementation is independent from - //! other points comprising a lane and thus may produce results deviating from OpenDRIVE specification. - //! The point is interpolated perpendicular to the (possibly extended) points and is thus ambiguous near the start/end of line segments. + //! Returns the global point of any given s- & t-coordinate relative to this chain of points. + //! If the s-coordinate falls outside the length of this chain, the nearest edge is extended infinitely to allow conversion. //! //! \return XY XY GetXY(const ST &) const; @@ -77,6 +77,7 @@ struct Points : Iterable<Type, From> //! Returns a st-coordinate representation of the given xy-coordinates localized to this chain of points. //! If the input contains a global angle, the angle will be localized as well as a counter-clockwise //! ascending angle in radians within [-π, π] from the x-axis. + //! If the point lies behind or after the chain of points the nearest edge is extended infinitely to localize said point. //! //! \return Either ST or Pose<ST> template <typename GlobalPoint> @@ -91,6 +92,7 @@ struct Points : Iterable<Type, From> //! Returns the global angle of the point set's edge at the given value in the template direction. //! If the value hits a corner, the angle of the latter edge in the template direction is returned. + //! If the s-coordinate falls outside the length of this chain, the nearest edge is extended infinitely to allow conversion. //! //! \tparam RoadDirection Downstream or Upstream //! \param s s-coordinate @@ -100,34 +102,42 @@ struct Points : Iterable<Type, From> //! Returns the global angle of the point set's edge at the given value in the given direction. //! If the value hits a corner, the angle of the latter edge in the given direction is returned. + //! If the s-coordinate falls outside the length of this chain, the nearest edge is extended infinitely to allow conversion. //! //! \param s s-coordinate //! \param RoadDirection Downstream or Upstream //! \return double counter-clockwise ascending angle in radians within [-π, π] from the x-axis double GetAngle(double s, RoadDirection) const; - //! Treats this chain of points as though it were a smooth curve and - //! returns the change in angle per unit of length at the given s-coordinate. + //! Returns the interpolated rate of change in angle at the given s-coordinate as though this chain of points were a smooth curve. + //! If the s-coordinate falls outside the length of this chain, NaN is returned. //! //! \tparam RoadDirection Downstream or Upstream - //! \param s s-coordinate at which the curvature will be measured - //! \param epsilon Rounding error tolerance. The result is interpolated by the change in angle - //! of the closest vertex before and after the given s-coordinate. Any value closer to 0 than - //! the given epsilon will be replaced by 0. - //! \return double + //! \param s The s-coordinate at which the curvature is to be measured. + //! \return Change in angle per unit of length at the given s-coordinate. template <RoadDirection = RoadDirection::Downstream> - double GetCurvature(double s, double epsilon = 0.0) const; + double GetCurvature(double s) const; //! Treats this chain of points as though it were a smooth curve and //! returns the change in angle per unit of length at the given s-coordinate. + //! If the s-coordinate falls outside the length of this chain, NaN is returned. //! - //! \param s s-coordinate at which the curvature will be measured + //! \param s s-coordinate at which the curvature will be measured //! \param RoadDirection Downstream or Upstream - //! \param epsilon Rounding error tolerance. The result is interpolated by the change in angle - //! of the closest vertex before and after the given s-coordinate. Any value closer to 0 than - //! the given epsilon will be replaced by 0. - //! \return double - double GetCurvature(double s, RoadDirection, double epsilon = 0.0) const; + //! \return Interpolated rate of change in angle at the given s-coordinate + double GetCurvature(double s, RoadDirection) const; + + //! Treats this chain of points as though its edges were the local tangents of a curve and + //! returns the interpolated rate of change in angle at the given s-coordinate. + //! + //! \tparam RoadDirection Downstream or Upstream + //! \param s s-coordinate at which the curvature will be measured + //! \param epsilon Rounding error tolerance. The result is interpolated by the change in angle + //! of the closest vertex before and after the given s-coordinate. Any value closer + //! to 0 than the given epsilon will be replaced by 0. + //!\return Change in angle per unit of length at the given s-coordinate + template <RoadDirection D> + double GetCurvatureFromLocalTangents(double s, double epsilon = .0) const; // NOLINT(bugprone-easily-swappable-parameters) }; template <typename Type, typename From> diff --git a/include/OsiQueryLibrary/Component/Points.tpp b/include/OsiQueryLibrary/Component/Points.tpp index 87f53098531ce3b70f341da739d2b5349f3c599f..652ae2fce13e500358b4f5f4b1347d95201e42fd 100644 --- a/include/OsiQueryLibrary/Component/Points.tpp +++ b/include/OsiQueryLibrary/Component/Points.tpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2023-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -14,6 +14,7 @@ #include "Iterable.tpp" #include "OsiQueryLibrary/Point/Pose.h" #include "OsiQueryLibrary/Point/XY.tpp" +#include "OsiQueryLibrary/Types/Circle.h" #include "OsiQueryLibrary/Types/Constants.h" #include "OsiQueryLibrary/Utility/Extract.tpp" @@ -95,13 +96,13 @@ double Points<Type, From>::GetAngle(double s) const const double endAngle{(edge + post).Angle()}; if (startAngle - endAngle > pi) { - return std::fmod(startAngle * (1.0 - ratio) + (endAngle + twoPi) * ratio, twoPi); + return std::fmod(Interpolate(startAngle, endAngle + twoPi, ratio), twoPi); } if (endAngle - startAngle > pi) { - return std::fmod((startAngle + twoPi) * (1.0 - ratio) + endAngle * ratio, twoPi); + return std::fmod(Interpolate(startAngle + twoPi, endAngle, ratio), twoPi); } - return startAngle * (1.0 - ratio) + endAngle * ratio; + return Interpolate(startAngle, endAngle, ratio); } } @@ -111,56 +112,6 @@ double Points<Type, From>::GetAngle(double s, RoadDirection direction) const return direction == RoadDirection::Upstream ? GetAngle<RoadDirection::Upstream>(s) : GetAngle<RoadDirection::Downstream>(s); } -template <typename Type, typename From> -template <RoadDirection D> -double Points<Type, From>::GetCurvature(double s, double epsilon) const // NOLINT(bugprone-easily-swappable-parameters) -{ - const auto edgeEnd{GetNearestEdge<D>(s)}; - const auto edgeStart{std::prev(edgeEnd)}; - if (Extract<D>::less(s, *edgeStart) || Extract<D>::greater(s, *edgeEnd)) - { - return std::numeric_limits<double>::quiet_NaN(); - } - const XY edge{*edgeEnd - *edgeStart}; - const double halfEdgeLength{edge.Length() * 0.5}; - if (halfEdgeLength <= epsilon) - { - return 0.0; - } - double startCurvature{0.0}; - if (edgeStart != begin<D>()) - { - const XY priorEdge{*edgeStart - *std::prev(edgeStart)}; - const double deltaAngle{std::atan2(priorEdge.Cross(edge), priorEdge.Dot(edge))}; - const double length{halfEdgeLength + priorEdge.Length() * 0.5}; - startCurvature = deltaAngle / length; - } - - double endCurvature{0.0}; - if (std::next(edgeEnd) != end<D>()) - { - const XY nextEdge{*std::next(edgeEnd) - *edgeEnd}; - const double deltaAngle{std::atan2(edge.Cross(nextEdge), edge.Dot(nextEdge))}; - const double length{halfEdgeLength + nextEdge.Length() * 0.5}; - endCurvature = deltaAngle / length; - } - - const double startS{get<S>(*edgeStart)}; - const double endS{get<S>(*edgeEnd)}; - if (std::abs(endS - startS) < epsilon) - { - return 0.0; - } - const double ratio{(s - startS) / (endS - startS)}; - return startCurvature * (1.0 - ratio) + endCurvature * ratio; -} - -template <typename Type, typename From> -double Points<Type, From>::GetCurvature(double s, RoadDirection direction, double epsilon) const -{ - return direction == RoadDirection::Upstream ? GetCurvature<RoadDirection::Upstream>(s, epsilon) : GetCurvature<RoadDirection::Downstream>(s, epsilon); -} - template <typename Type, typename From> double Points<Type, From>::GetT(double s) const { @@ -178,7 +129,7 @@ double Points<Type, From>::GetT(double s) const return extract<Side::Left>(*it); } const double ratio{(s - get<S>(*it)) / (get<S>(*std::next(it)) - get<S>(*it))}; - return extract<Side::Left>(*it) * (1.0 - ratio) + extract<Side::Left>(*std::next(it)) * ratio; + return Interpolate(extract<Side::Left>(*it), extract<Side::Left>(*std::next(it)), ratio); } template <typename Type, typename From> @@ -188,7 +139,7 @@ XY Points<Type, From>::GetXY(double s) const const double prevS{get<S>(*std::prev(it))}; const double currS{get<S>(*it)}; const double ratio{(s - prevS) / (currS - prevS)}; - return XY{*std::prev(it)} * (1.0 - ratio) + XY{*it} * ratio; + return Interpolate(XY{*std::prev(it)}, XY{*it}, ratio); } template <typename Type, typename From> @@ -203,7 +154,7 @@ XY Points<Type, From>::GetXY(const ST &coordinates) const const double startDistance{get<S>(*std::prev(it))}; const double endDistance{get<S>(*it)}; const double ratio{(coordinates.s - startDistance) / (endDistance - startDistance)}; - const XY pointOnEdge{edgeStart * (1.0 - ratio) + edgeEnd * ratio}; + const XY pointOnEdge{Interpolate(edgeStart, edgeEnd, ratio)}; return pointOnEdge + normal * coordinates.t; } @@ -229,6 +180,111 @@ decltype(auto) Points<Type, From>::Localize(const GlobalPoint &input) const } } +namespace detail { +//! Returns the curvature of the circle passing through the three given points. +//! +//! \return Signed curvature based on the shorter winding direction from the first to the +//! last given point along the circle. Positive if counter-clockwise, negative if clockwise. +double GetCurvature(const XY &, const XY &, const XY &); + +//! Computes two circles, One passing through the first three points and another through the last three, +//! and returns the interpolated curvature between the two circles +//! +//!\param ratio Linear interpolation ratio. At 0, the curvature of the first circle is returned, at 1 that of the latter. +//!\return Signed curvature based on the shorter winding direction from the first to the +//! last given point along the circle. Positive if counter-clockwise, negative if clockwise. +double GetCurvature(double ratio, const XY &, const XY &, const XY &, const XY &); +} // namespace detail + +template <typename Type, typename From> +template <RoadDirection D> +double Points<Type, From>::GetCurvature(double s) const // NOLINT(bugprone-easily-swappable-parameters) +{ + if (s < get<S>(this->front()) || s > get<S>(this->back())) + { + return std::numeric_limits<double>::quiet_NaN(); + } + if (this->size() <= 2) + { + return .0; + } + auto edge{GetNearestEdge<D>(s)}; + if (std::prev(edge) == begin<D>()) // No prior edge + { + if (this->size() > 3) // But with successor edge + { + const double ratio{(s - get<S>(*std::prev(edge))) / (get<S>(*std::next(edge, 2)) - get<S>(*std::prev(edge)))}; + return detail::GetCurvature(ratio, *std::prev(edge), *edge, *std::next(edge), *std::next(edge, 2)); + } + return detail::GetCurvature(*std::prev(edge), *edge, *std::next(edge)); + } + if (std::next(edge) == end<D>()) // No successor edge + { + if (this->size() > 3) // But with prior edge + { + const double ratio{(s - get<S>(*std::prev(edge, 3))) / (get<S>(*edge) - get<S>(*std::prev(edge, 3)))}; + return detail::GetCurvature(ratio, *std::prev(edge, 3), *std::prev(edge, 2), *std::prev(edge), *edge); + } + return detail::GetCurvature(*std::prev(edge, 2), *std::prev(edge), *edge); + } + const double ratio{(s - get<S>(*std::prev(edge, 2))) / (get<S>(*std::next(edge)) - get<S>(*std::prev(edge, 2)))}; + return detail::GetCurvature(ratio, *std::prev(edge, 2), *std::prev(edge), *edge, *std::next(edge)); +} + +template <typename Type, typename From> +double Points<Type, From>::GetCurvature(double s, RoadDirection direction) const +{ + return direction == RoadDirection::Upstream ? GetCurvature<RoadDirection::Upstream>(s) : GetCurvature<RoadDirection::Downstream>(s); +} + +template <typename Type, typename From> +template <RoadDirection D> +double Points<Type, From>::GetCurvatureFromLocalTangents(double s, double epsilon) const // NOLINT(bugprone-easily-swappable-parameters) +{ + if (s < get<S>(this->front()) || s > get<S>(this->back())) + { + return std::numeric_limits<double>::quiet_NaN(); + } + if (this->size() <= 2) + { + return .0; + } + const auto edgeEnd{GetNearestEdge<D>(s)}; + const auto edgeStart{std::prev(edgeEnd)}; + const XY edge{*edgeEnd - *edgeStart}; + const double halfEdgeLength{edge.Length() * 0.5}; + if (halfEdgeLength <= epsilon) + { + return .0; + } + double startCurvature{.0}; + if (edgeStart != begin<D>()) + { + const XY priorEdge{*edgeStart - *std::prev(edgeStart)}; + const double deltaAngle{std::atan2(priorEdge.Cross(edge), priorEdge.Dot(edge))}; + const double length{halfEdgeLength + priorEdge.Length() * 0.5}; + startCurvature = deltaAngle / length; + } + + double endCurvature{.0}; + if (std::next(edgeEnd) != end<D>()) + { + const XY nextEdge{*std::next(edgeEnd) - *edgeEnd}; + const double deltaAngle{std::atan2(edge.Cross(nextEdge), edge.Dot(nextEdge))}; + const double length{halfEdgeLength + nextEdge.Length() * 0.5}; + endCurvature = deltaAngle / length; + } + + const double startS{get<S>(*edgeStart)}; + const double endS{get<S>(*edgeEnd)}; + if (std::abs(endS - startS) < epsilon) + { + return .0; + } + const double ratio{(s - startS) / (endS - startS)}; + return Interpolate(startCurvature, endCurvature, ratio); +} + template <typename Type, typename From> std::ostream &operator<<(std::ostream &os, const Points<Type, From> &line) { diff --git a/include/OsiQueryLibrary/Object/StationaryObject.h b/include/OsiQueryLibrary/Object/StationaryObject.h index 370d147dd1d7cd5ed9a131bc4ae9400a89b7d976..c096a45a623b962b0eb311c8571ae22c8e12e40d 100644 --- a/include/OsiQueryLibrary/Object/StationaryObject.h +++ b/include/OsiQueryLibrary/Object/StationaryObject.h @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -21,13 +21,18 @@ namespace osiql { //! \brief A stationary object that will never move. struct StationaryObject : Object<osi3::StationaryObject>, Collidable<StationaryObject> { + using Collidable<StationaryObject>::GetId; + template <typename Base, OSIQL_REQUIRES(std::is_constructible_v<Object<osi3::StationaryObject>, Base>)> constexpr StationaryObject(Base &&base) : // NOLINT(bugprone-forwarding-reference-overload) Object<osi3::StationaryObject>{std::forward<Base>(base)}, Collidable<StationaryObject>{Object<osi3::StationaryObject>::GetId()} { } - using Collidable<StationaryObject>::GetId; + //! Replaces the handle of this object + //! + //! \return This object after having replaced its handle + StationaryObject &operator=(const osi3::StationaryObject &); }; std::ostream &operator<<(std::ostream &, const StationaryObject &); diff --git a/include/OsiQueryLibrary/Point/XY.tpp b/include/OsiQueryLibrary/Point/XY.tpp index 9ed0579b7a84e320233261e80d4556cd1cfe2e51..2b68ed6aa8f6a6a2c89d09491b15a4773b33101d 100644 --- a/include/OsiQueryLibrary/Point/XY.tpp +++ b/include/OsiQueryLibrary/Point/XY.tpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -12,6 +12,7 @@ #include "XY.h" #include "Coordinates.tpp" +#include "OsiQueryLibrary/Types/Geometry.h" #include "OsiQueryLibrary/Utility/XYZ.h" #include "Vector.tpp" @@ -107,7 +108,7 @@ XY XY::GetPathTo(const A &a, const B &b, double epsilon) const if (const double length{ab.SquaredLength()}; length > epsilon) { const double ratio{std::max(0.0, std::min(1.0, ac.Dot(ab) / length))}; - const XY projection{a * (1.0 - ratio) + b * ratio}; + const XY projection{Interpolate(as<XY>(a), as<XY>(b), ratio)}; return *this - projection; } return ac; @@ -192,7 +193,7 @@ ST XY::GetST(const A &a, const B &b) const } else { - t += extract<Side::Left>(a) * (1.0 - ratio) + extract<Side::Left>(b) * ratio; + t += Interpolate(extract<Side::Left>(a), extract<Side::Left>(b), ratio); } return {s, t}; } diff --git a/include/OsiQueryLibrary/Routing/Route.h b/include/OsiQueryLibrary/Routing/Route.h index 14e739d7364d5179908d4b618c05b266cb279ea4..f104bc51de7f8354b9bebe73e2f37749e30acc52 100644 --- a/include/OsiQueryLibrary/Routing/Route.h +++ b/include/OsiQueryLibrary/Routing/Route.h @@ -35,9 +35,9 @@ using StoredNode = std::shared_ptr<const Node<D, Scope>>; //! \brief A local origin point, destination point and //! a chain of nodes, one for each traversed road template <LaneDirection D = LaneDirection::Forward, typename Scope = Road> -struct Route : public Iterable<std::vector<StoredNode<D, Scope>>, Route<D>, LaneDirection> +struct Route : public Iterable<std::vector<StoredNode<D, Scope>>, Route<D, Scope>, LaneDirection> { - using Base = Iterable<std::vector<StoredNode<D, Scope>>, Route<D>, LaneDirection>; + using Base = Iterable<std::vector<StoredNode<D, Scope>>, Route<D, Scope>, LaneDirection>; static constexpr LaneDirection direction = D; @@ -282,7 +282,7 @@ struct Route : public Iterable<std::vector<StoredNode<D, Scope>>, Route<D>, Lane std::deque<std::shared_ptr<const Node<D, Lane>>> GetLaneNodes(const Lane &) const; template <LaneDirection Toward = LaneDirection::Any> - std::deque<std::shared_ptr<const Node<D, Lane>>> GetLaneNodes(const Lane &, ConstIterator<Route<D>>) const; + std::deque<std::shared_ptr<const Node<D, Lane>>> GetLaneNodes(const Lane &, ConstIterator<Route<D, Road>>) const; Point<> origin; Point<> destination; diff --git a/include/OsiQueryLibrary/SensorView.h b/include/OsiQueryLibrary/SensorView.h index 25514f3e23c7449022db89271bfd6f1ab59dd245..f432a46a9937677cbfec2795ef1e8627013c30fb 100644 --- a/include/OsiQueryLibrary/SensorView.h +++ b/include/OsiQueryLibrary/SensorView.h @@ -81,7 +81,7 @@ struct SensorView : Wrapper<osi3::SensorView> //! Returns the route of this SensorView's VehicleData. HasRoute must be true. //! - //! @return Returns the route of this SensorView's VehicleData. + //! \return Returns the route of this SensorView's VehicleData. const Route<> &GetRoute() const; std::shared_ptr<World> world; diff --git a/include/OsiQueryLibrary/Street/Lane/BoundaryEnclosure.h b/include/OsiQueryLibrary/Street/Lane/BoundaryEnclosure.h index cf4dc5934622b0b9d029b50bd601c29f1209dd8e..d8bd509222c0d99cd20c9cab420f15b8df7daa91 100644 --- a/include/OsiQueryLibrary/Street/Lane/BoundaryEnclosure.h +++ b/include/OsiQueryLibrary/Street/Lane/BoundaryEnclosure.h @@ -232,7 +232,7 @@ struct BoundaryEnclosure : Adjoinable<Lane>, Area<XY> //! Returns the global angle of this lane's centerline at the given s-coordinate. //! - //! \param s + //! \param s s-coordinate at which the angle is computed //! \param LaneDirection Forward or Backward //! \return double template <auto Towards = LaneDirection::Forward> @@ -240,27 +240,31 @@ struct BoundaryEnclosure : Adjoinable<Lane>, Area<XY> //! Returns the global angle of this lane's centerline at the given s-coordinate. //! - //! \tparam Towards LaneDirection or Downstream - //! \param Towards The traversal direction/orientation - //! \param s + //! \tparam Towards LaneDirection or Downstream + //! \param Towards The direction in which this lane is traversed. If the s-coordinate is at the start of + //! one edge and at the end of another, the angle of the latter edge is preferred. + //! \param s s-coordinate at which the angle is computed //! \return double template <typename Towards> double GetAngle(Towards, double s) const; //! Returns the average of the curvatures of this lane's left and right boundary at the given s-coordinate. + //! If the s-coordinate lies outside the range of this lane, NaN is returned. //! - //! \param s - //! \param LaneDirection Forward or Backward - //! \return double + //! \tparam LaneDirection Forward or Backward + //! \param s s-coordinate at which the curvature shall be interpolated + //! \return Curvature at the given s-coordinate template <auto Towards = LaneDirection::Forward> double GetCurvature(double s) const; //! Returns the average of the curvatures of this lane's left and right boundary at the given s-coordinate. + //! If the s-coordinate lies outside the range of this lane, NaN is returned. //! - //! \tparam RoadDirection Downstream or Upstream - //! \param s - //! \return double - template <typename Towards> + //! \tparam Towards LaneDirection or RoadDirection. The direction in which this lane is traversed. + //! A leftward/counter-clockwise curvature is positive, a rightward/clockwise curvature is negative. + //! \param s s-coordinate at which the curvature is computed + //! \return Curvature at the given s-coordinate + template <typename Towards = LaneDirection> double GetCurvature(Towards, double s) const; //! Returns the t-coordinate of this lane's centerline at the given s-coordinate. If there is no centerline diff --git a/include/OsiQueryLibrary/Street/Lane/BoundaryEnclosure.tpp b/include/OsiQueryLibrary/Street/Lane/BoundaryEnclosure.tpp index 1f1805fc32fa4dd18ee8c30528820467c702c4a0..7745cd4b3cf040929c4efb8a12956292f508a91b 100644 --- a/include/OsiQueryLibrary/Street/Lane/BoundaryEnclosure.tpp +++ b/include/OsiQueryLibrary/Street/Lane/BoundaryEnclosure.tpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2023-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -16,6 +16,7 @@ #include "Adjoinable.tpp" #include "OsiQueryLibrary/Point/XY.tpp" #include "OsiQueryLibrary/Street/BoundaryChain.tpp" +#include "OsiQueryLibrary/Types/Geometry.h" namespace osiql::detail { template <typename Lane> @@ -238,7 +239,7 @@ double BoundaryEnclosure<Lane>::GetAngle(double s) const { const double leftLaneAngle{GetBoundary<Side::Left, Towards>(s).template GetAngle<Towards>(s)}; const double rightLaneAngle{GetBoundary<Side::Right, Towards>(s).template GetAngle<Towards>(s)}; - return 0.5 * leftLaneAngle + 0.5 * rightLaneAngle; // NOLINT(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) + return Average(leftLaneAngle, rightLaneAngle); } else { @@ -257,27 +258,24 @@ template <typename Lane> template <auto Towards> double BoundaryEnclosure<Lane>::GetCurvature(double s) const { + static_assert(std::is_same_v<decltype(Towards), LaneDirection> || std::is_same_v<decltype(Towards), RoadDirection>); if constexpr (std::is_same_v<decltype(Towards), LaneDirection>) { return GetCurvature(this->GetDirection(Towards), s); } - else if constexpr (std::is_same_v<decltype(Towards), RoadDirection>) + else { const double leftCurvature{GetBoundary<Side::Left, Towards>(s).template GetCurvature<Towards>(s)}; const double rightCurvature{GetBoundary<Side::Right, Towards>(s).template GetCurvature<Towards>(s)}; - return 0.5 * leftCurvature + 0.5 * rightCurvature; // NOLINT(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) - } - else - { - static_assert(always_false<decltype(Towards)>, "Not supported"); + return Average(leftCurvature, rightCurvature); } } template <typename Lane> -template <typename Orientation> -double BoundaryEnclosure<Lane>::GetCurvature(Orientation toward, double s) const +template <typename Toward> +double BoundaryEnclosure<Lane>::GetCurvature(Toward toward, double s) const { - return IsInverse(toward) ? GetCurvature<!Default<Orientation>>(s) : GetCurvature<Default<Orientation>>(s); + return IsInverse(toward) ? GetCurvature<!Default<Toward>>(s) : GetCurvature<Default<Toward>>(s); } template <typename Lane> @@ -285,7 +283,7 @@ double BoundaryEnclosure<Lane>::GetCenterlineT(double s) const { const double leftT{GetBoundary<Side::Left>(s).GetT(s)}; const double rightT{GetBoundary<Side::Right>(s).GetT(s)}; - return 0.5 * leftT + 0.5 * rightT; // NOLINT(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) + return Average(leftT, rightT); } template <typename Lane> diff --git a/include/OsiQueryLibrary/Street/ReferenceLine.h b/include/OsiQueryLibrary/Street/ReferenceLine.h index a38069a0e5ab60217614ef8e3322888ddda74998..0e3e9f6d8486c155259b47af895817aec4e5fb25 100644 --- a/include/OsiQueryLibrary/Street/ReferenceLine.h +++ b/include/OsiQueryLibrary/Street/ReferenceLine.h @@ -84,6 +84,11 @@ struct ReferenceLine : Identifiable<osi3::ReferenceLine>, Points<Container<osi3: //! \return Type The type of this reference line or the fallback value if it has no type. constexpr Type GetType(Type fallback = Type::Polyline) const; + //! Returns the length of this reference line + //! + //! \return s-coordinate of the last point of this reference line + double GetLength() const; + //! Returns the local st-coordinate representation of the given global point //! according to the coordinate system defined by this reference line. //! diff --git a/include/OsiQueryLibrary/Trait/Collection.h b/include/OsiQueryLibrary/Trait/Collection.h index 3e0a77162fbc0f59ac9790ef64076f2a6d671eef..84d1fb6350ba9bb8e0bd8bc6f98932efe0058393 100644 --- a/include/OsiQueryLibrary/Trait/Collection.h +++ b/include/OsiQueryLibrary/Trait/Collection.h @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -18,6 +18,7 @@ #include "Id.h" #include "OsiQueryLibrary/Component/Iterable.h" #include "OsiQueryLibrary/Object/MovingObject.hpp" +#include "OsiQueryLibrary/Object/StationaryObject.hpp" #include "OsiQueryLibrary/Types/Container.h" namespace osiql { @@ -53,12 +54,20 @@ struct Collection<MovingObject> using type = HashMap<std::unique_ptr<MovingObject>>; }; +//! \brief Type trait specifying how stationary objects are stored in osiql::GroundTruth +template <> +struct Collection<StationaryObject> +{ + //! \brief How stationary objects are stored in osiql::GroundTruth + using type = HashMap<std::unique_ptr<StationaryObject>>; +}; + //! \brief Type trait specifying how light bulbs are stored in osiql::GroundTruth template <> struct Collection<LightBulb> { //! \brief How light bulbs are stored in osiql::GroundTruth - using type = std::map<Id, TrafficLight *>; + using type = HashMap<TrafficLight *>; }; //! \brief Type trait specifying how roads are stored in osiql::GroundTruth diff --git a/include/OsiQueryLibrary/Trait/Handle.h b/include/OsiQueryLibrary/Trait/Handle.h index 5a50635fc9b40ade55d3eea33339468363a80f27..8e2d45a0a3a6a351f21e38856c08e82d7b5e1b4f 100644 --- a/include/OsiQueryLibrary/Trait/Handle.h +++ b/include/OsiQueryLibrary/Trait/Handle.h @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2023-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -32,6 +32,13 @@ struct Handle<osi3::MovingObject> //! \brief The full non-decayed type of the wrapper object's handle using type = const osi3::MovingObject *; }; + +template <> +struct Handle<osi3::StationaryObject> +{ + //! \brief The full non-decayed type of the wrapper object's handle + using type = const osi3::StationaryObject *; +}; } // namespace trait //! OSI object handle. Usually a reference_wrapper diff --git a/include/OsiQueryLibrary/Types/Circle.h b/include/OsiQueryLibrary/Types/Circle.h new file mode 100644 index 0000000000000000000000000000000000000000..abc57954331518c55595b0021c1141a4b59b9048 --- /dev/null +++ b/include/OsiQueryLibrary/Types/Circle.h @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +#pragma once + +#include <iostream> +#include <optional> + +#include "OsiQueryLibrary/Point/XY.h" +#include "OsiQueryLibrary/Types/Common.h" + +namespace osiql { +//! \brief Circle represented by a global 2d xy-coordinate pair center and a radius +struct Circle +{ + //! Constructs a circle from its components + //! + //! \param center The global center of the circle + //! \param radius The radius of the circle + constexpr Circle(XY center, double radius); + + //! Creates a circle from three unique points on its perimeter + //! + //! \param a First point on the circle + //! \param b Second point on the circle + //! \param c Third point on the circle + Circle(const XY &a, const XY &b, const XY &c); + + //! Returns the curvature of this circle's perimeter, which is the reciprocal of its radius. + //! + //! \return Curvature of this circle's perimeter + //! \note If the radius is 0, a curvature of 0 is returned + constexpr double GetCurvature() const; + + //! \brief Center of this circle + XY center; + + //! \brief Distance from the circle's center to its perimeter + double radius; +}; + +//! Returns the center of the circle on which the three given points lie +//! +//! \param XY First point on the circle +//! \param XY Second point on the circle +//! \param XY Third point on the circle +//! \param epsilon Squared rounding error to test for duplicate points. If points are sufficiently +//! close to one another, returns 0. The first and third point are assumed to be different. +//! \return nullopt if the points are collinear, otherwise the center of the circle +std::optional<XY> GetCenterOf(const XY &, const XY &, const XY &, double epsilon = EPSILON); + +std::optional<XY> GetTangentCenterOf(const XY &a, const XY &b, const XY &c, double epsilon = EPSILON); + +std::ostream &operator<<(std::ostream &, const Circle &); +} // namespace osiql + +namespace osiql { +constexpr Circle::Circle(XY center, double radius) : + center{std::move(center)}, radius{radius} +{ +} + +constexpr double Circle::GetCurvature() const +{ + return radius == .0 ? .0 : (1.0 / radius); +} +} // namespace osiql diff --git a/include/OsiQueryLibrary/Types/Geometry.h b/include/OsiQueryLibrary/Types/Geometry.h index 173d6a85123a59b60dfb5113a09e91e1635144e2..89ca2624ae828c1aebf7de8b20e625cfc343ccdb 100644 --- a/include/OsiQueryLibrary/Types/Geometry.h +++ b/include/OsiQueryLibrary/Types/Geometry.h @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -34,8 +34,26 @@ struct Line double GetRatio(const XY &) const; }; -std::ostream & -operator<<(std::ostream &, const Line &); +//! Returns the linear interpolation of two inputs at a given value, ergo "A + (B - A) * value". +//! +//! \tparam A Type that supports addition with type B and scalar multiplication +//! \tparam B Type that supports addition with type A and scalar multiplication +//! \param A "Start" of the interpolation. At value=0, A will be returned +//! \param B "End" of the interpolation. At value=1, B will be returned +//! \param value Factor affecting the weighting of each input. It is not restricted to [0, 1]. +//! \return The interpolated value between A and B +template <typename A, typename B> +constexpr auto Interpolate(const A &, const B &, double value); + +//! Returns the arithmetic average of the two given inputs +//! +//! \tparam A Type that supports addition with type B and scalar multiplication +//! \tparam B Type that supports addition with type A and scalar multiplication +//! \return The mean between A and B +template <typename A, typename B> +constexpr auto Average(const A &, const B &); + +std::ostream &operator<<(std::ostream &, const Line &); //! Returns the equivalent to the given angle within the range (-pi, pi] //! @@ -54,4 +72,16 @@ constexpr double WrapAngle(double angle) noexcept return result + twoPi; return result; } + +template <typename A, typename B> +constexpr auto Interpolate(const A &a, const B &b, double value) +{ + return a + value * (b - a); +} + +template <typename A, typename B> +constexpr auto Average(const A &a, const B &b) +{ + return Interpolate(a, b, .5); +} } // namespace osiql diff --git a/include/OsiQueryLibrary/Types/Interval.h b/include/OsiQueryLibrary/Types/Interval.h index 2ccf3859902416a7ab1de69c5c925f59bd3a5b11..f8c3e8c7199846c4556f9ef775dabbfb81f7dec4 100644 --- a/include/OsiQueryLibrary/Types/Interval.h +++ b/include/OsiQueryLibrary/Types/Interval.h @@ -11,6 +11,7 @@ #include <iostream> #include <limits> +#include <type_traits> #include "OsiQueryLibrary/Utility/Common.h" @@ -63,10 +64,35 @@ struct Interval template <typename Type> Interval(Type &&, Type &&) -> Interval<Type>; -constexpr double GetDistanceBetween(const Interval<double> &, const Interval<double> &); -constexpr double GetDistanceBetween(const Interval<double> &, double); -constexpr double GetDistanceBetween(double, const Interval<double> &); -constexpr double GetDistanceBetween(double, double); +template <typename A, typename B, OSIQL_REQUIRES(std::is_arithmetic_v<A> &&std::is_arithmetic_v<B>)> +constexpr double GetDistanceBetween(const Interval<A> &a, const Interval<B> &b) +{ + if (b.min > a.max) { return b.min - a.max; } + if (a.min > b.max) { return b.max - a.min; } + return .0; +} + +template <typename A, typename B, OSIQL_REQUIRES(std::is_arithmetic_v<A> &&std::is_arithmetic_v<B>)> +constexpr double GetDistanceBetween(const Interval<A> &a, B &&b) +{ + if (b > a.max) { return b - a.max; } + if (b < a.min) { return b - a.min; } + return .0; +} + +template <typename A, typename B, OSIQL_REQUIRES(std::is_arithmetic_v<A> &&std::is_arithmetic_v<B>)> +constexpr double GetDistanceBetween(A &&a, const Interval<B> &b) +{ + if (a < b.min) { return b.min - a; } + if (a > b.max) { return b.max - a; } + return .0; +} + +template <typename A, typename B, OSIQL_REQUIRES(std::is_arithmetic_v<A> &&std::is_arithmetic_v<B>)> +constexpr double GetDistanceBetween(A &&a, B &&b) +{ + return b - a; +} template <typename Type> constexpr bool operator==(const Interval<Type> &, const Interval<Type> &); diff --git a/include/OsiQueryLibrary/Types/Interval.tpp b/include/OsiQueryLibrary/Types/Interval.tpp index 5be1148c2c8fdfcb047a6e7e5ab5b65c841fb5ae..882539c23966fccb7b4baa2c7188b48741ec5137 100644 --- a/include/OsiQueryLibrary/Types/Interval.tpp +++ b/include/OsiQueryLibrary/Types/Interval.tpp @@ -27,26 +27,6 @@ constexpr Interval<Type>::Interval(const OtherType &u) : { } -constexpr double GetDistanceBetween(const Interval<double> &a, const Interval<double> &b) -{ - return std::max({0.0, a.min - b.max, b.min - a.max}); -} - -constexpr double GetDistanceBetween(const Interval<double> &a, double b) -{ - return std::max({0.0, a.min - b, b - a.max}); -} - -constexpr double GetDistanceBetween(double a, const Interval<double> &b) -{ - return std::max({0.0, a - b.max, b.min - a}); -} - -constexpr double GetDistanceBetween(double a, double b) -{ - return std::abs(b - a); -} - template <typename Type> constexpr bool operator==(const Interval<Type> &lhs, const Interval<Type> &rhs) { diff --git a/include/OsiQueryLibrary/Utility/Compare.tpp b/include/OsiQueryLibrary/Utility/Compare.tpp index 91b4ff47db0c1f7b7189356e56e14c2de96066ef..1805f3534c0f55e757f3f21d45c7699c10864ed3 100644 --- a/include/OsiQueryLibrary/Utility/Compare.tpp +++ b/include/OsiQueryLibrary/Utility/Compare.tpp @@ -60,7 +60,7 @@ constexpr bool StableCompare<TypeA, TypeB, Comparator>::operator()(A &&a, B &&b) template <typename A> \ constexpr bool NAME<TypeA, TypeB, Comparator>::SUBNAME<B>::operator()(A &&a) const \ { \ - return NAME<TypeA, Forward, Comparator>{}(std::forward<A>(a), b); \ + return NAME<TypeA, TypeB, Comparator>{}(std::forward<A>(a), b); \ } OSIQL_DEFINE_COMPARISON_TO_OPERATOR(Compare, Than) diff --git a/include/OsiQueryLibrary/Utility/Has.h b/include/OsiQueryLibrary/Utility/Has.h index a1e40fd86c3b661691651d5e1a95753f90a71e64..b6bfe47ef01cb58a4016cdbca3a7d2cd3ff4f72c 100644 --- a/include/OsiQueryLibrary/Utility/Has.h +++ b/include/OsiQueryLibrary/Utility/Has.h @@ -46,9 +46,9 @@ namespace osiql { namespace detail { //! Wrapper of a type that can return its member using Get<Type>() //! -//! @tparam Type The decayed type of the stored member. Specified in order to -//! specialization of this class for sets of types that shared the same decayed type -//! @tparam StoredType The exact type of the stored member +//! \tparam Type The decayed type of the stored member. Specified in order to specialize +//! this class for sets of types that shared the same decayed type +//! \tparam StoredType The exact type of the stored member template <typename Type, typename StoredType = Type> class Has { diff --git a/include/OsiQueryLibrary/World.h b/include/OsiQueryLibrary/World.h index ee78247415e9cac7bdf4d07a51899652b1ea6775..571d102f37962370fbb8860551bd9b9217f67ba8 100644 --- a/include/OsiQueryLibrary/World.h +++ b/include/OsiQueryLibrary/World.h @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -50,17 +50,20 @@ bool HasIntersections(const PolygonA &, const PolygonB &); template <typename Type> using RTree = boost::geometry::index::rtree<Type *, boost::geometry::index::quadratic<8, 4>, osiql::Get<Bounds<XY>>>; -//! \brief A World offers location world methods, xy- <-> st-coordinate conversion and bidirectional road to lane to object mappings. +//! \brief A World is a spatially indexed, updatable GroundTruth. struct World : GroundTruth { //! Initialize the world with a fully defined ground truth. World(const osi3::GroundTruth &); - //! Updates (includes adding and removing) all moving objects given the - //! collection of object handles. Recomputes all lane & road overlaps + //! Replaces all objects given the collection of object handles. + //! Recomputes all shapes, bounds & lane/road overlaps //! - //! \param allObjects Container of osi3::MovingObject sorted by id - void Update(const Container<osi3::MovingObject> &allObjects); + //! \param Container Container of osi3::MovingObjects + template <typename Type> + void UpdateAll(const Container<typename Type::Handle> &); + + [[deprecated("Prefer 'UpdateAll<MovingObject>'")]] void Update(const Container<osi3::MovingObject> &); //! Returns the lane with the given id or nullptr if none exists //! @@ -223,7 +226,21 @@ struct World : GroundTruth //! \return std::ostream& friend std::ostream &operator<<(std::ostream &, const World &); + //! Returns the boost rectangle tree used to efficiently localize object of the given template type + //! + //! \tparam Type Lane, Road, MovingObject, StationaryObject, TrafficLight or TrafficSign + //! \return Rectangle tree of objects of the given template type + template <typename Type> + constexpr const RTree<Type> &GetRTree() const; + private: + //! Returns the boost rectangle tree used to efficiently localize object of the given template type + //! + //! \tparam Type Lane, Road, MovingObject, StationaryObject, TrafficLight or TrafficSign + //! \return Rectangle tree of objects of the given template type + template <typename Type> + constexpr RTree<Type> &GetRTree(); + template <typename Type = Lane> std::vector<Overlap<Type>> InternalFindOverlapping(const Area<XY> &); @@ -233,19 +250,39 @@ private: //! \return std::vector<const Lane*> Empty if no lane contains the given point std::vector<const Lane *> GetLanesContaining(const XY &) const; - //! Updates the global shape, local positions and lane intersection bounds of the given object. + //! Updates the local positions and lane intersection bounds of the given object. //! //! \param object template <typename ObjectType> - void UpdateObject(ObjectType &object); + void UpdateOverlaps(ObjectType &object); - template <typename Type> - constexpr const RTree<Type> &GetRTree() const; + //! Removes all lane and road overlaps. + //! + //! \tparam ObjectType MovingObject or StationaryObject + template <typename ObjectType> + void ClearOverlaps(); + + //! Updates and all objects of the given template type. Objects not in the input are removed. + //! This involves computing their global shape, global bounds, as well as lane and road intersections. + //! + //! \tparam ObjectType MovingObject or StationaryObject + //! \param handles OSI repated pointer field of object handles + //! \return Collection of ids of objects not among the given input + template <typename ObjectType> + std::unordered_set<Id> AddAll(const Container<typename ObjectType::Handle> &handles); + //! Removes objects with the given ids. + //! + //! \tparam Type MovingObject or StationaryObject template <typename Type> - constexpr RTree<Type> &GetRTree(); + void RemoveObjects(const std::unordered_set<Id> &); + + //! Clears and refills the RTree of the given template type + //! + //! \tparam ObjectType Lane, Road, MovingObject, StationaryObject, TrafficLight or TrafficSign + template <typename ObjectType> + void UpdateRTree(); -private: template <LaneDirection D, typename Scope, typename Origin, typename Destination> std::optional<Route<D, Scope>> GetRouteBetweenGlobalPoints(const Origin &, const Destination &) const; @@ -264,7 +301,6 @@ private: template <LaneDirection D, typename Scope, typename PointIterator> std::optional<Route<D, Scope>> GetRouteFromRangeOfLocalPoints(PointIterator first, PointIterator pastLast) const; -public: RTree<Lane> laneRTree; RTree<Road> roadRTree; RTree<MovingObject> movingObjectRTree; diff --git a/include/OsiQueryLibrary/World.tpp b/include/OsiQueryLibrary/World.tpp index 2ffdba971d719387417dd5474e5ce09aeda4ef46..1eb998474edbec3323ae6d1168ac434b015746d2 100644 --- a/include/OsiQueryLibrary/World.tpp +++ b/include/OsiQueryLibrary/World.tpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -20,9 +20,8 @@ namespace osiql { template <typename ObjectType> -void World::UpdateObject(ObjectType &object) +void World::UpdateOverlaps(ObjectType &object) { - object.UpdateShapeAndBounds(); object.template SetOverlaps<Road>(InternalFindOverlapping<Road>(object)); // TODO: Performance - Stop computing object centers object.positions.clear(); @@ -34,6 +33,69 @@ void World::UpdateObject(ObjectType &object) object.template SetOverlaps<Lane>(InternalFindOverlapping<Lane>(object)); } +template <typename Type> +void World::ClearOverlaps() +{ + for (auto &[id, lane] : lanes) + { + lane.template GetOverlapping<Type>().clear(); + } + for (auto &[id, road] : roads) + { + road.template GetOverlapping<Type>().clear(); + } +} + +template <typename Type> +std::unordered_set<Id> World::AddAll(const Container<typename Type::Handle> &objects) +{ + std::unordered_set<Id> leftovers; + std::transform(GetAll<Type>().begin(), GetAll<Type>().end(), std::inserter(leftovers, leftovers.end()), osiql::Get<Id>{}); + for (const auto &object : objects) + { + if (const auto match{GetAll<Type>().find(get<Id>(object))}; match != GetAll<Type>().end()) + { + auto &wrapper{get<Type>(*match)}; + wrapper = object; + UpdateOverlaps<Type>(wrapper); + leftovers.erase(get<Id>(object)); + } + else + { + UpdateOverlaps<Type>(Emplace<Type>(GetAll<Type>(), object)); + } + } + return leftovers; +} + +template <typename Type> +void World::UpdateRTree() +{ + GetRTree<Type>().clear(); + for (auto &item : GetAll<Type>()) + { + GetRTree<Type>().insert(&get<Type>(item)); + } +} + +template <typename Type> +void World::RemoveObjects(const std::unordered_set<Id> &ids) +{ + for (const auto &id : ids) + { + GetAll<Type>().erase(get<Id>(id)); + } +} + +template <typename Type> +void World::UpdateAll(const Container<typename Type::Handle> &objects) +{ + ClearOverlaps<Type>(); + auto danglingIds{AddAll<Type>(objects)}; + RemoveObjects<Type>(danglingIds); + UpdateRTree<Type>(); +} + template <typename GlobalPoint> std::vector<Localization<GlobalPoint>> World::Localize(const GlobalPoint &point) const { diff --git a/include/OsiQueryLibrary/osiql.h b/include/OsiQueryLibrary/osiql.h index 9c1cc43773892385b896b3c966dc31d061a494e3..8ce51e964e638b1f138216082762baf8c25b08a4 100644 --- a/include/OsiQueryLibrary/osiql.h +++ b/include/OsiQueryLibrary/osiql.h @@ -80,6 +80,7 @@ #include "OsiQueryLibrary/Types/Enum/Side.tpp" // #include "OsiQueryLibrary/Types/Bounds.h" +#include "OsiQueryLibrary/Types/Circle.h" #include "OsiQueryLibrary/Types/Common.h" #include "OsiQueryLibrary/Types/Constants.h" #include "OsiQueryLibrary/Types/Container.h" diff --git a/src/Component/Points.cpp b/src/Component/Points.cpp new file mode 100644 index 0000000000000000000000000000000000000000..96dbb8ebddb7297fc4e09664b42d130d87081b25 --- /dev/null +++ b/src/Component/Points.cpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +#include "OsiQueryLibrary/Component/Points.tpp" + +#include "OsiQueryLibrary/Types/Circle.h" + +namespace osiql::detail { +double GetCurvature(const XY &a, const XY &b, const XY &c) +{ + if (std::optional<XY> center{GetCenterOf(a, b, c)}; center.has_value()) + { + const double squaredLength{(center - a).SquaredLength()}; + if (squaredLength < 0.5) { return .0; } + const double curvature{1.0 / std::sqrt(squaredLength)}; + return center.value().GetSide(a, b) == Side::Left ? curvature : -curvature; + } + return .0; +} + +double GetCurvature(double ratio, const XY &a, const XY &b, const XY &c, const XY &d) // NOLINT(readability-identifier-length) +{ + return Interpolate(GetCurvature(a, b, c), GetCurvature(b, c, d), ratio); +} +} // namespace osiql::detail diff --git a/src/GroundTruth.cpp b/src/GroundTruth.cpp index 658b8c223523ed62daccaffbe4faf8fb8ce413e7..d2e1ecb1a54983788b21854d67781f32d25d3de5 100644 --- a/src/GroundTruth.cpp +++ b/src/GroundTruth.cpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -43,13 +43,13 @@ GroundTruth::~GroundTruth() // When a moving object is destroyed, it detaches itself from its touched lanes. Lanes shouldn't // be modified directly prior to destruction and object destruction fails if its connected lanes // no longer exist, so the best option is to prevent objects from detaching themselves in advance: - for (auto &[id, object] : movingObjects) + for (auto &entry : movingObjects) { - object->positions.clear(); + get<MovingObject>(entry).positions.clear(); } - for (auto &object : stationaryObjects) + for (auto &entry : stationaryObjects) { - object.positions.clear(); + get<StationaryObject>(entry).positions.clear(); } } diff --git a/src/Object/StationaryObject.cpp b/src/Object/StationaryObject.cpp index f96bbc3f57090ac38baace7fc77287cc34e16839..ca765a187a12782e21cd6ee7203cbcd2a77b979d 100644 --- a/src/Object/StationaryObject.cpp +++ b/src/Object/StationaryObject.cpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -12,10 +12,20 @@ #include "OsiQueryLibrary/Object/Collidable.tpp" #include "OsiQueryLibrary/Object/Object.tpp" #include "OsiQueryLibrary/Utility/Common.tpp" +#include "OsiQueryLibrary/Utility/Name.h" namespace osiql { +StationaryObject &StationaryObject::operator=(const osi3::StationaryObject &handle) +{ + this->handle = &handle; + OutdateTranslation(); + OutdateRotation(); + UpdateShapeAndBounds(); + return *this; +} + std::ostream &operator<<(std::ostream &os, const StationaryObject &object) { - return os << "Static object " << object.GetId() << " [" << object.GetShape() << ']'; + return os << name<StationaryObject> << ' ' << object.GetId() << " [" << object.GetShape() << ']'; } } // namespace osiql diff --git a/src/Street/ReferenceLine.cpp b/src/Street/ReferenceLine.cpp index 4352cfa684d0e4c3c6448cc8247cb84248121fc4..8567bbb1403b459cab5b1052042da67feccdb068 100644 --- a/src/Street/ReferenceLine.cpp +++ b/src/Street/ReferenceLine.cpp @@ -61,7 +61,7 @@ XY ReferenceLine::GetXY(const ST &point) const --it; } const double ratio{(point.s - std::prev(it)->s_position()) / (it->s_position() - std::prev(it)->s_position())}; - const XY pointOnEdge{XY{std::prev(it)->world_position()} * (1.0 - ratio) + XY{it->world_position()} * ratio}; + const XY pointOnEdge{Interpolate(XY{std::prev(it)->world_position()}, XY{it->world_position()}, ratio)}; const size_t edgeIndex{static_cast<size_t>(std::distance(begin(), it) - 1)}; const std::optional<XY> &vanishingPoint{GetLongitudinalAxisIntersection(edgeIndex)}; if (vanishingPoint.has_value()) @@ -95,7 +95,7 @@ ST ReferenceLine::LocalizeUsingTAxes(const XY &input) const const XY intersection{Line{vanishingPoint.value(), input}.GetIntersection(edge).value()}; const double ratio{edge.GetRatio(intersection)}; return ST{ - extract<RoadDirection::Downstream>(*std::prev(it)) * (1.0 - ratio) + extract<RoadDirection::Downstream>(*it) * ratio, + Interpolate(extract<RoadDirection::Downstream>(*std::prev(it)), extract<RoadDirection::Downstream>(*it), ratio), input.GetSide(edge.start, edge.end) == Side::Right ? -(input - intersection).Length() : (input - intersection).Length() // }; } @@ -114,13 +114,18 @@ ST ReferenceLine::LocalizeUsingTAxes(const XY &input) const const XY intersection{edge.GetIntersection(Line{input, input + axis}).value()}; const double ratio{edge.GetRatio(intersection)}; return ST{ - extract<RoadDirection::Downstream>(*std::prev(it)) * (1.0 - ratio) + extract<RoadDirection::Downstream>(*it) * ratio, + Interpolate(extract<RoadDirection::Downstream>(*std::prev(it)), extract<RoadDirection::Downstream>(*it), ratio), input.GetSide(edge.start, edge.end) == Side::Right ? -(input - intersection).Length() : (input - intersection).Length() // }; } return input.GetST(*std::prev(it), *it); } +double ReferenceLine::GetLength() const +{ + return get<S>(back()); +} + std::ostream &operator<<(std::ostream &os, const ReferenceLine &line) { return os << "Reference Line " << line.GetId() << ": " << static_cast<const ReferenceLine::Base &>(line); diff --git a/src/Types/Circle.cpp b/src/Types/Circle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e78e890f18982691f8d9245327f058f7834aa4ef --- /dev/null +++ b/src/Types/Circle.cpp @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +#include "OsiQueryLibrary/Types/Circle.h" + +#include "OsiQueryLibrary/Point/XY.tpp" +#include "OsiQueryLibrary/Types/Geometry.h" + +namespace osiql { +Circle::Circle(const XY &a, const XY &b, const XY &c) : + center{GetCenterOf(a, b, c).value()}, radius{(center - a).Length()} +{ +} + +std::optional<XY> GetCenterOf(const XY &a, const XY &b, const XY &c, double epsilon) +{ + if ((b - a).SquaredLength() <= epsilon || (c - b).SquaredLength() <= epsilon) + { + return std::nullopt; + } + const XY abMid{Average(a, b)}; + const Line abMirror{abMid, abMid + (b - a).GetPerpendicular<Winding::Clockwise>()}; + + const XY bcMid{Average(b, c)}; + const Line bcMirror{bcMid, bcMid + (c - b).GetPerpendicular<Winding::Clockwise>()}; + + return abMirror.GetIntersection(bcMirror); +} + +std::ostream &operator<<(std::ostream &os, const Circle &circle) +{ + return os << "(Center: " << circle.center << ", Radius: " << circle.radius << ')'; +} +} // namespace osiql diff --git a/src/Types/LaneDirection.cpp b/src/Types/Enum/LaneDirection.cpp similarity index 100% rename from src/Types/LaneDirection.cpp rename to src/Types/Enum/LaneDirection.cpp diff --git a/src/Types/RoadDirection.cpp b/src/Types/Enum/RoadDirection.cpp similarity index 100% rename from src/Types/RoadDirection.cpp rename to src/Types/Enum/RoadDirection.cpp diff --git a/src/Types/Side.cpp b/src/Types/Enum/Side.cpp similarity index 100% rename from src/Types/Side.cpp rename to src/Types/Enum/Side.cpp diff --git a/src/World.cpp b/src/World.cpp index 29deb180c76b2c785a3f5a090c99dd3cf22902c0..4bd82933c9d78e73cceb384e53aed4d8def35e08 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -30,14 +30,14 @@ World::World(const osi3::GroundTruth &groundTruth) : for (auto &item : GetAll<MovingObject>()) { - UpdateObject(get<MovingObject>(item)); + UpdateOverlaps(get<MovingObject>(item)); movingObjectRTree.insert(&get<MovingObject>(item)); } - for (auto &object : GetAll<StationaryObject>()) + for (auto &item : GetAll<StationaryObject>()) { - UpdateObject(object); - stationaryObjectRTree.insert(&object); + UpdateOverlaps(get<StationaryObject>(item)); + stationaryObjectRTree.insert(&get<StationaryObject>(item)); } } @@ -62,49 +62,7 @@ const Vehicle *World::GetVehicle(Id id) const void World::Update(const Container<osi3::MovingObject> &objects) { - // Remove all overlaps - for (auto &[id, lane] : lanes) - { - lane.template GetOverlapping<MovingObject>().clear(); - } - for (auto &[id, road] : roads) - { - road.template GetOverlapping<MovingObject>().clear(); - } - // Spawn/Update objects - for (const auto &object : objects) - { - if (const auto match{GetAll<MovingObject>().find(get<Id>(object))}; match != GetAll<MovingObject>().end()) - { - auto &wrapper{*match->second}; - wrapper = object; - UpdateObject(wrapper); - } - else - { - UpdateObject(Emplace<MovingObject>(GetAll<MovingObject>(), object)); - } - } - // Despawn objects - if (auto &leftovers{GetAll<MovingObject>()}; leftovers.size() > static_cast<size_t>(objects.size())) - { - std::unordered_set<Id> ids; - std::transform(leftovers.begin(), leftovers.end(), std::inserter(ids, ids.end()), osiql::Get<Id>{}); - for (const auto &object : objects) - { - ids.erase(get<Id>(object)); - } - for (Id id : ids) - { - leftovers.erase(id); - } - } - // Refresh RTree - GetRTree<MovingObject>().clear(); - for (auto &item : GetAll<MovingObject>()) - { - GetRTree<MovingObject>().insert(&get<MovingObject>(item)); - } + UpdateAll<MovingObject>(objects); } World *GetWorld(const osi3::GroundTruth &groundTruth) diff --git a/tests/unitTests/CMakeLists.txt b/tests/unitTests/CMakeLists.txt index 0eb1f6fd2d0fc61f2dfc2d316493dc27869d8b07..a16aa511f797fe93ffadae155e43a6e34805be6a 100644 --- a/tests/unitTests/CMakeLists.txt +++ b/tests/unitTests/CMakeLists.txt @@ -45,10 +45,13 @@ function(add_unit_test NAME FILENAME) set_tests_properties(${NAME} PROPERTIES DEPENDS ${NAME}_build) endfunction() +add_unit_test(Distance_Tests DistanceTests) +add_unit_test(Functor_Tests FunctorTests) add_unit_test(Lane_Tests LaneTests) add_unit_test(Node_Tests NodeTests) -add_unit_test(Route_Tests RouteTests) add_unit_test(Pathfinding_Tests PathfindingTests) +add_unit_test(ReferenceLine_Tests ReferenceLineTests) +add_unit_test(Route_Tests RouteTests) add_unit_test(SensorView_Tests SensorViewTests) # ---------- All Tests ---------- @@ -62,8 +65,11 @@ add_executable(${COMPONENT_TEST_NAME} EXCLUDE_FROM_ALL # Tests ${COMPONENT_TEST_DIR}/gtest/unitTestMain.cpp + ${COMPONENT_TEST_DIR}/unitTests/FunctorTests.cpp ${COMPONENT_TEST_DIR}/unitTests/LaneTests.cpp ${COMPONENT_TEST_DIR}/unitTests/NodeTests.cpp + ${COMPONENT_TEST_DIR}/unitTests/PathfindingTests.cpp + ${COMPONENT_TEST_DIR}/unitTests/ReferenceLineTests.cpp ${COMPONENT_TEST_DIR}/unitTests/RouteTests.cpp ${COMPONENT_TEST_DIR}/unitTests/SensorViewTests.cpp diff --git a/tests/unitTests/DistanceTests.cpp b/tests/unitTests/DistanceTests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..637036c95942f0539ee2bcbcebbadb20cdd864dc --- /dev/null +++ b/tests/unitTests/DistanceTests.cpp @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +#include "util/Common.tpp" + +using namespace osiql; + +TEST(GetDistanceBetween, Given2Intervals_WhenComputingDistance_ThenResultsAreAsExpected) +{ + EXPECT_EQ(GetDistanceBetween(Interval{-5, -3}, Interval{2, 6}), 5); // a behind b + EXPECT_EQ(GetDistanceBetween(Interval{-5, -3}, Interval{-4, 1}), 0); // a partially behind b + EXPECT_EQ(GetDistanceBetween(Interval{2, 2}, Interval{2, 2}), 0); // a identical to b + EXPECT_EQ(GetDistanceBetween(Interval{-4, 1}, Interval{-5, -3}), 0); // b partially behind a + EXPECT_EQ(GetDistanceBetween(Interval{2, 6}, Interval{-5, -3}), -5); // b behind a +} + +TEST(GetDistanceBetween, Given1IntervalAnd1Value_WhenComputingDistance_ThenResultsAreAsExpected) +{ + EXPECT_EQ(GetDistanceBetween(Interval{-5, -3}, 2), 5); // Interval behind value + EXPECT_EQ(GetDistanceBetween(Interval{-5, -3}, -5), 0); // Value at start of interval + EXPECT_EQ(GetDistanceBetween(Interval{-5, -3}, -4), 0); // Value inside interval + EXPECT_EQ(GetDistanceBetween(Interval{-5, -3}, -3), 0); // Value at end of interval + EXPECT_EQ(GetDistanceBetween(Interval{-5, -3}, -7), -2); // Value behind interval +} + +TEST(GetDistanceBetween, Given1ValueAnd1Interval_WhenComputingDistance_ThenResultsAreAsExpected) +{ + EXPECT_EQ(GetDistanceBetween(2, Interval{-5, -3}), -5); // Interval behind value + EXPECT_EQ(GetDistanceBetween(-5, Interval{-5, -3}), 0); // Value at start of interval + EXPECT_EQ(GetDistanceBetween(-4, Interval{-5, -3}), 0); // Value inside interval + EXPECT_EQ(GetDistanceBetween(-3, Interval{-5, -3}), 0); // Value at end of interval + EXPECT_EQ(GetDistanceBetween(-7, Interval{-5, -3}), 2); // Value behind interval +} + +TEST(GetDistanceBetween, Given2Values_WhenComputingDistance_ThenResultsAreAsExpected) +{ + EXPECT_EQ(GetDistanceBetween(-3, 2), 5); // a behind b + EXPECT_EQ(GetDistanceBetween(1, 1), 0); // identical a and b + EXPECT_EQ(GetDistanceBetween(2, -3), -5); // b behind a +} diff --git a/tests/unitTests/FunctorTests.cpp b/tests/unitTests/FunctorTests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6466e45c810a206d741736fb189f2b13913433dc --- /dev/null +++ b/tests/unitTests/FunctorTests.cpp @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +#include "util/Common.tpp" + +using namespace osiql; + +TEST(Equal_To, GivenMovingObject_WhenComparingId_ThenSucceeds) +{ + osi3::MovingObject handle; + osiql::SetId(handle, 1); + osiql::SetDimensions(handle, 4, 2); + osiql::MovingObject object{handle}; + EXPECT_TRUE(Equal<Id>{}(object, &object)); + EXPECT_TRUE(Equal<Id>::to(object)(std::ref(object))); + EXPECT_FALSE(Equal<Id>{}(object, 2)); + EXPECT_FALSE(Equal<Id>::to(object)(2)); +} + +TEST(Equal_To, GivenMovingObjectPointer_WhenComparingId_ThenSucceeds) +{ + osi3::MovingObject handle; + osiql::SetId(handle, 1); + osiql::SetDimensions(handle, 4, 2); + osiql::MovingObject object{handle}; + EXPECT_TRUE(Equal<Id>{}(&object, object)); + EXPECT_TRUE(Equal<Id>::to(&object)(std::optional(object))); + EXPECT_FALSE(Equal<Id>{}(&object, 2)); + EXPECT_FALSE(Equal<Id>::to(&object)(2)); +} + +TEST(Equal_To, GivenMovingObject_WhenCurrying_ThenStoresReference) +{ + osi3::MovingObject handle; + osiql::SetId(handle, 1); + osiql::SetDimensions(handle, 4, 2); + osiql::MovingObject object{handle}; + auto functor{Equal<Id>::to(object)}; + EXPECT_EQ(&functor.b, &object); +} diff --git a/tests/unitTests/LaneTests.cpp b/tests/unitTests/LaneTests.cpp index bca78911d3f550d7fc27c318b5231bca522cc7a3..55a080c8edc5b7456615c1700f7eecc3cded6f49 100644 --- a/tests/unitTests/LaneTests.cpp +++ b/tests/unitTests/LaneTests.cpp @@ -14,7 +14,7 @@ using namespace osiql; using ::testing::Contains; using ::testing::Key; -TEST(Lane, osiqlLaneTypes_Match_osiLogicalLaneTypes) +TEST(Lane, GetType_GivenOSIValue_WhenReturnedAsOSIQLValue_ThenValueMatchesExpectation) { osi3::LogicalLane handle; const Lane lane{handle}; diff --git a/tests/unitTests/NodeTests.cpp b/tests/unitTests/NodeTests.cpp index f18b1f998282ae5b5e1cccf02015c68b0912d855..06b051db7ff35a7614bbb9b451692d567335f97e 100644 --- a/tests/unitTests/NodeTests.cpp +++ b/tests/unitTests/NodeTests.cpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -553,7 +553,7 @@ TEST_F(RoadNetwork, FindAll_GivenObjectsOnEveryRoad_WhenSearchingForwardAndBackw { for (const auto &reference_line : groundTruth.handle.reference_line()) { // Add an object at the center of every road - XY xy{0.5 * XY{reference_line.poly_line().begin()->world_position()} + 0.5 * XY{reference_line.poly_line().rbegin()->world_position()}}; + XY xy{Average(XY{reference_line.poly_line().begin()->world_position()}, XY{reference_line.poly_line().rbegin()->world_position()})}; groundTruth.Add<MovingObject>(xy); } World world{groundTruth.handle}; diff --git a/tests/unitTests/ReferenceLineTests.cpp b/tests/unitTests/ReferenceLineTests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..92ef1d062de38c5810284cd80af179fe107f2695 --- /dev/null +++ b/tests/unitTests/ReferenceLineTests.cpp @@ -0,0 +1,137 @@ +/******************************************************************************** + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +#include <limits> + +#include "util/Common.tpp" + +using namespace osiql; + +TEST(ReferenceLine_GetCurvature, GivenSCoordinate_WhenBeforeOrAfterLine_ThenCurvatureIsNaN) +{ + test::GroundTruth groundTruth; + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{0, 0}, XY{10, 0})}; + EXPECT_TRUE(std::isnan(referenceLine.GetCurvature(-1))); + EXPECT_TRUE(std::isnan(referenceLine.GetCurvature(11))); +} + +TEST(ReferenceLine_GetCurvature, GivenStraightLine_WhenSCoordinateWithinLine_ThenCurvatureIs0) +{ + test::GroundTruth groundTruth; + { + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{0, 0}, XY{10, 0})}; + EXPECT_EQ(referenceLine.GetCurvature(0), .0); + EXPECT_EQ(referenceLine.GetCurvature(5), .0); + EXPECT_EQ(referenceLine.GetCurvature(10), .0); + } + { + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{0, 0}, XY{5, 0}, XY{10, 0})}; + EXPECT_EQ(referenceLine.GetCurvature(0), .0); + EXPECT_EQ(referenceLine.GetCurvature(3), .0); + EXPECT_EQ(referenceLine.GetCurvature(6), .0); + EXPECT_EQ(referenceLine.GetCurvature(9), .0); + } + { + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{0, 0}, XY{3, 0}, XY{6, 0}, XY{9, 0})}; + EXPECT_EQ(referenceLine.GetCurvature(0), .0); + EXPECT_EQ(referenceLine.GetCurvature(2), .0); + EXPECT_EQ(referenceLine.GetCurvature(4), .0); + EXPECT_EQ(referenceLine.GetCurvature(6), .0); + EXPECT_EQ(referenceLine.GetCurvature(8), .0); + } +} + +TEST(ReferenceLine_GetCurvature, Given3PointsOnUnitCircle_WhenSCoordinateWithinLine_ThenCurvatureIs1) +{ + { // Counter-clockwise -> positve curvature + test::GroundTruth groundTruth; + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{1, 0}, XY{1, 0}.Rotate(quarterPi), XY{0, 1})}; + + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(0), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .25), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .5), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .75), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength()), 1); + } + { // Clockwise -> negative curvature + test::GroundTruth groundTruth; + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{1, 0}, XY{1, 0}.Rotate(-quarterPi), XY{0, -1})}; + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(0), -1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .25), -1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .5), -1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .75), -1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength()), -1); + } +} + +TEST(ReferenceLine_GetCurvature, Given4PointsOnUnitCircle_WhenSCoordinateWithinLine_ThenCurvatureIs1) +{ + { // Counter-clockwise -> positve curvature + test::GroundTruth groundTruth; + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{1, 0}, XY{1, 0}.Rotate(quarterPi), XY{0, 1}, XY{1, 0}.Rotate(threeQuarterPi))}; + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(0), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * (1.0 / 6.0)), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * (1.0 / 3.0)), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .5), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * (2.0 / 3.0)), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * (5.0 / 6.0)), 1); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(referenceLine.GetLength()), 1); + } + { // Clockwise -> negative curvature + test::GroundTruth groundTruth; + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{1, 0}, XY{1, 0}.Rotate(-quarterPi), XY{0, -1}, XY{1, 0}.Rotate(-threeQuarterPi))}; + EXPECT_EQ(referenceLine.GetCurvature(0), -1); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * (1.0 / 6.0)), -1); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * (1.0 / 3.0)), -1); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .5), -1); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * (2.0 / 3.0)), -1); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * (5.0 / 6.0)), -1); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength()), -1); + } +} + +TEST(ReferenceLine_GetCurvature, GivenUniSquare_WhenSCoordinateWithinLine_ThenCurvatureReachesPiHalf) +{ + test::GroundTruth groundTruth; + // The center of a unit square has a distance of sqrt(2) between its center and each corner + ReferenceLine referenceLine{groundTruth.AddReferenceLine(XY{0, 0}, XY{1, 0}, XY{1, 1}, XY{0, 1}, XY{0, 0})}; + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(0), std::sqrt(2)); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(.5), std::sqrt(2)); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(1), std::sqrt(2)); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(1.5), std::sqrt(2)); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(2), std::sqrt(2)); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(2.5), std::sqrt(2)); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(3), std::sqrt(2)); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(3.5), std::sqrt(2)); + EXPECT_DOUBLE_EQ(referenceLine.GetCurvature(4), std::sqrt(2)); +} + +TEST(ReferenceLine_GetCurvature, Given4PointSpiral_WhenValidSCoordinate_ThenReturnsInterpolatedCurvature) +{ + test::GroundTruth groundTruth; + // First three points form a circle with center (1, 0) and radius sqrt(5) + // Last three points form a circle with center (1, 1) and radius sqrt(2) + ReferenceLine referenceLine{groundTruth.AddReferenceLine( + XY{0, -2}, + XY{0, 2}, + XY{2, 2}, + XY{2, 0} + )}; + const double START_CURVATURE{-1.0 / std::sqrt(5)}; + const double END_CURVATURE{-1.0 / std::sqrt(2)}; + EXPECT_EQ(referenceLine.GetCurvature(0), START_CURVATURE); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .125), Interpolate(START_CURVATURE, END_CURVATURE, .125)); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .25), Interpolate(START_CURVATURE, END_CURVATURE, .25)); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .375), Interpolate(START_CURVATURE, END_CURVATURE, .375)); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .5), Interpolate(START_CURVATURE, END_CURVATURE, .5)); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .625), Interpolate(START_CURVATURE, END_CURVATURE, .625)); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .75), Interpolate(START_CURVATURE, END_CURVATURE, .75)); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength() * .875), Interpolate(START_CURVATURE, END_CURVATURE, .875)); + EXPECT_EQ(referenceLine.GetCurvature(referenceLine.GetLength()), END_CURVATURE); +} diff --git a/tests/unitTests/WorldTests/Tests.cpp b/tests/unitTests/WorldTests/Tests.cpp index ec477f160287bf276b7c44353154287859ce317c..12b60c16563bae8e1b4de734ddf9879f87b599b5 100644 --- a/tests/unitTests/WorldTests/Tests.cpp +++ b/tests/unitTests/WorldTests/Tests.cpp @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -11,10 +11,10 @@ using namespace osiql; -class QueryTest : public ::testing::Test +class WorldTest : public ::testing::Test { public: - QueryTest() + WorldTest() { roads.push_back(groundTruth.AddRoad( // ROAD 0 groundTruth.AddReferenceLine(Pose<XY>{{0, 0}, halfPi}, Pose<XY>{{2, 0}, halfPi}), @@ -64,7 +64,7 @@ public: std::vector<std::vector<osi3::LogicalLane *>> roads; }; -TEST_F(QueryTest, LaneIndices) +TEST_F(WorldTest, LaneIndices) { // On a road with 2 downstream and 2 upstream lanes: @@ -88,7 +88,7 @@ TEST_F(QueryTest, LaneIndices) EXPECT_EQ(leftmostLane->GetIndex(Side::Left), 1); } -TEST_F(QueryTest, AdjacentLanes) +TEST_F(WorldTest, AdjacentLanes) { const Lane *rightmostLane{world->GetLane(get<Id>(roads[0][3]))}; ASSERT_NE(rightmostLane, nullptr); @@ -132,7 +132,7 @@ TEST_F(QueryTest, AdjacentLanes) EXPECT_EQ(leftLane->GetAdjacentLane<Side::Left>(2), rightmostLane); } -TEST_F(QueryTest, AdjacentBoundaries) +TEST_F(WorldTest, AdjacentBoundaries) { const Lane *rightmostLane{world->GetLane(get<Id>(roads[0][3]))}; ASSERT_NE(rightmostLane, nullptr); @@ -151,7 +151,7 @@ TEST_F(QueryTest, AdjacentBoundaries) // Lane Width Tests // NOTE: Fails prior to OSI 3.6 due to lack of t_axis_yaw support -TEST_F(QueryTest, GetLaneWidth_Forward) +TEST_F(WorldTest, GetLaneWidth_Forward) { const Lane *lane{world->GetLane(get<Id>(roads[0][2]))}; ASSERT_NE(lane, nullptr); @@ -178,7 +178,7 @@ TEST_F(QueryTest, GetLaneWidth_Forward) } // NOTE: Fails prior to OSI 3.6 due to lack of t_axis_yaw support -TEST_F(QueryTest, GetLaneWidth_Backward) +TEST_F(WorldTest, GetLaneWidth_Backward) { const Lane *lane{world->GetLane(get<Id>(roads[2][1]))}; ASSERT_NE(lane, nullptr); @@ -193,7 +193,7 @@ TEST_F(QueryTest, GetLaneWidth_Backward) // Distance from Object to Polyline Tests -TEST_F(QueryTest, GetDistanceTo_LaneBoundaries_Returns_CorrectValue) +TEST_F(WorldTest, GetDistanceTo_LaneBoundaries_Returns_CorrectValue) { const auto &object{world->GetHostVehicle()}; const Lane *lane{world->GetLane(get<Id>(roads[0][2]))}; @@ -203,7 +203,7 @@ TEST_F(QueryTest, GetDistanceTo_LaneBoundaries_Returns_CorrectValue) EXPECT_DOUBLE_EQ(right, 0.0); } -TEST_F(QueryTest, GetDistanceTo_AdjacentLanePolyline_Returns_CorrectValue) +TEST_F(WorldTest, GetDistanceTo_AdjacentLanePolyline_Returns_CorrectValue) { const auto &object{world->GetHostVehicle()}; const auto &rightmostBoundary{*world->GetLane(get<Id>(roads[0][3]))->GetBoundaries<Side::Right>()[0]}; @@ -218,11 +218,11 @@ TEST_F(QueryTest, GetDistanceTo_AdjacentLanePolyline_Returns_CorrectValue) EXPECT_DOUBLE_EQ(object.GetSignedDistancesTo(rightmostBoundary.begin(), rightmostBoundary.end()).max, -0.75); } -TEST_F(QueryTest, GetDistanceTo_IntersectingPolyline_Returns_Positive_And_Negative_Distance) +TEST_F(WorldTest, GetDistanceTo_IntersectingPolyline_Returns_Positive_And_Negative_Distance) { // Make the object wider so that it overlaps the boundary groundTruth.handle.mutable_moving_object(0)->mutable_base()->mutable_dimension()->set_width(2.5); - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); const auto &object{world->GetHostVehicle()}; const Lane *lane{world->GetLane(get<Id>(roads[0][2]))}; ASSERT_NE(lane, nullptr); @@ -233,14 +233,14 @@ TEST_F(QueryTest, GetDistanceTo_IntersectingPolyline_Returns_Positive_And_Negati // Lane Curvature Tests -TEST_F(QueryTest, GetCurvature_StraightReferenceLine_Returns_Zero) +TEST_F(WorldTest, GetCurvature_StraightReferenceLine_Returns_Zero) { const ReferenceLine *referenceLine{world->GetReferenceLine(roads[0][0]->reference_line_id().value())}; ASSERT_NE(referenceLine, nullptr); EXPECT_DOUBLE_EQ(referenceLine->GetCurvature(0.0), 0.0); } -TEST_F(QueryTest, GetCurvature_Forward_Equals_BackwardInverted) +TEST_F(WorldTest, GetCurvature_Forward_Equals_BackwardInverted) { const ReferenceLine *referenceLine{world->GetReferenceLine(roads[3][0]->reference_line_id().value())}; ASSERT_NE(referenceLine, nullptr); @@ -253,7 +253,7 @@ TEST_F(QueryTest, GetCurvature_Forward_Equals_BackwardInverted) EXPECT_DOUBLE_EQ(referenceLine->GetCurvature(6.0, RoadDirection::Downstream), -referenceLine->GetCurvature(6.0, RoadDirection::Upstream)); } -TEST_F(QueryTest, GetCurvature_OfLane_IsDifferentFromReferenceLine_OnCurvedRoad) +TEST_F(WorldTest, GetCurvature_OfLane_IsDifferentFromReferenceLine_OnCurvedRoad) { const ReferenceLine *referenceLine{world->GetReferenceLine(roads[3][0]->reference_line_id().value())}; ASSERT_NE(referenceLine, nullptr); @@ -281,7 +281,7 @@ TEST_F(QueryTest, GetCurvature_OfLane_IsDifferentFromReferenceLine_OnCurvedRoad) EXPECT_GT(lane1->GetCurvature<RoadDirection::Downstream>(3.0), referenceLine->GetCurvature<RoadDirection::Downstream>(3.0)); } -TEST_F(QueryTest, GetObstruction_OfVehicleBy_Itself) +TEST_F(WorldTest, GetObstruction_OfVehicleBy_Itself) { const Vehicle &ego{world->GetHostVehicle()}; ASSERT_THAT(ego.GetOverlaps<Road>(), ::testing::Not(IsEmpty())); @@ -300,7 +300,7 @@ TEST_F(QueryTest, GetObstruction_OfVehicleBy_Itself) } // NOTE: Fails prior to OSI 3.6 due to lack of t_axis_yaw support -TEST_F(QueryTest, GetObstruction_OfVehicleBy_IndividualPoints) +TEST_F(WorldTest, GetObstruction_OfVehicleBy_IndividualPoints) { const Vehicle &ego{world->GetHostVehicle()}; ASSERT_THAT(ego.GetOverlaps<Road>(), ::testing::Not(IsEmpty())); @@ -326,11 +326,11 @@ TEST_F(QueryTest, GetObstruction_OfVehicleBy_IndividualPoints) } // NOTE: Fails prior to OSI 3.6 due to lack of t_axis_yaw support -TEST_F(QueryTest, GetObstruction_OfVehicleBy_ObjectOneRoadApart) +TEST_F(WorldTest, GetObstruction_OfVehicleBy_ObjectOneRoadApart) { // Object on road 2 groundTruth.Add<MovingObject>(XY{7.5, -2.5}, 1.0, 1.0); - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); const auto &startObject{world->GetHostVehicle()}; const auto *endObject{world->GetMovingObject(startObject.GetId() + 1)}; @@ -348,11 +348,11 @@ TEST_F(QueryTest, GetObstruction_OfVehicleBy_ObjectOneRoadApart) } // NOTE: Fails prior to OSI 3.6 due to lack of t_axis_yaw support -TEST_F(QueryTest, GetObstruction_OfVehicleBy_ObjectTwoRoadsApart) +TEST_F(WorldTest, GetObstruction_OfVehicleBy_ObjectTwoRoadsApart) { // Object on road 2 groundTruth.Add<MovingObject>(XY{5.5, 1.5}, 1.0, 1.0); - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); const auto &startObject{world->GetHostVehicle()}; const auto *endObject{world->GetMovingObject(startObject.GetId() + 1)}; @@ -370,11 +370,11 @@ TEST_F(QueryTest, GetObstruction_OfVehicleBy_ObjectTwoRoadsApart) EXPECT_DOUBLE_EQ(max1, -min2); } -TEST_F(QueryTest, GetObstruction_OfVehicleBy_ObjectNotOnRoute) +TEST_F(WorldTest, GetObstruction_OfVehicleBy_ObjectNotOnRoute) { // Object on road 2 groundTruth.Add<MovingObject>(XY{5.5, 1.5}, 1.0, 1.0); - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); const auto &startObject{world->GetHostVehicle()}; const auto *endObject{world->GetMovingObject(startObject.GetId() + 1)}; @@ -392,10 +392,10 @@ TEST_F(QueryTest, GetObstruction_OfVehicleBy_ObjectNotOnRoute) EXPECT_DOUBLE_EQ(max2, std::numeric_limits<double>::lowest()); } -TEST_F(QueryTest, GetObstruction_OfVehicleBy_ObjectNotOnAnyRoad) +TEST_F(WorldTest, GetObstruction_OfVehicleBy_ObjectNotOnAnyRoad) { auto id{groundTruth.Add<MovingObject>(XY{100.0, 100.0}, 1.0, 1.0).id().value()}; - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); const auto *objectOnRoad{world->GetMovingObject(id - 1)}; ASSERT_NE(objectOnRoad, nullptr); @@ -413,26 +413,28 @@ TEST_F(QueryTest, GetObstruction_OfVehicleBy_ObjectNotOnAnyRoad) EXPECT_DOUBLE_EQ(max2, std::numeric_limits<double>::lowest()); } -TEST_F(QueryTest, Update_Objects) +TEST_F(WorldTest, UpdateAll_MovingObjects) { - // One object + // Ensure deprecated version is still callable: world->Update(groundTruth.handle.moving_object()); + // One object + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); EXPECT_THAT(world->GetAll<MovingObject>(), SizeIs(1)); // No objects anymore groundTruth.handle.mutable_moving_object()->Clear(); - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); EXPECT_THAT(world->GetAll<MovingObject>(), IsEmpty()); // Two objects groundTruth.Add<MovingObject>(XY{0, 0}, 2, 1); groundTruth.Add<MovingObject>(XY{1, 0}, 2, 1); - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); EXPECT_THAT(world->GetAll<MovingObject>(), SizeIs(2)); // Replace one of the objects by first deleting then adding { auto *objects{groundTruth.handle.mutable_moving_object()}; objects->erase(std::prev(objects->end())); groundTruth.Add<MovingObject>(XY{2, 0}, 2, 1); - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); EXPECT_THAT(world->GetAll<MovingObject>(), SizeIs(2)); } // Replace one of the objects by first adding then deleting @@ -440,18 +442,58 @@ TEST_F(QueryTest, Update_Objects) groundTruth.Add<MovingObject>(XY{3, 0}, 2, 1); auto *objects{groundTruth.handle.mutable_moving_object()}; objects->erase(objects->begin()); - world->Update(groundTruth.handle.moving_object()); + world->UpdateAll<MovingObject>(groundTruth.handle.moving_object()); EXPECT_THAT(world->GetAll<MovingObject>(), SizeIs(2)); } // Swap the order of the underlying moving objects { auto &range{*groundTruth.handle.mutable_moving_object()}; std::reverse(range.begin(), range.end()); - world->Update(range); + world->UpdateAll<MovingObject>(range); EXPECT_THAT(world->GetAll<MovingObject>(), SizeIs(2)); } } +TEST_F(WorldTest, UpdateAll_StationaryObjects) +{ + // One object + groundTruth.Add<StationaryObject>(XY{0, 0}, 2, 1); + world->UpdateAll<StationaryObject>(groundTruth.handle.stationary_object()); + EXPECT_THAT(world->GetAll<StationaryObject>(), SizeIs(1)); + // No objects anymore + groundTruth.handle.mutable_stationary_object()->Clear(); + world->UpdateAll<StationaryObject>(groundTruth.handle.stationary_object()); + EXPECT_THAT(world->GetAll<StationaryObject>(), IsEmpty()); + // Two objects + groundTruth.Add<StationaryObject>(XY{0, 0}, 2, 1); + groundTruth.Add<StationaryObject>(XY{1, 0}, 2, 1); + world->UpdateAll<StationaryObject>(groundTruth.handle.stationary_object()); + EXPECT_THAT(world->GetAll<StationaryObject>(), SizeIs(2)); + // Replace one of the objects by first deleting then adding + { + auto *objects{groundTruth.handle.mutable_stationary_object()}; + objects->erase(std::prev(objects->end())); + groundTruth.Add<StationaryObject>(XY{2, 0}, 2, 1); + world->UpdateAll<StationaryObject>(groundTruth.handle.stationary_object()); + EXPECT_THAT(world->GetAll<StationaryObject>(), SizeIs(2)); + } + // Replace one of the objects by first adding then deleting + { + groundTruth.Add<StationaryObject>(XY{3, 0}, 2, 1); + auto *objects{groundTruth.handle.mutable_stationary_object()}; + objects->erase(objects->begin()); + world->UpdateAll<StationaryObject>(groundTruth.handle.stationary_object()); + EXPECT_THAT(world->GetAll<StationaryObject>(), SizeIs(2)); + } + // Swap the order of the underlying moving objects + { + auto &range{*groundTruth.handle.mutable_stationary_object()}; + std::reverse(range.begin(), range.end()); + world->UpdateAll<StationaryObject>(range); + EXPECT_THAT(world->GetAll<StationaryObject>(), SizeIs(2)); + } +} + struct OverlapTests : ::testing::Test { // Two moving objects: