dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
pathfillet.cpp
Go to the documentation of this file.
1 
11 /*
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 3 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20  * for more details.
21  *
22  * You should have received a copy of the GNU General Public License along
23  * with this program; if not, see <http://www.gnu.org/licenses/>
24  */
25 #define _USE_MATH_DEFINES
26 #include <cmath>
27 #include <QInputDialog>
28 #include <cmath>
29 #include <algorithms/pathfillet.h>
30 #include <waypoint.h>
31 
32 #include <physical_constants.h>
33 
34 #define SIGN(x) (x < 0 ? -1 : 1)
35 
36 PathFillet::PathFillet(QObject *parent)
37  : IPathAlgorithm(parent)
38 {
39  // TODO: move into the constructor and come from the UI
40  fillet_radius = 5;
41 }
42 
49 bool PathFillet::configure(QWidget *callingUi)
50 {
51  bool ok;
52  fillet_radius = QInputDialog::getDouble(callingUi, tr("Select filleting radius"), tr("In m:"),
53  fillet_radius, 0, 1000, 1, &ok);
54 
55  return ok;
56 }
63 bool PathFillet::verifyPath(FlightDataModel *model, QString &err)
64 {
65  Q_UNUSED(model);
66  Q_UNUSED(err);
67 
68  return true;
69 }
70 
84 {
85  new_model = new FlightDataModel(this);
86 
87  int newWaypointIdx = 0;
88 
89  float pos_prev[3];
90  float pos_current[3];
91  float pos_next[3];
92 
93  float previous_curvature;
94 
95  for (int wpIdx = 0; wpIdx < model->rowCount(); wpIdx++) {
96 
97  // Get the location
98  pos_current[0] = model->data(model->index(wpIdx, FlightDataModel::NED_NORTH)).toDouble();
99  pos_current[1] = model->data(model->index(wpIdx, FlightDataModel::NED_EAST)).toDouble();
100  pos_current[2] = model->data(model->index(wpIdx, FlightDataModel::NED_DOWN)).toDouble();
101 
102  // Get the internal parameters
103  quint8 Mode = model->data(model->index(wpIdx, FlightDataModel::MODE), Qt::UserRole).toInt();
104  float ModeParameters =
105  model->data(model->index(wpIdx, FlightDataModel::MODE_PARAMS)).toFloat();
106  float finalVelocity = model->data(model->index(wpIdx, FlightDataModel::VELOCITY)).toFloat();
107 
108  // Determine if the path is a straight line or if it arcs
109  float curvature = 0;
110  switch (Mode) {
111  case Waypoint::MODE_CIRCLEPOSITIONRIGHT:
112  return false;
113  case Waypoint::MODE_CIRCLERIGHT:
114  curvature = 1.0f / ModeParameters;
115  break;
116  case Waypoint::MODE_CIRCLEPOSITIONLEFT:
117  return false;
118  case Waypoint::MODE_CIRCLELEFT:
119  curvature = -1.0f / ModeParameters;
120  break;
121  }
122 
123  // First waypoint cannot be fileting since we don't have start. Keep intact.
124  if (wpIdx == 0) {
125  setNewWaypoint(newWaypointIdx++, pos_current, finalVelocity, curvature);
126  continue;
127  }
128 
129  // Only add fillets if the radius is greater than 0, and this is not the last waypoint
130  if (fillet_radius > 0 && wpIdx < (model->rowCount() - 1)) {
131  // If waypoints have been set on the new model then use that to know the previous
132  // location. Otherwise this is setting the first segment. On board that uses the
133  // current location but while planning offline this is unknown so we use home.
134  if (newWaypointIdx > 0) {
135  pos_prev[0] =
136  new_model
137  ->data(new_model->index(newWaypointIdx - 1, FlightDataModel::NED_NORTH))
138  .toDouble();
139  pos_prev[1] =
140  new_model->data(new_model->index(newWaypointIdx - 1, FlightDataModel::NED_EAST))
141  .toDouble();
142  pos_prev[2] =
143  new_model->data(new_model->index(newWaypointIdx - 1, FlightDataModel::NED_DOWN))
144  .toDouble();
145  // TODO: fix sign
146  float previous_radius =
147  new_model
148  ->data(new_model->index(newWaypointIdx - 1, FlightDataModel::MODE_PARAMS))
149  .toDouble();
150  previous_curvature = (previous_radius < 1e-4) ? 0 : 1.0 / previous_radius;
151  } else {
152  // Use the home location as the starting point of paths.
153  pos_prev[0] = 0;
154  pos_prev[1] = 0;
155  pos_prev[2] = 0;
156  previous_curvature = 0;
157  }
158 
159  // Get the settings for the upcoming waypoint
160  pos_next[0] =
161  model->data(model->index(wpIdx + 1, FlightDataModel::NED_NORTH)).toDouble();
162  pos_next[1] =
163  model->data(model->index(wpIdx + 1, FlightDataModel::NED_EAST)).toDouble();
164  pos_next[2] =
165  model->data(model->index(wpIdx + 1, FlightDataModel::NED_DOWN)).toDouble();
166  quint8 NextMode =
167  model->data(model->index(wpIdx + 1, FlightDataModel::MODE), Qt::UserRole).toInt();
168  float NextModeParameter =
169  model->data(model->index(wpIdx + 1, FlightDataModel::MODE_PARAMS), Qt::UserRole)
170  .toInt();
171 
172  NextModeParameter = 0;
173  bool future_path_is_circle = NextMode == Waypoint::MODE_CIRCLEPOSITIONRIGHT
174  || NextMode == Waypoint::MODE_CIRCLEPOSITIONLEFT;
175 
176  // The vector in and out of the current waypoint
177  float q_future[3];
178  float q_future_mag = 0;
179  float q_current[3];
180  float q_current_mag = 0;
181 
182  // In the case of line-line intersection lines, this is simply the direction of
183  // the old and new segments.
184  if (curvature == 0
185  && (NextModeParameter == 0
186  || future_path_is_circle)) { // Fixme: waypoint_future.ModeParameters needs to
187  // be replaced by waypoint_future.Mode. FOr this,
188  // we probably need a new function to handle the
189  // switch(waypoint.Mode)
190 
191  // Vector from past to present switching locus
192  q_current[0] = pos_current[0] - pos_prev[0];
193  q_current[1] = pos_current[1] - pos_prev[1];
194 
195  // Calculate vector from preset to future switching locus
196  q_future[0] = pos_next[0] - pos_current[0];
197  q_future[1] = pos_next[1] - pos_current[1];
198  }
199  // In the case of line-arc intersections, calculate the tangent of the new section.
200  else if (curvature == 0
201  && (NextModeParameter != 0
202  && !future_path_is_circle)) { // Fixme: waypoint_future.ModeParameters
203  // needs to be replaced by
204  // waypoint_future.Mode. FOr this, we
205  // probably need a new function to handle the
206  // switch(waypoint.Mode)
207  // Old segment: straight line
208  q_current[0] = pos_current[0] - pos_prev[0];
209  q_current[1] = pos_current[1] - pos_prev[1];
210 
211  // New segment: Vector perpendicular to the vector from arc center to tangent point
212  bool clockwise = curvature > 0;
213  qint8 lambda;
214 
215  if (clockwise == true) { // clockwise
216  lambda = 1;
217  } else { // counterclockwise
218  lambda = -1;
219  }
220 
221  // Calculate circle center
222  float arcCenter_NE[2];
223  find_arc_center(pos_current, pos_next, 1.0f / curvature, arcCenter_NE,
224  curvature > 0, true);
225 
226  // Vector perpendicular to the vector from arc center to tangent point
227  q_future[0] = -lambda * (pos_current[1] - arcCenter_NE[1]);
228  q_future[1] = lambda * (pos_current[0] - arcCenter_NE[0]);
229  }
230  // In the case of arc-line intersections, calculate the tangent of the old section.
231  else if (curvature != 0
232  && (NextModeParameter == 0
233  || future_path_is_circle)) { // Fixme: waypoint_future.ModeParameters needs
234  // to be replaced by waypoint_future.Mode. FOr
235  // this, we probably need a new function to
236  // handle the switch(waypoint.Mode)
237  // Old segment: Vector perpendicular to the vector from arc center to tangent point
238  bool clockwise = previous_curvature > 0;
239  bool minor = true;
240  qint8 lambda;
241 
242  if ((clockwise == true && minor == true)
243  || (clockwise == false
244  && minor == false)) { // clockwise minor OR counterclockwise major
245  lambda = 1;
246  } else { // counterclockwise minor OR clockwise major
247  lambda = -1;
248  }
249 
250  // Calculate old circle center
251  float arcCenter_NE[2];
252  find_arc_center(pos_prev, pos_current, 1.0f / previous_curvature, arcCenter_NE,
253  clockwise, minor);
254 
255  // Vector perpendicular to the vector from arc center to tangent point
256  q_current[0] = -lambda * (pos_current[1] - arcCenter_NE[1]);
257  q_current[1] = lambda * (pos_current[0] - arcCenter_NE[0]);
258 
259  // New segment: straight line
260  q_future[0] = pos_next[0] - pos_current[0];
261  q_future[1] = pos_next[1] - pos_current[1];
262  }
263  // In the case of arc-arc intersections, calculate the tangent of the old and new
264  // sections.
265  else if (curvature != 0
266  && (NextModeParameter != 0
267  && !future_path_is_circle)) { // Fixme: waypoint_future.ModeParameters
268  // needs to be replaced by
269  // waypoint_future.Mode. FOr this, we
270  // probably need a new function to handle the
271  // switch(waypoint.Mode)
272  // Old segment: Vector perpendicular to the vector from arc center to tangent point
273  bool clockwise = previous_curvature > 0;
274  bool minor = true;
275  qint8 lambda;
276 
277  if ((clockwise == true && minor == true)
278  || (clockwise == false
279  && minor == false)) { // clockwise minor OR counterclockwise major
280  lambda = 1;
281  } else { // counterclockwise minor OR clockwise major
282  lambda = -1;
283  }
284 
285  // Calculate old arc center
286  float arcCenter_NE[2];
287  find_arc_center(pos_prev, pos_current, 1.0f / previous_curvature, arcCenter_NE,
288  clockwise, minor);
289 
290  // New segment: Vector perpendicular to the vector from arc center to tangent point
291  q_current[0] = -lambda * (pos_prev[1] - arcCenter_NE[1]);
292  q_current[1] = lambda * (pos_prev[0] - arcCenter_NE[0]);
293 
294  if (curvature > 0) { // clockwise
295  lambda = 1;
296  } else { // counterclockwise
297  lambda = -1;
298  }
299 
300  // Calculate new arc center
301  find_arc_center(pos_current, pos_next, 1.0f / curvature, arcCenter_NE,
302  curvature > 0, true);
303 
304  // Vector perpendicular to the vector from arc center to tangent point
305  q_future[0] = -lambda * (pos_current[1] - arcCenter_NE[1]);
306  q_future[1] = lambda * (pos_current[0] - arcCenter_NE[0]);
307  }
308 
309  q_current[2] = 0;
310  q_current_mag = VectorMagnitude(q_current); // Normalize
311  q_future[2] = 0;
312  q_future_mag = VectorMagnitude(q_future); // Normalize
313 
314  // Normalize q_current and q_future
315  if (q_current_mag > 0) {
316  for (int i = 0; i < 3; i++)
317  q_current[i] = q_current[i] / q_current_mag;
318  }
319  if (q_future_mag > 0) {
320  for (int i = 0; i < 3; i++)
321  q_future[i] = q_future[i] / q_future_mag;
322  }
323 
324  // Compute heading difference between current and future tangents.
325  float theta = angle_between_2d_vectors(q_current, q_future);
326 
327  // Compute angle between current and future tangents.
328  float rho = circular_modulus_rad(theta - M_PI);
329 
330  // Compute half angle
331  float rho2 = rho / 2.0f;
332 
333  // Circle the outside of acute angles
334  if (fabsf(rho) < M_PI / 3.0f) {
335  float R = fillet_radius;
336  if (q_current_mag > 0 && q_current_mag < R * sqrtf(3))
337  R = q_current_mag / sqrtf(3)
338  - 0.1f; // Remove 10cm to guarantee that no two points overlap.
339  if (q_future_mag > 0 && q_future_mag < R * sqrtf(3))
340  R = q_future_mag / sqrtf(3)
341  - 0.1f; // Remove 10cm to guarantee that no two points overlap.
342 
343  // The sqrt(3) term comes from the fact that the triangle that connects the center
344  // of
345  // the first/second arc with the center of the second/third arc is a 1-2-sqrt(3)
346  // triangle
347  float f1[3] = { pos_current[0] - R * q_current[0] * sqrtf(3),
348  pos_current[1] - R * q_current[1] * sqrtf(3), pos_current[2] };
349  float f2[3] = { pos_current[0] + R * q_future[0] * sqrtf(3),
350  pos_current[1] + R * q_future[1] * sqrtf(3), pos_current[2] };
351 
352  // Add the waypoint segment
353  newWaypointIdx +=
354  addNonCircleToSwitchingLoci(f1, finalVelocity, curvature, newWaypointIdx);
355 
356  float gamma = atan2f(q_current[1], q_current[0]);
357 
358  // Compute eta, which is the angle between the horizontal and the center of the
359  // filleting arc f1 and
360  // sigma, which is the angle between the horizontal and the center of the filleting
361  // arc f2.
362  float eta;
363  float sigma;
364  if (theta > 0) { // Change in direction is clockwise, so fillets are clockwise
365  eta = gamma - M_PI / 2.0f;
366  sigma = gamma + theta - M_PI / 2.0f;
367  } else {
368  eta = gamma + M_PI / 2.0f;
369  sigma = gamma + theta + M_PI / 2.0f;
370  }
371 
372  // This starts the fillet into the circle
373  float pos[3] = { (pos_current[0] + f1[0] + R * cosf(eta)) / 2,
374  (pos_current[1] + f1[1] + R * sinf(eta)) / 2, pos_current[2] };
375  setNewWaypoint(newWaypointIdx++, pos, finalVelocity, -SIGN(theta) * 1.0f / R);
376 
377  // This is the halfway point through the circle
378  pos[0] = pos_current[0] + R * cosf(gamma);
379  pos[1] = pos_current[1] + R * sinf(gamma);
380  pos[2] = pos_current[2];
381  setNewWaypoint(newWaypointIdx++, pos, finalVelocity, SIGN(theta) * 1.0f / R);
382 
383  // This is the transition from the circle to the fillet back onto the path
384  pos[0] = (pos_current[0] + (f2[0] + R * cosf(sigma))) / 2;
385  pos[1] = (pos_current[1] + (f2[1] + R * sinf(sigma))) / 2;
386  pos[2] = pos_current[2];
387  setNewWaypoint(newWaypointIdx++, pos, finalVelocity, SIGN(theta) * 1.0f / R);
388 
389  // This is the point back on the path
390  pos[0] = f2[0];
391  pos[1] = f2[1];
392  pos[2] = pos_current[2];
393  setNewWaypoint(newWaypointIdx++, pos, finalVelocity, -SIGN(theta) * 1.0f / R);
394  } else if (theta != 0) { // The two tangents have different directions
395  float R = fillet_radius;
396 
397  // Remove 10cm to guarantee that no two points overlap. This would be better if we
398  // solved it by removing the next point instead.
399  if (q_current_mag > 0 && q_current_mag < fabsf(R / tanf(rho2)))
400  R = qMin(R, q_current_mag * fabsf(tanf(rho2)) - 0.1f);
401  if (q_future_mag > 0 && q_future_mag < fabsf(R / tanf(rho2)))
402  R = qMin(R, q_future_mag * fabsf(tanf(rho2)) - 0.1f);
403 
404  // Add the waypoint segment
405  float f1[3];
406  f1[0] = pos_current[0] - R / fabsf(tanf(rho2)) * q_current[0];
407  f1[1] = pos_current[1] - R / fabsf(tanf(rho2)) * q_current[1];
408  f1[2] = pos_current[2];
409  newWaypointIdx +=
410  addNonCircleToSwitchingLoci(f1, finalVelocity, curvature, newWaypointIdx);
411 
412  // Add the filleting segment in preparation for the next waypoint
413  float pos[3] = { pos_current[0] + R / fabsf(tanf(rho2)) * q_future[0],
414  pos_current[1] + R / fabsf(tanf(rho2)) * q_future[1],
415  pos_current[2] };
416  setNewWaypoint(newWaypointIdx++, pos, finalVelocity, SIGN(theta) * 1.0f / R);
417 
418  } else {
419  // In this case, the two tangents are colinear
420  newWaypointIdx += addNonCircleToSwitchingLoci(pos_current, finalVelocity, curvature,
421  newWaypointIdx);
422  }
423  } else if (wpIdx == model->rowCount() - 1)
424  // This is the final waypoint
425  newWaypointIdx +=
426  addNonCircleToSwitchingLoci(pos_current, finalVelocity, curvature, newWaypointIdx);
427  }
428 
429  // Migrate the data to the original model now it is complete
430  model->replaceData(new_model);
431  delete new_model;
432  new_model = NULL;
433 
434  return true;
435 }
436 
444 void PathFillet::setNewWaypoint(int index, float *pos, float velocity, float curvature)
445 {
446  if (index >= new_model->rowCount() - 1)
447  new_model->insertRow(index);
448 
449  // Convert from curvature representation to waypoint
450  quint8 mode = Waypoint::MODE_VECTOR;
451  float radius = 0;
452  if (curvature > 0 && !std::isinf(curvature)) {
453  mode = Waypoint::MODE_CIRCLERIGHT;
454  radius = 1.0 / curvature;
455  } else if (curvature < 0 && !std::isinf(curvature)) {
456  mode = Waypoint::MODE_CIRCLELEFT;
457  radius = -1.0 / curvature;
458  }
459 
460  new_model->setData(new_model->index(index, FlightDataModel::NED_NORTH), pos[0]);
461  new_model->setData(new_model->index(index, FlightDataModel::NED_EAST), pos[1]);
462  new_model->setData(new_model->index(index, FlightDataModel::NED_DOWN), pos[2]);
463  new_model->setData(new_model->index(index, FlightDataModel::VELOCITY), velocity);
464  new_model->setData(new_model->index(index, FlightDataModel::MODE_PARAMS), radius);
465  new_model->setData(new_model->index(index, FlightDataModel::MODE), mode);
466 }
467 
478 int PathFillet::addNonCircleToSwitchingLoci(float position[3], float finalVelocity, float curvature,
479  int index)
480 {
481  setNewWaypoint(index, position, finalVelocity, curvature);
482 
483  return 1;
484 }
485 
487 float PathFillet::VectorMagnitude(float *v)
488 {
489  return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
490 }
491 
493 double PathFillet::VectorMagnitude(double *v)
494 {
495  return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
496 }
497 
505 float PathFillet::circular_modulus_rad(float err)
506 {
507  float val = fmodf(err + M_PI, 2 * M_PI);
508 
509  // fmodf converts negative values into the negative remainder
510  // so we must add 360 to make sure this ends up correct and
511  // behaves like positive output modulus
512  if (val < 0)
513  val += M_PI;
514  else
515  val -= M_PI;
516 
517  return val;
518 }
519 
532 enum PathFillet::arc_center_results PathFillet::find_arc_center(float start_point[2],
533  float end_point[2], float radius,
534  float center[2], bool clockwise,
535  bool minor)
536 {
537  // Sanity check
538  if (fabsf(start_point[0] - end_point[0]) < 1e-6
539  && fabsf(start_point[1] - end_point[1]) < 1e-6) {
540  // This means that the start point and end point are directly on top of each other. In the
541  // case of coincident points, there is not enough information to define the circle
542  center[0] = NAN;
543  center[1] = NAN;
544  return COINCIDENT_POINTS;
545  }
546 
547  float m_n, m_e, p_n, p_e, d, d2;
548 
549  // Center between start and end
550  m_n = (start_point[0] + end_point[0]) / 2;
551  m_e = (start_point[1] + end_point[1]) / 2;
552 
553  // Normal vector to the line between start and end points
554  if ((clockwise == true && minor == true)
555  || (clockwise == false && minor == false)) { // clockwise minor OR counterclockwise major
556  p_n = -(end_point[1] - start_point[1]);
557  p_e = (end_point[0] - start_point[0]);
558  } else { // counterclockwise minor OR clockwise major
559  p_n = (end_point[1] - start_point[1]);
560  p_e = -(end_point[0] - start_point[0]);
561  }
562 
563  // Work out how far to go along the perpendicular bisector. First check there is a solution.
564  d2 = radius * radius / (p_n * p_n + p_e * p_e) - 0.25f;
565  if (d2 < 0) {
566  if (d2 > -powf(radius * 0.01f, 2)) // Make a 1% allowance for roundoff error
567  d2 = 0;
568  else {
569  center[0] = NAN;
570  center[1] = NAN;
571  return INSUFFICIENT_RADIUS; // In this case, the radius wasn't big enough to connect the
572  // two points
573  }
574  }
575 
576  d = sqrtf(d2);
577 
578  if (fabsf(p_n) < 1e-3 && fabsf(p_e) < 1e-3) {
579  center[0] = m_n;
580  center[1] = m_e;
581  } else {
582  center[0] = m_n + p_n * d;
583  center[1] = m_e + p_e * d;
584  }
585 
586  return CENTER_FOUND;
587 }
588 
596 float PathFillet::measure_arc_rad(float oldPosition_NE[2], float newPosition_NE[2],
597  float arcCenter_NE[2])
598 {
599  float a[2] = { oldPosition_NE[0] - arcCenter_NE[0], oldPosition_NE[1] - arcCenter_NE[1] };
600  float b[2] = { newPosition_NE[0] - arcCenter_NE[0], newPosition_NE[1] - arcCenter_NE[1] };
601 
602  float theta = angle_between_2d_vectors(a, b);
603  return theta;
604 }
605 
613 float PathFillet::angle_between_2d_vectors(float a[2], float b[2])
614 {
615  // We cannot directly use the vector calculus formula for cos(theta) and sin(theta) because each
616  // is only unique on half the circle. Instead, we combine the two because tangent is unique
617  // across
618  // [-pi,pi]. Use the definition of the cross-product for 2-D vectors, a x b = |a||b| sin(theta),
619  // and
620  // the definition of the dot product, a.b = |a||b| cos(theta), and divide the first by the
621  // second,
622  // yielding a x b / (a.b) = sin(theta)/cos(theta) == tan(theta)
623  float theta = atan2f(a[0] * b[1] - a[1] * b[0], (a[0] * b[0] + a[1] * b[1]));
624  return theta;
625 }
for i
Definition: OPPlots.m:140
int rowCount(const QModelIndex &parent=QModelIndex()) const
Return the number of waypoints.
end a
Definition: OPPlots.m:98
PathFillet(QObject *parent=nullptr)
Definition: pathfillet.cpp:36
bool replaceData(FlightDataModel *newModel)
Replace a model data with another model.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole)
FlightDataModel::setData Set the data at a given location.
virtual bool configure(QWidget *callingUi=nullptr)
Definition: pathfillet.cpp:49
virtual bool processPath(FlightDataModel *model)
Definition: pathfillet.cpp:83
Algorithm to add filtets to path.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
FlightDataModel::data Fetch the data from the model.
e
Definition: OPPlots.m:99
virtual bool verifyPath(FlightDataModel *model, QString &err)
Definition: pathfillet.cpp:63