You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

spline_parametric_curve.cpp 6.6 kB

2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #include "util/util.h"
  2. #include "geo/private/math/spline_parametric_curve.h"
  3. #include "Eigen/LU"
  4. #include "Eigen/Dense"
  5. #include "unsupported/Eigen/NonLinearOptimization"
  6. namespace ns
  7. {
  8. namespace geo
  9. {
  10. /**
  11. * if points' num less than 4, then use a line interpolation to generate 4 ctrl
  12. * points
  13. * @param points origin ctrl points
  14. * @return 4 ctrl points
  15. */
  16. Eigen::MatrixXd get_ctrl_points(const Eigen::MatrixXd& points)
  17. {
  18. using namespace Eigen;
  19. Index n = points.cols();
  20. NS_ASSERT(n > 1);
  21. NS_ASSERT(n < 4);
  22. MatrixXd ctrls(points.rows(), 4);
  23. if (n == 2)
  24. {
  25. VectorXd v = (points.col(1) - points.col(0)) / 3;
  26. for (int i = 0; i < 4; ++i)
  27. {
  28. ctrls.col(i) = points.col(0) + i * v;
  29. }
  30. }
  31. else if (n == 3)
  32. {
  33. RowVectorXd sd =
  34. (points.rightCols(2) - points.leftCols(2)).colwise().squaredNorm();
  35. Index i = (sd(0) > sd(1)) ? 1 : 2;
  36. ctrls.leftCols(i) = points.leftCols(i);
  37. ctrls.col(i) = (points.col(i - 1) + points.col(i)) / 2;
  38. ctrls.rightCols(3 - i) = points.rightCols(3 - i);
  39. }
  40. return ctrls;
  41. }
  42. SplineParametricCurve::SplineParametricCurve(const Eigen::MatrixXd& points)
  43. {
  44. NS_ASSERT(points.cols() >= 2) << "SplineParametricCurve: need at least 2 "
  45. "points to create a spline (got "
  46. << points.cols() << ")." << std::endl;
  47. const Eigen::Index min_points = 4;
  48. if (points.cols() < min_points)
  49. {
  50. init_with_matrix(get_ctrl_points(points));
  51. }
  52. else
  53. {
  54. init_with_matrix(points);
  55. }
  56. }
  57. const Eigen::Spline<double, 2, 3>& SplineParametricCurve::get_spline() const
  58. {
  59. return m_spline;
  60. }
  61. void SplineParametricCurve::init_with_matrix(const Eigen::MatrixXd& ctrls)
  62. {
  63. using namespace Eigen;
  64. int num_controls = static_cast<int>(ctrls.cols()); // n + 1 control points
  65. int num_knots = num_controls + 3 + 1; // m+1, m = n + p + 1
  66. VectorXd knots(num_knots);
  67. knots.head(3).setZero();
  68. knots.tail(3).setConstant(1);
  69. // internal knots are uniformly spaced
  70. knots.segment(3, num_knots - 6) = VectorXd::LinSpaced(num_knots - 6, 0, 1);
  71. m_spline = Spline<double, 2, 3>(knots, ctrls);
  72. }
  73. Eigen::Vector2d SplineParametricCurve::operator()(double tau) const
  74. {
  75. return m_spline(tau);
  76. }
  77. Eigen::MatrixXd SplineParametricCurve::operator()(
  78. const Eigen::VectorXd& tau) const
  79. {
  80. Eigen::MatrixXd ret(2, tau.size());
  81. for (int i = 0; i < tau.size(); ++i)
  82. {
  83. ret.col(i) = m_spline(tau(i));
  84. }
  85. return ret;
  86. }
  87. Eigen::Vector2d SplineParametricCurve::tangent(double tau) const
  88. {
  89. Eigen::Vector2d ret = m_spline.derivatives(tau, 1).col(1);
  90. return ret.normalized();
  91. }
  92. Eigen::Vector2d SplineParametricCurve::norm(double tau) const
  93. {
  94. Eigen::Vector2d t = tangent(tau);
  95. return {-t(1), t(0)};
  96. }
  97. Eigen::MatrixXd SplineParametricCurve::norm(const Eigen::VectorXd& tau) const
  98. {
  99. auto n = tau.size();
  100. Eigen::MatrixXd ret(2, n);
  101. for (int i = 0; i < n; ++i)
  102. {
  103. ret.col(i) = norm(tau(i));
  104. }
  105. return ret;
  106. }
  107. Eigen::MatrixXd SplineParametricCurve::curvature(
  108. const Eigen::VectorXd& tau) const
  109. {
  110. auto n = tau.size();
  111. Eigen::VectorXd ret(n);
  112. Eigen::Matrix2d der;
  113. for (int i = 0; i < n; ++i)
  114. {
  115. der = m_spline.derivatives(tau(i), 2).rightCols(2);
  116. double t = der.col(0).norm();
  117. ret(i) = (t < 1e-10) ? 0 : der.determinant() / (t * t * t);
  118. }
  119. return ret;
  120. }
  121. /*
  122. * Get the closest τ value for given point
  123. *
  124. * Let F(s) to be the square distance from point (x(s), y(s)) to p, this is to
  125. * get a τ value so that F(τ) minimize F(s).
  126. *
  127. * The implementation uses Levenberg-Marquardt Algorithm (LMA). For details of
  128. * the algorithm, search it on the wikipedia.
  129. *
  130. * F(s) = |p(s) - p|^2
  131. *
  132. * The jacobi matrix in matrix form is
  133. * J(s) = 2(p(s) - p) ⋅ p'(s)
  134. *
  135. * , which is a 1 x 1 matrix
  136. *
  137. * LMA is robust for smooth one dimensional problem like this case. However, be
  138. * aware that it could fall into local minimal.
  139. */
  140. double SplineParametricCurve::closest_tau(const Eigen::Vector2d& p,
  141. double t0) const
  142. {
  143. using namespace Eigen;
  144. using CubicSpline = Spline<double, 2, 3>;
  145. struct Functor
  146. {
  147. Functor(const CubicSpline& spline, const Eigen::Vector2d& p)
  148. : m_spline(spline), m_point(p)
  149. {
  150. }
  151. int inputs() const
  152. {
  153. return 1;
  154. }
  155. int values() const
  156. {
  157. return 1;
  158. }
  159. int operator()(const VectorXd& x, VectorXd& fvec) const
  160. {
  161. fvec(0) = (m_spline(x(0)).matrix() - m_point).squaredNorm();
  162. return 0;
  163. }
  164. int df(const VectorXd& x, MatrixXd& fjac) const
  165. {
  166. double t = x(0);
  167. Matrix2d der = m_spline.derivatives(t, 1);
  168. fjac(0, 0) = 2 * (der.col(0) - m_point).dot(der.col(1));
  169. return 0;
  170. }
  171. private:
  172. const CubicSpline& m_spline;
  173. const Eigen::Vector2d& m_point;
  174. };
  175. Functor functor(m_spline, p);
  176. LevenbergMarquardt<Functor> lm(functor);
  177. lm.parameters.ftol = 1.e-4;
  178. lm.parameters.xtol = 1.e-4;
  179. VectorXd x(1);
  180. x(0) = t0;
  181. // for smooth cubic function, LMA should be able to find a solution
  182. (void)lm.minimize(x);
  183. return x(0);
  184. }
  185. std::tuple<bool, double, double> SplineParametricCurve::closest_tau_and_offset(
  186. const Eigen::Vector2d& pos, const double acceptable_dis) const
  187. {
  188. bool found = false;
  189. double opt_tau, opt_offset = 0.;
  190. // try with multiple initial guesses
  191. for (int i = 0; i <= 5; ++i)
  192. {
  193. double t0 = i * 0.2;
  194. double tau = closest_tau(pos, t0);
  195. if (tau >= 0. && tau < 1.0)
  196. {
  197. // 0 means we disallow extrapolation
  198. // note that if |tau| is too large, the result become unreliable
  199. double offset = (pos - m_spline(tau).matrix()).dot(norm(tau));
  200. if (std::abs(offset) < acceptable_dis)
  201. {
  202. return std::make_tuple(true, tau, offset);
  203. }
  204. else if (!found)
  205. {
  206. opt_offset = offset;
  207. opt_tau = tau;
  208. found = true;
  209. }
  210. else if (std::abs(offset) < std::abs(opt_offset))
  211. {
  212. opt_offset = offset;
  213. opt_tau = tau;
  214. }
  215. }
  216. }
  217. return std::make_tuple(found, opt_tau, opt_offset);
  218. }
  219. } // namespace geo
  220. } // namespace ns