Line data Source code
1 : #include <AP_HAL/AP_HAL.h>
2 : #include <AP_Vehicle/AP_Vehicle_Type.h>
3 : #include "AC_WPNav.h"
4 :
5 : extern const AP_HAL::HAL& hal;
6 :
7 : // Default waypoint navigation constraints
8 : #define WP_SPD_DEFAULT 10.0f // default horizontal speed between waypoints (m/s)
9 : #define WP_SPD_MIN 0.01f // minimum horizontal speed allowed (m/s)
10 : #define WP_RADIUS_M_DEFAULT 2.0f // default radius within which a waypoint is considered reached (m)
11 : #define WP_RADIUS_M_MIN 0.05f // minimum allowable waypoint radius (m)
12 : #define WP_SPD_UP_DEFAULT 2.5f // default maximum climb speed (m/s)
13 : #define WP_SPD_DOWN_DEFAULT 1.5f // default maximum descent speed (m/s)
14 : #define WP_ACC_Z_DEFAULT 1.0f // default vertical acceleration limit (m/s²)
15 :
16 : const AP_Param::GroupInfo AC_WPNav::var_info[] = {
17 : // 0 was SPEED
18 : // 1 was RADIUS
19 : // 2 was SPEED_UP
20 : // 3 was SPEED_DN
21 : // 5 was ACCEL
22 : // 6 was ACCEL_Z
23 :
24 : // @Param: RFND_USE
25 : // @DisplayName: Waypoint missions use rangefinder for terrain following
26 : // @Description: This controls if waypoint missions use rangefinder for terrain following
27 : // @Values: 0:Disable,1:Enable
28 : // @User: Advanced
29 : AP_GROUPINFO("RFND_USE", 10, AC_WPNav, _rangefinder_use, 1),
30 :
31 : // @Param: JERK
32 : // @DisplayName: Waypoint Jerk
33 : // @Description: Defines the horizontal jerk in m/s/s used during missions
34 : // @Units: m/s/s/s
35 : // @Range: 1 20
36 : // @User: Standard
37 : AP_GROUPINFO("JERK", 11, AC_WPNav, _wp_jerk_msss, 1.0f),
38 :
39 : // @Param: TER_MARGIN
40 : // @DisplayName: Waypoint Terrain following altitude margin
41 : // @Description: Waypoint Terrain following altitude margin. Vehicle will stop if distance from target altitude is larger than this margin (in meters)
42 : // @Units: m
43 : // @Range: 0.1 100
44 : // @User: Advanced
45 : AP_GROUPINFO("TER_MARGIN", 12, AC_WPNav, _terrain_margin_m, 10.0),
46 :
47 : // 13 was ACCEL_C
48 :
49 : // @Param: SPD
50 : // @DisplayName: Waypoint Horizontal Speed Target
51 : // @Description: Speed in m/s which the aircraft will attempt to maintain horizontally during a WP mission
52 : // @Units: m/s
53 : // @Range: 0.10 20.00
54 : // @Increment: 0.10
55 : // @User: Standard
56 : AP_GROUPINFO("SPD", 14, AC_WPNav, _wp_speed_ms, WP_SPD_DEFAULT),
57 :
58 : // @Param: RADIUS_M
59 : // @DisplayName: Waypoint Radius
60 : // @Description: Distance from a waypoint, that when crossed indicates the wp has been reached.
61 : // @Units: m
62 : // @Range: 0.05 10.00
63 : // @Increment: 0.01
64 : // @User: Standard
65 : AP_GROUPINFO("RADIUS_M", 15, AC_WPNav, _wp_radius_m, WP_RADIUS_M_DEFAULT),
66 :
67 : // @Param: SPD_UP
68 : // @DisplayName: Waypoint Climb Speed Target
69 : // @Description: Speed in m/s which the aircraft will attempt to maintain while climbing during a WP mission
70 : // @Units: m/s
71 : // @Range: 0.10 10.00
72 : // @Increment: 0.10
73 : // @User: Standard
74 : AP_GROUPINFO("SPD_UP", 16, AC_WPNav, _wp_speed_up_ms, WP_SPD_UP_DEFAULT),
75 :
76 : // @Param: SPD_DN
77 : // @DisplayName: Waypoint Descent Speed Target
78 : // @Description: Speed in m/s which the aircraft will attempt to maintain while descending during a WP mission
79 : // @Units: m/s
80 : // @Range: 0.10 10.00
81 : // @Increment: 0.10
82 : // @User: Standard
83 : AP_GROUPINFO("SPD_DN", 17, AC_WPNav, _wp_speed_down_ms, WP_SPD_DOWN_DEFAULT),
84 :
85 : // @Param: ACC
86 : // @DisplayName: Waypoint Acceleration
87 : // @Description: Horizontal acceleration in m/s/s used during missions
88 : // @Units: m/s/s
89 : // @Range: 0.50 5.00
90 : // @Increment: 0.10
91 : // @User: Standard
92 : AP_GROUPINFO("ACC", 18, AC_WPNav, _wp_accel_mss, WPNAV_ACCELERATION_MS),
93 :
94 : // @Param: ACC_Z
95 : // @DisplayName: Waypoint Vertical Acceleration
96 : // @Description: Vertical acceleration in m/s/s used during missions
97 : // @Units: m/s/s
98 : // @Range: 0.50 5.00
99 : // @Increment: 0.10
100 : // @User: Standard
101 : AP_GROUPINFO("ACC_Z", 19, AC_WPNav, _wp_accel_z_mss, WP_ACC_Z_DEFAULT),
102 :
103 : // @Param: ACC_CNR
104 : // @DisplayName: Waypoint Cornering Acceleration
105 : // @Description: Maximum cornering acceleration in m/s/s used during missions. If zero uses 2x accel value.
106 : // @Units: m/s/s
107 : // @Range: 0 5.00
108 : // @Increment: 0.10
109 : // @User: Standard
110 : AP_GROUPINFO("ACC_CNR", 20, AC_WPNav, _wp_accel_c_mss, 0.0),
111 :
112 : AP_GROUPEND
113 : };
114 :
115 : // perform any required parameter conversions
116 1285 : void AC_WPNav::convert_parameters()
117 : {
118 : // PARAMETER_CONVERSION - Added: Feb-2026 for ArduPilot-4.7
119 :
120 : // return immediately if no conversion is needed
121 3460 : if (_wp_speed_ms.configured() || _wp_radius_m.configured() || _wp_speed_up_ms.configured() ||
122 4532 : _wp_speed_down_ms.configured() || _wp_accel_mss.configured() || _wp_accel_z_mss.configured() ||
123 1072 : _wp_accel_c_mss.configured()) {
124 213 : return;
125 : }
126 :
127 : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
128 : // convert QuadPlane parameters from cm to meters
129 : static const AP_Param::ConversionInfo conversion_info[] = {
130 : { 205, 4052, AP_PARAM_FLOAT, "Q_WP_SPD" }, // old Q_WP_SPEED at GroupElement 4052
131 : { 205, 84, AP_PARAM_FLOAT, "Q_WP_RADIUS_M" }, // old Q_WP_RADIUS at GroupElement 84
132 : { 205, 148, AP_PARAM_FLOAT, "Q_WP_SPD_UP" }, // old Q_WP_SPEED_UP at GroupElement 148
133 : { 205, 212, AP_PARAM_FLOAT, "Q_WP_SPD_DN" }, // old Q_WP_SPEED_DN at GroupElement 212
134 : { 205, 340, AP_PARAM_FLOAT, "Q_WP_ACC" }, // old Q_WP_ACCEL at GroupElement 340
135 : { 205, 404, AP_PARAM_FLOAT, "Q_WP_ACC_Z" }, // old Q_WP_ACCEL_Z at GroupElement 404
136 : { 205, 852, AP_PARAM_FLOAT, "Q_WP_ACC_CNR" }, // old Q_WP_ACCEL_C at GroupElement 852
137 : };
138 : #elif APM_BUILD_TYPE(APM_BUILD_ArduSub)
139 : // convert Sub parameters from cm to meters
140 : static const AP_Param::ConversionInfo conversion_info[] = {
141 : { 55, 0, AP_PARAM_FLOAT, "WP_SPD" }, // old WPNAV_SPEED at index 0
142 : { 55, 1, AP_PARAM_FLOAT, "WP_RADIUS_M" }, // old WPNAV_RADIUS at index 1
143 : { 55, 2, AP_PARAM_FLOAT, "WP_SPD_UP" }, // old WPNAV_SPEED_UP at index 2
144 : { 55, 3, AP_PARAM_FLOAT, "WP_SPD_DN" }, // old WPNAV_SPEED_DN at index 3
145 : { 55, 5, AP_PARAM_FLOAT, "WP_ACC" }, // old WPNAV_ACCEL at index 5
146 : { 55, 6, AP_PARAM_FLOAT, "WP_ACC_Z" }, // old WPNAV_ACCEL_Z at index 6
147 : { 55, 13, AP_PARAM_FLOAT, "WP_ACC_CNR" }, // old WPNAV_ACCEL_C at index 13
148 : };
149 : #else
150 : // convert Copter parameters from cm to meters
151 : static const AP_Param::ConversionInfo conversion_info[] = {
152 : { 101, 0, AP_PARAM_FLOAT, "WP_SPD" }, // old WPNAV_SPEED at index 0
153 : { 101, 1, AP_PARAM_FLOAT, "WP_RADIUS_M" }, // old WPNAV_RADIUS at index 1
154 : { 101, 2, AP_PARAM_FLOAT, "WP_SPD_UP" }, // old WPNAV_SPEED_UP at index 2
155 : { 101, 3, AP_PARAM_FLOAT, "WP_SPD_DN" }, // old WPNAV_SPEED_DN at index 3
156 : { 101, 5, AP_PARAM_FLOAT, "WP_ACC" }, // old WPNAV_ACCEL at index 5
157 : { 101, 6, AP_PARAM_FLOAT, "WP_ACC_Z" }, // old WPNAV_ACCEL_Z at index 6
158 : { 101, 13, AP_PARAM_FLOAT, "WP_ACC_CNR" }, // old WPNAV_ACCEL_C at index 13
159 : };
160 : #endif
161 :
162 1072 : AP_Param::convert_old_parameters_scaled(conversion_info, ARRAY_SIZE(conversion_info), 0.01, 0);
163 : }
164 :
165 : // Default constructor.
166 : // Note that the Vector/Matrix constructors already implicitly zero their values.
167 1285 : AC_WPNav::AC_WPNav(const AP_AHRS_View& ahrs, AC_PosControl& pos_control, const AC_AttitudeControl& attitude_control) :
168 1285 : _ahrs(ahrs),
169 1285 : _pos_control(pos_control),
170 1285 : _attitude_control(attitude_control)
171 : {
172 1285 : AP_Param::setup_object_defaults(this, var_info);
173 :
174 : // init flags
175 1285 : _flags.reached_destination = false;
176 1285 : _flags.fast_waypoint = false;
177 :
178 : // record WP_SPD, WP_SPD_UP, WP_SPD_DN values so we can detect changes
179 1285 : _last_wp_speed_ms = _wp_speed_ms;
180 1285 : _last_wp_speed_up_ms = _wp_speed_up_ms;
181 1285 : _last_wp_speed_down_ms = get_default_speed_down_ms();
182 1285 : }
183 :
184 : // Returns the expected source of terrain data when using alt-above-terrain commands.
185 : // Used by systems like ModeRTL to determine which terrain provider is active.
186 425195 : AC_WPNav::TerrainSource AC_WPNav::get_terrain_source() const
187 : {
188 : // use range finder if connected
189 : // prioritise rangefinder when enabled and available
190 425195 : if (_rangefinder_available && _rangefinder_use) {
191 191984 : return AC_WPNav::TerrainSource::TERRAIN_FROM_RANGEFINDER;
192 : }
193 : #if AP_TERRAIN_AVAILABLE
194 233211 : const AP_Terrain *terrain = AP::terrain();
195 : // use terrain database if enabled
196 233211 : if (terrain != nullptr && terrain->enabled()) {
197 233054 : return AC_WPNav::TerrainSource::TERRAIN_FROM_TERRAINDATABASE;
198 : } else {
199 157 : return AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE;
200 : }
201 : #else
202 : // fallback if no terrain support is compiled in
203 : return AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE;
204 : #endif
205 : }
206 :
207 : ///
208 : /// waypoint navigation
209 : ///
210 :
211 : // Initializes waypoint and spline navigation using inputs in meters.
212 : // Sets speed and acceleration limits, calculates jerk constraints,
213 : // and initializes spline or S-curve leg with a defined starting point.
214 15182 : void AC_WPNav::wp_and_spline_init_m(float speed_ms, Vector3p stopping_point_ned_m)
215 : {
216 : // ensure waypoint radius is not below minimum allowed value
217 15182 : _wp_radius_m.set_and_save_ifchanged(MAX(_wp_radius_m, WP_RADIUS_M_MIN));
218 :
219 : // ensure waypoint speed is not below minimum allowed value
220 15182 : _wp_speed_ms.set_and_save_ifchanged(MAX(_wp_speed_ms, WP_SPD_MIN));
221 :
222 : // initialise position controller
223 15182 : _pos_control.D_init_controller_stopping_point();
224 15182 : _pos_control.NE_init_controller_stopping_point();
225 :
226 : // determine desired waypoint speed; fallback to default if not provided
227 15182 : _check_wp_speed_change = !is_positive(speed_ms);
228 15182 : _wp_desired_speed_ne_ms = is_positive(speed_ms) ? speed_ms : get_default_speed_NE_ms();
229 15182 : _wp_desired_speed_ne_ms = MAX(_wp_desired_speed_ne_ms, WP_SPD_MIN);
230 :
231 : // initialise position controller speed and acceleration
232 15182 : _pos_control.NE_set_max_speed_accel_m(_wp_desired_speed_ne_ms, get_wp_acceleration_mss());
233 15182 : _pos_control.NE_set_correction_speed_accel_m(_wp_desired_speed_ne_ms, get_wp_acceleration_mss());
234 15182 : _pos_control.D_set_max_speed_accel_m(get_default_speed_down_ms(), get_default_speed_up_ms(), get_accel_D_mss());
235 15182 : _pos_control.D_set_correction_speed_accel_m(get_default_speed_down_ms(), get_default_speed_up_ms(), get_accel_D_mss());
236 :
237 : // calculate jerk limit if not explicitly set by parameter
238 15182 : if (!is_positive(_wp_jerk_msss)) {
239 0 : _wp_jerk_msss.set(get_wp_acceleration_mss());
240 : }
241 15182 : calc_scurve_jerk_and_snap();
242 :
243 15182 : _scurve_prev_leg.init();
244 15182 : _scurve_this_leg.init();
245 15182 : _scurve_next_leg.init();
246 15182 : _track_dt_scalar = 1.0f;
247 :
248 15182 : _flags.reached_destination = true;
249 15182 : _flags.fast_waypoint = false;
250 :
251 : // determine initial origin and destination; fallback to current stopping point if not provided
252 15182 : if (stopping_point_ned_m.is_zero()) {
253 15125 : get_wp_stopping_point_NED_m(stopping_point_ned_m);
254 : }
255 15182 : _origin_ned_m = _destination_ned_m = stopping_point_ned_m;
256 15182 : _is_terrain_alt = false;
257 15182 : _this_leg_is_spline = false;
258 15182 : _this_leg_is_circle = false;
259 :
260 : // initialise velocity profile for terrain margin shaping
261 15182 : _offset_vel_ms = _wp_desired_speed_ne_ms;
262 15182 : _offset_accel_mss = 0.0;
263 15182 : _paused = false;
264 :
265 : // mark as active
266 15182 : _wp_last_update_ms = AP_HAL::millis();
267 15182 : }
268 :
269 : // Sets the target horizontal speed in cm/s during waypoint navigation.
270 : // See set_speed_NE_ms() for full details.
271 6 : void AC_WPNav::set_speed_NE_cms(float speed_cms)
272 : {
273 6 : set_speed_NE_ms(speed_cms * 0.01);
274 6 : }
275 :
276 : // Sets the target horizontal speed in m/s during waypoint navigation.
277 : // Also updates internal velocity offsets and path shaping limits.
278 667 : void AC_WPNav::set_speed_NE_ms(float speed_ms)
279 : {
280 : // validate input: speed must be above minimum and current desired speed must be non-zero
281 667 : if (speed_ms >= WP_SPD_MIN && is_positive(_wp_desired_speed_ne_ms)) {
282 : // adjust internal velocity offset to preserve relative motion scaling
283 667 : _offset_vel_ms = speed_ms * _offset_vel_ms / _wp_desired_speed_ne_ms;
284 :
285 : // set new desired horizontal speed for waypoint controller
286 667 : _wp_desired_speed_ne_ms = speed_ms;
287 :
288 : // update horizontal shaping and correction constraints
289 667 : _pos_control.NE_set_max_speed_accel_m(_wp_desired_speed_ne_ms, get_wp_acceleration_mss());
290 667 : _pos_control.NE_set_correction_speed_accel_m(_wp_desired_speed_ne_ms, get_wp_acceleration_mss());
291 :
292 : // update internal trajectory with new limits
293 667 : update_track_with_speed_accel_limits();
294 : }
295 667 : }
296 :
297 : // Sets the climb speed for waypoint navigation in m/s.
298 : // Updates the vertical controller with the new ascent rate limit.
299 11 : void AC_WPNav::set_speed_up_ms(float speed_up_ms)
300 : {
301 : // update vertical controller's max speed and accel limits (U axis)
302 11 : _pos_control.D_set_max_speed_accel_m(_pos_control.get_max_speed_down_ms(), speed_up_ms, _pos_control.D_get_max_accel_mss());
303 :
304 : // recompute the trajectory using updated limits
305 11 : update_track_with_speed_accel_limits();
306 11 : }
307 :
308 : // Sets the descent speed for waypoint navigation in m/s.
309 : // Updates the vertical controller with the new descent rate limit.
310 8 : void AC_WPNav::set_speed_down_ms(float speed_down_ms)
311 : {
312 : // update vertical controller descent speed
313 8 : _pos_control.D_set_max_speed_accel_m(speed_down_ms, _pos_control.get_max_speed_up_ms(), _pos_control.D_get_max_accel_mss());
314 :
315 : // recompute the trajectory using updated limits
316 8 : update_track_with_speed_accel_limits();
317 8 : }
318 :
319 : // Sets the current waypoint destination using a Location object.
320 : // Converts global coordinates to NED position and sets destination.
321 : // arc_rad specifies the signed arc angle in radians for an ARC_WAYPOINT segment (0 for straight path)
322 : // Returns false if conversion fails (e.g. missing terrain data).
323 850 : bool AC_WPNav::set_wp_destination_loc(const Location& destination, float arc_rad)
324 : {
325 : bool is_terrain_alt;
326 850 : Vector3p dest_ned_m;
327 :
328 : // convert Location to NED position vector in meters and determine altitude reference frame
329 850 : if (!get_vector_NED_m(destination, dest_ned_m, is_terrain_alt)) {
330 0 : return false;
331 : }
332 :
333 : // apply destination as the active waypoint leg
334 850 : return set_wp_destination_NED_m(dest_ned_m, is_terrain_alt, arc_rad);
335 : }
336 :
337 : // Sets the next waypoint destination using a Location object.
338 : // Converts global coordinates to NED position and preloads the trajectory.
339 : // arc_rad specifies the signed arc angle in radians for an ARC_WAYPOINT segment (0 for straight path)
340 : // Returns false if conversion fails or terrain data is unavailable.
341 446 : bool AC_WPNav::set_wp_destination_next_loc(const Location& destination, float arc_rad)
342 : {
343 : bool is_terrain_alt;
344 446 : Vector3p dest_ned_m;
345 :
346 : // convert Location to NED position vector in meters and determine altitude reference frame
347 446 : if (!get_vector_NED_m(destination, dest_ned_m, is_terrain_alt)) {
348 0 : return false;
349 : }
350 :
351 : // apply destination as the next waypoint leg
352 446 : return set_wp_destination_next_NED_m(dest_ned_m, is_terrain_alt, arc_rad);
353 : }
354 :
355 : // Gets the current waypoint destination as a Location object.
356 : // Altitude frame will be ABOVE_TERRAIN or ABOVE_ORIGIN depending on path configuration.
357 : // Returns false if origin is not set or coordinate conversion fails.
358 60461 : bool AC_WPNav::get_wp_destination_loc(Location& destination) const
359 : {
360 : // retrieve global origin for coordinate conversion
361 60461 : if (!AP::ahrs().get_origin(destination)) {
362 376 : return false;
363 : }
364 :
365 : // convert NED waypoint to global Location format with appropriate altitude frame
366 60085 : destination = Location::from_ekf_offset_NED_m(get_wp_destination_NED_m(), _is_terrain_alt ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN);
367 60085 : return true;
368 : }
369 :
370 : // Sets waypoint destination using NEU position vector in centimeters from EKF origin.
371 : // See set_wp_destination_NED_m() for full details.
372 12 : bool AC_WPNav::set_wp_destination_NEU_cm(const Vector3f& destination_neu_cm, bool is_terrain_alt)
373 : {
374 12 : Vector3p destination_ned_m = Vector3p(destination_neu_cm.x, destination_neu_cm.y, -destination_neu_cm.z) * 0.01;
375 12 : return set_wp_destination_NED_m(destination_ned_m, is_terrain_alt);
376 : }
377 :
378 : // Sets waypoint destination using NED position vector in meters from EKF origin.
379 : // If `is_terrain_alt` is true, altitude is interpreted as height above terrain.
380 : // Reinitializes the current leg if interrupted, updates origin, and computes trajectory.
381 : // arc_rad specifies the signed arc angle in radians for an ARC_WAYPOINT segment (0 for straight path)
382 : // Returns false if terrain offset cannot be determined when required.
383 950 : bool AC_WPNav::set_wp_destination_NED_m(const Vector3p& destination_ned_m, bool is_terrain_alt, float arc_rad)
384 : {
385 : // re-initialise if previous destination has been interrupted
386 950 : if (!is_active() || !_flags.reached_destination) {
387 26 : wp_and_spline_init_m(_wp_desired_speed_ne_ms);
388 : }
389 :
390 950 : _scurve_prev_leg.init();
391 950 : float origin_speed_m = 0.0f;
392 :
393 : // use previous destination as origin
394 950 : _origin_ned_m = _destination_ned_m;
395 :
396 950 : if (is_terrain_alt == _is_terrain_alt) {
397 937 : if (_this_leg_is_spline) {
398 : // Use velocity from end of spline leg to seed new S-curve origin speed
399 5 : Vector3f curr_target_vel_ned_ms = _pos_control.get_vel_desired_NED_ms();
400 5 : curr_target_vel_ned_ms.z -= _pos_control.get_vel_offset_D_ms();
401 5 : origin_speed_m = curr_target_vel_ned_ms.length();
402 : } else {
403 : // Preserve current leg profile to enable blending with new leg
404 932 : _scurve_prev_leg = _scurve_this_leg;
405 : }
406 13 : } else if (!convert_origin_to_alt_frame(is_terrain_alt)) {
407 0 : return false;
408 : }
409 :
410 : // update destination
411 950 : _destination_ned_m = destination_ned_m;
412 950 : _is_terrain_alt = is_terrain_alt;
413 :
414 950 : if (_flags.fast_waypoint && !_this_leg_is_spline && !_next_leg_is_spline && !_scurve_next_leg.finished()) {
415 : // Reuse preloaded next leg if valid to avoid unnecessary recalculation
416 394 : _scurve_this_leg = _scurve_next_leg;
417 : } else {
418 : // Generate a new S-curve segment to the new destination
419 1112 : _scurve_this_leg.calculate_track(_origin_ned_m, _destination_ned_m, arc_rad,
420 556 : _pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
421 : get_wp_acceleration_mss(), get_accel_D_mss(), get_corner_acceleration_mss(),
422 : _scurve_snap_max_mssss, _scurve_jerk_max_msss);
423 556 : if (!is_zero(origin_speed_m)) {
424 : // If we have a valid starting speed, seed it into the S-curve
425 5 : _scurve_this_leg.set_origin_speed_max(origin_speed_m);
426 : }
427 : }
428 :
429 950 : _this_leg_is_spline = false;
430 950 : _this_leg_is_circle = false;
431 950 : _scurve_next_leg.init();
432 950 : _next_destination_ned_m.zero(); // clear next destination_ned_m
433 950 : _flags.fast_waypoint = false; // default waypoint back to slow
434 950 : _flags.reached_destination = false;
435 :
436 950 : return true;
437 : }
438 :
439 : // Sets a circular-orbit destination about center_ne_m using the S-curve engine.
440 : // See the header for the full parameter description. The current destination is used as the
441 : // leg origin and must lie on the circle; the radius is derived from origin-to-center distance.
442 4 : bool AC_WPNav::set_circle_destination_NED_m(const Vector2f& center_ne_m, float turns_signed, float dest_d_m, bool is_terrain_alt)
443 : {
444 : // re-initialise if previous destination has been interrupted
445 4 : if (!is_active() || !_flags.reached_destination) {
446 0 : wp_and_spline_init_m(_wp_desired_speed_ne_ms);
447 : }
448 :
449 4 : _scurve_prev_leg.init();
450 :
451 : // use previous destination as origin; it is assumed to lie on the circle
452 4 : _origin_ned_m = _destination_ned_m;
453 :
454 4 : if ((is_terrain_alt != _is_terrain_alt) && !convert_origin_to_alt_frame(is_terrain_alt)) {
455 0 : return false;
456 : }
457 :
458 : // build the S-curve orbit leg; net climb is applied linearly along the arc
459 4 : const float total_angle_rad = turns_signed * M_2PI;
460 4 : const float climb_d_m = dest_d_m - _origin_ned_m.z;
461 8 : _scurve_this_leg.calculate_circle_track(_origin_ned_m, center_ne_m, total_angle_rad, climb_d_m,
462 4 : _pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
463 : get_wp_acceleration_mss(), get_accel_D_mss(), get_corner_acceleration_mss(),
464 : _scurve_snap_max_mssss, _scurve_jerk_max_msss);
465 :
466 : // the leg itself is the source of truth for where the orbit ends (for whole turns it
467 : // returns to the origin's NE position). A degenerate arc leaves the origin-to-destination
468 : // vector zero, so the destination stays at the origin and the leg completes in place.
469 4 : _destination_ned_m = _origin_ned_m + _scurve_this_leg.get_origin_to_destination().topostype();
470 4 : _is_terrain_alt = is_terrain_alt;
471 :
472 4 : _this_leg_is_spline = false;
473 4 : _this_leg_is_circle = true;
474 4 : _scurve_next_leg.init();
475 4 : _next_destination_ned_m.zero();
476 4 : _flags.fast_waypoint = false;
477 4 : _flags.reached_destination = false;
478 :
479 4 : return true;
480 : }
481 :
482 : // Sets the next waypoint destination using a NED position vector in meters.
483 : // Only updates if terrain frame matches current leg.
484 : // Calculates trajectory preview for smoother transition into next segment.
485 : // Updates velocity handoff if previous leg is a spline.
486 : // arc_rad specifies the signed arc angle in radians for an ARC_WAYPOINT segment (0 for straight path)
487 478 : bool AC_WPNav::set_wp_destination_next_NED_m(const Vector3p& destination_ned_m, bool is_terrain_alt, float arc_rad)
488 : {
489 : // do not add next point if alt types don't match
490 478 : if (is_terrain_alt != _is_terrain_alt) {
491 0 : return true;
492 : }
493 :
494 : // Preload next S-curve leg with current speed and acceleration constraints
495 956 : _scurve_next_leg.calculate_track(_destination_ned_m, destination_ned_m, arc_rad,
496 478 : _pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
497 : get_wp_acceleration_mss(), get_accel_D_mss(), get_corner_acceleration_mss(),
498 : _scurve_snap_max_mssss, _scurve_jerk_max_msss);
499 478 : if (_this_leg_is_spline) {
500 5 : const float this_leg_dest_speed_max_ms = _spline_this_leg.get_destination_speed_max();
501 : // Pass velocity forward to allow continuous spline-scurve transition
502 5 : const float next_leg_origin_speed_max_ms = _scurve_next_leg.set_origin_speed_max(this_leg_dest_speed_max_ms);
503 5 : _spline_this_leg.set_destination_speed_max(next_leg_origin_speed_max_ms);
504 : }
505 478 : _next_leg_is_spline = false;
506 :
507 : // Enable fast waypoint transition since next leg is known
508 478 : _flags.fast_waypoint = true;
509 :
510 : // Store the upcoming destination for reference
511 478 : _next_destination_ned_m = destination_ned_m;
512 :
513 478 : return true;
514 : }
515 :
516 : // Computes the horizontal stopping point in NE frame, returned in centimeters.
517 : // See get_wp_stopping_point_NE_m() for full details.
518 0 : void AC_WPNav::get_wp_stopping_point_NE_cm(Vector2f& stopping_point_ne_cm) const
519 : {
520 0 : Vector2p stopping_point_ne_m;
521 0 : get_wp_stopping_point_NE_m(stopping_point_ne_m);
522 0 : stopping_point_ne_cm = stopping_point_ne_m.tofloat() * 100.0;
523 0 : }
524 :
525 : // Computes the horizontal stopping point in NE frame, in meters, based on current velocity and configured acceleration.
526 : // This is the point where the vehicle would come to a stop if decelerated using the configured limits.
527 0 : void AC_WPNav::get_wp_stopping_point_NE_m(Vector2p& stopping_point_ne_m) const
528 : {
529 : // request stopping point from the position controller
530 0 : _pos_control.get_stopping_point_NE_m(stopping_point_ne_m);
531 0 : }
532 :
533 : // Computes the full 3D NEU stopping point vector in centimeters based on current kinematics.
534 : // See get_wp_stopping_point_NED_m() for full details.
535 12 : void AC_WPNav::get_wp_stopping_point_NEU_cm(Vector3f& stopping_point_neu_cm) const
536 : {
537 12 : Vector3p stopping_point_ned_m;
538 : // compute stopping point using meters
539 12 : get_wp_stopping_point_NED_m(stopping_point_ned_m);
540 : // convert result back to centimeters
541 12 : stopping_point_neu_cm = Vector3f(stopping_point_ned_m.x, stopping_point_ned_m.y, -stopping_point_ned_m.z) * 100.0;
542 12 : }
543 :
544 : // Computes the full 3D NED stopping point in meters based on current velocity and configured acceleration in all axes.
545 : // Represents where the vehicle will stop if decelerated from current velocity using configured limits.
546 15140 : void AC_WPNav::get_wp_stopping_point_NED_m(Vector3p& stopping_point_ned_m) const
547 : {
548 : // get horizontal stopping point (North and East)
549 15140 : _pos_control.get_stopping_point_NE_m(stopping_point_ned_m.xy());
550 : // get vertical stopping point (Down)
551 15140 : _pos_control.get_stopping_point_D_m(stopping_point_ned_m.z);
552 15140 : }
553 :
554 : // Advances the target location along the current path segment.
555 : // Updates target position, velocity, and acceleration based on jerk-limited profile (or spline).
556 : // Returns true if the update succeeded (e.g., terrain data was available).
557 4010709 : bool AC_WPNav::advance_wp_target_along_track(float dt)
558 : {
559 : // calculate terrain offset if using alt-above-terrain frame
560 4010709 : float terr_offset_d_m = 0.0f;
561 4010709 : if (_is_terrain_alt && !get_terrain_D_m(terr_offset_d_m)) {
562 1660 : return false;
563 : }
564 :
565 : // calculate terrain-based velocity scaling factor
566 4009049 : const float offset_d_scalar = _pos_control.terrain_scaler_D_m(terr_offset_d_m, get_terrain_margin_m());
567 :
568 : // input shape the terrain offset
569 4009049 : _pos_control.set_pos_terrain_target_D_m(terr_offset_d_m);
570 :
571 : // get position controller's post-shaped position offset for use in position error computation
572 4009049 : const Vector3p& psc_pos_offset_ned_m = _pos_control.get_pos_offset_NED_m();
573 :
574 : // compute current position in NED frame, adjusted to destination frame (e.g., terrain-relative if needed)
575 4009049 : Vector3p curr_pos_ned_m = _pos_control.get_pos_estimate_NED_m() - psc_pos_offset_ned_m;
576 4009049 : curr_pos_ned_m.z -= terr_offset_d_m;
577 :
578 : // get desired velocity and remove offset
579 4009049 : Vector3f curr_target_vel_ned_ms = _pos_control.get_vel_desired_NED_ms();
580 4009049 : curr_target_vel_ned_ms.z -= _pos_control.get_vel_offset_D_ms();
581 :
582 : // scale progression time (track_dt_scalar) based on aircraft’s speed alignment with desired path
583 4009049 : float track_dt_scalar = 1.0f;
584 4009049 : if (is_positive(curr_target_vel_ned_ms.length_squared())) {
585 3294620 : Vector3f track_direction_neu = curr_target_vel_ned_ms.normalized();
586 3294620 : const float track_error_ned_m = _pos_control.get_pos_error_NED_m().dot(track_direction_neu);
587 3294620 : const float track_velocity_ned_ms = _pos_control.get_vel_estimate_NED_ms().dot(track_direction_neu);
588 : // limit time step scalar to [0,1], with 5% buffer
589 3294620 : track_dt_scalar = constrain_float(0.05f + (track_velocity_ned_ms - _pos_control.NE_get_pos_p().kP() * track_error_ned_m) / curr_target_vel_ned_ms.length(), 0.0f, 1.0f);
590 : }
591 :
592 : // compute velocity scaling (vel_dt_scalar) and apply jerk-limited velocity shaping
593 4009049 : float vel_dt_scalar = 1.0;
594 4009049 : if (is_positive(_wp_desired_speed_ne_ms)) {
595 4009049 : update_vel_accel(_offset_vel_ms, _offset_accel_mss, dt, 0.0, 0.0);
596 4009049 : const float vel_input_ms = !_paused ? _wp_desired_speed_ne_ms * offset_d_scalar : 0.0;
597 4009049 : shape_vel_accel(vel_input_ms, 0.0, _offset_vel_ms, _offset_accel_mss, -get_wp_acceleration_mss(), get_wp_acceleration_mss(),
598 4009049 : _pos_control.get_shaping_jerk_NE_msss(), dt, true);
599 4009049 : vel_dt_scalar = _offset_vel_ms / _wp_desired_speed_ne_ms;
600 : }
601 :
602 : // apply exponential filter to track_dt_scalar using jerk-based time constant
603 4009049 : float track_dt_scalar_tc = 1.0f;
604 4009049 : if (!is_zero(_wp_jerk_msss)) {
605 4009049 : track_dt_scalar_tc = get_wp_acceleration_mss()/_wp_jerk_msss;
606 : }
607 4009049 : _track_dt_scalar += (track_dt_scalar - _track_dt_scalar) * (dt / track_dt_scalar_tc);
608 :
609 : // select path generator (S-curve or spline) and compute target state
610 4009049 : Vector3p target_pos_ned_m;
611 4009049 : Vector3f target_vel_ned_ms, target_accel_ned_mss;
612 : bool s_finished;
613 4009049 : if (!_this_leg_is_spline) {
614 : // update target position, velocity and acceleration
615 3901332 : target_pos_ned_m = _origin_ned_m;
616 3901332 : s_finished = _scurve_this_leg.advance_target_along_track(_scurve_prev_leg, _scurve_next_leg, _wp_radius_m, get_corner_acceleration_mss(), _flags.fast_waypoint, _track_dt_scalar * vel_dt_scalar * dt, target_pos_ned_m, target_vel_ned_ms, target_accel_ned_mss);
617 : } else {
618 : // splinetarget_vel
619 107717 : target_vel_ned_ms = curr_target_vel_ned_ms;
620 107717 : _spline_this_leg.advance_target_along_track(_track_dt_scalar * vel_dt_scalar * dt, target_pos_ned_m, target_vel_ned_ms);
621 107717 : s_finished = _spline_this_leg.reached_destination();
622 : }
623 :
624 : // apply scaled acceleration offset along current direction of travel
625 4009049 : Vector3f accel_offset_ned_mss;
626 4009049 : if (is_positive(target_vel_ned_ms.length_squared())) {
627 3296409 : Vector3f track_direction_neu = target_vel_ned_ms.normalized();
628 3296409 : accel_offset_ned_mss = track_direction_neu * _offset_accel_mss * target_vel_ned_ms.length() / _wp_desired_speed_ne_ms;
629 : }
630 :
631 4009049 : target_vel_ned_ms *= vel_dt_scalar;
632 4009049 : target_accel_ned_mss *= sq(vel_dt_scalar);
633 4009049 : target_accel_ned_mss += accel_offset_ned_mss;
634 :
635 : // send updated position, velocity, and acceleration targets to position controller
636 4009049 : _pos_control.set_pos_vel_accel_NED_m(target_pos_ned_m, target_vel_ned_ms, target_accel_ned_mss);
637 :
638 : // check if waypoint has been reached based on mode and radius
639 4009049 : if (!_flags.reached_destination) {
640 3329592 : if (s_finished) {
641 : // "fast" waypoints are complete once the intermediate point reaches the destination
642 1247 : if (_flags.fast_waypoint) {
643 445 : _flags.reached_destination = true;
644 : } else {
645 : // regular waypoints also require the copter to be within the waypoint radius
646 802 : const Vector3f dist_to_dest_m = (curr_pos_ned_m - _destination_ned_m).tofloat();
647 802 : if (dist_to_dest_m.length_squared() <= sq(_wp_radius_m)) {
648 399 : _flags.reached_destination = true;
649 : }
650 : }
651 : }
652 : }
653 :
654 : // successfully advanced along track
655 4009049 : return true;
656 : }
657 :
658 : // Updates the current and next path segment to reflect new speed and acceleration limits.
659 : // Should be called after modifying NE/U controller limits or vehicle configuration.
660 686 : void AC_WPNav::update_track_with_speed_accel_limits()
661 : {
662 : // update speed and acceleration limits for the current segment
663 686 : if (_this_leg_is_spline) {
664 0 : _spline_this_leg.set_speed_accel(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
665 : get_wp_acceleration_mss(), get_accel_D_mss());
666 : } else {
667 686 : _scurve_this_leg.set_speed_max(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms());
668 : }
669 :
670 : // update speed and acceleration limits for the next segment
671 686 : if (_next_leg_is_spline) {
672 0 : _spline_next_leg.set_speed_accel(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
673 : get_wp_acceleration_mss(), get_accel_D_mss());
674 : } else {
675 686 : _scurve_next_leg.set_speed_max(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms());
676 : }
677 686 : }
678 :
679 : // Returns the horizontal distance to the destination waypoint in centimeters.
680 : // See get_wp_distance_to_destination_m() for full details.
681 57479 : float AC_WPNav::get_wp_distance_to_destination_cm() const
682 : {
683 : // convert distance from meters to centimeters
684 57479 : return get_wp_distance_to_destination_m() * 100.0;
685 : }
686 :
687 : // Returns the horizontal distance in meters between the current position and the destination waypoint.
688 190474 : float AC_WPNav::get_wp_distance_to_destination_m() const
689 : {
690 : // calculate 2D ground distance between current position and destination in NE frame
691 190474 : return get_horizontal_distance(_pos_control.get_pos_estimate_NED_m().xy().tofloat(), _destination_ned_m.xy().tofloat());
692 : }
693 :
694 : // Returns the bearing to the current waypoint destination in centidegrees.
695 : // See get_wp_bearing_to_destination_rad() for full details.
696 61213 : int32_t AC_WPNav::get_wp_bearing_to_destination_cd() const
697 : {
698 : // compute heading from current position to destination in centidegrees
699 61213 : return get_bearing_cd(_pos_control.get_pos_estimate_NED_m().xy().tofloat(), _destination_ned_m.xy().tofloat());
700 : }
701 :
702 : // Returns the bearing to the current waypoint destination in radians.
703 : // The bearing is measured clockwise from North, with 0 = North.
704 126811 : float AC_WPNav::get_wp_bearing_to_destination_rad() const
705 : {
706 : // compute heading from current position to destination in radians
707 126811 : return get_bearing_rad(_pos_control.get_pos_estimate_NED_m().xy().tofloat(), _destination_ned_m.xy().tofloat());
708 : }
709 :
710 : // Runs the waypoint navigation controller.
711 : // Advances the target position and updates the position controller.
712 : // Should be called at 100 Hz or higher for accurate tracking.
713 4010709 : bool AC_WPNav::update_wpnav()
714 : {
715 : // check for changes in WPNAV_SPEED parameter (horizontal speed target)
716 4010709 : if (_check_wp_speed_change) {
717 3708381 : if (!is_equal(_wp_speed_ms.get(), _last_wp_speed_ms)) {
718 : // apply new WPNAV_SPEED value
719 23 : set_speed_NE_ms(get_default_speed_NE_ms());
720 23 : _last_wp_speed_ms = _wp_speed_ms;
721 : }
722 : }
723 : // check for climb and descent speed updates
724 4010709 : if (!is_equal(_wp_speed_up_ms.get(), _last_wp_speed_up_ms)) {
725 11 : set_speed_up_ms(get_default_speed_up_ms());
726 11 : _last_wp_speed_up_ms = _wp_speed_up_ms;
727 : }
728 4010709 : if (!is_equal(_wp_speed_down_ms.get(), _last_wp_speed_down_ms)) {
729 8 : set_speed_down_ms(get_default_speed_down_ms());
730 8 : _last_wp_speed_down_ms = _wp_speed_down_ms;
731 : }
732 :
733 : // advance the waypoint target based on current position and timing
734 4010709 : bool ret = true;
735 4010709 : if (!advance_wp_target_along_track(_pos_control.get_dt_s())) {
736 : // To-Do: handle inability to advance along track (probably because of missing terrain data)
737 1660 : ret = false;
738 : }
739 :
740 : // run the horizontal position controller
741 4010709 : _pos_control.NE_update_controller();
742 :
743 : // record update time for is_active()
744 4010709 : _wp_last_update_ms = AP_HAL::millis();
745 :
746 4010709 : return ret;
747 : }
748 :
749 : // Returns true if update_wpnav() has been called within the last 200 ms.
750 : // Used to check if waypoint navigation is currently active.
751 51660 : bool AC_WPNav::is_active() const
752 : {
753 : // consider WPNAV active if last update was within 200 milliseconds
754 51660 : return (AP_HAL::millis() - _wp_last_update_ms) < 200;
755 : }
756 :
757 : // Forces a stop at the next waypoint instead of continuing to the subsequent one.
758 : // Used by Dijkstra’s object avoidance when the future path is obstructed.
759 : // Only affects regular (non-spline) waypoints.
760 : // Returns true if the stop behavior was newly enforced.
761 0 : bool AC_WPNav::force_stop_at_next_wp()
762 : {
763 : // skip if vehicle was already going to stop at the next waypoint
764 0 : if (!_flags.fast_waypoint) {
765 0 : return false;
766 : }
767 :
768 0 : _flags.fast_waypoint = false;
769 :
770 : // override final velocity for current leg and reset preview for next leg
771 0 : if (!_this_leg_is_spline) {
772 0 : _scurve_this_leg.set_destination_speed_max(0);
773 : }
774 0 : if (!_next_leg_is_spline) {
775 0 : _scurve_next_leg.init();
776 : }
777 :
778 0 : return true;
779 : }
780 :
781 : // Returns terrain offset in meters above the EKF origin at the current position.
782 : // Positive values mean terrain lies above the EKF origin altitude.
783 : // Source may be rangefinder or terrain database depending on availability.
784 246547 : bool AC_WPNav::get_terrain_U_m(float& terrain_u_m)
785 : {
786 : // determine terrain data source and compute offset accordingly
787 246547 : switch (get_terrain_source()) {
788 0 : case AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE:
789 0 : return false;
790 :
791 189740 : case AC_WPNav::TerrainSource::TERRAIN_FROM_RANGEFINDER:
792 189740 : if (_rangefinder_healthy) {
793 : // return previously saved offset based on rangefinder
794 188080 : terrain_u_m = _rangefinder_terrain_u_m;
795 188080 : return true;
796 : }
797 1660 : return false;
798 :
799 56807 : case AC_WPNav::TerrainSource::TERRAIN_FROM_TERRAINDATABASE:
800 : #if AP_TERRAIN_AVAILABLE
801 56807 : float height_above_terrain_m = 0.0f;
802 56807 : AP_Terrain *terrain = AP::terrain();
803 113614 : if (terrain != nullptr &&
804 56807 : terrain->height_above_terrain(height_above_terrain_m, true)) {
805 : // compute offset as difference between current altitude and terrain height
806 56807 : terrain_u_m = _pos_control.get_pos_estimate_U_m() - height_above_terrain_m;
807 56807 : return true;
808 : }
809 : #endif
810 0 : return false;
811 : }
812 :
813 : // unreachable fallback path
814 0 : return false;
815 : }
816 : // Converts _origin_ned_m.z between the terrain-relative and origin-relative altitude frames and
817 : // re-seeds the position controller's terrain offset to match. Call only when the new leg's
818 : // frame differs from the current one.
819 : // Returns false if terrain data is required but unavailable.
820 13 : bool AC_WPNav::convert_origin_to_alt_frame(bool is_terrain_alt)
821 : {
822 : float terrain_d_m;
823 13 : if (!get_terrain_D_m(terrain_d_m)) {
824 0 : return false;
825 : }
826 :
827 13 : if (is_terrain_alt) {
828 : // Convert origin.z to terrain-relative altitude
829 13 : _origin_ned_m.z -= terrain_d_m;
830 13 : _pos_control.init_pos_terrain_D_m(terrain_d_m);
831 : } else {
832 : // Convert origin.z to origin-relative altitude
833 0 : _origin_ned_m.z += terrain_d_m;
834 0 : _pos_control.init_pos_terrain_D_m(0.0);
835 : }
836 :
837 13 : return true;
838 : }
839 :
840 211829 : bool AC_WPNav::get_terrain_D_m(float& terrain_d_m)
841 : {
842 211829 : if (!get_terrain_U_m(terrain_d_m)) {
843 1660 : return false;
844 : }
845 210169 : terrain_d_m *= -1.0;
846 210169 : return true;
847 : }
848 :
849 : ///
850 : /// spline methods
851 : ///
852 :
853 : // Sets the current spline waypoint using global coordinates.
854 : // Converts `destination` and `next_destination` to NED position vectors and sets up a spline between them.
855 : // Returns false if conversion from location to vector fails.
856 24 : bool AC_WPNav::set_spline_destination_loc(const Location& destination, const Location& next_destination, bool next_is_spline)
857 : {
858 : // convert destination location to NED vector and altitude frame
859 24 : Vector3p dest_ned_m;
860 : bool dest_is_terrain_alt;
861 24 : if (!get_vector_NED_m(destination, dest_ned_m, dest_is_terrain_alt)) {
862 0 : return false;
863 : }
864 :
865 : // convert next destination location to NED vector and altitude frame
866 24 : Vector3p next_dest_ned_m;
867 : bool next_dest_is_terrain_alt;
868 24 : if (!get_vector_NED_m(next_destination, next_dest_ned_m, next_dest_is_terrain_alt)) {
869 0 : return false;
870 : }
871 :
872 : // initialize spline path between converted destination and next_destination
873 24 : return set_spline_destination_NED_m(dest_ned_m, dest_is_terrain_alt, next_dest_ned_m, next_dest_is_terrain_alt, next_is_spline);
874 : }
875 :
876 : // Sets the next spline segment using global coordinates.
877 : // Converts the next and next-next destinations to NED position vectors and initializes the spline transition.
878 : // Returns false if any conversion from location to vector fails.
879 14 : bool AC_WPNav::set_spline_destination_next_loc(const Location& next_destination, const Location& next_next_destination, bool next_next_is_spline)
880 : {
881 : // convert next_destination location to NED vector and determine if it uses terrain-relative altitude
882 14 : Vector3p next_dest_ned_m;
883 : bool next_dest_is_terrain_alt;
884 14 : if (!get_vector_NED_m(next_destination, next_dest_ned_m, next_dest_is_terrain_alt)) {
885 0 : return false;
886 : }
887 :
888 : // convert next_next_destination location to NED vector and determine if it uses terrain-relative altitude
889 14 : Vector3p next_next_dest_ned_m;
890 : bool next_next_dest_is_terr_alt;
891 14 : if (!get_vector_NED_m(next_next_destination, next_next_dest_ned_m, next_next_dest_is_terr_alt)) {
892 0 : return false;
893 : }
894 :
895 : // initialize next spline segment using converted waypoints
896 14 : return set_spline_destination_next_NED_m(next_dest_ned_m, next_dest_is_terrain_alt, next_next_dest_ned_m, next_next_dest_is_terr_alt, next_next_is_spline);
897 : }
898 :
899 : // Sets the current spline waypoint using NED position vectors in meters.
900 : // Initializes a spline path from `destination_ned_m` to `next_destination_ned_m`, respecting terrain altitude framing.
901 : // Both waypoints must use the same altitude frame (either above terrain or above origin).
902 : // Returns false if terrain altitude cannot be determined when required.
903 24 : bool AC_WPNav::set_spline_destination_NED_m(const Vector3p& destination_ned_m, bool is_terrain_alt, const Vector3p& next_destination_ned_m, bool next_is_terrain_alt, bool next_is_spline)
904 : {
905 : // re-initialise path state if previous destination was not completed or controller inactive
906 24 : if (!is_active() || !_flags.reached_destination) {
907 2 : wp_and_spline_init_m(_wp_desired_speed_ne_ms);
908 : }
909 :
910 : // update spline calculators speeds and accelerations
911 24 : _spline_this_leg.set_speed_accel(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
912 24 : _pos_control.NE_get_max_accel_mss(), _pos_control.D_get_max_accel_mss());
913 :
914 : // calculate origin and origin velocity vector
915 24 : Vector3f origin_vector_ned_m;
916 24 : if (is_terrain_alt == _is_terrain_alt) {
917 24 : if (_flags.fast_waypoint) {
918 : // calculate origin vector
919 14 : if (_this_leg_is_spline) {
920 : // use velocity vector from end of previous spline segment
921 12 : origin_vector_ned_m = _spline_this_leg.get_destination_vel();
922 : } else {
923 : // use direction vector from previous straight-line segment
924 2 : origin_vector_ned_m = (_destination_ned_m - _origin_ned_m).tofloat();
925 : }
926 : }
927 :
928 : // use previous destination as origin
929 24 : _origin_ned_m = _destination_ned_m;
930 : } else {
931 :
932 : // use previous destination as origin
933 0 : _origin_ned_m = _destination_ned_m;
934 :
935 : // get current alt above terrain
936 : float terrain_d_m;
937 0 : if (!get_terrain_D_m(terrain_d_m)) {
938 0 : return false;
939 : }
940 :
941 : // convert origin to alt-above-terrain if necessary
942 0 : if (is_terrain_alt) {
943 : // new destination is alt-above-terrain, previous destination was alt-above-ekf-origin
944 0 : _origin_ned_m.z -= terrain_d_m;
945 0 : _pos_control.init_pos_terrain_D_m(terrain_d_m);
946 : } else {
947 : // new destination is alt-above-ekf-origin, previous destination was alt-above-terrain
948 0 : _origin_ned_m.z += terrain_d_m;
949 0 : _pos_control.init_pos_terrain_D_m(0.0);
950 : }
951 : }
952 :
953 : // store destination location
954 24 : _destination_ned_m = destination_ned_m;
955 24 : _is_terrain_alt = is_terrain_alt;
956 :
957 : // calculate destination velocity vector
958 24 : Vector3f destination_vector_ned_m;
959 24 : if (is_terrain_alt == next_is_terrain_alt) {
960 24 : if (next_is_spline) {
961 : // aim to leave segment in direction of full arc (origin to next)
962 12 : destination_vector_ned_m = (next_destination_ned_m - _origin_ned_m).tofloat();
963 : } else {
964 : // aim to leave segment in direction of following leg
965 12 : destination_vector_ned_m = (next_destination_ned_m - _destination_ned_m).tofloat();
966 : }
967 : }
968 24 : _flags.fast_waypoint = !destination_vector_ned_m.is_zero();
969 :
970 : // setup spline leg using origin and destination vectors
971 24 : _spline_this_leg.set_origin_and_destination(_origin_ned_m, _destination_ned_m, origin_vector_ned_m, destination_vector_ned_m);
972 24 : _this_leg_is_spline = true;
973 24 : _this_leg_is_circle = false;
974 24 : _flags.reached_destination = false;
975 :
976 24 : return true;
977 : }
978 :
979 : // Sets the next spline segment using NED position vectors in meters.
980 : // Creates a spline path from the current destination to `next_destination_ned_m`, and prepares transition toward `next_next_destination_ned_m`.
981 : // All waypoints must use the same altitude frame (above terrain or origin).
982 : // Returns false if terrain data is missing and required.
983 14 : bool AC_WPNav::set_spline_destination_next_NED_m(const Vector3p& next_destination_ned_m, bool next_is_terrain_alt, const Vector3p& next_next_destination_ned_m, bool next_next_is_terrain_alt, bool next_next_is_spline)
984 : {
985 : // do not add next point if alt types don't match
986 14 : if (next_is_terrain_alt != _is_terrain_alt) {
987 0 : return true;
988 : }
989 :
990 : // calculate origin and origin velocity vector
991 14 : Vector3f origin_vector_ned_m;
992 14 : if (_this_leg_is_spline) {
993 : // use final velocity vector from current spline segment
994 12 : origin_vector_ned_m = _spline_this_leg.get_destination_vel();
995 : } else {
996 : // use vector from previous origin to current destination
997 2 : origin_vector_ned_m = (_destination_ned_m - _origin_ned_m).tofloat();
998 : }
999 :
1000 : // calculate destination velocity vector
1001 14 : Vector3f destination_vector_ned_m;
1002 14 : if (next_is_terrain_alt == next_next_is_terrain_alt) {
1003 14 : if (next_next_is_spline) {
1004 : // aim to leave segment in direction of the arc from current to next-next
1005 11 : destination_vector_ned_m = (next_next_destination_ned_m - _destination_ned_m).tofloat();
1006 : } else {
1007 : // aim to leave segment in direction of the upcoming straight leg
1008 3 : destination_vector_ned_m = (next_next_destination_ned_m - next_destination_ned_m).tofloat();
1009 : }
1010 : }
1011 :
1012 : // update spline calculators speeds and accelerations
1013 14 : _spline_next_leg.set_speed_accel(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
1014 14 : _pos_control.NE_get_max_accel_mss(), _pos_control.D_get_max_accel_mss());
1015 :
1016 : // setup next spline leg. Note this could be made local
1017 14 : _spline_next_leg.set_origin_and_destination(_destination_ned_m, next_destination_ned_m, origin_vector_ned_m, destination_vector_ned_m);
1018 14 : _next_leg_is_spline = true;
1019 :
1020 : // next destination provided so fast waypoint
1021 14 : _flags.fast_waypoint = true;
1022 :
1023 : // update this_leg's final velocity to match next spline leg
1024 14 : if (!_this_leg_is_spline) {
1025 2 : _scurve_this_leg.set_destination_speed_max(_spline_next_leg.get_origin_speed_max());
1026 : } else {
1027 12 : _spline_this_leg.set_destination_speed_max(_spline_next_leg.get_origin_speed_max());
1028 : }
1029 :
1030 14 : return true;
1031 : }
1032 :
1033 : // Converts a Location to a NED position vector in meters from the EKF origin.
1034 : // Sets `is_terrain_alt` to true if the resulting Z position is relative to terrain.
1035 : // Returns false if terrain data is unavailable or conversion fails.
1036 1793 : bool AC_WPNav::get_vector_NED_m(const Location &loc, Vector3p &pos_from_origin_ned_m, bool &is_terrain_alt)
1037 : {
1038 : // convert horizontal position (latitude/longitude) to NE vector from EKF origin
1039 1793 : Vector2p loc_pos_from_origin_ned_m;
1040 1793 : if (!loc.get_vector_xy_from_origin_NE_m(loc_pos_from_origin_ned_m)) {
1041 8 : return false;
1042 : }
1043 :
1044 : // convert altitude
1045 1785 : if (loc.get_alt_frame() == Location::AltFrame::ABOVE_TERRAIN) {
1046 : float terrain_u_m;
1047 109 : if (!loc.get_alt_m(Location::AltFrame::ABOVE_TERRAIN, terrain_u_m)) {
1048 0 : return false;
1049 : }
1050 109 : pos_from_origin_ned_m.z = -terrain_u_m;
1051 109 : is_terrain_alt = true;
1052 : } else {
1053 1676 : is_terrain_alt = false;
1054 : float origin_alt_m;
1055 1676 : if (!loc.get_alt_m(Location::AltFrame::ABOVE_ORIGIN, origin_alt_m)) {
1056 0 : return false;
1057 : }
1058 1676 : pos_from_origin_ned_m.z = -origin_alt_m;
1059 1676 : is_terrain_alt = false;
1060 : }
1061 :
1062 : // set horizontal components (x/y) of NED vector after successful conversion
1063 1785 : pos_from_origin_ned_m.xy() = loc_pos_from_origin_ned_m;
1064 :
1065 1785 : return true;
1066 : }
1067 :
1068 : // Calculates s-curve jerk and snap limits based on attitude controller capabilities.
1069 : // Updates _scurve_jerk_max_msss and _scurve_snap_max_mssss with constrained values.
1070 15182 : void AC_WPNav::calc_scurve_jerk_and_snap()
1071 : {
1072 : // calculate max horizontal jerk based on roll/pitch angular velocity limits
1073 15182 : _scurve_jerk_max_msss = MIN(_attitude_control.get_ang_vel_roll_max_rads() * GRAVITY_MSS, _attitude_control.get_ang_vel_pitch_max_rads() * GRAVITY_MSS);
1074 15182 : if (is_zero(_scurve_jerk_max_msss)) {
1075 14922 : _scurve_jerk_max_msss = _wp_jerk_msss;
1076 : } else {
1077 260 : _scurve_jerk_max_msss = MIN(_scurve_jerk_max_msss, _wp_jerk_msss);
1078 : }
1079 :
1080 : // calculate maximum snap (rate of change of jerk)
1081 : // uses input time constant as proxy for lean angle actuation latency
1082 15182 : _scurve_snap_max_mssss = (_scurve_jerk_max_msss * M_PI) / (2.0 * MAX(_attitude_control.get_input_tc(), 0.1f));
1083 :
1084 : // constrain snap based on roll/pitch angular acceleration capability
1085 15182 : const float snap = MIN(_attitude_control.get_accel_roll_max_radss(), _attitude_control.get_accel_pitch_max_radss()) * GRAVITY_MSS;
1086 15182 : if (is_positive(snap)) {
1087 15182 : _scurve_snap_max_mssss = MIN(_scurve_snap_max_mssss, snap);
1088 : }
1089 :
1090 : // apply safety margin: reduce snap to 50% of calculated maximum
1091 15182 : _scurve_snap_max_mssss *= 0.5;
1092 15182 : }
|