// Copyright 2019-2021 The MathWorks, Inc. /** * @file * @brief Metric used for common configuration space (CS) in motion planing */ #ifndef PLANNINGCODEGEN_COMMONCSMETRIC_HPP #define PLANNINGCODEGEN_COMMONCSMETRIC_HPP #include #include "planningcodegen_DistanceMetric.hpp" namespace nav { /// template class for common configuration space metrics /** * @tparam T Data type * */ template class CommonCSMetric : public DistanceMetric { private: const double M_PI_ = 3.141592653589793238462643383; public: enum Topology { EUCLIDEAN = 0, CIRCLE, REALPROJECTIVE }; CommonCSMetric(std::size_t dim) { this->m_dim = dim; m_topologies.assign(this->m_dim, Topology::EUCLIDEAN); m_weights.assign(this->m_dim, static_cast(1.0)); } CommonCSMetric(std::size_t dim, const std::vector& topologies, const std::vector& weights) { this->m_dim = dim; assert(topologies.size() == this->m_dim); assert(weights.size() == this->m_dim); m_topologies = topologies; m_weights = weights; } void configure(const std::vector& topologies, const std::vector& weights) { assert(topologies.size() == this->m_dim); assert(weights.size() == this->m_dim); m_topologies = topologies; m_weights = weights; } /// compute distances from a query state to a collection of states (1 or more) std::vector distance(const std::vector& states, const std::vector& queryState) { typename std::vector::const_iterator queryIt = queryState.begin(); typename std::vector::const_iterator statesIt = states.begin(); std::size_t numStates = states.size() / this->m_dim; std::vector output; for (std::size_t j = 0; j < numStates; j++) { output.push_back(distanceInternal(queryIt, statesIt)); statesIt += this->m_dim; } return output; } protected: std::vector m_topologies; std::vector m_weights; /// distance between two states as the Cartesian product of the three metric spaces T distanceInternal(const typename std::vector::const_iterator& state1It, const typename std::vector::const_iterator& state2It) { T result = static_cast(0.0); T dist = static_cast(0.0); T t = static_cast(0.0); T dotProd = static_cast(0.0); for (std::size_t k = 0; k < this->m_dim; k++) { switch (m_topologies[k]) { case CIRCLE: /*S1 circle, i.e. 2D rotation*/ t = std::fabs(wrapToPi(*(state2It + k)) - wrapToPi(*(state1It + k))); dist = std::fmin(t, 2 * M_PI_ - t); break; case REALPROJECTIVE: /* quaternion, [k, k+3] */ dotProd = unitQuatDotProduct(state1It, state2It, k); dist = std::fmin(std::acos(dotProd), std::acos(-dotProd)); // angle between two unit quaternions k += 3; // RP3 (quaternion) always has 4 (3+1) elements, another k addition will // happen as part of the for-loop break; case EUCLIDEAN: /*Euclidean 1-space*/ default: dist = *(state2It + k) - *(state1It + k); } result += m_weights[k] * std::pow(dist, 2.0); } return std::sqrt(result); } /// dot product of two unit quaternions T unitQuatDotProduct(const typename std::vector::const_iterator& state1It, const typename std::vector::const_iterator& state2It, std::size_t idxStart) { T dotProd = static_cast(0.0); T norm1 = static_cast(0.0); T norm2 = static_cast(0.0); for (std::size_t k = idxStart; k < idxStart + 4; k++) { norm1 += (*(state1It + k)) * (*(state1It + k)); norm2 += (*(state2It + k)) * (*(state2It + k)); dotProd += (*(state1It + k)) * (*(state2It + k)); } return dotProd / (norm1 * norm2); } /// wrap to pi T wrapToPi(T inputRadians) { T x = std::fmod(inputRadians + M_PI_, 2 * M_PI_); if (!std::signbit(x) /* true if negative*/ && (x == 0)) // positive multiples of 2 * pi map to 2 * pi { x = 2 * M_PI_; } return x - M_PI_; } }; } // namespace nav #endif