diff --git a/conf/modules/digital_cam.xml b/conf/modules/digital_cam.xml
index e0fed1d203..f6a7739942 100644
--- a/conf/modules/digital_cam.xml
+++ b/conf/modules/digital_cam.xml
@@ -53,6 +53,7 @@
digital_cam_i2c,digital_cam_servo,digital_cam_uart,digital_cam_video
+ digital_cam
diff --git a/conf/modules/digital_cam_i2c.xml b/conf/modules/digital_cam_i2c.xml
index c97c856cb2..e746d23c98 100644
--- a/conf/modules/digital_cam_i2c.xml
+++ b/conf/modules/digital_cam_i2c.xml
@@ -16,6 +16,7 @@
digital_cam,digital_cam_servo,digital_cam_uart,digital_cam_video
+ digital_cam
diff --git a/conf/modules/digital_cam_servo.xml b/conf/modules/digital_cam_servo.xml
index 9d965b5de8..e6c1b2d636 100644
--- a/conf/modules/digital_cam_servo.xml
+++ b/conf/modules/digital_cam_servo.xml
@@ -19,6 +19,7 @@
digital_cam,digital_cam_i2c,digital_cam_uart,digital_cam_video
+ digital_cam
diff --git a/conf/modules/digital_cam_shoot_rc.xml b/conf/modules/digital_cam_shoot_rc.xml
index bcd0c979ef..1f96943b97 100644
--- a/conf/modules/digital_cam_shoot_rc.xml
+++ b/conf/modules/digital_cam_shoot_rc.xml
@@ -13,6 +13,7 @@
digital_cam|digital_cam_servo|digital_cam_uart|digital_cam_i2c|digital_cam_video
+ digital_cam
diff --git a/conf/modules/digital_cam_uart.xml b/conf/modules/digital_cam_uart.xml
index 6ae8abf512..4faf486ea9 100644
--- a/conf/modules/digital_cam_uart.xml
+++ b/conf/modules/digital_cam_uart.xml
@@ -55,6 +55,7 @@
digital_cam,digital_cam_servo,digital_cam_i2c
+ digital_cam
diff --git a/conf/modules/digital_cam_video.xml b/conf/modules/digital_cam_video.xml
index a6e080cf82..5821842e65 100644
--- a/conf/modules/digital_cam_video.xml
+++ b/conf/modules/digital_cam_video.xml
@@ -37,6 +37,7 @@
video_capture
digital_cam,digital_cam_servo,digital_cam_i2c,digital_cam_uart
+ digital_cam
diff --git a/conf/modules/nav_survey_hybrid.xml b/conf/modules/nav_survey_hybrid.xml
new file mode 100644
index 0000000000..55d799a5ed
--- /dev/null
+++ b/conf/modules/nav_survey_hybrid.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+ Polygon survey for hybrid aircraft.
+
+ Based on poly_osam algorithm.
+ Compatible with generic rotorcraft.
+
+ Support mission mode with custom elements:
+ - points in local NED: SRVHL orientation sweep_distance radius height p1x p1y p2x p2y p3x p3y [p4x p4y]
+ - points in global LLA: SRVHG orientation sweep_distance radius height p1lat p1lon p2lat p2lon p3lat p3lon [p4lat p4lon]
+ - orientation is in degrees
+ - sweep_distance in meters
+ - radius can be: negative, automatically set to sweep/2; zero, not turning on circles; positive, fixed radius
+ - height in meters above reference point
+ - the polygon in mission mode can have either 3 or 4 points.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @navigation
+ @mission,@digital_cam
+
+
+
+
+
+
+
+
+
diff --git a/sw/airborne/modules/nav/nav_survey_hybrid.c b/sw/airborne/modules/nav/nav_survey_hybrid.c
new file mode 100644
index 0000000000..8852a4e6c7
--- /dev/null
+++ b/sw/airborne/modules/nav/nav_survey_hybrid.c
@@ -0,0 +1,654 @@
+/*
+ * Copyright (C) 2023 Gautier Hattenberger
+ * Based on OSAM poly survey
+ *
+ * This file is part of paparazzi.
+ *
+ * paparazzi is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * paparazzi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with paparazzi; see the file COPYING. If not, see
+ * .
+ */
+
+/**
+ * @file modules/nav/nav_survey_hybrid.c
+ *
+ * This routine will cover the enitre area of any Polygon defined in the
+ * flightplan which is a convex polygon.
+ *
+ */
+#include "modules/nav/nav_survey_hybrid.h"
+
+#include "firmwares/rotorcraft/navigation.h"
+#include "math/pprz_algebra_float.h"
+#include "state.h"
+#include "autopilot.h"
+#include "generated/flight_plan.h"
+
+#ifdef DIGITAL_CAM
+#include "modules/digital_cam/dc.h"
+#endif
+
+// turn anticipation
+#ifndef SURVEY_HYBRID_APPROACHING_TIME
+#define SURVEY_HYBRID_APPROACHING_TIME 3.f
+#endif
+
+// maximum number of polygon corners
+#ifndef SURVEY_HYBRID_MAX_POLYGON_SIZE
+#define SURVEY_HYBRID_MAX_POLYGON_SIZE 20
+#endif
+
+// use half sweep at the end of polygon
+#ifndef SURVEY_HYBRID_HALF_SWEEP_ENABLED
+#define SURVEY_HYBRID_HALF_SWEEP_ENABLED true
+#endif
+
+// maximum number of sweep lines (0 for unlimited)
+#ifndef SURVEY_HYBRID_MAX_SWEEP
+#define SURVEY_HYBRID_MAX_SWEEP 0
+#endif
+
+// maximum number of sweep back (0 for unlimited)
+#ifndef SURVEY_HYBRID_MAX_SWEEP_BACK
+#define SURVEY_HYBRID_MAX_SWEEP_BACK 0
+#endif
+
+// entry distance (default, half sweep distance)
+#ifndef SURVEY_HYBRID_ENTRY_DISTANCE
+#define SURVEY_HYBRID_ENTRY_DISTANCE (survey_private.sweep_distance / 2.f)
+#endif
+
+struct Line {float m; float b; float x;};
+enum SurveyStatus { Init, Entry, Sweep, Turn };
+
+struct SurveyHybridPrivate {
+ float sweep_distance; ///< requested sweep distance
+ float orientation; ///< requested orientation in radians
+ enum SurveyStatus status; ///< current state
+ struct EnuCoor_f corners[SURVEY_HYBRID_MAX_POLYGON_SIZE]; ///< corners location
+ struct Line edges[SURVEY_HYBRID_MAX_POLYGON_SIZE]; ///< polygon edges
+ float edge_max_y[SURVEY_HYBRID_MAX_POLYGON_SIZE]; ///< tmp point in rotated frame
+ float edge_min_y[SURVEY_HYBRID_MAX_POLYGON_SIZE]; ///< tmp point in rotated frame
+ struct EnuCoor_f smallest_corner; ///< tmp point in rotated frame
+ struct EnuCoor_f to_wp; ///< tmp point in rotated frame
+ struct EnuCoor_f from_wp; ///< tmp point in rotated frame
+ float max_y; ///< tmp value
+ float sweep; ///< oriented sweep distance
+ struct EnuCoor_f entry; ///< entry point
+ struct EnuCoor_f segment_from; ///< start of current segment
+ struct EnuCoor_f segment_to; ///< end of current segment
+ struct EnuCoor_f circle; ///< circle center
+ float radius; ///< turn radius
+ bool valid; ///< setup is valid
+ bool circle_turns; ///< turns with circles (or lines between points otherwise)
+ uint8_t size; ///< size of the polygon
+};
+
+struct SurveyHybrid survey_hybrid;
+static struct SurveyHybridPrivate survey_private;
+
+static void nav_survey_hybrid_setup(float orientation, float sweep, float radius);
+
+static void TranslateAndRotateFromWorld(struct EnuCoor_f *p, float Zrot, struct EnuCoor_f *trans);
+static void RotateAndTranslateToWorld(struct EnuCoor_f *p, float Zrot, struct EnuCoor_f *trans);
+static void FindInterceptOfTwoLines(float *x, float *y, struct Line L1, struct Line L2);
+static float EvaluateLineForX(float y, struct Line L);
+static float CrossProductZ(struct EnuCoor_f *p1_start, struct EnuCoor_f *p1_end, struct EnuCoor_f *p2_start, struct EnuCoor_f *p2_end);
+
+#define MaxFloat 1000000000
+#define MinFloat -1000000000
+
+#ifndef LINE_START_FUNCTION
+#define LINE_START_FUNCTION {}
+#endif
+#ifndef LINE_STOP_FUNCTION
+#define LINE_STOP_FUNCTION {}
+#endif
+
+#if USE_MISSION
+#include "modules/mission/mission_common.h"
+
+static bool nav_survey_hybrid_mission_local(uint8_t nb, float *params, enum MissionRunFlag flag)
+{
+ if (flag == MissionInit) {
+ if (nb == 10 || nb == 12) {
+ float orientation = params[0];
+ float sweep = params[1];
+ float radius = params[2];
+ float height = params[3];
+ if (nb == 10) { survey_private.size = 3; }
+ else { survey_private.size = 4; }
+ for (int i = 0; i < survey_private.size; i++) {
+ survey_private.corners[i].x = params[4+2*i];
+ survey_private.corners[i].y = params[5+2*i];
+ survey_private.corners[i].z = height;
+ }
+ nav_survey_hybrid_setup(orientation, sweep, radius);
+ return nav_survey_hybrid_run();
+ }
+ }
+ else if (flag == MissionRun) {
+ return nav_survey_hybrid_run();
+ }
+ return false; // not a valid case
+}
+
+static bool nav_survey_hybrid_mission_global(uint8_t nb, float *params, enum MissionRunFlag flag)
+{
+ if (flag == MissionInit) {
+ if (nb == 10 || nb == 12) {
+ float orientation = params[0];
+ float sweep = params[1];
+ float radius = params[2];
+ float height = params[3];
+ if (nb == 10) { survey_private.size = 3; }
+ else { survey_private.size = 4; }
+ for (int i = 0; i < survey_private.size; i++) {
+ struct LlaCoor_f lla = {
+ .lat = params[4+2*i],
+ .lon = params[4+2*i],
+ .alt = state.ned_origin_f.lla.alt + height
+ };
+ struct EnuCoor_f corner;
+ enu_of_lla_point_f(&corner, &state.ned_origin_f, &lla);
+ survey_private.corners[i] = corner;
+ }
+ nav_survey_hybrid_setup(orientation, sweep, radius);
+ return nav_survey_hybrid_run();
+ }
+ }
+ else if (flag == MissionRun) {
+ return nav_survey_hybrid_run();
+ }
+ return false; // not a valid case
+}
+
+#endif
+
+void nav_survey_hybrid_init(void)
+{
+ survey_hybrid.sweep_nb_max = SURVEY_HYBRID_MAX_SWEEP;
+ survey_hybrid.sweep_back_nb_max = SURVEY_HYBRID_MAX_SWEEP_BACK;
+ survey_hybrid.sweep_nb = 0;
+ survey_hybrid.sweep_back_nb = 0;
+ survey_hybrid.half_sweep_enabled = SURVEY_HYBRID_HALF_SWEEP_ENABLED;
+
+ memset(&survey_private, 0, sizeof(struct SurveyHybridPrivate));
+
+#if USE_MISSION
+ mission_register(nav_survey_hybrid_mission_local, "SRVHL");
+ mission_register(nav_survey_hybrid_mission_global, "SRVHG");
+#endif
+}
+
+/** finish preparation of survey based on private structure
+ */
+static void nav_survey_hybrid_setup(float orientation, float sweep, float radius)
+{
+ FLOAT_VECT2_ZERO(survey_private.smallest_corner);
+ int i = 0;
+ float ys = 0.f;
+ float LeftYInt;
+ float RightYInt;
+ float temp;
+ float XIntercept1 = 0.f;
+ float XIntercept2 = 0.f;
+
+ // cap orientation angle to [-pi; +pi]
+ survey_private.orientation = RadOfDeg(orientation);
+ NormRadAngle(survey_private.orientation);
+ survey_private.sweep_distance = sweep;
+
+ // set auto radius mode if needed
+ if (radius < -0.1f) {
+ survey_private.radius = sweep / 2.f;
+ survey_private.circle_turns = true;
+ } else if (radius > 0.1f) {
+ survey_private.radius = radius;
+ survey_private.circle_turns = true;
+ } else {
+ survey_private.radius = 0.f;
+ survey_private.circle_turns = false;
+ }
+
+ float entry_distance = SURVEY_HYBRID_ENTRY_DISTANCE;
+ survey_private.sweep = survey_private.sweep_distance;
+ survey_hybrid.sweep_nb = 0;
+ survey_hybrid.sweep_back_nb = 0;
+ survey_private.status = Init;
+
+ // Rotate Corners so sweeps are parellel with x axis
+ for (i = 0; i < survey_private.size; i++) {
+ struct EnuCoor_f zero = { 0 };
+ TranslateAndRotateFromWorld(&survey_private.corners[i], survey_private.orientation, &zero);
+ }
+
+ // Find min x and min y
+ VECT2_COPY(survey_private.smallest_corner, survey_private.corners[0]);
+ for (i = 1; i < survey_private.size; i++) {
+ if (survey_private.corners[i].y < survey_private.smallest_corner.y) {
+ survey_private.smallest_corner.y = survey_private.corners[i].y;
+ }
+ if (survey_private.corners[i].x < survey_private.smallest_corner.x) {
+ survey_private.smallest_corner.x = survey_private.corners[i].x;
+ }
+ }
+
+ // Translate Corners all exist in quad #1
+ for (i = 0; i < survey_private.size; i++) {
+ TranslateAndRotateFromWorld(&survey_private.corners[i], 0.f, &survey_private.smallest_corner);
+ }
+
+ // Find max y
+ survey_private.max_y = survey_private.corners[0].y;
+ for (i = 1; i < survey_private.size; i++) {
+ if (survey_private.corners[i].y > survey_private.max_y) {
+ survey_private.max_y = survey_private.corners[i].y;
+ }
+ }
+
+ // Find polygon edges
+ for (i = 0; i < survey_private.size; i++) {
+ if (i == 0) {
+ if (survey_private.corners[survey_private.size - 1].x == survey_private.corners[i].x) { // Don't divide by zero!
+ survey_private.edges[i].m = MaxFloat;
+ } else {
+ survey_private.edges[i].m = ((survey_private.corners[survey_private.size - 1].y - survey_private.corners[i].y) / (survey_private.corners[survey_private.size - 1].x - survey_private.corners[i].x));
+ }
+ }
+ else if (survey_private.corners[i].x == survey_private.corners[i - 1].x) {
+ survey_private.edges[i].m = MaxFloat;
+ } else {
+ survey_private.edges[i].m = ((survey_private.corners[i].y - survey_private.corners[i - 1].y) / (survey_private.corners[i].x - survey_private.corners[i - 1].x));
+ }
+
+ survey_private.edges[i].b = (survey_private.corners[i].y - (survey_private.corners[i].x * survey_private.edges[i].m));
+ }
+
+ // Find Min and Max y for each line
+ FindInterceptOfTwoLines(&temp, &LeftYInt, survey_private.edges[0], survey_private.edges[1]);
+ FindInterceptOfTwoLines(&temp, &RightYInt, survey_private.edges[0], survey_private.edges[survey_private.size - 1]);
+
+ if (LeftYInt > RightYInt) {
+ survey_private.edge_max_y[0] = LeftYInt;
+ survey_private.edge_min_y[0] = RightYInt;
+ } else {
+ survey_private.edge_max_y[0] = RightYInt;
+ survey_private.edge_min_y[0] = LeftYInt;
+ }
+
+ for (i = 1; i < survey_private.size - 1; i++) {
+ FindInterceptOfTwoLines(&temp, &LeftYInt, survey_private.edges[i], survey_private.edges[i + 1]);
+ FindInterceptOfTwoLines(&temp, &RightYInt, survey_private.edges[i], survey_private.edges[i - 1]);
+
+ if (LeftYInt > RightYInt) {
+ survey_private.edge_max_y[i] = LeftYInt;
+ survey_private.edge_min_y[i] = RightYInt;
+ } else {
+ survey_private.edge_max_y[i] = RightYInt;
+ survey_private.edge_min_y[i] = LeftYInt;
+ }
+ }
+
+ FindInterceptOfTwoLines(&temp, &LeftYInt, survey_private.edges[survey_private.size - 1], survey_private.edges[0]);
+ FindInterceptOfTwoLines(&temp, &RightYInt, survey_private.edges[survey_private.size - 1], survey_private.edges[survey_private.size - 2]);
+
+ if (LeftYInt > RightYInt) {
+ survey_private.edge_max_y[survey_private.size - 1] = LeftYInt;
+ survey_private.edge_min_y[survey_private.size - 1] = RightYInt;
+ } else {
+ survey_private.edge_max_y[survey_private.size - 1] = RightYInt;
+ survey_private.edge_min_y[survey_private.size - 1] = LeftYInt;
+ }
+
+ // Find amount to increment by every sweep
+ if (survey_private.corners[0].y >= survey_private.max_y / 2.f) {
+ entry_distance = -entry_distance;
+ survey_private.sweep = -survey_private.sweep_distance;
+ } else {
+ survey_private.sweep = survey_private.sweep_distance;
+ }
+
+ // Find y value of the first sweep
+ ys = survey_private.corners[0].y + entry_distance;
+
+ // Find the edges which intercet the sweep line first
+ for (i = 0; i < survey_private.size; i++) {
+ if (survey_private.edge_min_y[i] <= ys && survey_private.edge_max_y[i] > ys) {
+ XIntercept2 = XIntercept1;
+ XIntercept1 = EvaluateLineForX(ys, survey_private.edges[i]);
+ }
+ }
+
+ // Find point to come from and point to go to
+ if (fabsf(survey_private.corners[0].x - XIntercept2) <= fabsf(survey_private.corners[0].x - XIntercept1)) {
+ survey_private.to_wp.x = XIntercept1;
+ survey_private.to_wp.y = ys;
+ survey_private.from_wp.x = XIntercept2;
+ survey_private.from_wp.y = ys;
+ } else {
+ survey_private.to_wp.x = XIntercept2;
+ survey_private.to_wp.y = ys;
+ survey_private.from_wp.x = XIntercept1;
+ survey_private.from_wp.y = ys;
+ }
+
+ // Find the entry point
+ survey_private.entry.x = survey_private.from_wp.x;
+ survey_private.entry.y = survey_private.corners[0].y + entry_distance;
+ survey_private.entry.z = survey_private.corners[0].z;
+
+ // Go into entry state
+ survey_private.status = Entry;
+
+ LINE_STOP_FUNCTION;
+ NavVerticalAltitudeMode(survey_private.entry.z, 0.f);
+}
+
+void nav_survey_hybrid_setup_orientation(uint8_t start_wp, float orientation, uint8_t size, float sweep, float radius)
+{
+ survey_private.valid = false;
+ if (size < 3 || size > SURVEY_HYBRID_MAX_POLYGON_SIZE) {
+ return; // polygon is too small or too big
+ }
+ for (int i = 0; i < size; i++) {
+ struct EnuCoor_f *wp = waypoint_get_enu_f(start_wp + i);
+ if (wp == NULL) {
+ return; // not a valid waypoint
+ }
+ survey_private.corners[i] = *wp;
+ }
+ survey_private.size = size;
+
+ survey_private.valid = true;
+ nav_survey_hybrid_setup(orientation, sweep, radius);
+}
+
+void nav_survey_hybrid_setup_towards(uint8_t start_wp, uint8_t second_wp, uint8_t size, float sweep, float radius)
+{
+ survey_private.valid = false;
+ struct EnuCoor_f *start = waypoint_get_enu_f(start_wp);
+ struct EnuCoor_f *second = waypoint_get_enu_f(second_wp);
+ if (start == NULL || second == NULL) {
+ return;
+ }
+ survey_private.valid = true;
+
+ float dx = second->x - start->x;
+ float dy = second->y - start->y;
+ float angle = DegOfRad(atan2f(dy, dx));
+ nav_survey_hybrid_setup_orientation(start_wp, angle, size, sweep, radius);
+}
+
+//=========================================================================================================================
+bool nav_survey_hybrid_run(void)
+{
+ if (!survey_private.valid) {
+ return false; // don't start survey
+ }
+
+ struct EnuCoor_f C;
+ float ys = 0.f;
+ static struct EnuCoor_f LastPoint;
+ int i = 0;
+ bool LastHalfSweep = false;
+ static bool HalfSweep = false;
+ float XIntercept1 = 0.f;
+ float XIntercept2 = 0.f;
+ float DInt1 = 0.f;
+ float DInt2 = 0.f;
+ struct EnuCoor_f zero = { 0 };
+ float qdr = 0.f;
+ float qdr_out = 0.f;
+
+ switch (survey_private.status) {
+ case Entry:
+ C = survey_private.entry;
+ RotateAndTranslateToWorld(&C, 0.f, &survey_private.smallest_corner);
+ RotateAndTranslateToWorld(&C, survey_private.orientation, &zero);
+
+ if (survey_private.circle_turns) {
+ // align segment at entry point with a circle
+ survey_private.circle.x = C.x - (cosf(survey_private.orientation + M_PI_2) * survey_private.radius);
+ survey_private.circle.y = C.y - (sinf(survey_private.orientation + M_PI_2) * survey_private.radius);
+ survey_private.circle.z = C.z;
+ nav.nav_circle(&survey_private.circle, survey_private.radius);
+
+ qdr = atan2f(stateGetPositionEnu_f()->x - survey_private.circle.x, stateGetPositionEnu_f()->y - survey_private.circle.y);
+ qdr_out = - survey_private.orientation;
+ if (CloseRadAngles(qdr, qdr_out) && NavCircleCount() > 0.5f) {
+ survey_private.status = Sweep;
+ nav_init_stage();
+ LINE_START_FUNCTION;
+ }
+ } else {
+ // goto entry point
+ VECT3_COPY(survey_private.segment_to, C);
+ nav.nav_goto(&survey_private.segment_to);
+
+ if (((nav.nav_approaching(&survey_private.segment_to, NULL, SURVEY_HYBRID_APPROACHING_TIME))
+ && (fabsf(stateGetPositionEnu_f()->z - survey_private.entry.z)) < 5.f)) {
+ survey_private.status = Sweep;
+ nav_init_stage();
+ LINE_START_FUNCTION;
+ }
+ }
+ break;
+ case Sweep:
+ LastHalfSweep = HalfSweep;
+ survey_private.segment_to = survey_private.to_wp;
+ survey_private.segment_from = survey_private.from_wp;
+
+ // Rotate and Translate Line points into real world
+ RotateAndTranslateToWorld(&survey_private.segment_to, 0.f, &survey_private.smallest_corner);
+ RotateAndTranslateToWorld(&survey_private.segment_to, survey_private.orientation, &zero);
+ RotateAndTranslateToWorld(&survey_private.segment_from, 0.f, &survey_private.smallest_corner);
+ RotateAndTranslateToWorld(&survey_private.segment_from, survey_private.orientation, &zero);
+
+ // follow the line
+ nav.nav_route(&survey_private.segment_from, &survey_private.segment_to);
+
+ if (nav.nav_approaching(&survey_private.segment_to, &survey_private.segment_from, SURVEY_HYBRID_APPROACHING_TIME)) {
+ LastPoint = survey_private.to_wp;
+
+#ifdef DIGITAL_CAM
+ float line_length = fabsf((fabsf(survey_private.segment_from.x) - fabsf(survey_private.segment_to.x)));
+ double inteiro;
+ double fract = modf(line_length / dc_distance_interval, &inteiro);
+ if (fract > .5) {
+ //if last shot is more than shot_distance/2 from the corner then take a picture in the corner before go to the next sweep
+ dc_send_command(DC_SHOOT);
+ }
+#endif
+
+ if (LastPoint.y + survey_private.sweep >= survey_private.max_y || LastPoint.y + survey_private.sweep <= 0) {
+ // Your out of the Polygon so Sweep Back or Half Sweep
+ if ((LastPoint.y + (survey_private.sweep / 2.f)) <= survey_private.max_y ||
+ (LastPoint.y + (survey_private.sweep / 2.f)) >= 0.f ||
+ !survey_hybrid.half_sweep_enabled) {
+ // Sweep back
+ survey_private.sweep = -survey_private.sweep;
+ }
+ if (LastHalfSweep) {
+ HalfSweep = false;
+ ys = LastPoint.y + survey_private.sweep;
+ } else {
+ HalfSweep = true;
+ ys = LastPoint.y + (survey_private.sweep / 2.f);
+ }
+ survey_hybrid.sweep_back_nb++;
+ } else { // Normal sweep
+ // Find y value of the first sweep
+ HalfSweep = false;
+ ys = LastPoint.y + survey_private.sweep;
+ }
+
+ // Find the edges which intercet the sweep line first
+ for (i = 0; i < survey_private.size; i++) {
+ if (survey_private.edge_min_y[i] < ys && survey_private.edge_max_y[i] >= ys) {
+ XIntercept2 = XIntercept1;
+ XIntercept1 = EvaluateLineForX(ys, survey_private.edges[i]);
+ }
+ }
+
+ // Find point to come from and point to go to
+ DInt1 = XIntercept1 - LastPoint.x;
+ DInt2 = XIntercept2 - LastPoint.x;
+
+ if (DInt1 *DInt2 >= 0) {
+ if (fabsf(DInt2) <= fabsf(DInt1)) {
+ survey_private.to_wp.x = XIntercept1;
+ survey_private.to_wp.y = ys;
+ survey_private.from_wp.x = XIntercept2;
+ survey_private.from_wp.y = ys;
+ } else {
+ survey_private.to_wp.x = XIntercept2;
+ survey_private.to_wp.y = ys;
+ survey_private.from_wp.x = XIntercept1;
+ survey_private.from_wp.y = ys;
+ }
+ } else {
+ if ((survey_private.to_wp.x - survey_private.from_wp.x) > 0 && DInt2 > 0) {
+ survey_private.to_wp.x = XIntercept1;
+ survey_private.to_wp.y = ys;
+ survey_private.from_wp.x = XIntercept2;
+ survey_private.from_wp.y = ys;
+ } else if ((survey_private.to_wp.x - survey_private.from_wp.x) < 0 && DInt2 < 0) {
+ survey_private.to_wp.x = XIntercept1;
+ survey_private.to_wp.y = ys;
+ survey_private.from_wp.x = XIntercept2;
+ survey_private.from_wp.y = ys;
+ } else {
+ survey_private.to_wp.x = XIntercept2;
+ survey_private.to_wp.y = ys;
+ survey_private.from_wp.x = XIntercept1;
+ survey_private.from_wp.y = ys;
+ }
+ }
+
+ // Go into Turn state
+ survey_private.status = Turn;
+ nav_init_stage();
+ LINE_STOP_FUNCTION;
+
+ survey_hybrid.sweep_nb++;
+ if ((survey_hybrid.sweep_nb_max > 0 && survey_hybrid.sweep_nb >= survey_hybrid.sweep_nb_max) ||
+ (survey_hybrid.sweep_back_nb_max > 0 && survey_hybrid.sweep_back_nb >= survey_hybrid.sweep_back_nb_max)) {
+ // end survey if nb > nb_max and nb_max is not 0
+ // or if nb_back > nb_back_max and nb_back_max is not 0
+ return false;
+ }
+ }
+
+ break;
+ case Turn:
+ survey_private.segment_from = LastPoint;
+ survey_private.segment_to = survey_private.from_wp;
+
+ // Rotate and Translate Line points into real world
+ RotateAndTranslateToWorld(&survey_private.segment_to, 0.f, &survey_private.smallest_corner);
+ RotateAndTranslateToWorld(&survey_private.segment_to, survey_private.orientation, &zero);
+ RotateAndTranslateToWorld(&survey_private.segment_from, 0.f, &survey_private.smallest_corner);
+ RotateAndTranslateToWorld(&survey_private.segment_from, survey_private.orientation, &zero);
+
+ if (survey_private.circle_turns) {
+ if (CrossProductZ(&LastPoint, &survey_private.from_wp, &survey_private.from_wp, &survey_private.to_wp) < 0.f) {
+ survey_private.radius = fabsf(survey_private.radius);
+ } else {
+ survey_private.radius = - fabsf(survey_private.radius);
+ }
+ float dir = (survey_private.sweep < 0.f ? -1.f : 1.f) * fabsf(survey_private.radius);
+ survey_private.circle.x = survey_private.segment_to.x - (cosf(survey_private.orientation + M_PI_2) * dir);
+ survey_private.circle.y = survey_private.segment_to.y - (sinf(survey_private.orientation + M_PI_2) * dir);
+ survey_private.circle.z = survey_private.segment_to.z;
+ // circle turns
+ nav.nav_circle(&survey_private.circle, survey_private.radius);
+
+ qdr = atan2f(stateGetPositionEnu_f()->x - survey_private.circle.x, stateGetPositionEnu_f()->y - survey_private.circle.y);
+ qdr_out = (survey_private.sweep > 0 ? -survey_private.orientation : -survey_private.orientation + M_PI);
+ if (CloseRadAngles(qdr, qdr_out)) {
+ survey_private.status = Sweep;
+ nav_init_stage();
+ LINE_START_FUNCTION;
+ }
+ } else {
+ // use straight lines to reach next point
+ nav.nav_route(&survey_private.segment_from, &survey_private.segment_to);
+
+ if (nav.nav_approaching(&survey_private.segment_to, &survey_private.segment_from, SURVEY_HYBRID_APPROACHING_TIME)) {
+ survey_private.status = Sweep;
+ nav_init_stage();
+ LINE_START_FUNCTION;
+ }
+ }
+
+ break;
+ case Init:
+ return false;
+ default:
+ return false;
+ }
+
+ return true;
+
+}
+
+//============================================================================================================================================
+/*
+ Translates point so (transX, transY) are (0,0) then rotates the point around z by Zrot
+*/
+void TranslateAndRotateFromWorld(struct EnuCoor_f *p, float Zrot, struct EnuCoor_f *trans)
+{
+ float temp;
+
+ p->x = p->x - trans->x;
+ p->y = p->y - trans->y;
+
+ temp = p->x;
+ p->x = p->x * cosf(Zrot) + p->y * sinf(Zrot);
+ p->y = -temp * sinf(Zrot) + p->y * cosf(Zrot);
+}
+
+/// Rotates point round z by -Zrot then translates so (0,0) becomes (transX,transY)
+void RotateAndTranslateToWorld(struct EnuCoor_f *p, float Zrot, struct EnuCoor_f *trans)
+{
+ float temp = p->x;
+
+ p->x = p->x * cosf(Zrot) - p->y * sinf(Zrot);
+ p->y = temp * sinf(Zrot) + p->y * cosf(Zrot);
+
+ p->x = p->x + trans->x;
+ p->y = p->y + trans->y;
+}
+
+void FindInterceptOfTwoLines(float *x, float *y, struct Line L1, struct Line L2)
+{
+ *x = ((L2.b - L1.b) / (L1.m - L2.m));
+ *y = L1.m * (*x) + L1.b;
+}
+
+
+float EvaluateLineForX(float y, struct Line L)
+{
+ return ((y - L.b) / L.m);
+}
+
+float CrossProductZ(struct EnuCoor_f *p1_start, struct EnuCoor_f *p1_end, struct EnuCoor_f *p2_start, struct EnuCoor_f *p2_end)
+{
+ float d1x = p1_end->x - p1_start->x;
+ float d1y = p1_end->y - p1_start->y;
+ float d2x = p2_end->x - p2_start->x;
+ float d2y = p2_end->y - p2_start->y;
+ return d1x * d2y - d1y * d2x;
+}
+
diff --git a/sw/airborne/modules/nav/nav_survey_hybrid.h b/sw/airborne/modules/nav/nav_survey_hybrid.h
new file mode 100644
index 0000000000..a11452ede6
--- /dev/null
+++ b/sw/airborne/modules/nav/nav_survey_hybrid.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 Gautier Hattenberger
+ * Based on OSAM poly survey
+ *
+ * This file is part of paparazzi.
+ *
+ * paparazzi is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * paparazzi is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with paparazzi; see the file COPYING. If not, see
+ * .
+ */
+
+/**
+ * @file modules/nav/nav_survey_hybrid.h
+ *
+ */
+
+#ifndef NAV_SURVEY_HYBRID_H
+#define NAV_SURVEY_HYBRID_H
+
+#include "std.h"
+
+struct SurveyHybrid {
+ uint16_t sweep_nb_max;
+ uint16_t sweep_back_nb_max;
+ uint16_t sweep_nb;
+ uint16_t sweep_back_nb;
+ bool half_sweep_enabled;
+};
+
+extern struct SurveyHybrid survey_hybrid;
+
+/** Init function
+ */
+extern void nav_survey_hybrid_init(void);
+
+/**
+ * Setup polygon survey.
+ * @param start_wp first waypoint/corner of the polygon
+ * @param orientation angle of scan lines in degrees (CCW, east)
+ * @param size number of waypoints/corners used to define the polygon
+ * @param sweep distance between scan lines
+ * @param radius turn radius (<0: automatic, radius = sweep/2; 0: no turns, use straight lines only; >0: fixed radius)
+ */
+extern void nav_survey_hybrid_setup_orientation(uint8_t start_wp, float orientation, uint8_t size, float sweep, float radius);
+
+/**
+ * Setup "dynamic" polygon survey with sweep orientation towards a waypoint.
+ * Computes the sweep orientation angle from the line first-second WP.
+ * @param start_wp first waypoint/corner of the polygon
+ * @param second_wp second waypoint towards which the sweep orientation is computed
+ * @param size number of waypoints/corners used to define the polygon
+ * @param sweep distance between scan lines, if zero uses Poly_Distance
+ * @param radius turn radius (<0: automatic, radius = sweep/2; 0: no turns, use straight lines only; >0: fixed radius)
+ */
+extern void nav_survey_hybrid_setup_towards(uint8_t start_wp, uint8_t second_wp, uint8_t size, float sweep, float radius);
+
+/** Run polygon hybrid survey */
+extern bool nav_survey_hybrid_run(void);
+
+#endif