LCOV - code coverage report
Current view: top level - AC_Avoidance - AP_OABendyRuler.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 170 291 58.4 %
Date: 2026-09-09 11:02:08 Functions: 10 12 83.3 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : #include "AC_Avoidance_config.h"
      17             : 
      18             : #if AP_OAPATHPLANNER_BENDYRULER_ENABLED
      19             : 
      20             : #include "AP_OABendyRuler.h"
      21             : #include <AC_Avoidance/AP_OADatabase.h>
      22             : #include <AC_Fence/AC_Fence.h>
      23             : #include <AP_AHRS/AP_AHRS.h>
      24             : #include <AP_Logger/AP_Logger.h>
      25             : #include <AP_Vehicle/AP_Vehicle_Type.h>
      26             : 
      27             : // parameter defaults
      28             : const float OA_BENDYRULER_LOOKAHEAD_DEFAULT = 15.0f;
      29             : const float OA_BENDYRULER_RATIO_DEFAULT = 1.5f;
      30             : const int16_t OA_BENDYRULER_ANGLE_DEFAULT = 75;
      31             : const int16_t OA_BENDYRULER_TYPE_DEFAULT = 1;
      32             : 
      33             : const int16_t OA_BENDYRULER_BEARING_INC_XY = 5;            // check every 5 degrees around vehicle
      34             : const int16_t OA_BENDYRULER_BEARING_INC_VERTICAL = 90;
      35             : const float OA_BENDYRULER_LOOKAHEAD_STEP2_RATIO = 1.0f; // step2's lookahead length as a ratio of step1's lookahead length
      36             : const float OA_BENDYRULER_LOOKAHEAD_STEP2_MIN = 2.0f;   // step2 checks at least this many meters past step1's location
      37             : const float OA_BENDYRULER_LOOKAHEAD_PAST_DEST = 2.0f;   // lookahead length will be at least this many meters past the destination
      38             : const float OA_BENDYRULER_LOW_SPEED_SQUARED = (0.2f * 0.2f);    // when ground course is below this speed squared, vehicle's heading will be used
      39             : 
      40             : #define VERTICAL_ENABLED APM_BUILD_COPTER_OR_HELI
      41             : 
      42             : const AP_Param::GroupInfo AP_OABendyRuler::var_info[] = {
      43             : 
      44             :     // @Param: LOOKAHEAD
      45             :     // @DisplayName: Object Avoidance look ahead distance maximum
      46             :     // @Description: Object Avoidance will look this many meters ahead of vehicle
      47             :     // @Units: m
      48             :     // @Range: 1 100
      49             :     // @Increment: 1
      50             :     // @User: Standard
      51             :     AP_GROUPINFO("LOOKAHEAD", 1, AP_OABendyRuler, _lookahead, OA_BENDYRULER_LOOKAHEAD_DEFAULT),
      52             : 
      53             :     // @Param: CONT_RATIO
      54             :     // @DisplayName: Obstacle Avoidance margin ratio for BendyRuler to change bearing significantly 
      55             :     // @Description:  BendyRuler will avoid changing bearing unless ratio of previous margin from obstacle (or fence) to present calculated margin is atleast this much.
      56             :     // @Range: 1.1 2
      57             :     // @Increment: 0.1
      58             :     // @User: Standard
      59             :     AP_GROUPINFO("CONT_RATIO", 2, AP_OABendyRuler, _bendy_ratio, OA_BENDYRULER_RATIO_DEFAULT),
      60             : 
      61             :     // @Param: CONT_ANGLE
      62             :     // @DisplayName: BendyRuler's bearing change resistance threshold angle   
      63             :     // @Description:  BendyRuler will resist changing current bearing if the change in bearing is over this angle
      64             :     // @Range: 20 180
      65             :     // @Increment: 5
      66             :     // @User: Standard
      67             :     AP_GROUPINFO("CONT_ANGLE", 3, AP_OABendyRuler, _bendy_angle, OA_BENDYRULER_ANGLE_DEFAULT),
      68             : 
      69             :     // @Param{Copter}: TYPE
      70             :     // @DisplayName: Type of BendyRuler
      71             :     // @Description: BendyRuler will search for clear path along the direction defined by this parameter
      72             :     // @Values: 1:Horizontal search, 2:Vertical search
      73             :     // @User: Standard
      74             :     AP_GROUPINFO_FRAME("TYPE", 4, AP_OABendyRuler, _bendy_type, OA_BENDYRULER_TYPE_DEFAULT, AP_PARAM_FRAME_COPTER | AP_PARAM_FRAME_HELI | AP_PARAM_FRAME_TRICOPTER),
      75             : 
      76             :     AP_GROUPEND
      77             : };
      78             : 
      79           2 : AP_OABendyRuler::AP_OABendyRuler() 
      80             : { 
      81           2 :     AP_Param::setup_object_defaults(this, var_info); 
      82           2 :     _bearing_prev = FLT_MAX;
      83           2 : }
      84             : 
      85             : // run background task to find best path
      86             : // returns true and updates origin_new and destination_new if a best path has been found.  returns false if OA is not required
      87             : // bendy_type is set to the type of BendyRuler used
      88         296 : bool AP_OABendyRuler::update(const Location& current_loc, const Location& destination, const Vector2f &ground_speed_vec, Location &origin_new, Location &destination_new, OABendyType &bendy_type, bool proximity_only)
      89             : {
      90             :     // bendy ruler always sets origin to current_loc
      91         296 :     origin_new = current_loc;
      92             : 
      93             :     // init bendy_type returned
      94         296 :     bendy_type = OABendyType::OA_BENDY_DISABLED;
      95             : 
      96             :     // calculate bearing and distance to final destination
      97         296 :     const float bearing_to_dest = current_loc.get_bearing_to(destination) * 0.01f;
      98         296 :     const float distance_to_dest = current_loc.get_distance(destination);
      99             : 
     100             :     // make sure user has set a meaningful value for _lookahead
     101         296 :     _lookahead.set(MAX(_lookahead,1.0f));
     102             : 
     103             :     // lookahead distance is adjusted dynamically based on avoidance results
     104         296 :     _current_lookahead = constrain_float(_current_lookahead, _lookahead * 0.5f, _lookahead);
     105             : 
     106             :     // calculate lookahead dist and time for step1.  distance can be slightly longer than
     107             :     // the distance to the destination to allow room to dodge after reaching the destination
     108         296 :     const float lookahead_step1_dist = MIN(_current_lookahead, distance_to_dest + OA_BENDYRULER_LOOKAHEAD_PAST_DEST);
     109             : 
     110             :     // calculate lookahead dist for step2
     111         296 :     const float lookahead_step2_dist = _current_lookahead * OA_BENDYRULER_LOOKAHEAD_STEP2_RATIO;
     112             : 
     113             :     // get ground course
     114             :     float ground_course_deg;
     115         296 :     if (ground_speed_vec.length_squared() < OA_BENDYRULER_LOW_SPEED_SQUARED) {
     116             :         // with zero ground speed use vehicle's heading
     117           6 :         ground_course_deg = AP::ahrs().get_yaw_deg();
     118             :     } else {
     119         290 :         ground_course_deg = degrees(ground_speed_vec.angle());
     120             :     }
     121             : 
     122             :     bool ret;
     123         296 :     switch (get_type()) {
     124           0 :         case OABendyType::OA_BENDY_VERTICAL:
     125             :         #if VERTICAL_ENABLED 
     126           0 :             ret = search_vertical_path(current_loc, destination, destination_new, lookahead_step1_dist, lookahead_step2_dist, bearing_to_dest, distance_to_dest, proximity_only);
     127           0 :             bendy_type = OABendyType::OA_BENDY_VERTICAL;
     128           0 :             break;
     129             :         #endif
     130             : 
     131           0 :         case OABendyType::OA_BENDY_HORIZONTAL:
     132             :         default:
     133         296 :             ret = search_xy_path(current_loc, destination, ground_course_deg, destination_new, lookahead_step1_dist, lookahead_step2_dist, bearing_to_dest, distance_to_dest, proximity_only);
     134         296 :             bendy_type = OABendyType::OA_BENDY_HORIZONTAL;
     135             :     }
     136             : 
     137         296 :     return ret;
     138             : }
     139             : 
     140             : // Search for path in the horizontal directions
     141         296 : bool AP_OABendyRuler::search_xy_path(const Location& current_loc, const Location& destination, float ground_course_deg, Location &destination_new, float lookahead_step1_dist, float lookahead_step2_dist, float bearing_to_dest, float distance_to_dest, bool proximity_only) 
     142             : {
     143             :     // check OA_BEARING_INC definition allows checking in all directions
     144             :     static_assert(360 % OA_BENDYRULER_BEARING_INC_XY == 0, "check 360 is a multiple of OA_BEARING_INC");
     145             : 
     146             :     // search in OA_BENDYRULER_BEARING_INC degree increments around the vehicle alternating left
     147             :     // and right. For each direction check if vehicle would avoid all obstacles
     148         296 :     float best_bearing = bearing_to_dest;
     149         296 :     float best_bearing_margin = -FLT_MAX;
     150         296 :     bool have_best_bearing = false;
     151         296 :     float best_margin = -FLT_MAX;
     152         296 :     float best_margin_bearing = best_bearing;
     153             : 
     154        2079 :     for (uint8_t i = 0; i <= (170 / OA_BENDYRULER_BEARING_INC_XY); i++) {
     155        5737 :         for (uint8_t bdir = 0; bdir <= 1; bdir++) {
     156             :             // skip duplicate check of bearing straight towards destination
     157        3954 :             if ((i==0) && (bdir > 0)) {
     158         197 :                 continue;
     159             :             }
     160             :             // bearing that we are probing
     161        3757 :             const float bearing_delta = i * OA_BENDYRULER_BEARING_INC_XY * (bdir == 0 ? -1.0f : 1.0f);
     162        3757 :             const float bearing_test = wrap_180(bearing_to_dest + bearing_delta);
     163             : 
     164             :             // ToDo: add effective groundspeed calculations using airspeed
     165             :             // ToDo: add prediction of vehicle's position change as part of turn to desired heading
     166             : 
     167             :             // test location is projected from current location at test bearing
     168        3757 :             Location test_loc = current_loc;
     169        3757 :             test_loc.offset_bearing(bearing_test, lookahead_step1_dist);
     170             : 
     171             :             // calculate margin from obstacles for this scenario
     172        3757 :             float margin = calc_avoidance_margin(current_loc, test_loc, proximity_only);
     173        3757 :             if (margin > best_margin) {
     174        1654 :                 best_margin_bearing = bearing_test;
     175        1654 :                 best_margin = margin;
     176             :             }
     177        3757 :             if (margin > _margin_max) {
     178             :                 // this bearing avoids obstacles out to the lookahead_step1_dist
     179             :                 // now check in there is a clear path in three directions towards the destination
     180        1149 :                 if (!have_best_bearing) {
     181         294 :                     best_bearing = bearing_test;
     182         294 :                     best_bearing_margin = margin;
     183         294 :                     have_best_bearing = true;
     184         855 :                 } else if (fabsf(wrap_180(ground_course_deg - bearing_test)) <
     185         855 :                            fabsf(wrap_180(ground_course_deg - best_bearing))) {
     186             :                     // replace bearing with one that is closer to our current ground course
     187         312 :                     best_bearing = bearing_test;
     188         312 :                     best_bearing_margin = margin;
     189             :                 }
     190             : 
     191             :                 // perform second stage test in three directions looking for obstacles
     192        1149 :                 const float test_bearings[] { 0.0f, 45.0f, -45.0f };
     193        1149 :                 const float bearing_to_dest2 = test_loc.get_bearing_to(destination) * 0.01f;
     194        1149 :                 float distance2 = constrain_float(lookahead_step2_dist, OA_BENDYRULER_LOOKAHEAD_STEP2_MIN, test_loc.get_distance(destination));
     195        3893 :                 for (uint8_t j = 0; j < ARRAY_SIZE(test_bearings); j++) {
     196        3038 :                     float bearing_test2 = wrap_180(bearing_to_dest2 + test_bearings[j]);
     197        3038 :                     Location test_loc2 = test_loc;
     198        3038 :                     test_loc2.offset_bearing(bearing_test2, distance2);
     199             : 
     200             :                     // calculate minimum margin to fence and obstacles for this scenario
     201        3038 :                     float margin2 = calc_avoidance_margin(test_loc, test_loc2, proximity_only);
     202        3038 :                     if (margin2 > _margin_max) {
     203             :                         // if the chosen direction is directly towards the destination avoidance can be turned off
     204             :                         // i == 0 && j == 0 implies no deviation from bearing to destination 
     205         294 :                         const bool active = (i != 0 || j != 0);
     206         294 :                         float final_bearing = bearing_test;
     207         294 :                         float final_margin = margin;
     208             :                         // check if we need ignore test_bearing and continue on previous bearing
     209         294 :                         const bool ignore_bearing_change = resist_bearing_change(destination, current_loc, active, bearing_test, lookahead_step1_dist, margin, _destination_prev,_bearing_prev, final_bearing, final_margin, proximity_only);
     210             : 
     211             :                         // all good, now project in the chosen direction by the full distance
     212         294 :                         destination_new = current_loc;
     213         294 :                         destination_new.offset_bearing(final_bearing, MIN(distance_to_dest, lookahead_step1_dist));
     214         294 :                         _current_lookahead = MIN(_lookahead, _current_lookahead * 1.1f);
     215         294 :                         Write_OABendyRuler((uint8_t)OABendyType::OA_BENDY_HORIZONTAL, active, bearing_to_dest, 0.0f, ignore_bearing_change, final_margin, destination, destination_new);
     216         294 :                         return active;
     217             :                     }
     218             :                 }
     219             :             }
     220             :         }
     221             :     }
     222             : 
     223             :     float chosen_bearing;
     224             :     float chosen_distance;
     225           2 :     if (have_best_bearing) {
     226             :         // none of the directions tested were OK for 2-step checks. Choose the direction
     227             :         // that was best for the first step
     228           0 :         chosen_bearing = best_bearing;
     229           0 :         chosen_distance = MAX(lookahead_step1_dist + MIN(best_bearing_margin, 0), 0);
     230           0 :         _current_lookahead = MIN(_lookahead, _current_lookahead * 1.05f);
     231             :     } else {
     232             :         // none of the possible paths had a positive margin. Choose
     233             :         // the one with the highest margin
     234           2 :         chosen_bearing = best_margin_bearing;
     235           2 :         chosen_distance = MAX(lookahead_step1_dist + MIN(best_margin, 0), 0);
     236           2 :         _current_lookahead = MAX(_lookahead * 0.5f, _current_lookahead * 0.9f);
     237             :     }
     238             : 
     239             :     // calculate new target based on best effort
     240           2 :     destination_new = current_loc;
     241           2 :     destination_new.offset_bearing(chosen_bearing, chosen_distance);
     242             : 
     243             :     // log results
     244           2 :     Write_OABendyRuler((uint8_t)OABendyType::OA_BENDY_HORIZONTAL, true, chosen_bearing, 0.0f, false, best_margin, destination, destination_new);
     245             : 
     246           2 :     return true;
     247             : }
     248             : 
     249             : // Search for path in the vertical directions
     250           0 : bool AP_OABendyRuler::search_vertical_path(const Location &current_loc, const Location &destination, Location &destination_new, float lookahead_step1_dist, float lookahead_step2_dist, float bearing_to_dest, float distance_to_dest, bool proximity_only)
     251             : {
     252             :     // check OA_BEARING_INC_VERTICAL definition allows checking in all directions
     253             :     static_assert(360 % OA_BENDYRULER_BEARING_INC_VERTICAL == 0, "check 360 is a multiple of OA_BEARING_INC_VERTICAL");
     254           0 :     float best_pitch = 0.0f;
     255           0 :     bool  have_best_pitch = false;
     256           0 :     float best_margin = -FLT_MAX;
     257           0 :     float best_margin_pitch = best_pitch;
     258           0 :     const uint8_t angular_limit = 180 / OA_BENDYRULER_BEARING_INC_VERTICAL;
     259             : 
     260           0 :     for (uint8_t i = 0; i <= angular_limit; i++) {
     261           0 :         for (uint8_t bdir = 0; bdir <= 1; bdir++) {
     262             :             // skip duplicate check of bearing straight towards destination or 180 degrees behind
     263           0 :             if (((i==0) && (bdir > 0)) || ((i == angular_limit) && (bdir > 0))) {
     264           0 :                 continue;
     265             :             }
     266             : 
     267             :             // bearing that we are probing
     268           0 :             const float pitch_delta = i * OA_BENDYRULER_BEARING_INC_VERTICAL * (bdir == 0 ? 1.0f : -1.0f);
     269             : 
     270           0 :             Location test_loc = current_loc;
     271           0 :             test_loc.offset_bearing_and_pitch(bearing_to_dest, pitch_delta, lookahead_step1_dist);
     272             : 
     273             :             // calculate margin from obstacles for this scenario
     274           0 :             float margin = calc_avoidance_margin(current_loc, test_loc, proximity_only);
     275             : 
     276           0 :             if (margin > best_margin) {
     277           0 :                 best_margin_pitch = pitch_delta;
     278           0 :                 best_margin = margin;
     279             :             }
     280             : 
     281           0 :             if (margin > _margin_max) {
     282             :                 // this path avoids the obstacles with the required margin, now check for the path ahead
     283           0 :                 if (!have_best_pitch) {
     284           0 :                     best_pitch = pitch_delta;
     285           0 :                     have_best_pitch = true;
     286             :                 }
     287           0 :                 const float test_pitch_step2[] { 0.0f, 90.0f, -90.0f, 180.0f};
     288             :                 float bearing_to_dest2;
     289           0 :                 if (is_equal(fabsf(pitch_delta), 90.0f)) {
     290           0 :                     bearing_to_dest2 = bearing_to_dest; 
     291             :                 } else { 
     292           0 :                     bearing_to_dest2 = test_loc.get_bearing_to(destination) * 0.01f;
     293             :                 }
     294           0 :                 float distance2 = constrain_float(lookahead_step2_dist, OA_BENDYRULER_LOOKAHEAD_STEP2_MIN, test_loc.get_distance(destination));
     295             : 
     296           0 :                 for (uint8_t j = 0; j < ARRAY_SIZE(test_pitch_step2); j++) {
     297           0 :                     float bearing_test2 = wrap_180(test_pitch_step2[j]);
     298           0 :                     Location test_loc2 = test_loc;
     299           0 :                     test_loc2.offset_bearing_and_pitch(bearing_to_dest2, bearing_test2, distance2);
     300             : 
     301             :                     // calculate minimum margin to fence and obstacles for this scenario
     302           0 :                     float margin2 = calc_avoidance_margin(test_loc, test_loc2, proximity_only);
     303           0 :                     if (margin2 > _margin_max) {
     304             :                         // if the chosen direction is directly towards the destination we might turn off avoidance
     305             :                         // i == 0 && j == 0 implies no deviation from bearing to destination 
     306           0 :                         bool active = (i != 0 || j != 0);
     307           0 :                         if (!active) {
     308             :                             // do a sub test for proximity obstacles to confirm if we should really turn of BendyRuler
     309           0 :                             const float sub_test_pitch_step2[] {-90.0f, 90.0f};
     310           0 :                             for (uint8_t k = 0; k < ARRAY_SIZE(sub_test_pitch_step2); k++) {
     311           0 :                                 Location test_loc_sub_test = test_loc;
     312           0 :                                 test_loc_sub_test.offset_bearing_and_pitch(bearing_to_dest2, sub_test_pitch_step2[k], _margin_max);
     313           0 :                                 float margin_sub_test = calc_avoidance_margin(test_loc, test_loc_sub_test, true);
     314           0 :                                 if (margin_sub_test < _margin_max) {
     315             :                                     // BendyRuler will remain active
     316           0 :                                     active = true;
     317           0 :                                     break;
     318             :                                 }
     319             :                             }
     320             :                         }
     321             :                         // project in the chosen direction by the full distance
     322           0 :                         destination_new = current_loc;
     323           0 :                         destination_new.offset_bearing_and_pitch(bearing_to_dest, pitch_delta, distance_to_dest);
     324           0 :                         _current_lookahead = MIN(_lookahead, _current_lookahead * 1.1f);
     325             : 
     326           0 :                         Write_OABendyRuler((uint8_t)OABendyType::OA_BENDY_VERTICAL, active, bearing_to_dest, pitch_delta, false, margin, destination, destination_new);
     327           0 :                         return active;
     328             :                     }
     329             :                 }
     330             :             }
     331             :         }        
     332             :     }   
     333             : 
     334             :     float chosen_pitch;
     335           0 :     if (have_best_pitch) {
     336             :         // none of the directions tested were OK for 2-step checks. Choose the direction
     337             :         // that was best for the first step
     338           0 :         chosen_pitch = best_pitch;
     339           0 :         _current_lookahead = MIN(_lookahead, _current_lookahead * 1.05f);
     340             :     } else {
     341             :         // none of the possible paths had a positive margin. Choose
     342             :         // the one with the highest margin
     343           0 :         chosen_pitch = best_margin_pitch;
     344           0 :         _current_lookahead = MAX(_lookahead * 0.5f, _current_lookahead * 0.9f);
     345             :     }
     346             : 
     347             :     // calculate new target based on best effort
     348           0 :     destination_new = current_loc;
     349           0 :     destination_new.offset_bearing_and_pitch(bearing_to_dest, chosen_pitch, distance_to_dest);
     350             : 
     351             :     // log results
     352           0 :     Write_OABendyRuler((uint8_t)OABendyType::OA_BENDY_VERTICAL, true, bearing_to_dest, chosen_pitch,false, best_margin, destination, destination_new);
     353             : 
     354           0 :     return true;
     355             : }
     356             : 
     357         296 : AP_OABendyRuler::OABendyType AP_OABendyRuler::get_type() const
     358             : {
     359         296 :     switch (_bendy_type) {
     360           0 :         case (uint8_t)OABendyType::OA_BENDY_VERTICAL:
     361             :         #if VERTICAL_ENABLED 
     362           0 :             return OABendyType::OA_BENDY_VERTICAL;
     363             :         #endif
     364             : 
     365           0 :         case (uint8_t)OABendyType::OA_BENDY_HORIZONTAL:
     366             :         default:
     367         296 :             return OABendyType::OA_BENDY_HORIZONTAL;
     368             :     }
     369             :     // should never reach here
     370             :     return OABendyType::OA_BENDY_HORIZONTAL;
     371             : }
     372             : 
     373             : /*
     374             : This function is called when BendyRuler has found a bearing which is obstacles free at atleast lookahead_step1_dist and  then lookahead_step2_dist from the present location
     375             : In many situations, this new bearing can be either left or right of the obstacle, and BendyRuler can have a tough time deciding between the two.
     376             : It has the tendency to move the vehicle back and forth, if the margin obtained is even slightly better in the newer iteration.
     377             : Therefore, this method attempts to avoid changing direction of the vehicle by more than _bendy_angle degrees, 
     378             : unless the new margin is atleast _bendy_ratio times better than the margin with previously calculated bearing.
     379             : We return true if we have resisted the change and will follow the last calculated bearing. 
     380             : */
     381         294 : bool AP_OABendyRuler::resist_bearing_change(const Location &destination, const Location &current_loc, bool active, float bearing_test, float lookahead_step1_dist, float margin, Location &prev_dest, float &prev_bearing, float &final_bearing, float &final_margin, bool proximity_only) const
     382             : {      
     383         294 :     bool resisted_change = false;
     384             :     // see if there was a change in destination, if so, do not resist changing bearing 
     385         294 :     bool dest_change = false;
     386         294 :     if (!destination.same_latlon_as(prev_dest)) {
     387           4 :         dest_change = true;
     388           4 :         prev_dest = destination;
     389             :     }
     390             :                         
     391             :     // check if we need to resist the change in direction of the vehicle. If we have a clear path to destination, go there any how  
     392         294 :     if (active && !dest_change && is_positive(_bendy_ratio)) { 
     393             :         // check the change in bearing between freshly calculated and previous stored BendyRuler bearing
     394         195 :         if ((fabsf(wrap_180(prev_bearing-bearing_test)) > _bendy_angle) && (!is_equal(prev_bearing,FLT_MAX))) {
     395             :             // check margin in last bearing's direction
     396          29 :             Location test_loc_previous_bearing = current_loc;
     397          29 :             test_loc_previous_bearing.offset_bearing(wrap_180(prev_bearing), lookahead_step1_dist);
     398          29 :             float previous_bearing_margin = calc_avoidance_margin(current_loc,test_loc_previous_bearing, proximity_only);
     399             : 
     400          29 :             if (margin < (_bendy_ratio * previous_bearing_margin)) {
     401             :                 // don't change direction abruptly. If margin difference is not significant, follow the last direction
     402          24 :                 final_bearing = prev_bearing;
     403          24 :                 final_margin  = previous_bearing_margin;
     404          24 :                 resisted_change = true;
     405             :             } 
     406             :         } 
     407             :     } else {
     408             :         // reset stored bearing if BendyRuler is not active or if WP has changed for unnecessary resistance to path change
     409          99 :         prev_bearing = FLT_MAX;
     410             :     }
     411         294 :     if (!resisted_change) {
     412             :         // we are not resisting the change, hence store BendyRuler's presently calculated bearing for future iterations
     413         270 :         prev_bearing = bearing_test;
     414             :     }
     415             : 
     416         294 :     return resisted_change;
     417             : }
     418             : 
     419             : // calculate minimum distance between a segment and any obstacle
     420        6824 : float AP_OABendyRuler::calc_avoidance_margin(const Location &start, const Location &end, bool proximity_only) const
     421             : {
     422        6824 :     float margin_min = FLT_MAX;
     423             : 
     424             :     float latest_margin;
     425             :     
     426        6824 :     if (calc_margin_from_object_database(start, end, latest_margin)) {
     427           0 :         margin_min = MIN(margin_min, latest_margin);
     428             :     }
     429             :     
     430        6824 :     if (proximity_only) {
     431             :         // only need margin from proximity data
     432           0 :         return margin_min;
     433             :     }
     434             :     
     435        6824 :     if (calc_margin_from_circular_fence(start, end, latest_margin)) {
     436        6824 :         margin_min = MIN(margin_min, latest_margin);
     437             :     }
     438             :     
     439             :     #if VERTICAL_ENABLED 
     440             :     // alt fence only is only needed in vertical avoidance
     441           0 :     if (get_type() == OABendyType::OA_BENDY_VERTICAL) {
     442           0 :         if (calc_margin_from_alt_fence(start, end, latest_margin)) {
     443           0 :             margin_min = MIN(margin_min, latest_margin);
     444             :         }
     445             :     }
     446             :     #endif
     447             : 
     448        6824 :     if (calc_margin_from_inclusion_and_exclusion_polygons(start, end, latest_margin)) {
     449        6824 :         margin_min = MIN(margin_min, latest_margin);
     450             :     }
     451             : 
     452        6824 :     if (calc_margin_from_inclusion_and_exclusion_circles(start, end, latest_margin)) {
     453        6824 :         margin_min = MIN(margin_min, latest_margin);
     454             :     }
     455             : 
     456             :     // return smallest margin from any obstacle
     457        6824 :     return margin_min;
     458             : }
     459             : 
     460             : // calculate minimum distance between a path and the circular fence (centered on home)
     461             : // on success returns true and updates margin
     462        6824 : bool AP_OABendyRuler::calc_margin_from_circular_fence(const Location &start, const Location &end, float &margin) const
     463             : {
     464             : #if AP_FENCE_ENABLED
     465             :     // exit immediately if polygon fence is not enabled
     466        6824 :     const AC_Fence *fence = AC_Fence::get_singleton();
     467        6824 :     if (fence == nullptr) {
     468           0 :         return false;
     469             :     }
     470        6824 :     if ((fence->get_enabled_fences() & AC_FENCE_TYPE_CIRCLE) == 0) {
     471           0 :         return false;
     472             :     }
     473             : 
     474             :     // calculate start and end point's distance from home
     475        6824 :     const Location &ahrs_home = AP::ahrs().get_home();
     476        6824 :     const float start_dist_sq = ahrs_home.get_distance_NE(start).length_squared();
     477        6824 :     const float end_dist_sq = ahrs_home.get_distance_NE(end).length_squared();
     478             : 
     479             :     // get circular fence radius + margin
     480        6824 :     const float fence_radius_plus_margin = fence->get_radius_m() - fence->get_margin_ne_m();
     481             : 
     482             :     // margin is fence radius minus the longer of start or end distance
     483        6824 :     margin = fence_radius_plus_margin - sqrtf(MAX(start_dist_sq, end_dist_sq));
     484        6824 :     return true;
     485             : #else
     486             :     return false;
     487             : #endif // AP_FENCE_ENABLED
     488             : }
     489             : 
     490             : // calculate minimum distance between a path and the altitude fence
     491             : // on success returns true and updates margin
     492           0 : bool AP_OABendyRuler::calc_margin_from_alt_fence(const Location &start, const Location &end, float &margin) const
     493             : {
     494             : #if AP_FENCE_ENABLED
     495             :     // exit immediately if polygon fence is not enabled
     496           0 :     const AC_Fence *fence = AC_Fence::get_singleton();
     497           0 :     if (fence == nullptr) {
     498           0 :         return false;
     499             :     }
     500           0 :     if ((fence->get_enabled_fences() & AC_FENCE_TYPE_ALT_MAX) == 0) {
     501           0 :         return false;
     502             :     }
     503             : 
     504             :     int32_t alt_above_frame_cm_start, alt_above_frame_cm_end;    
     505           0 :     if (!start.get_alt_cm(fence->get_alt_max_frame(), alt_above_frame_cm_start)) {
     506           0 :         return false;
     507             :     }
     508           0 :     if (!end.get_alt_cm(fence->get_alt_max_frame(), alt_above_frame_cm_end )) {
     509           0 :         return false;
     510             :     }
     511             : 
     512             :     // safe max alt = fence alt - fence margin
     513           0 :     const float max_fence_alt = fence->get_safe_alt_max_m();
     514           0 :     const float margin_start =  max_fence_alt - alt_above_frame_cm_start * 0.01f;
     515           0 :     const float margin_end =  max_fence_alt - alt_above_frame_cm_end * 0.01f;
     516             : 
     517             :     // margin is minimum distance to fence from either start or end location
     518           0 :     margin = MIN(margin_start,margin_end);
     519             : 
     520           0 :     return true;
     521             : #else
     522             :     return false;
     523             : #endif // AP_FENCE_ENABLED
     524             : }
     525             : 
     526             : // calculate minimum distance between a path and all inclusion and exclusion polygons
     527             : // on success returns true and updates margin
     528        6824 : bool AP_OABendyRuler::calc_margin_from_inclusion_and_exclusion_polygons(const Location &start, const Location &end, float &margin) const
     529             : {
     530             : #if AP_FENCE_ENABLED
     531        6824 :     const AC_Fence *fence = AC_Fence::get_singleton();
     532        6824 :     if (fence == nullptr) {
     533           0 :         return false;
     534             :     }
     535             : 
     536             :     // exclusion polygons enabled along with polygon fences
     537        6824 :     if ((fence->get_enabled_fences() & AC_FENCE_TYPE_POLYGON) == 0) {
     538           0 :         return false;
     539             :     }
     540             : 
     541             :     // return immediately if no inclusion nor exclusion polygons
     542        6824 :     const uint8_t num_inclusion_polygons = fence->polyfence().get_inclusion_polygon_count();
     543        6824 :     const uint8_t num_exclusion_polygons = fence->polyfence().get_exclusion_polygon_count();
     544        6824 :     if ((num_inclusion_polygons == 0) && (num_exclusion_polygons == 0)) {
     545           0 :         return false;
     546             :     }
     547             : 
     548             :     // convert start and end to offsets from EKF origin
     549        6824 :     Vector2f start_NE, end_NE;
     550       13648 :     if (!start.get_vector_xy_from_origin_NE_cm(start_NE) ||
     551        6824 :         !end.get_vector_xy_from_origin_NE_cm(end_NE)) {
     552           0 :         return false;
     553             :     }
     554             : 
     555             :     // get fence margin
     556        6824 :     const float fence_margin = fence->get_margin_ne_m();
     557             : 
     558             :     // iterate through inclusion polygons and calculate minimum margin
     559        6824 :     bool margin_updated = false;
     560       13648 :     for (uint8_t i = 0; i < num_inclusion_polygons; i++) {
     561             :         uint16_t num_points;
     562        6824 :         const Vector2f* boundary = fence->polyfence().get_inclusion_polygon(i, num_points);
     563             :      
     564             :         // if outside the fence margin is the closest distance but with negative sign
     565        6824 :         const float sign = Polygon_outside(start_NE, boundary, num_points) ? -1.0f : 1.0f;
     566             : 
     567             :         // calculate min distance (in meters) from line to polygon
     568        6824 :         float margin_new = (sign * Polygon_closest_distance_line(boundary, num_points, start_NE, end_NE) * 0.01f) - fence_margin;
     569        6824 :         if (!margin_updated || (margin_new < margin)) {
     570        6824 :             margin_updated = true;
     571        6824 :             margin = margin_new;
     572             :         }
     573             :     }
     574             : 
     575             :     // iterate through exclusion polygons and calculate minimum margin
     576        6824 :     for (uint8_t i = 0; i < num_exclusion_polygons; i++) {
     577             :         uint16_t num_points;
     578           0 :         const Vector2f* boundary = fence->polyfence().get_exclusion_polygon(i, num_points);
     579             :    
     580             :         // if start is inside the polygon the margin's sign is reversed
     581           0 :         const float sign = Polygon_outside(start_NE, boundary, num_points) ? 1.0f : -1.0f;
     582             : 
     583             :         // calculate min distance (in meters) from line to polygon
     584           0 :         float margin_new = (sign * Polygon_closest_distance_line(boundary, num_points, start_NE, end_NE) * 0.01f) - fence_margin;
     585           0 :         if (!margin_updated || (margin_new < margin)) {
     586           0 :             margin_updated = true;
     587           0 :             margin = margin_new;
     588             :         }
     589             :     }
     590             : 
     591        6824 :     return margin_updated;
     592             : #else
     593             :     return false;
     594             : #endif // AP_FENCE_ENABLED
     595             : }
     596             : 
     597             : // calculate minimum distance between a path and all inclusion and exclusion circles
     598             : // on success returns true and updates margin
     599        6824 : bool AP_OABendyRuler::calc_margin_from_inclusion_and_exclusion_circles(const Location &start, const Location &end, float &margin) const
     600             : {
     601             : #if AP_FENCE_ENABLED
     602             :     // exit immediately if fence is not enabled
     603        6824 :     const AC_Fence *fence = AC_Fence::get_singleton();
     604        6824 :     if (fence == nullptr) {
     605           0 :         return false;
     606             :     }
     607             : 
     608             :     // inclusion/exclusion circles enabled along with polygon fences
     609        6824 :     if ((fence->get_enabled_fences() & AC_FENCE_TYPE_POLYGON) == 0) {
     610           0 :         return false;
     611             :     }
     612             : 
     613             :     // return immediately if no inclusion nor exclusion circles
     614        6824 :     const uint8_t num_inclusion_circles = fence->polyfence().get_inclusion_circle_count();
     615        6824 :     const uint8_t num_exclusion_circles = fence->polyfence().get_exclusion_circle_count();
     616        6824 :     if ((num_inclusion_circles == 0) && (num_exclusion_circles == 0)) {
     617           0 :         return false;
     618             :     }
     619             : 
     620             :     // convert start and end to offsets from EKF origin
     621        6824 :     Vector2f start_NE, end_NE;
     622       13648 :     if (!start.get_vector_xy_from_origin_NE_cm(start_NE) ||
     623        6824 :         !end.get_vector_xy_from_origin_NE_cm(end_NE)) {
     624           0 :         return false;
     625             :     }
     626             : 
     627             :     // get fence margin
     628        6824 :     const float fence_margin = fence->get_margin_ne_m();
     629             : 
     630             :     // iterate through inclusion circles and calculate minimum margin
     631        6824 :     bool margin_updated = false;
     632        6824 :     for (uint8_t i = 0; i < num_inclusion_circles; i++) {
     633           0 :         Vector2f center_pos_cm;
     634             :         float radius;
     635           0 :         if (fence->polyfence().get_inclusion_circle(i, center_pos_cm, radius)) {
     636             : 
     637             :             // calculate start and ends distance from the center of the circle
     638           0 :             const float start_dist_sq = (start_NE - center_pos_cm).length_squared();
     639           0 :             const float end_dist_sq = (end_NE - center_pos_cm).length_squared();
     640             : 
     641             :             // margin is fence radius minus the longer of start or end distance
     642           0 :             const float margin_new = (radius + fence_margin) - (sqrtf(MAX(start_dist_sq, end_dist_sq)) * 0.01f);
     643             : 
     644             :             // update margin with lowest value so far
     645           0 :             if (!margin_updated || (margin_new < margin)) {
     646           0 :                 margin_updated = true;
     647           0 :                 margin = margin_new;
     648             :             }
     649             :         }
     650             :     }
     651             : 
     652             :     // iterate through exclusion circles and calculate minimum margin
     653       20472 :     for (uint8_t i = 0; i < num_exclusion_circles; i++) {
     654       13648 :         Vector2f center_pos_cm;
     655             :         float radius;
     656       13648 :         if (fence->polyfence().get_exclusion_circle(i, center_pos_cm, radius)) {
     657             : 
     658             :             // first calculate distance between circle's center and segment
     659       13648 :             const float dist_cm = Vector2f::closest_distance_between_line_and_point(start_NE, end_NE, center_pos_cm);
     660             : 
     661             :             // margin is distance to the center minus the radius
     662       13648 :             const float margin_new = (dist_cm * 0.01f) - (radius + fence_margin);
     663             : 
     664             :             // update margin with lowest value so far
     665       13648 :             if (!margin_updated || (margin_new < margin)) {
     666       10908 :                 margin_updated = true;
     667       10908 :                 margin = margin_new;
     668             :             }
     669             :         }
     670             :     }
     671             : 
     672        6824 :     return margin_updated;
     673             : #else
     674             :     return false;
     675             : #endif // AP_FENCE_ENABLED
     676             : }
     677             : 
     678             : // calculate minimum distance between a path and proximity sensor obstacles
     679             : // on success returns true and updates margin
     680        6824 : bool AP_OABendyRuler::calc_margin_from_object_database(const Location &start, const Location &end, float &margin) const
     681             : {
     682             :     // exit immediately if db is empty
     683        6824 :     AP_OADatabase *oaDb = AP::oadatabase();
     684        6824 :     if (oaDb == nullptr || !oaDb->healthy()) {
     685           0 :         return false;
     686             :     }
     687             : 
     688             :     // convert start and end to offsets (in cm) from EKF origin
     689        6824 :     Vector3f start_NEU,end_NEU;
     690       13648 :     if (!start.get_vector_from_origin_NEU_cm(start_NEU) ||
     691        6824 :         !end.get_vector_from_origin_NEU_cm(end_NEU)) {
     692           0 :         return false;
     693             :     }
     694        6824 :     if (start_NEU == end_NEU) {
     695           0 :         return false;
     696             :     }
     697             : 
     698             :     // check each obstacle's distance from segment
     699        6824 :     float smallest_margin = FLT_MAX;
     700        6824 :     for (uint16_t i=0; i<oaDb->database_count(); i++) {
     701           0 :         const AP_OADatabase::OA_DbItem& item = oaDb->get_item(i);
     702           0 :         const Vector3f point_cm = item.pos * 100.0f;
     703             :         // margin is distance between line segment and obstacle minus obstacle's radius
     704           0 :         const float m = Vector3f::closest_distance_between_line_and_point(start_NEU, end_NEU, point_cm) * 0.01f - item.radius;
     705           0 :         if (m < smallest_margin) {
     706           0 :             smallest_margin = m;
     707             :         }
     708             :     }
     709             : 
     710             :     // return smallest margin
     711        6824 :     if (smallest_margin < FLT_MAX) {
     712           0 :         margin = smallest_margin;
     713           0 :         return true;
     714             :     }
     715             : 
     716        6824 :     return false;
     717             : }
     718             : 
     719             : #endif  // AP_OAPATHPLANNER_BENDYRULER_ENABLED

Generated by: LCOV version 1.14