World Builder  1.1.0-pre
A geodynamic initial conditions generator
bezier_curve.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2018-2024 by the authors of the World Builder code.
3 
4  This file is part of the World Builder.
5 
6  This program is free software: you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as published
8  by the Free Software Foundation, either version 2 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public License
17  along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19 #include "world_builder/assert.h"
20 #include "world_builder/nan.h"
22 
23 #include <cmath>
24 #include <cstddef>
25 #include <iomanip>
26 #include <limits>
27 #include <sstream>
28 
29 using namespace WorldBuilder;
30 
31 namespace WorldBuilder
32 {
33  namespace Objects
34  {
35  /*
36  Takes a vector of points and interpolates a spline using a cubic Bezier curve, for more information see this wikipedia
37  page https://en.wikipedia.org/wiki/B%C3%A9zier_curve
38  */
39  BezierCurve::BezierCurve(const std::vector<Point<2> > &p, const std::vector<double> &angle_constraints_input)
40  {
41  points = p;
42  const size_t n_points = p.size();
43  control_points.resize(n_points-1, {{p[0],p[0]}});
44  lengths.resize(n_points-1,NaN::DSNAN);
45  angles.resize(n_points,NaN::DSNAN);
46  std::vector<double> angle_constraints = angle_constraints_input;
47  angle_constraints.resize(n_points,NaN::DQNAN);
48 
49  // if no angle is provided, compute the angle as the average angle between the previous and next point.
50  // The first angle points at the second point and the last angle points at the second to last point.
51  // The check points are set at a distance of 1/10th the line length from the point in the direction of the angle.
52  if (std::isnan(angle_constraints[0]))
53  {
54  Point<2> P1P2 = points[1]-points[0];
55  angles[0] = atan2(P1P2[1],P1P2[0]);
56  }
57  else
58  {
59  angles[0] = angle_constraints[0];
60  }
61 
62  // If there are more than 2 points provided, calculate the average angle between a given point and the previous point,
63  // and the following point. If only 2 points are provided, this for loop is skipped.
64  for (size_t p_i = 1; p_i < n_points-1; ++p_i)
65  {
66  // first determine the angle
67  if (std::isnan(angle_constraints[p_i]))
68  {
69  // Calculate the line between the current point and the previous point
70  const Point<2> P1P2 = points[p_i-1]-points[p_i];
71  // Calculate the line between the current point and the following point
72  const Point<2> P3P2 = points[p_i+1]-points[p_i];
73 
74  // Calculate the angles of the two lines determined above
75  const double angle_p1p2 = atan2(P1P2[1],P1P2[0]);
76  const double angle_p3p1 = atan2(P3P2[1],P3P2[0]);
77  // Calculate the average angle
78  const double average_angle = (angle_p1p2 + angle_p3p1)*0.5;
79  angles[p_i] = average_angle;
80  angles[p_i] -= Consts::PI*0.5;
81  }
82  else
83  {
84  angles[p_i] = angle_constraints[p_i];
85  }
86 
87  }
88 
89  if (std::isnan(angle_constraints[n_points-1]))
90  {
91  Point<2> P1P2 = points[n_points-2]-points[n_points-1];
92  angles[n_points-1] = atan2(P1P2[1],P1P2[0]);
93  }
94  else
95  {
96  angles[n_points-1] = angle_constraints[n_points-1];
97  }
98 
99  // If there are more than 2 points provided, loop through the points and determine the control points
100  if (points.size() > 2)
101  {
102  // next determine the location of the control points
103  // the location of the control point is 2/10th p1p2 distance in the direction of the angle.
104  // make sure the angle is pointing away from the next point, e.g.
105  // the check point is on the other side of the of the line p1p2 compared to p3.
106  // This first block determines the control points for the special case of the first
107  // 3 points.
108  const double fraction_of_length = 0.2;
109  {
110  const Point<2> &p1 = points[0];
111  const Point<2> &p2 = points[1];
112  const Point<2> &p3 = points[2];
113  const double length = (points[0]-points[1]).norm(); // can be squared
114  control_points[0][0][0] = cos(angles[0])*length*fraction_of_length+p1[0];
115  control_points[0][0][1] = sin(angles[0])*length*fraction_of_length+p1[1];
116  control_points[0][1][0] = cos(angles[1])*length*fraction_of_length+p2[0];
117  control_points[0][1][1] = sin(angles[1])*length*fraction_of_length+p2[1];
118  {
119  // Determine which side of the line the control points lie on
120  const bool side_of_line_1 = (p1[0] - p2[0]) * (control_points[0][1][1] - p1[1])
121  - (p1[1] - p2[1]) * (control_points[0][1][0] - p1[0])
122  < 0;
123  const bool side_of_line_2 = (p1[0] - p2[0]) * (p3[1] - p1[1])
124  - (p1[1] - p2[1]) * (p3[0] - p1[0])
125  < 0;
126  if (side_of_line_1 == side_of_line_2)
127  {
128  // use a 180 degree rotated angle to create this control_point
129  control_points[0][1][0] = cos(angles[1]+Consts::PI)*length*fraction_of_length+p2[0];
130  control_points[0][1][1] = sin(angles[1]+Consts::PI)*length*fraction_of_length+p2[1];
131  }
132  }
133  }
134 
135  for (size_t p_i = 1; p_i < n_points-1; ++p_i)
136  {
137  const Point<2> &p1 = points[p_i];
138  const Point<2> &p2 = points[p_i+1];
139  const double length = (points[p_i]-points[p_i+1]).norm(); // can be squared
140  control_points[p_i][0][0] = cos(angles[p_i])*length*fraction_of_length+p1[0];
141  control_points[p_i][0][1] = sin(angles[p_i])*length*fraction_of_length+p1[1];
142 
143  {
144  // Determine which side of the line the control points lie on
145  const bool side_of_line_1 = (p1[0] - p2[0]) * (control_points[p_i-1][1][1] - p1[1])
146  - (p1[1] - p2[1]) * (control_points[p_i-1][1][0] - p1[0])
147  < 0;
148  const bool side_of_line_2 = (p1[0] - p2[0]) * (control_points[p_i][0][1] - p1[1])
149  - (p1[1] - p2[1]) * (control_points[p_i][0][0] - p1[0])
150  < 0;
151  if (side_of_line_1 == side_of_line_2)
152  {
153  // use a 180 degree rotated angle to create this control_point
154  control_points[p_i][0][0] = cos(angles[p_i]+Consts::PI)*length*fraction_of_length+p1[0];
155  control_points[p_i][0][1] = sin(angles[p_i]+Consts::PI)*length*fraction_of_length+p1[1];
156  }
157  }
158 
159  control_points[p_i][1][0] = cos(angles[p_i+1])*length*fraction_of_length+points[p_i+1][0];
160  control_points[p_i][1][1] = sin(angles[p_i+1])*length*fraction_of_length+points[p_i+1][1];
161 
162  if (p_i+1 < n_points-1)
163  {
164  const Point<2> &p3 = points[p_i+2];
165  const bool side_of_line_1 = (p1[0] - p2[0]) * (control_points[p_i][1][1] - p1[1])
166  - (p1[1] - p2[1]) * (control_points[p_i][1][0] - p1[0])
167  < 0;
168  const bool side_of_line_2 = (p1[0] - p2[0]) * (p3[1] - p1[1])
169  - (p1[1] - p2[1]) * (p3[0] - p1[0])
170  < 0;
171  if (side_of_line_1 == side_of_line_2)
172  {
173  // use a 180 degree rotated angle to create this control_point
174  control_points[p_i][1][0] = cos(angles[p_i+1]+Consts::PI)*length*fraction_of_length+p2[0];
175  control_points[p_i][1][1] = sin(angles[p_i+1]+Consts::PI)*length*fraction_of_length+p2[1];
176  }
177  }
178  }
179  }
180  }
181 
182 
183  Point<2>
184  BezierCurve::operator()(const size_t i, const double t) const
185  {
186  WBAssert(i < points.size()-1 && i < control_points.size() ,
187  "Trying to access index " << i << ", but points.size() = " << points.size() << ", and control_points = " << control_points.size() << ".");
188  return (1-t)*(1-t)*(1-t)*points[i] + 3*(1-t)*(1-t)*t*control_points[i][0] + 3.*(1-t)*t*t*control_points[i][1]+t*t*t*points[i+1];
189  }
190 
191 
194  const bool verbose) const
195  {
196  ClosestPointOnCurve closest_point_on_curve;
197  const Point<2> &cp = check_point;
198  double min_squared_distance = std::numeric_limits<double>::infinity();
200  {
201  for ( size_t cp_i = 0; cp_i < control_points.size(); ++cp_i)
202  {
203 #ifndef NDEBUG
204  std::stringstream output;
205 #endif
206  const Point<2> &p1 = points[cp_i];
207  const Point<2> &p2 = points[cp_i+1];
208  //min_squared_distance = std::min(std::min(min_squared_distance,(check_point-p1).norm_square()),(check_point-p1).norm_square());
209 
210  // Getting an estimate for where the closest point is with a linear approximation
211  const Point<2> P1P2 = p2-p1;
212  const Point<2> P1Pc = check_point-p1;
213 
214  const double P2P2_dot = P1P2*P1P2;
215 
216  double est = P2P2_dot > 0.0 ? std::min(1.,std::max(0.,(P1Pc*P1P2) / P2P2_dot)) : 1.0; // est=estimate of solution
217  bool found = false;
218 
219  // based on https://stackoverflow.com/questions/2742610/closest-point-on-a-cubic-bezier-curve
220  const double a_0 = 3.*control_points[cp_i][0][0]-3.*control_points[cp_i][1][0]+points[cp_i+1][0]-points[cp_i][0];
221  const double a_1 = 3.*control_points[cp_i][0][1]-3.*control_points[cp_i][1][1]+points[cp_i+1][1]-points[cp_i][1];
222  const double b_0 = 3.*points[cp_i][0] - 6.*control_points[cp_i][0][0]+3.*control_points[cp_i][1][0];
223  const double b_1 = 3.*points[cp_i][1] - 6.*control_points[cp_i][0][1]+3.*control_points[cp_i][1][1];
224  const double c_0 = -3.*points[cp_i][0] + 3.*control_points[cp_i][0][0];
225  const double c_1 = -3.*points[cp_i][1] + 3.*control_points[cp_i][0][1];
226  const double d_0 = points[cp_i][0];
227  const double d_1 = points[cp_i][1];
228 
229  const double d_min_cp_0 = d_0-cp[0];
230  const double d_min_cp_1 = d_1-cp[1];
231 
232 #ifndef NDEBUG
233  double estimate_point_min_cp_0_dg = a_0*est*est*est+b_0*est*est+c_0*est+d_min_cp_0;
234  double estimate_point_min_cp_1_dg = a_1*est*est*est+b_1*est*est+c_1*est+d_min_cp_1;
235  double min_squared_distance_cartesian_temp_dg = (estimate_point_min_cp_0_dg*estimate_point_min_cp_0_dg)+(estimate_point_min_cp_1_dg*estimate_point_min_cp_1_dg);
236 #endif
237 
238  for (size_t newton_i = 0; newton_i < 150; newton_i++)
239  {
240 #ifndef NDEBUG
241  output << " wolfram alpha: (" << a_0 << "*x^3+" << b_0 << "*x^2+"<< c_0 << "*x+" << d_0 << "-" << cp[0] << ")^2+(" << a_1 << "*x^3+" << b_1 << "*x^2+"<< c_1 << "*x+" << d_1 << "-" << cp[1] << ")^2 with x=" << est << std::endl;
242 #endif
243  const double est_sq = est*est;
244  const double estimate_point_min_cp_0 = a_0*est_sq*est+b_0*est_sq+c_0*est+d_min_cp_0;
245  const double estimate_point_min_cp_1 = a_1*est_sq*est+b_1*est_sq+c_1*est+d_min_cp_1;
246 
247  const double deriv_0 = 3.0*a_0*est_sq+2.0*b_0*est+c_0;
248  const double deriv_1 = 3.0*a_1*est_sq+2.0*b_1*est+c_1;
249  const double squared_distance_cartesian = (estimate_point_min_cp_0*estimate_point_min_cp_0)+(estimate_point_min_cp_1*estimate_point_min_cp_1);
250 
251  const double squared_distance_cartesian_derivative = 2.0*(deriv_0*estimate_point_min_cp_0 + deriv_1*estimate_point_min_cp_1);
252  const double squared_distance_cartesian_second_derivative_abs = std::fabs(2.0*((6.0*a_0*est+2.0*b_0)*estimate_point_min_cp_0 + deriv_0*deriv_0
253  + (6.0*a_1*est+2.0*b_1)*estimate_point_min_cp_1 + deriv_1*deriv_1));
254 
255  if (squared_distance_cartesian_second_derivative_abs <= 0.0)
256  {
257  found = true;
258  break;
259  }
260 
261  // the local minimum is where squared_distance_cartesian_derivative=0 and squared_distance_cartesian_derivative>=0
262  const double update = std::min(0.5,std::max(-0.5,squared_distance_cartesian_derivative/squared_distance_cartesian_second_derivative_abs));
263  double line_search = 1.;
264 
265  if (std::fabs(update) > 1e-1)
266  {
267  double est_test = est-update*line_search;
268  double squared_distance_cartesian_test = squared_distance_cartesian;
269  double squared_distance_cartesian_test_previous = squared_distance_cartesian;
270 
271  for (unsigned int i = 0; i < 10; i++)
272  {
273  est_test = est-update*line_search;
274  const double est_test_sq = est_test*est_test;
275  const double estimate_point_min_cp_test_0 = a_0*est_test_sq*est_test+b_0*est_test_sq+c_0*est_test+d_min_cp_0;
276  const double estimate_point_min_cp_test_1 = a_1*est_test_sq*est_test+b_1*est_test_sq+c_1*est_test+d_min_cp_1;
277 
278  squared_distance_cartesian_test = (estimate_point_min_cp_test_0*estimate_point_min_cp_test_0)+(estimate_point_min_cp_test_1*estimate_point_min_cp_test_1);
279 
280 #ifndef NDEBUG
281  const Point<2> a = 3.*control_points[cp_i][0]-3.*control_points[cp_i][1]+points[cp_i+1]-points[cp_i];
282  const Point<2> b = 3.*points[cp_i] - 6.*control_points[cp_i][0]+3.*control_points[cp_i][1];
283  const Point<2> c = -3.*points[cp_i] + 3.*control_points[cp_i][0];
284  const Point<2> d = points[cp_i];
285  const double squared_distance_cartesian_derivative_test = 2.0*(3.0*a_0*est_test*est_test+2.0*b_0*est_test+c_0)*(a_0*est_test*est_test*est_test+b_0*est_test*est_test+c_0*est_test+d_0-cp[0])
286  + 2.0*(3.0*a_1*est_test*est_test+2.0*b_1*est_test+c_1)*(a_1*est_test*est_test*est_test+b_1*est_test*est_test+c_1*est_test+d_1-cp[1]);
287  const double squared_distance_cartesian_second_derivative_test = 2.0*(6.0*a_0*est_test + 2.0*b_0)*(a_0*est_test*est_test*est_test+b_0*est_test*est_test+c_0*est_test+d_0-cp[0])
288  + 2.0*(3.0*a_0*est_test*est_test + 2.0*b_0*est_test + c_0)*(3.0*a_0*est_test*est_test + 2.0*b_0*est_test + c_0)
289  + 2.0*(6.0*a_1*est_test + 2.0*b_1)*(a_1*est_test*est_test*est_test+b_1*est_test*est_test+c_1*est_test+d_1-cp[1])
290  + 2.0*(3.0*a_1*est_test*est_test + 2.0*b_1*est_test + c_1)*(3.0*a_1*est_test*est_test + 2.0*b_1*est_test + c_1) ;
291  output << " i: " << cp_i << ", ni: " << newton_i<< ", lsi: " << i << ", line_search_step=" << 2./3. << ": squared_distance_cartesian_test = " << squared_distance_cartesian_test << ", diff= " << squared_distance_cartesian_test-squared_distance_cartesian
292  << ", tests: " << (squared_distance_cartesian_test_previous < squared_distance_cartesian ? "true" : "false") << ":" << (squared_distance_cartesian_test > squared_distance_cartesian_test_previous ? "true" : "false") << ", est_test=" << est_test
293  << ", update=" << update << ", ls=" << line_search << ", up*ls=" << update *line_search << ", test deriv =" << squared_distance_cartesian_derivative_test << ", test update=" << squared_distance_cartesian_derivative_test/fabs(squared_distance_cartesian_second_derivative_test)
294  << ", p1=" << p1 << ", p2= " << p2 << ", poc= " << a *est_test *est_test *est_test + b *est_test *est_test+c *est_test+d << ", cp= " << check_point << ", ds:" << ((a*est_test*est_test*est_test+b*est_test*est_test+c*est_test+d)-check_point).norm_square() << ":" << min_squared_distance_cartesian_temp_dg
295  << ", diff = " << squared_distance_cartesian_test-min_squared_distance_cartesian_temp_dg << std::endl;
296 #endif
297  if (i > 0 && (squared_distance_cartesian_test > squared_distance_cartesian_test_previous))
298  {
299  if (squared_distance_cartesian_test_previous-squared_distance_cartesian < 0)
300  {
301  line_search *= 3./2.;
302  break;
303  }
304  }
305  squared_distance_cartesian_test_previous = squared_distance_cartesian_test;
306 
307  line_search *= 2./3.;
308  }
309  }
310 
311  est -= update*line_search;
312 
313  if (std::fabs(update) < 1e-4 || est < -0.1 || est > 1.1)
314  {
315  found = true;
316  break;
317  }
318  }
319 #ifndef NDEBUG
320  WBAssertThrow(found, "Could not find a good solution. " << output.str());
321 #else
322  WBAssertThrow(found, "Could not find a good solution. Enable debug mode for more info.");
323 #endif
324 
325  const double est_min_cp_end_0 = a_0*est*est*est+b_0*est*est+c_0*est+d_min_cp_0;
326  const double est_min_cp_end_1 = a_1*est*est*est+b_1*est*est+c_1*est+d_min_cp_1;
327  const double min_squared_distance_temp = (est_min_cp_end_0*est_min_cp_end_0)+(est_min_cp_end_1*est_min_cp_end_1);
328  if (min_squared_distance_temp < min_squared_distance)
329  {
330  if (est >= -1e-8 && static_cast<double>(cp_i)+est > 0 && est-1. <= 1e-8 && est-1. < static_cast<double>(cp_i))
331  {
332  min_squared_distance = min_squared_distance_temp;
333  const Point<2> point_on_curve = Point<2>(a_0*est*est*est+b_0*est*est+c_0*est+d_0,a_1*est*est*est+b_1*est*est+c_1*est+d_1,cp.get_coordinate_system());
334  WBAssert(!std::isnan(point_on_curve[0]) && !std::isnan(point_on_curve[1]), "Point on curve has NAN entries: " << point_on_curve);
335  // the sign is rotating the derivative by 90 degrees.
336  // When moving in the direction of increasing t, left is positive and right is negative.
337  // https://www.wolframalpha.com/input?i=d%2Fdt+%281-t%29*%281-t%29*%281-t%29*a+%2B+3*%281-t%29*%281-t%29*t*b+%2B+3.*%281-t%29*t*t*c%2Bt*t*t*d
338  const Point<2> derivative_point = points[cp_i]*((6.-3.*est)*est-3.) + control_points[cp_i][0]*(est*(9*est-12)+3)
339  + control_points[cp_i][1]*(6.-9.*est)*est + points[cp_i+1]*3.*est*est;
340 
341  Point<2> tangent_point = derivative_point - point_on_curve;
342  // if angle between check point and tangent point is larger than 90 degrees, return a negative distance
343  const double dot_product = (tangent_point*(check_point-point_on_curve));
344  const double sign = dot_product < 0. ? -1. : 1.;
345  tangent_point = Point<2>(-tangent_point[1],tangent_point[0],tangent_point.get_coordinate_system());
346 
347  closest_point_on_curve.distance = sign*std::sqrt(min_squared_distance);
348  closest_point_on_curve.parametric_fraction = est;
349  closest_point_on_curve.interpolation_fraction = NaN::DSNAN; //arc_length(i,real_roots[root_i])/lengths[i];
350  closest_point_on_curve.index = cp_i;
351  closest_point_on_curve.point = point_on_curve;
352  WBAssert(!std::isnan(point_on_curve[0]) && !std::isnan(point_on_curve[1]), "Point on curve has NAN entries: " << point_on_curve);
353  Point<2> normal = point_on_curve;
354  {
355  Point<2> derivative = Point<2>(a_0*est*est+b_0*est+c_0,a_1*est*est+b_1*est+c_1,cp.get_coordinate_system());
356  normal=derivative;
357  const double normal_size = derivative.norm();
358  if (normal_size > 0.)
359  {
360  normal[0] = derivative[1]/normal_size;
361  normal[1] = -derivative[0]/normal_size;
362  }
363  }
364  closest_point_on_curve.normal = normal;
365  }
366  }
367  }
368  }
369  else
370  {
371  const double cos_cp_lat = cos(cp[1]);
372  for ( size_t cp_i = 0; cp_i < control_points.size(); ++cp_i)
373  {
374  const Point<2> &p1 = points[cp_i];
375  const Point<2> &p2 = points[cp_i+1];
376  // Getting an estimate for where the closest point is with a linear approximation
377  const Point<2> P1P2 = p2-p1;
378  const Point<2> P1Pc = check_point-p1;
379 
380  const double P2P2_dot = P1P2*P1P2;
381 
382  double est = P2P2_dot > 0.0 ? std::min(1.,std::max(0.,(P1Pc*P1P2) / P2P2_dot)) : 1.0; // est=estimate of solution
383  bool found = false;
384 
385  // only used if verbose is true
386  std::stringstream output;
387 
388  Point<2> a = 3.*control_points[cp_i][0]-3.*control_points[cp_i][1]+points[cp_i+1]-points[cp_i];
389  Point<2> b = 3.*points[cp_i] - 6.*control_points[cp_i][0]+3.*control_points[cp_i][1];
390  Point<2> c = -3.*points[cp_i] + 3.*control_points[cp_i][0];
391  const Point<2> d = points[cp_i];
392 
393  Point<2> estimate_point = a*est*est*est+b*est*est+c*est+d;
394 
395  double cos_lat_dg = NaN::DSNAN;
396  double sin_d_long_h_dg = NaN::DSNAN;
397  double sin_d_lat_h_dg = NaN::DSNAN;
398  double min_squared_distance_cartesian_temp_dg = NaN::DSNAN;
399  if (verbose == true)
400  {
401  cos_lat_dg = cos(estimate_point[1]);
402  sin_d_long_h_dg = sin((estimate_point[0]-cp[0])*0.5);
403  sin_d_lat_h_dg = sin((estimate_point[1]-cp[1])*0.5);
404  min_squared_distance_cartesian_temp_dg = sin_d_lat_h_dg*sin_d_lat_h_dg+sin_d_long_h_dg*sin_d_long_h_dg*cos_cp_lat*cos_lat_dg;
405  output << "cp_i=" << cp_i << ", init est = " << est << ", min_squared_distance = " << min_squared_distance << ", min_squared_distance_cartesian_temp_dg: " << min_squared_distance_cartesian_temp_dg << ", p1: " << p1 << ", p2: " << p2 << std::endl;
406  output << std::setprecision(6) << " wolfram: sin((" << a[1] << "*x^3+" << b[1] << "*x^2+"<< c[1] << "*x+" << d[1] << "-" << cp[1] << ")*.5)^2+sin((" << a[0] << "*x^3+" << b[0] << "*x^2+"<< c[0] << "*x+" << d[0] << "-" << cp[0] << ")*.5)^2*cos(" << cp[1] << ")*cos(" << a[1] << "*x^3+" << b[1] << "*x^2+"<< c[1] << "*x+" << d[1] << "-" << cp[1] << ") with x=" << est << std::endl;
407  output << std::setprecision(10) << " python: y=np.sin((" << a[1] << "*x**3+" << b[1] << "*x**2+"<< c[1] << "*x+" << d[1] << "-" << cp[1] << ")*.5)**2+np.sin((" << a[0] << "*x**3+" << b[0] << "*x**2+"<< c[0] << "*x+" << d[0] << "-" << cp[0] << ")*.5)**2*np.cos(" << cp[1] << ")*np.cos(" << a[1] << "*x**3+" << b[1] << "*x**2+"<< c[1] << "*x+" << d[1] << "-" << cp[1] << "); x=" << est << std::endl;
408  }
409  for (size_t newton_i = 0; newton_i < 150; newton_i++)
410  {
411  // based on https://stackoverflow.com/questions/2742610/closest-point-on-a-cubic-bezier-curve
412  estimate_point = a*est*est*est+b*est*est+c*est+d;
413 
414  double sin_d_long_h = sin((estimate_point[0]-cp[0])*0.5);
415  double sin_d_lat_h = sin((estimate_point[1]-cp[1])*0.5);
416  const double cos_d_lat = cos(estimate_point[1]-cp[1]);
417  const double squared_distance_cartesian = sin_d_lat_h*sin_d_lat_h+sin_d_long_h*sin_d_long_h*cos_cp_lat*cos_d_lat;
418 
419  double sin_dlat = sin(estimate_point[1]-cp[1]);
420  const double cos_dlong_h = cos(0.5*(estimate_point[0]-cp[0]));
421  double cos_dlat_h = cos(0.5*(estimate_point[1]-cp[1]));
422  double deriv_long = (3.0*a[0]*est*est+2.0*b[0]*est+c[0]);
423  double deriv_lat = (3.0*a[1]*est*est+2.0*b[1]*est+c[1]);
424 
425  const double squared_distance_cartesian_derivative = cos_cp_lat*(-deriv_lat)*sin_d_long_h*sin_d_long_h*sin_dlat+cos_cp_lat*deriv_long*sin_d_long_h*cos_dlong_h*cos_d_lat+deriv_lat*sin_d_lat_h*cos_dlat_h;
426  double update = NaN::DSNAN;
427  if (std::fabs(squared_distance_cartesian_derivative) > 1e-15)
428  {
429  const double squared_distance_cartesian_second_derivative = cos_cp_lat*cos_d_lat*(-0.5*deriv_long*deriv_long*sin_d_long_h*sin_d_long_h+0.5*deriv_long*deriv_long*cos_dlong_h*cos_dlong_h+(6.0*a[0]*est+2.0*b[0])*sin_d_long_h*cos_dlong_h)+cos_cp_lat*sin_d_long_h*sin_d_long_h*(deriv_lat*deriv_lat*(-cos_d_lat)-(6.0*a[1]*est+2.0*b[1])*sin_dlat)-2.0*cos_cp_lat*deriv_long*deriv_lat*sin_d_long_h*cos_dlong_h*sin_dlat-0.5*deriv_lat*deriv_lat*sin_d_lat_h*sin_d_lat_h+0.5*deriv_lat*deriv_lat*cos_dlat_h*cos_dlat_h+(6.0*a[1]*est+2.0*b[1])*sin_d_lat_h*cos_dlat_h;
430 
431  if (verbose == true)
432  {
433  const double squared_distance_cartesian_full = sin((a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1])*0.5)*sin((a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1])*0.5)+sin((a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0])*0.5)*sin((a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0])*0.5)*cos(cp[1])*cos(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]);
434  const double squared_distance_cartesian_derivative_full = cos(cp[1])*(-(3.0*a[1]*est*est+2.0*b[1]*est+c[1]))*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*sin(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1])+cos(cp[1])*(3.0*a[0]*est*est+2.0*b[0]*est+c[0])*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*cos(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*cos(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1])+(3.0*a[1]*est*est+2.0*b[1]*est+c[1])*sin(0.5*(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]))*cos(0.5*(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]));
435  const double squared_distance_cartesian_second_derivative_full = cos(cp[1])*cos(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1])*(-0.5*(3.0*a[0]*est*est+2.0*b[0]*est+c[0])*(3.0*a[0]*est*est+2.0*b[0]*est+c[0])*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))+0.5*(3.0*a[0]*est*est+2.0*b[0]*est+c[0])*(3.0*a[0]*est*est+2.0*b[0]*est+c[0])*cos(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*cos(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))+(6.0*a[0]*est+2.0*b[0])*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*cos(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0])))+cos(cp[1])*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*((3.0*a[1]*est*est+2.0*b[1]*est+c[1])*(3.0*a[1]*est*est+2.0*b[1]*est+c[1])*(-cos(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]))-(6.0*a[1]*est+2.0*b[1])*sin(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]))-2.0*cos(cp[1])*(3.0*a[0]*est*est+2.0*b[0]*est+c[0])*(3.0*a[1]*est*est+2.0*b[1]*est+c[1])*sin(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*cos(0.5*(a[0]*est*est*est+b[0]*est*est+c[0]*est+d[0]-cp[0]))*sin(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1])-0.5*(3.0*a[1]*est*est+2.0*b[1]*est+c[1])*(3.0*a[1]*est*est+2.0*b[1]*est+c[1])*sin(0.5*(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]))*sin(0.5*(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]))+0.5*(3.0*a[1]*est*est+2.0*b[1]*est+c[1])*(3.0*a[1]*est*est+2.0*b[1]*est+c[1])*cos(0.5*(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]))*cos(0.5*(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]))+(6.0*a[1]*est+2.0*b[1])*sin(0.5*(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]))*cos(0.5*(a[1]*est*est*est+b[1]*est*est+c[1]*est+d[1]-cp[1]));
436  output <<"sqd = " << squared_distance_cartesian <<":" << squared_distance_cartesian_full << ", diff=" << squared_distance_cartesian-squared_distance_cartesian_full << ", sqdd: " << squared_distance_cartesian_derivative <<":" << squared_distance_cartesian_derivative_full << ", diff="<< squared_distance_cartesian_derivative-squared_distance_cartesian_derivative_full << ", sqdd: " << squared_distance_cartesian_second_derivative << ":" << squared_distance_cartesian_second_derivative_full << ", diff= " << squared_distance_cartesian_second_derivative-squared_distance_cartesian_second_derivative_full << ", est: " << est << std::endl;
437  }
438  // the local minimum is where squared_distance_cartesian_derivative=0 and squared_distance_cartesian_derivative>=0
439  update = std::min(0.5,std::max(-0.5,squared_distance_cartesian_derivative/std::fabs(squared_distance_cartesian_second_derivative)));
440  double line_search = 1.;
441  double est_test = est-update*line_search;
442  double squared_distance_cartesian_test = squared_distance_cartesian;
443  double squared_distance_cartesian_test_previous = squared_distance_cartesian;
444  double line_search_step = 2./3.;
445 
446  for (unsigned int i = 0; i < 10; i++)
447  {
448  est_test = est-update*line_search;
449  estimate_point = a*est_test*est_test*est_test+b*est_test*est_test+c*est_test+d;
450 
451  sin_d_long_h = sin((estimate_point[0]-cp[0])*0.5);
452  sin_d_lat_h = sin((estimate_point[1]-cp[1])*0.5);
453  squared_distance_cartesian_test = sin_d_lat_h*sin_d_lat_h+sin_d_long_h*sin_d_long_h*cos_cp_lat*cos(estimate_point[1]-cp[1]);
454 
455  if (verbose == true)
456  {
457  sin_dlat = sin(estimate_point[1]-cp[1]);
458  deriv_long = (3.0*a[0]*est_test*est_test+2.0*b[0]*est_test+c[0]);
459  deriv_lat = (3.0*a[1]*est_test*est_test+2.0*b[1]*est_test+c[1]);
460  const double squared_distance_cartesian_derivative_test = cos_cp_lat*(-deriv_lat)*sin_d_long_h*sin_d_long_h*sin_dlat+cos_cp_lat*deriv_long*sin_d_long_h*cos_dlong_h*cos_d_lat+deriv_lat*sin_d_lat_h*cos_dlat_h;
461  const double squared_distance_cartesian_second_derivative_test = cos_cp_lat*cos_d_lat*(-0.5*deriv_long*deriv_long*sin_d_long_h*sin_d_long_h+0.5*deriv_long*deriv_long*cos_dlong_h*cos_dlong_h+(6.0*a[0]*est_test+2.0*b[0])*sin_d_long_h*cos_dlong_h)+cos_cp_lat*sin_d_long_h*sin_d_long_h*(deriv_lat*deriv_lat*(-cos_d_lat)-(6.0*a[1]*est_test+2.0*b[1])*sin_dlat)-2.0*cos_cp_lat*deriv_long*deriv_lat*sin_d_long_h*cos_dlong_h*sin_dlat-0.5*deriv_lat*deriv_lat*sin_d_lat_h*sin_d_lat_h+0.5*deriv_lat*deriv_lat*cos_dlat_h*cos_dlat_h+(6.0*a[1]*est_test+2.0*b[1])*sin_d_lat_h*cos_dlat_h;
462  output << " i: " << cp_i << ", ni: " << newton_i<< ", lsi: " << i << ", line_search_step=" << line_search_step << ": squared_distance_cartesian_test = " << squared_distance_cartesian_test << ", diff= " << squared_distance_cartesian_test-squared_distance_cartesian << ", tests: " << (squared_distance_cartesian_test_previous < squared_distance_cartesian ? "true" : "false") << ":" << (squared_distance_cartesian_test > squared_distance_cartesian_test_previous ? "true" : "false") << ", est_test=" << est_test << ", update=" << update << ", ls=" << line_search << ", up*ls=" << update *line_search << ", test deriv =" << squared_distance_cartesian_derivative_test << ", test update=" << squared_distance_cartesian_derivative_test/fabs(squared_distance_cartesian_second_derivative_test) << ", p1=" << p1 << ", p2= " << p2 << ", poc= " << a *est_test *est_test *est_test+b *est_test *est_test+c *est_test+d << ", cp= " << check_point << ", ds:" << ((a*est_test*est_test*est_test+b*est_test*est_test+c*est_test+d)-check_point).norm_square() << ":" << min_squared_distance_cartesian_temp_dg << ", diff = " << squared_distance_cartesian_test-min_squared_distance_cartesian_temp_dg<< std::endl;
463  }
464  if (i > 0 && (squared_distance_cartesian_test > squared_distance_cartesian_test_previous))
465  {
466  if (squared_distance_cartesian_test_previous-squared_distance_cartesian < 0)
467  {
468  line_search *= 1/line_search_step;
469  break;
470  }
471  if (i> 1)
472  {
473  line_search *= (1/line_search_step)*(1/line_search_step);
474  est_test = est-update*line_search;
475  estimate_point = a*est_test*est_test*est_test+b*est_test*est_test+c*est_test+d;
476 
477  sin_d_long_h = sin((estimate_point[0]-cp[0])*0.5);
478  sin_d_lat_h = sin((estimate_point[1]-cp[1])*0.5);
479  squared_distance_cartesian_test_previous = sin_d_lat_h*sin_d_lat_h+sin_d_long_h*sin_d_long_h*cos_cp_lat*cos(estimate_point[1]-cp[1]);
480  line_search_step = std::min(line_search_step*(11./10.),0.95);
481  continue;
482  }
483  }
484  squared_distance_cartesian_test_previous = squared_distance_cartesian_test;
485 
486  line_search *= line_search_step;
487  }
488  if (verbose == true)
489  {
490  output << " i: " << cp_i << ", ni: " << newton_i<< ", est= " << est-update *line_search << ", ls:" << line_search << ": squared_distance_cartesian_test = " << squared_distance_cartesian_test << ", diff= " << squared_distance_cartesian_test-squared_distance_cartesian << std::endl;
491  }
492  est -= update*line_search;
493  }
494 
495  if (std::fabs(squared_distance_cartesian_derivative) <= 1e-15 || std::fabs(update) < 1e-4 || est < -0.1 || est > 1.1)
496  {
497  found = true;
498  break;
499  }
500  }
501 
502  if (verbose == true && found == false)
503  {
504  // report the error and print the output
505  WBAssertThrow(found, "Could not find a good solution. " << output.str());
506  }
507  else if (verbose == false && found == false)
508  {
509  // redo the iteration with verbose=true to be able to report the error
510  return closest_point_on_curve_segment(check_point, true);
511  }
512 
513  estimate_point = a*est*est*est+b*est*est+c*est+d;
514 
515  const double sin_d_long_h = sin((estimate_point[0]-cp[0])*0.5);
516  const double sin_d_lat_h = sin((estimate_point[1]-cp[1])*0.5);
517 
518  const double min_squared_distance_cartesian_temp = sin_d_lat_h*sin_d_lat_h+sin_d_long_h*sin_d_long_h*cos_cp_lat*cos(estimate_point[1]-cp[1]);
519 
520  if (min_squared_distance_cartesian_temp < min_squared_distance)
521  {
522  if (est >= -1e-8 && static_cast<double>(cp_i)+est > 0 && est-1. <= 1e-8 && est-1. < static_cast<double>(cp_i))
523  {
524  min_squared_distance = min_squared_distance_cartesian_temp;
525  const Point<2> point_on_curve = a*est*est*est+b*est*est+c*est+d;
526 
527  // the sign is rotating the derivative by 90 degrees.
528  // When moving in the direction of increasing t, left is positive and right is negative.
529  // https://www.wolframalpha.com/input?i=d%2Fdt+%281-t%29*%281-t%29*%281-t%29*a+%2B+3*%281-t%29*%281-t%29*t*b+%2B+3.*%281-t%29*t*t*c%2Bt*t*t*d
530  const Point<2> derivative_point = points[cp_i]*((6.-3.*est)*est-3.) + control_points[cp_i][0]*(est*(9*est-12)+3)
531  + control_points[cp_i][1]*(6.-9.*est)*est + points[cp_i+1]*3.*est*est;
532 
533  Point<2> tangent_point = derivative_point - point_on_curve;
534  // if angle between check point and tangent point is larger than 90 degrees, return a negative distance
535  const double dot_product = (tangent_point*(check_point-point_on_curve));
536  const double sign = dot_product < 0. ? -1. : 1.;
537  tangent_point = Point<2>(-tangent_point[1],tangent_point[0],tangent_point.get_coordinate_system());
538 
539  closest_point_on_curve.distance = sign*std::sqrt(min_squared_distance);
540  closest_point_on_curve.parametric_fraction = est;
541  closest_point_on_curve.interpolation_fraction = NaN::DSNAN; //arc_length(i,real_roots[root_i])/lengths[i];
542  closest_point_on_curve.index = cp_i;
543  closest_point_on_curve.point = point_on_curve;
544  Point<2> normal = point_on_curve;
545  {
546  Point<2> derivative = a*est*est+b*est+c;
547  normal=derivative;
548  const double normal_size = derivative.norm();
549  if (normal_size > 0.)
550  {
551  normal[0] = derivative[1]/normal_size;
552  normal[1] = -derivative[0]/normal_size;
553  }
554  }
555  closest_point_on_curve.normal = normal;
556  }
557  }
558  }
559 
560  }
561  return closest_point_on_curve;
562  }
563  } // namespace Objects
564 } // namespace WorldBuilder
Point< 2 > operator()(const size_t i, const double x) const
BezierCurve()=default
Construct a new Bezier Curve object.
const double DSNAN
Definition: nan.h:41
double norm_square() const
Definition: point.h:424
std::vector< Point< 2 > > points
Definition: bezier_curve.h:78
std::vector< std::array< Point< 2 >, 2 > > control_points
Definition: bezier_curve.h:79
CoordinateSystem get_coordinate_system() const
Definition: point.h:403
const double DQNAN
Definition: nan.h:32
#define WBAssert(condition, message)
Definition: assert.h:27
double norm() const
Definition: point.h:413
std::vector< double > angles
Definition: bezier_curve.h:81
double cos(const double angle)
Definition: point.h:97
ClosestPointOnCurve closest_point_on_curve_segment(const Point< 2 > &p, const bool verbose=false) const
Finds the closest point on the curve. If the the closest point doesn&#39;t fall on the segment...
#define WBAssertThrow(condition, message)
Definition: assert.h:40
std::vector< double > lengths
Definition: bezier_curve.h:80
double sin(const double raw_angle)
Definition: point.h:81
constexpr double PI
Definition: consts.h:30