From fa31fe646402636c1de553a6c7212e7c7a6a0b84 Mon Sep 17 00:00:00 2001 From: Oswin So Date: Mon, 1 Jul 2019 14:48:43 -0400 Subject: [PATCH] Initial dump from main repo --- .clang-format | 65 +++ .clang-tidy | 60 +++ .gitignore | 0 README.md | 6 + boustrophedon_msgs/CMakeLists.txt | 40 ++ .../action/PlanMowingPath.action | 5 + boustrophedon_msgs/msg/StripingPlan.msg | 2 + boustrophedon_msgs/msg/StripingPoint.msg | 6 + boustrophedon_msgs/package.xml | 25 ++ boustrophedon_msgs/srv/ConvertPlanToPath.srv | 3 + boustrophedon_server/CMakeLists.txt | 61 +++ .../boustrophedon_planner_server.h | 46 ++ .../boustrophedon_types.h | 22 + .../cellular_decomposition/cell.h | 74 +++ .../polygon_decomposer.h | 79 ++++ .../include/boustrophedon_server/cgal_types.h | 16 + .../include/boustrophedon_server/cgal_utils.h | 25 ++ .../boustrophedon_server/outline_planner.h | 32 ++ .../boustrophedon_server/striping_planner.h | 35 ++ .../launch/boustrophedon_server.launch | 7 + boustrophedon_server/package.xml | 32 ++ .../boustrophedon_planner_node.cpp | 13 + .../boustrophedon_planner_server.cpp | 123 +++++ .../boustrophedon_types.cpp | 5 + .../cellular_decomposition/cell.cpp | 147 ++++++ .../polygon_decomposer.cpp | 420 ++++++++++++++++++ .../src/boustrophedon_server/cgal_utils.cpp | 44 ++ .../boustrophedon_server/outline_planner.cpp | 60 +++ .../boustrophedon_server/striping_planner.cpp | 97 ++++ 29 files changed, 1550 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .gitignore create mode 100644 README.md create mode 100644 boustrophedon_msgs/CMakeLists.txt create mode 100644 boustrophedon_msgs/action/PlanMowingPath.action create mode 100644 boustrophedon_msgs/msg/StripingPlan.msg create mode 100644 boustrophedon_msgs/msg/StripingPoint.msg create mode 100644 boustrophedon_msgs/package.xml create mode 100644 boustrophedon_msgs/srv/ConvertPlanToPath.srv create mode 100644 boustrophedon_server/CMakeLists.txt create mode 100644 boustrophedon_server/include/boustrophedon_server/boustrophedon_planner_server.h create mode 100644 boustrophedon_server/include/boustrophedon_server/boustrophedon_types.h create mode 100644 boustrophedon_server/include/boustrophedon_server/cellular_decomposition/cell.h create mode 100644 boustrophedon_server/include/boustrophedon_server/cellular_decomposition/polygon_decomposer.h create mode 100644 boustrophedon_server/include/boustrophedon_server/cgal_types.h create mode 100644 boustrophedon_server/include/boustrophedon_server/cgal_utils.h create mode 100644 boustrophedon_server/include/boustrophedon_server/outline_planner.h create mode 100644 boustrophedon_server/include/boustrophedon_server/striping_planner.h create mode 100644 boustrophedon_server/launch/boustrophedon_server.launch create mode 100644 boustrophedon_server/package.xml create mode 100644 boustrophedon_server/src/boustrophedon_server/boustrophedon_planner_node.cpp create mode 100644 boustrophedon_server/src/boustrophedon_server/boustrophedon_planner_server.cpp create mode 100644 boustrophedon_server/src/boustrophedon_server/boustrophedon_types.cpp create mode 100644 boustrophedon_server/src/boustrophedon_server/cellular_decomposition/cell.cpp create mode 100644 boustrophedon_server/src/boustrophedon_server/cellular_decomposition/polygon_decomposer.cpp create mode 100644 boustrophedon_server/src/boustrophedon_server/cgal_utils.cpp create mode 100644 boustrophedon_server/src/boustrophedon_server/outline_planner.cpp create mode 100644 boustrophedon_server/src/boustrophedon_server/striping_planner.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3069f94 --- /dev/null +++ b/.clang-format @@ -0,0 +1,65 @@ +--- +BasedOnStyle: Google +AccessModifierOffset: -2 +ConstructorInitializerIndentWidth: 2 +AlignEscapedNewlinesLeft: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AlwaysBreakTemplateDeclarations: true +AlwaysBreakBeforeMultilineStrings: false +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: true +BinPackParameters: true +ColumnLimit: 120 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +DerivePointerBinding: false +PointerBindsToType: true +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 60 +PenaltyBreakString: 1 +PenaltyBreakFirstLessLess: 1000 +PenaltyExcessCharacter: 1000 +PenaltyReturnTypeOnItsOwnLine: 90 +SpacesBeforeTrailingComments: 2 +Cpp11BracedListStyle: false +Standard: Auto +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +IndentFunctionDeclarationAfterType: false +SpacesInParentheses: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true +SpaceBeforeAssignmentOperators: true +ContinuationIndentWidth: 4 +SortIncludes: false +SpaceAfterCStyleCast: false + +# Configure each individual brace in BraceWrapping +BreakBeforeBraces: Custom + +# Control of individual brace wrapping cases +BraceWrapping: { + AfterClass: 'true' + AfterControlStatement: 'true' + AfterEnum : 'true' + AfterFunction : 'true' + AfterNamespace : 'true' + AfterStruct : 'true' + AfterUnion : 'true' + BeforeCatch : 'true' + BeforeElse : 'true' + IndentBraces : 'false' +} +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..4f03f0e --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,60 @@ +--- +Checks: '*, + -llvm-header-guard, + -google-build-using-namespace, + -clang-analyzer-alpha.clone.CloneChecker, + -google-runtime-int, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-constant-array-index, + -clang-analyzer-alpha.deadcode.UnreachableCode, + -misc-use-after-move, + -cppcoreguidelines-pro-type-vararg, + -modernize-use-emplace, + -android-*, + -absel-*, + -cert-*, + -objc-*, + -zircon-*, + -hicpp-vararg, + -hicpp-no-array-decay, + -google-runtime-references, + -llvm-include-order, + -fuchsia-*, + -misc-unused-parameters' + +WarningsAsErrors: '*' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +CheckOptions: + - key: llvm-namespace-comment.ShortNamespaceLines + value: '10' + - key: llvm-namespace-comment.SpacesBeforeComments + value: '2' + - key: readability-braces-around-statements.ShortStatementLines + value: '2' + # type names + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.UnionCase + value: CamelCase + # method names are camelCase + - key: readability-identifier-naming.MethodCase + value: camelBack + # variable names are snake_case + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.ClassMemberSuffix + value: '_' + # global variables are g_snake_case + - key: readability-identifier-naming.GlobalVariablePrefix + value: g_ + - key: readability-identifier-naming.GlobalVariableCase + value: lower_case + # namespaces are snake_case + - key: readability-identifier-naming.NamespaceCase + value: lower_case +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..64435ab --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Boustrophedon Planner +Boustrophedon Planner is a coverage path planner that implements the [Boustrophedon Cell Decomposition](https://en.wikipedia.org/wiki/Boustrophedon_cell_decomposition) algorithm. + +## Overview +The path planner is an actionlib server that takes in a `geometry_msgs/PolygonStamped` and a `geometry_msgs/PoseStamped`, +and returns a `StripingPlan` message which contains a list of waypoints to stripe the passed in polygon. diff --git a/boustrophedon_msgs/CMakeLists.txt b/boustrophedon_msgs/CMakeLists.txt new file mode 100644 index 0000000..1617b5f --- /dev/null +++ b/boustrophedon_msgs/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 2.8.3) +project(boustrophedon_msgs) + +set(CMAKE_CXX_STANDARD 14) + +find_package(catkin REQUIRED + genmsg + std_msgs + geometry_msgs + actionlib_msgs + nav_msgs + ) + +add_message_files( + DIRECTORY msg + FILES + StripingPoint.msg + StripingPlan.msg +) + +add_service_files( + FILES + ConvertPlanToPath.srv +) + +add_action_files( + DIRECTORY action + FILES + PlanMowingPath.action +) + +generate_messages( + DEPENDENCIES + std_msgs + geometry_msgs + actionlib_msgs + nav_msgs +) + +catkin_package() diff --git a/boustrophedon_msgs/action/PlanMowingPath.action b/boustrophedon_msgs/action/PlanMowingPath.action new file mode 100644 index 0000000..2f17b89 --- /dev/null +++ b/boustrophedon_msgs/action/PlanMowingPath.action @@ -0,0 +1,5 @@ +geometry_msgs/PolygonStamped property +geometry_msgs/PoseStamped robot_position +--- +boustrophedon_msgs/StripingPlan plan +--- diff --git a/boustrophedon_msgs/msg/StripingPlan.msg b/boustrophedon_msgs/msg/StripingPlan.msg new file mode 100644 index 0000000..afca5de --- /dev/null +++ b/boustrophedon_msgs/msg/StripingPlan.msg @@ -0,0 +1,2 @@ +Header header +boustrophedon_msgs/StripingPoint[] points diff --git a/boustrophedon_msgs/msg/StripingPoint.msg b/boustrophedon_msgs/msg/StripingPoint.msg new file mode 100644 index 0000000..8dc90a5 --- /dev/null +++ b/boustrophedon_msgs/msg/StripingPoint.msg @@ -0,0 +1,6 @@ +geometry_msgs/Point point +uint8 type + +uint8 OUTLINE=0 +uint8 STRIPE_START=1 +uint8 STRIPE_END=2 diff --git a/boustrophedon_msgs/package.xml b/boustrophedon_msgs/package.xml new file mode 100644 index 0000000..6ecd03f --- /dev/null +++ b/boustrophedon_msgs/package.xml @@ -0,0 +1,25 @@ + + + boustrophedon_msgs + 0.0.1 + Custom messages for the Boustrophedon Planner + + Charles Brian Quinn + Oswin So + + TODO + + Charles Brian Quinn + Oswin So + + actionlib_msgs + nav_msgs + geometry_msgs + + message_generation + + catkin + + + + diff --git a/boustrophedon_msgs/srv/ConvertPlanToPath.srv b/boustrophedon_msgs/srv/ConvertPlanToPath.srv new file mode 100644 index 0000000..2ae8192 --- /dev/null +++ b/boustrophedon_msgs/srv/ConvertPlanToPath.srv @@ -0,0 +1,3 @@ +boustrophedon_msgs/StripingPlan plan +--- +nav_msgs/Path path diff --git a/boustrophedon_server/CMakeLists.txt b/boustrophedon_server/CMakeLists.txt new file mode 100644 index 0000000..6e6ca9b --- /dev/null +++ b/boustrophedon_server/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 2.8.3) +project(boustrophedon_server) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_CLANG_TIDY clang-tidy) + +find_package(catkin REQUIRED COMPONENTS + roscpp + boustrophedon_msgs + actionlib + tf + geometry_msgs + nav_msgs + rosparam_shortcuts + ) + +find_package(CGAL REQUIRED COMPONENTS Core) +catkin_package( + INCLUDE_DIRS include + CATKIN_DEPENDS + roscpp + boustrophedon_msgs + actionlib + tf + geometry_msgs + nav_msgs + rosparam_shortcuts +) + +include_directories( + include + ${catkin_INCLUDE_DIRS} +) + +add_executable(boustrophedon_planner_server + src/boustrophedon_server/boustrophedon_planner_node.cpp + src/boustrophedon_server/boustrophedon_planner_server.cpp + src/boustrophedon_server/striping_planner.cpp + src/boustrophedon_server/outline_planner.cpp + src/boustrophedon_server/cgal_utils.cpp + src/boustrophedon_server/boustrophedon_types.cpp + src/boustrophedon_server/cellular_decomposition/polygon_decomposer.cpp + src/boustrophedon_server/cellular_decomposition/cell.cpp + ) +target_link_libraries(boustrophedon_planner_server + ${catkin_LIBRARIES} + CGAL::CGAL + CGAL::CGAL_Core) +add_dependencies(boustrophedon_planner_server ${catkin_EXPORTED_TARGETS}) + +install( + TARGETS + boustrophedon_planner_server + ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} + LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} + RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +) + +install(DIRECTORY launch/ + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch + ) diff --git a/boustrophedon_server/include/boustrophedon_server/boustrophedon_planner_server.h b/boustrophedon_server/include/boustrophedon_server/boustrophedon_planner_server.h new file mode 100644 index 0000000..9a073e5 --- /dev/null +++ b/boustrophedon_server/include/boustrophedon_server/boustrophedon_planner_server.h @@ -0,0 +1,46 @@ +#ifndef SRC_BOUSTROPHEDON_PLANNER_SERVER_H +#define SRC_BOUSTROPHEDON_PLANNER_SERVER_H + +#include +#include +#include + +#include +#include + +#include "cgal_utils.h" +#include "striping_planner.h" +#include "outline_planner.h" +#include "cellular_decomposition/polygon_decomposer.h" + +class BoustrophedonPlannerServer +{ +public: + BoustrophedonPlannerServer(); + + void executePlanPathAction(const boustrophedon_msgs::PlanMowingPathGoalConstPtr& goal); + +private: + using Server = actionlib::SimpleActionServer; + + ros::NodeHandle node_handle_; + ros::NodeHandle private_node_handle_; + Server action_server_; + ros::ServiceServer conversion_server_; + + StripingPlanner striping_planner_; + OutlinePlanner outline_planner_; + + int outline_layer_count_{}; + double stripe_separation_{}; + double stripe_angle_{}; + tf::TransformListener transform_listener_{}; + + bool convertStripingPlanToPath(boustrophedon_msgs::ConvertPlanToPath::Request& request, + boustrophedon_msgs::ConvertPlanToPath::Response& response); + boustrophedon_msgs::PlanMowingPathResult toResult(std::vector&& path, const std::string& frame) const; + Polygon fromBoundary(const geometry_msgs::PolygonStamped& boundary) const; + Point fromPositionWithFrame(const geometry_msgs::PoseStamped& pose, const std::string& target_frame) const; +}; + +#endif // SRC_BOUSTROPHEDON_PLANNER_SERVER_H diff --git a/boustrophedon_server/include/boustrophedon_server/boustrophedon_types.h b/boustrophedon_server/include/boustrophedon_server/boustrophedon_types.h new file mode 100644 index 0000000..f5188f7 --- /dev/null +++ b/boustrophedon_server/include/boustrophedon_server/boustrophedon_types.h @@ -0,0 +1,22 @@ +#ifndef SRC_BOUSTROPHEDON_TYPES_H +#define SRC_BOUSTROPHEDON_TYPES_H + +#include "cgal_types.h" + +enum class PointType +{ + Outline = 0, + StripeStart = 1, + StripeEnd = 2, + Travel = 3 +}; + +struct NavPoint +{ + PointType type{}; + Point point{}; + + NavPoint(PointType type, Point point); +}; + +#endif // SRC_BOUSTROPHEDON_TYPES_H diff --git a/boustrophedon_server/include/boustrophedon_server/cellular_decomposition/cell.h b/boustrophedon_server/include/boustrophedon_server/cellular_decomposition/cell.h new file mode 100644 index 0000000..444e79f --- /dev/null +++ b/boustrophedon_server/include/boustrophedon_server/cellular_decomposition/cell.h @@ -0,0 +1,74 @@ +#ifndef SRC_CELL_H +#define SRC_CELL_H + +#include +#include +#include "../cgal_types.h" + +namespace bcd +{ +using Circulator = Polygon::Vertex_const_circulator; +enum class Event +{ + Start, + Ceil, + Floor, + End, + Open, + Close, +}; + +struct Vertex +{ + Vertex() = default; + Vertex(Event type, Circulator point); + bool isAdjacentTo(const Vertex& other) const; + Circulator nextPoint() const; + Circulator previousPoint() const; + + Event type; + Circulator point; +}; + +struct Cell +{ + Cell() = default; + Cell(Vertex start); + bool connectsToFloor(const Vertex& other) const; + bool connectsToCeil(const Vertex& other) const; + bool enclosesVector(const Vertex& other) const; + + const Vertex& currentCeil() const; + const Vertex& currentFloor() const; + + const Point nextCeilPoint() const; + const Point nextFloorPoint() const; + + std::vector toPoints() const; + Polygon toPolygon() const; + + Vertex start; + std::vector ceiling_points; + std::vector floor_points; + Vertex end; + + std::vector neighbors; +}; +} // namespace bcd + +template <> +struct std::hash +{ + std::size_t operator()(const bcd::Cell* const& cell) const + { + std::size_t seed = 17; + boost::hash_combine(seed, std::make_pair(cell->start.point->x(), cell->start.point->y())); + boost::hash_combine(seed, std::make_pair(cell->end.point->x(), cell->end.point->y())); + return seed; + } +}; + +std::ostream& operator<<(std::ostream& out, bcd::Event event); +std::ostream& operator<<(std::ostream& out, const bcd::Vertex& vertex); +std::ostream& operator<<(std::ostream& out, const bcd::Cell& cell); +#endif // SRC_CELL_H diff --git a/boustrophedon_server/include/boustrophedon_server/cellular_decomposition/polygon_decomposer.h b/boustrophedon_server/include/boustrophedon_server/cellular_decomposition/polygon_decomposer.h new file mode 100644 index 0000000..4a289f2 --- /dev/null +++ b/boustrophedon_server/include/boustrophedon_server/cellular_decomposition/polygon_decomposer.h @@ -0,0 +1,79 @@ +#ifndef SRC_POLYGON_DECOMPOSER_H +#define SRC_POLYGON_DECOMPOSER_H + +#include "../cgal_types.h" +#include "cell.h" + +class PolygonDecomposer +{ +public: + void decompose(Polygon& polygon); + std::vector getSubPolygons(const Point& position) const; + +private: + std::vector> cells_; + std::vector vertices_; + + /** + * Creates vertices and assigns each vertex an event from the Polygon. + * During the first pass, it finds Open and Close events, and then inserts + * those intersections of those slices into the polygon. + * During the second pass, it creates Vertex objects and places them into the + * vertices_ vector + * @param polygon + */ + void createVertices(Polygon& polygon); + + /** + * Finds Open and close events. For each event, it finds the intersection vertices of the + * slice and inserts them into the polygon. + * @param polygon + */ + void insertOpenCloseVertices(Polygon& polygon); + + /** + * Inserts all intersections of the slice with the polygon into the polygon. + * @param polygon + * @param point + */ + static void sliceAndInsertIntoPolygon(Polygon& polygon, const Point& point); + + /** + * + */ + void createCells(); + + bcd::Cell* getClosestCell(const Point& position) const; + + std::vector visitCells(const bcd::Cell* starting_cell) const; + + std::vector toPolygons(std::vector cells) const; + + /** + * Dispatcher for the individual event types. + * @param current_vertex + * @param current_cells + * @return + */ + bool handleVertex(bcd::Vertex& current_vertex, std::vector>& current_cells); + + bool handleFloor(bcd::Vertex& current_vertex, std::vector>& current_cells); + bool handleCeil(bcd::Vertex& current_vertex, std::vector>& current_cells); + bool handleStart(bcd::Vertex& current_vertex, std::vector>& current_cells); + bool handleEnd(bcd::Vertex& current_vertex, std::vector>& current_cells); + bool handleOpen(bcd::Vertex& current_vertex, std::vector>& current_cells); + bool handleClose(bcd::Vertex& current_vertex, std::vector>& current_cells); + + std::vector>::iterator + getTopCell(bcd::Vertex& current_vertex, std::vector>& current_cells) const; + std::vector>::iterator + getBottomCell(bcd::Vertex& current_vertex, std::vector>& current_cells) const; + + /** + * Sorts the vertices first by x coordinate, then by type. + */ + void sortVertices(); + bcd::Event getEvent(const bcd::Circulator& vertex, bcd::Event last_event) const; +}; + +#endif // SRC_POLYGON_DECOMPOSER_H diff --git a/boustrophedon_server/include/boustrophedon_server/cgal_types.h b/boustrophedon_server/include/boustrophedon_server/cgal_types.h new file mode 100644 index 0000000..c77e416 --- /dev/null +++ b/boustrophedon_server/include/boustrophedon_server/cgal_types.h @@ -0,0 +1,16 @@ +#ifndef SRC_CGAL_TYPES_H +#define SRC_CGAL_TYPES_H + +#include +#include +#include + +using Kernel = CGAL::Cartesian; +using Polygon = CGAL::Polygon_2; +using Point = Kernel::Point_2; +using Traits = CGAL::Arr_linear_traits_2; +using Segment = Traits::Segment_2; +using Line = Traits::Line_2; +using AffineTransform = CGAL::Aff_transformation_2; + +#endif // SRC_CGAL_TYPES_H diff --git a/boustrophedon_server/include/boustrophedon_server/cgal_utils.h b/boustrophedon_server/include/boustrophedon_server/cgal_utils.h new file mode 100644 index 0000000..4b47547 --- /dev/null +++ b/boustrophedon_server/include/boustrophedon_server/cgal_utils.h @@ -0,0 +1,25 @@ +#ifndef SRC_CGAL_UTILS_H +#define SRC_CGAL_UTILS_H + +#include "cgal_types.h" +#include "boustrophedon_types.h" + +AffineTransform preprocessPolygon(Polygon& polygon, const Point& robot_position, const double& striping_angle); + +void postprocessPolygonAndPath(const AffineTransform& preprocess_transform, Polygon& polygon, + std::vector& path); + +template +static void transformPoints(const Iterator_t& first, const Iterator_t& last, + const CGAL::Aff_transformation_2& transform) +{ + for (auto current = first; current != last; current++) + { + *current = transform(*current); + } +} + +void transformNavPoints(const std::vector::iterator& first, const std::vector::iterator& last, + const CGAL::Aff_transformation_2& transform); + +#endif // SRC_CGAL_UTILS_H diff --git a/boustrophedon_server/include/boustrophedon_server/outline_planner.h b/boustrophedon_server/include/boustrophedon_server/outline_planner.h new file mode 100644 index 0000000..d96bb16 --- /dev/null +++ b/boustrophedon_server/include/boustrophedon_server/outline_planner.h @@ -0,0 +1,32 @@ +#ifndef SRC_OUTLINE_PLANNER_H +#define SRC_OUTLINE_PLANNER_H + +#include "cgal_types.h" +#include "boustrophedon_types.h" + +class OutlinePlanner +{ +public: + struct Parameters + { + /** The number of passes to make in the outline portion + */ + int outline_layers = 3; + /** Center-to-center distance (in meters) between passes in the coverage path. + * + * Applies to both outline and fill portions. + */ + double stripe_separation = 0.5; + }; + + void setParameters(Parameters parameters); + + void addToPath(Polygon polygon, const Point& robot_position, std::vector& path, Polygon& innermost_polygon); + +private: + Parameters params_; + + void shiftPolygonToStartNearRobot(Polygon& polygon, const Point& robot_position); +}; + +#endif // SRC_OUTLINE_PLANNER_H diff --git a/boustrophedon_server/include/boustrophedon_server/striping_planner.h b/boustrophedon_server/include/boustrophedon_server/striping_planner.h new file mode 100644 index 0000000..959a3ba --- /dev/null +++ b/boustrophedon_server/include/boustrophedon_server/striping_planner.h @@ -0,0 +1,35 @@ +#ifndef SRC_BOUSTROPHEDONPLANNER_H +#define SRC_BOUSTROPHEDONPLANNER_H + +#include "cgal_types.h" +#include "boustrophedon_types.h" + +class StripingPlanner +{ +public: + struct Parameters + { + /** Center-to-center distance (in meters) between passes in the coverage path. + * + * Applies to both outline and fill portions. + */ + double stripe_separation; + }; + + void setParameters(Parameters parameters); + + void addToPath(const Polygon& polygon, const Point& robot_position, std::vector& path); + +private: + Parameters params_; + + /** Constructs the fill portion of the mowing path, which stripes back and + * forth over the inner area left after the outline portion is complete. + * + */ + void fillPolygon(const Polygon& polygon, std::vector& path); + + std::vector getIntersectionPoints(const Polygon& polygon, const Line& line); +}; + +#endif // SRC_BOUSTROPHEDONPLANNER_H diff --git a/boustrophedon_server/launch/boustrophedon_server.launch b/boustrophedon_server/launch/boustrophedon_server.launch new file mode 100644 index 0000000..ff7d76a --- /dev/null +++ b/boustrophedon_server/launch/boustrophedon_server.launch @@ -0,0 +1,7 @@ + + + + + + + diff --git a/boustrophedon_server/package.xml b/boustrophedon_server/package.xml new file mode 100644 index 0000000..0003717 --- /dev/null +++ b/boustrophedon_server/package.xml @@ -0,0 +1,32 @@ + + + boustrophedon_server + 0.0.1 + Actionlib server for the Boustrophedon Planner + + Charles Brian Quinn + Oswin So + + + TODO + + + Charles Brian Quinn + Oswin So + + roscpp + boustrophedon_msgs + geometry_msgs + nav_msgs + actionlib + tf + rosparam_shortcuts + + cgal + + rosunit + + catkin + + + diff --git a/boustrophedon_server/src/boustrophedon_server/boustrophedon_planner_node.cpp b/boustrophedon_server/src/boustrophedon_server/boustrophedon_planner_node.cpp new file mode 100644 index 0000000..fb3b835 --- /dev/null +++ b/boustrophedon_server/src/boustrophedon_server/boustrophedon_planner_node.cpp @@ -0,0 +1,13 @@ +#include +#include + +int main(int argc, char** argv) +{ + ros::init(argc, argv, "boustrophedon_planner_node"); + + BoustrophedonPlannerServer server; + + ros::spin(); + + return 0; +} diff --git a/boustrophedon_server/src/boustrophedon_server/boustrophedon_planner_server.cpp b/boustrophedon_server/src/boustrophedon_server/boustrophedon_planner_server.cpp new file mode 100644 index 0000000..2b5e554 --- /dev/null +++ b/boustrophedon_server/src/boustrophedon_server/boustrophedon_planner_server.cpp @@ -0,0 +1,123 @@ +#include + +#include + +BoustrophedonPlannerServer::BoustrophedonPlannerServer() + : private_node_handle_("~") + , action_server_(node_handle_, "plan_path", boost::bind(&BoustrophedonPlannerServer::executePlanPathAction, this, _1), + false) + , conversion_server_{ node_handle_.advertiseService("convert_striping_plan_to_path", + &BoustrophedonPlannerServer::convertStripingPlanToPath, this) } +{ + std::size_t error = 0; + error += static_cast( + !rosparam_shortcuts::get("plan_path", private_node_handle_, "outline_layer_count", outline_layer_count_)); + error += static_cast( + !rosparam_shortcuts::get("plan_path", private_node_handle_, "stripe_separation", stripe_separation_)); + error += static_cast( + !rosparam_shortcuts::get("plan_path", private_node_handle_, "stripe_angle", stripe_angle_)); + rosparam_shortcuts::shutdownIfError("plan_path", error); + + striping_planner_.setParameters({ stripe_separation_ }); + outline_planner_.setParameters({ outline_layer_count_, stripe_separation_ }); + + action_server_.start(); +} + +void BoustrophedonPlannerServer::executePlanPathAction(const boustrophedon_msgs::PlanMowingPathGoalConstPtr& goal) +{ + std::string boundary_frame = goal->property.header.frame_id; + Polygon polygon = fromBoundary(goal->property); + + Point robot_position; + try + { + robot_position = fromPositionWithFrame(goal->robot_position, goal->property.header.frame_id); + } + catch (const tf::TransformException& ex) + { + action_server_.setAborted(Server::Result(), + std::string("Boustrophedon planner failed with a tf exception: ") + ex.what()); + return; + } + + std::vector path; + + auto preprocess_transform = preprocessPolygon(polygon, robot_position, stripe_angle_); + + Polygon fill_polygon; + outline_planner_.addToPath(polygon, robot_position, path, fill_polygon); + + PolygonDecomposer polygon_decomposer{}; + polygon_decomposer.decompose(fill_polygon); + std::vector sub_polygons = polygon_decomposer.getSubPolygons(robot_position); + + for (const auto& subpoly : sub_polygons) + { + striping_planner_.addToPath(subpoly, robot_position, path); + } + + postprocessPolygonAndPath(preprocess_transform, polygon, path); + + auto result = toResult(std::move(path), boundary_frame); + action_server_.setSucceeded(result); +} + +boustrophedon_msgs::PlanMowingPathResult BoustrophedonPlannerServer::toResult(std::vector&& path, + const std::string& frame) const +{ + boustrophedon_msgs::PlanMowingPathResult result; + result.plan.points.reserve(path.size()); + result.plan.header.stamp = ros::Time::now(); + result.plan.header.frame_id = frame; + + for (const auto& point : path) + { + boustrophedon_msgs::StripingPoint stripe_point{}; + stripe_point.point.x = point.point.x(); + stripe_point.point.y = point.point.y(); + stripe_point.point.z = 0; + stripe_point.type = static_cast(point.type); + result.plan.points.emplace_back(stripe_point); + } + return result; +} + +Polygon BoustrophedonPlannerServer::fromBoundary(const geometry_msgs::PolygonStamped& boundary) const +{ + Polygon polygon; + for (const auto& point : boundary.polygon.points) + { + polygon.push_back(Point(point.x, point.y)); + } + return polygon; +} + +Point BoustrophedonPlannerServer::fromPositionWithFrame(const geometry_msgs::PoseStamped& pose, + const std::string& target_frame) const +{ + geometry_msgs::PoseStamped transformed_pose; + transform_listener_.transformPose(target_frame, pose, transformed_pose); + return { transformed_pose.pose.position.x, transformed_pose.pose.position.y }; +} + +bool BoustrophedonPlannerServer::convertStripingPlanToPath(boustrophedon_msgs::ConvertPlanToPath::Request& request, + boustrophedon_msgs::ConvertPlanToPath::Response& response) +{ + response.path.header.frame_id = request.plan.header.frame_id; + response.path.header.stamp = request.plan.header.stamp; + + std::transform(request.plan.points.begin(), request.plan.points.end(), response.path.poses.begin(), + [&](const boustrophedon_msgs::StripingPoint& point) { + geometry_msgs::PoseStamped pose; + pose.header.frame_id = request.plan.header.frame_id; + pose.header.stamp = request.plan.header.stamp; + pose.pose.position = point.point; + pose.pose.orientation.x = 0.0; + pose.pose.orientation.y = 0.0; + pose.pose.orientation.z = 0.0; + pose.pose.orientation.w = 1.0; + return pose; + }); + return true; +} diff --git a/boustrophedon_server/src/boustrophedon_server/boustrophedon_types.cpp b/boustrophedon_server/src/boustrophedon_server/boustrophedon_types.cpp new file mode 100644 index 0000000..3886422 --- /dev/null +++ b/boustrophedon_server/src/boustrophedon_server/boustrophedon_types.cpp @@ -0,0 +1,5 @@ +#include + +NavPoint::NavPoint(PointType type, Point point) : type{ type }, point{ std::move(point) } +{ +} diff --git a/boustrophedon_server/src/boustrophedon_server/cellular_decomposition/cell.cpp b/boustrophedon_server/src/boustrophedon_server/cellular_decomposition/cell.cpp new file mode 100644 index 0000000..1cbdbc8 --- /dev/null +++ b/boustrophedon_server/src/boustrophedon_server/cellular_decomposition/cell.cpp @@ -0,0 +1,147 @@ +#include + +using namespace bcd; + +Vertex::Vertex(Event type, Circulator point) : type{ type }, point{ point } +{ +} + +bool Vertex::isAdjacentTo(const Vertex& other) const +{ + return *(point + 1) == *other.point || *(point - 1) == *other.point; +} + +Circulator Vertex::nextPoint() const +{ + return point + 1; +} + +Circulator Vertex::previousPoint() const +{ + return point - 1; +} + +std::ostream& operator<<(std::ostream& out, Event event) +{ + std::string string; + switch (event) + { + case Event::Start: + string = std::string("Start"); + break; + case Event::End: + string = std::string("End"); + break; + case Event::Open: + string = std::string("Open"); + break; + case Event::Close: + string = std::string("Close"); + break; + case Event::Ceil: + string = std::string("Ceil"); + break; + case Event::Floor: + string = std::string("Floor"); + break; + } + out << string; + return out; +} + +std::ostream& operator<<(std::ostream& out, const Vertex& vertex) +{ + out << *vertex.point << ": " << vertex.type; + return out; +} + +Cell::Cell(Vertex start) : start{ std::move(start) } +{ +} + +bool Cell::connectsToFloor(const Vertex& other) const +{ + bool connects_to_start_floor = floor_points.empty() && start.isAdjacentTo(other); + bool connects_to_end_floor = !floor_points.empty() && floor_points.back().isAdjacentTo(other); + return connects_to_start_floor || connects_to_end_floor; +} + +bool Cell::connectsToCeil(const Vertex& other) const +{ + bool connects_to_start_ceil = ceiling_points.empty() && start.isAdjacentTo(other); + bool connects_to_end_ceil = !ceiling_points.empty() && ceiling_points.back().isAdjacentTo(other); + return connects_to_start_ceil || connects_to_end_ceil; +} + +bool Cell::enclosesVector(const Vertex& other) const +{ + auto top_point = currentCeil().point; + auto bottom_point = currentFloor().point; + + return bottom_point->y() < other.point->y() && other.point->y() < top_point->y(); +} + +const Vertex& Cell::currentCeil() const +{ + return ceiling_points.empty() ? start : ceiling_points.back(); +} + +const Vertex& Cell::currentFloor() const +{ + return floor_points.empty() ? start : floor_points.back(); +} + +const Point Cell::nextCeilPoint() const +{ + return *(currentCeil().point - 1); +} + +const Point Cell::nextFloorPoint() const +{ + return *(currentFloor().point + 1); +} + +std::vector Cell::toPoints() const +{ + std::vector points; + points.reserve(2 + floor_points.size() + ceiling_points.size()); + points.emplace_back(*start.point); + std::transform(floor_points.begin(), floor_points.end(), std::back_inserter(points), + [](const Vertex& vertex) { return *vertex.point; }); + points.emplace_back(*end.point); + std::transform(ceiling_points.rbegin(), ceiling_points.rend(), std::back_inserter(points), + [](const Vertex& vertex) { return *vertex.point; }); + return points; +} + +Polygon Cell::toPolygon() const +{ + Polygon polygon; + auto points = toPoints(); + polygon.insert(polygon.vertices_end(), points.begin(), points.end()); + return polygon; +} + +std::ostream& operator<<(std::ostream& out, const bcd::Cell& cell) +{ + out << cell.start; + for (const auto& vertex : cell.floor_points) + { + out << ", " << vertex; + } + out << ", " << cell.end; + for (auto vertex = cell.ceiling_points.rbegin(); vertex != cell.ceiling_points.rend(); vertex++) + { + out << ", " << *vertex; + } + + out << std::endl << "neigbours: "; + + for (const auto& neighbour : cell.neighbors) + { + out << neighbour->start << " -> " << neighbour->end << std::endl; + } + out << std::endl; + + return out; +} diff --git a/boustrophedon_server/src/boustrophedon_server/cellular_decomposition/polygon_decomposer.cpp b/boustrophedon_server/src/boustrophedon_server/cellular_decomposition/polygon_decomposer.cpp new file mode 100644 index 0000000..7f26091 --- /dev/null +++ b/boustrophedon_server/src/boustrophedon_server/cellular_decomposition/polygon_decomposer.cpp @@ -0,0 +1,420 @@ +#include +#include + +using namespace bcd; + +void PolygonDecomposer::decompose(Polygon& polygon) +{ + createVertices(polygon); + createCells(); +} + +std::vector PolygonDecomposer::getSubPolygons(const Point& position) const +{ + Cell* closest_cell = getClosestCell(position); + std::vector cells = visitCells(closest_cell); + + std::vector polygons = toPolygons(cells); + return polygons; +} + +void PolygonDecomposer::createVertices(Polygon& polygon) +{ + insertOpenCloseVertices(polygon); + + auto left_vertex = CGAL::left_vertex_2(polygon.vertices_circulator(), --polygon.vertices_circulator()); + Event current_event = Event::Start; + + vertices_.emplace_back(current_event, left_vertex); + + auto vertex = left_vertex + 1; + + while (vertex != left_vertex) + { + current_event = getEvent(vertex, current_event); + vertices_.emplace_back(current_event, vertex); + vertex++; + } +} + +void PolygonDecomposer::insertOpenCloseVertices(Polygon& polygon) +{ + auto vertex = CGAL::left_vertex_2(polygon.vertices_circulator(), --polygon.vertices_circulator()); + Event current_event = Event::Start; + + // TODO(Oswin): Use something more efficient. I CBF to write a hash function for now. + std::vector visited_vertices; + + auto left_vertex = *vertex; + vertex++; + while (*vertex != left_vertex) + { + current_event = getEvent(vertex, current_event); + if ((current_event == Event::Open || current_event == Event::Close) && + std::find(visited_vertices.begin(), visited_vertices.end(), *vertex) == visited_vertices.end()) + { + visited_vertices.emplace_back(*vertex); + sliceAndInsertIntoPolygon(polygon, *vertex); + // Start from beginning lmao because iterators are (probably) invalidated. + vertex = CGAL::left_vertex_2(polygon.vertices_circulator(), --polygon.vertices_circulator()); + current_event = Event::Start; + } + + vertex++; + } +} + +void PolygonDecomposer::sliceAndInsertIntoPolygon(Polygon& polygon, const Point& point) +{ + auto vertical_line = Line(Point(point.x(), 0), Point(point.x(), 1)); + + for (auto it = polygon.vertices_begin(); it != polygon.vertices_end() - 1; it++) + { + auto polygon_edge = Segment(*it, *(it + 1)); + + auto result = CGAL::intersection(polygon_edge, vertical_line); + + if (result) + { + // Only one point => didn't intersect a vertical edge on the polygon. + // Insert into the polygon between it and it+1 + if (auto found_point = boost::get(&*result)) + { + if (*found_point != polygon_edge.source() && *found_point != polygon_edge.target()) + { + // Fuck pointer invalidation + it = polygon.insert((it + 1), *found_point) + 1; + } + } + } + } + + auto polygon_edge = Segment(*polygon.vertices_circulator(), *(polygon.vertices_circulator() - 1)); + // Check the last edge (last point -> first point) as well + auto result = CGAL::intersection(polygon_edge, vertical_line); + + if (result) + { + // Only one point => didn't intersect a vertical edge on the polygon. + // Insert into the polygon between it and it+1 + if (auto found_point = boost::get(&*result)) + { + if (*found_point != polygon_edge.source() && *found_point != polygon_edge.target()) + { + polygon.push_back(*found_point); + } + } + } +} + +void PolygonDecomposer::createCells() +{ + sortVertices(); + + std::vector> current_cells; + current_cells.emplace_back(std::make_unique(vertices_.front())); + + for (int i = 1; i < vertices_.size(); i++) + { + Vertex& current_vertex = vertices_[i]; + + bool done = handleVertex(current_vertex, current_cells); + // TODO(Oswinn): Actually fix this instead of this patch + // Swap the point with the other point that it's connected with, hopefully everything works out. + if (!done) + { + auto other = [&]() { + if (current_vertex.point->x() == current_vertex.nextPoint()->x()) + { + return std::find_if(vertices_.begin(), vertices_.end(), + [&](const Vertex& v) { return *v.point == *current_vertex.nextPoint(); }); + } + if (current_vertex.point->x() == current_vertex.previousPoint()->x()) + { + return std::find_if(vertices_.begin(), vertices_.end(), + [&](const Vertex& v) { return *v.point == *current_vertex.previousPoint(); }); + } + return vertices_.end(); + }(); + + if (other != vertices_.end() && other - vertices_.begin() > i) + { + std::swap(current_vertex, *other); + i--; + continue; + } + } + } +} + +bool PolygonDecomposer::handleVertex(Vertex& current_vertex, std::vector>& current_cells) +{ + switch (current_vertex.type) + { + case Event::Start: + return handleStart(current_vertex, current_cells); + case Event::Ceil: + return handleCeil(current_vertex, current_cells); + case Event::Floor: + return handleFloor(current_vertex, current_cells); + case Event::End: + return handleEnd(current_vertex, current_cells); + case Event::Open: + return handleOpen(current_vertex, current_cells); + case Event::Close: + return handleClose(current_vertex, current_cells); + } +} + +bool PolygonDecomposer::handleFloor(bcd::Vertex& current_vertex, std::vector>& current_cells) +{ + for (auto& cell : current_cells) + { + if (cell->connectsToFloor(current_vertex)) + { + cell->floor_points.emplace_back(current_vertex); + return true; + } + } + return false; +} + +bool PolygonDecomposer::handleCeil(Vertex& current_vertex, std::vector>& current_cells) +{ + for (auto& cell : current_cells) + { + if (cell->connectsToCeil(current_vertex)) + { + cell->ceiling_points.emplace_back(current_vertex); + return true; + } + } + return false; +} + +bool PolygonDecomposer::handleStart(Vertex& current_vertex, std::vector>& current_cells) +{ + current_cells.emplace_back(std::make_unique(current_vertex)); + return true; +} + +bool PolygonDecomposer::handleEnd(Vertex& current_vertex, std::vector>& current_cells) +{ + for (auto it = current_cells.begin(); it != current_cells.end(); it++) + { + Cell* cell = it->get(); + if (cell->connectsToCeil(current_vertex) && cell->connectsToFloor(current_vertex)) + { + cell->end = current_vertex; + cells_.emplace_back(std::move(*it)); + current_cells.erase(it); + return true; + } + } + return false; +} + +bool PolygonDecomposer::handleOpen(Vertex& current_vertex, std::vector>& current_cells) +{ + for (auto it = current_cells.begin(); it != current_cells.end(); it++) + { + Cell* closing_cell = it->get(); + if (closing_cell->enclosesVector(current_vertex)) + { + closing_cell->end = current_vertex; + + auto top = std::make_unique(current_vertex); + top->ceiling_points.emplace_back(closing_cell->ceiling_points.back()); + + auto bottom = std::make_unique(current_vertex); + bottom->floor_points.emplace_back(closing_cell->floor_points.back()); + + Cell* closed_cell = it->get(); + // Update neighbors list. Counterclockwise from bottom right corner: bottom, top, rest + closed_cell->neighbors.insert(closed_cell->neighbors.begin(), { bottom.get(), top.get() }); + + top->neighbors.emplace_back(closed_cell); + bottom->neighbors.emplace_back(closed_cell); + + cells_.emplace_back(std::move(*it)); + current_cells.erase(it); + + current_cells.emplace_back(std::move(top)); + current_cells.emplace_back(std::move(bottom)); + return true; + } + } + return false; +} + +bool PolygonDecomposer::handleClose(Vertex& current_vertex, std::vector>& current_cells) +{ + auto top_cell_it = getTopCell(current_vertex, current_cells); + if (top_cell_it == current_cells.end()) + { + return false; + } + top_cell_it->get()->end = current_vertex; + + cells_.emplace_back(std::move(*top_cell_it)); + current_cells.erase(top_cell_it); // Fuck pointer invalidation again + auto top_cell_ptr = cells_.back().get(); + + auto bottom_cell_it = getBottomCell(current_vertex, current_cells); + + if (bottom_cell_it == current_cells.end()) + { + return false; + } + + bottom_cell_it->get()->end = current_vertex; + cells_.emplace_back(std::move(*bottom_cell_it)); + current_cells.erase(bottom_cell_it); + auto bottom_cell_ptr = cells_.back().get(); + + auto new_cell = std::make_unique(current_vertex); + new_cell->ceiling_points.emplace_back(top_cell_ptr->ceiling_points.back()); + new_cell->floor_points.emplace_back(bottom_cell_ptr->floor_points.back()); + + // Update neighbors list. Counterclockwise from bottom right corner (back of vector to front): bottom, top + new_cell->neighbors.insert(new_cell->neighbors.begin(), { top_cell_ptr, bottom_cell_ptr }); + top_cell_ptr->neighbors.emplace_back(new_cell.get()); + bottom_cell_ptr->neighbors.emplace_back(new_cell.get()); + + current_cells.emplace_back(std::move(new_cell)); + + return true; +} + +std::vector>::iterator +PolygonDecomposer::getTopCell(Vertex& current_vertex, std::vector>& current_cells) const +{ + return std::find_if(current_cells.begin(), current_cells.end(), [&](const std::unique_ptr& cell) { + return cell->nextFloorPoint() == *current_vertex.point; + }); +} + +std::vector>::iterator +PolygonDecomposer::getBottomCell(Vertex& current_vertex, std::vector>& current_cells) const +{ + return std::find_if(current_cells.begin(), current_cells.end(), [&](const std::unique_ptr& cell) { + return cell->nextCeilPoint() == *current_vertex.point; + }); +} + +Event PolygonDecomposer::getEvent(const Polygon::Vertex_const_circulator& vertex, Event last_event) const +{ + // Transition out of special events + if (last_event == Event::Start || last_event == Event::Open) + { + last_event = Event::Floor; + } + else if (last_event == Event::End || last_event == Event::Close) + { + last_event = Event::Ceil; + } + + auto next_vertex = vertex + 1; + + if (last_event == Event::Floor && next_vertex->x() < vertex->x()) + { + if (next_vertex->y() < vertex->y()) + { + return Event::Close; + } + return Event::End; + } + if (last_event == Event::Ceil && next_vertex->x() > vertex->x()) + { + if (next_vertex->y() < vertex->y()) + { + return Event::Start; + } + return Event::Open; + } + return last_event; +} + +void PolygonDecomposer::sortVertices() +{ + std::sort(vertices_.begin(), vertices_.end(), [](const Vertex& lhs, const Vertex& rhs) { + if (lhs.point->x() != rhs.point->x()) + { + return lhs.point->x() < rhs.point->x(); + } + + return lhs.type < rhs.type; + }); +} + +std::vector PolygonDecomposer::toPolygons(std::vector cells) const +{ + std::vector polygons; + polygons.reserve(cells.size()); + + std::transform(cells.begin(), cells.end(), std::back_inserter(polygons), + [](const Cell* cell) { return cell->toPolygon(); }); + return polygons; +} + +bcd::Cell* PolygonDecomposer::getClosestCell(const Point& position) const +{ + auto distance_to_edge = [position](const Polygon::Edge_const_iterator& it) { + return CGAL::squared_distance(*it, position); + }; + + auto distance_to_cell = [&](const std::unique_ptr& cell) { + auto polygon = cell->toPolygon(); + double smallest_distance = std::numeric_limits::max(); + for (auto edge_it = polygon.edges_begin(); edge_it != polygon.edges_end(); edge_it++) + { + smallest_distance = std::min(smallest_distance, distance_to_edge(edge_it)); + } + return smallest_distance; + }; + + auto compare_distances = [&](const std::unique_ptr& lhs, const std::unique_ptr& rhs) { + return distance_to_cell(lhs) < distance_to_cell(rhs); + }; + + auto closest = std::min_element(cells_.begin(), cells_.end(), compare_distances); + return closest->get(); +} + +std::vector PolygonDecomposer::visitCells(const Cell* starting_cell) const +{ + std::unordered_set visited; + std::vector cells; + const Cell* current_cell = starting_cell; + + auto not_visited = [&](const Cell* cell) { return visited.find(cell) == visited.end(); }; + + auto get_next_unvisited_cell = [&](const Cell* cell) { + return std::find_if(cell->neighbors.begin(), cell->neighbors.end(), not_visited); + }; + + while (cells.size() < cells_.size()) + { + visited.emplace(current_cell); + cells.emplace_back(current_cell); + + auto not_visited_cell = get_next_unvisited_cell(current_cell); + if (not_visited_cell != current_cell->neighbors.end()) + { + current_cell = *not_visited_cell; + } + else + { + for (auto it = cells.rbegin(); it != cells.rend(); it++) + { + auto not_visited_cell = get_next_unvisited_cell(*it); + if (not_visited_cell != (*it)->neighbors.end()) + { + current_cell = *not_visited_cell; + break; + } + } + } + } + return cells; +} diff --git a/boustrophedon_server/src/boustrophedon_server/cgal_utils.cpp b/boustrophedon_server/src/boustrophedon_server/cgal_utils.cpp new file mode 100644 index 0000000..7b285be --- /dev/null +++ b/boustrophedon_server/src/boustrophedon_server/cgal_utils.cpp @@ -0,0 +1,44 @@ +#include + +AffineTransform preprocessPolygon(Polygon& polygon, const Point& robot_position, const double& striping_angle) +{ + AffineTransform transform(CGAL::IDENTITY); + + AffineTransform rotation_transform(CGAL::ROTATION, sin(striping_angle), cos(striping_angle)); + transform = transform * rotation_transform; + + auto max_x = CGAL::right_vertex_2(polygon.container().begin(), polygon.container().end())->x(); + if (robot_position.x() >= max_x) + { + AffineTransform flip_transform(-1, 0, 0, 1); + transform = transform * flip_transform; + } + + if (polygon.is_clockwise_oriented()) + { + polygon.reverse_orientation(); + } + + transformPoints(polygon.vertices_begin(), polygon.vertices_end(), transform); + + return transform; +} + +void postprocessPolygonAndPath(const AffineTransform& preprocess_transform, Polygon& polygon, + std::vector& path) +{ + auto transform = preprocess_transform.inverse(); + + transformPoints(polygon.vertices_begin(), polygon.vertices_end(), transform); + + transformNavPoints(path.begin(), path.end(), transform); +} + +void transformNavPoints(const std::vector::iterator& first, const std::vector::iterator& last, + const CGAL::Aff_transformation_2& transform) +{ + for (auto current = first; current != last; current++) + { + current->point = transform(current->point); + } +} diff --git a/boustrophedon_server/src/boustrophedon_server/outline_planner.cpp b/boustrophedon_server/src/boustrophedon_server/outline_planner.cpp new file mode 100644 index 0000000..65f9901 --- /dev/null +++ b/boustrophedon_server/src/boustrophedon_server/outline_planner.cpp @@ -0,0 +1,60 @@ +#include +#include + +void OutlinePlanner::setParameters(OutlinePlanner::Parameters parameters) +{ + params_ = parameters; +} + +void OutlinePlanner::addToPath(Polygon polygon, const Point& robot_position, std::vector& path, + Polygon& innermost_polygon) +{ + shiftPolygonToStartNearRobot(polygon, robot_position); + + if (polygon.is_clockwise_oriented()) + { + polygon.reverse_orientation(); + } + + // Add outermost outline + if (params_.outline_layers >= 1) + { + for (const auto& point : polygon.container()) + { + path.emplace_back(PointType::Outline, point); + } + path.emplace_back(PointType::Outline, polygon.container().front()); + } + innermost_polygon = polygon; + for (auto layer = 1; layer < params_.outline_layers; layer++) + { + auto offset_distance = layer * params_.stripe_separation; + auto inner_polygons = CGAL::create_interior_skeleton_and_offset_polygons_2(offset_distance, polygon); + assert(!inner_polygons.empty()); + for (const Point& p : inner_polygons.front()->container()) + { + path.emplace_back(PointType::Outline, p); + } + // Close polygon by repeating first point + path.emplace_back(PointType::Outline, inner_polygons.front()->container().front()); + innermost_polygon = *inner_polygons.front(); + } +} + +void OutlinePlanner::shiftPolygonToStartNearRobot(Polygon& polygon, const Point& robot_position) +{ + auto comp_dist_to_robot = [&robot_position](const Point& a, const Point& b) { + auto comp = CGAL::compare_distance_to_point(robot_position, a, b); + return (comp == CGAL::SMALLER); + }; + + std::vector points; + points.insert(points.end(), polygon.vertices_begin(), polygon.vertices_end()); + + auto closest_point_iter = std::min_element(points.begin(), points.end(), comp_dist_to_robot); + + std::rotate(points.begin(), closest_point_iter, points.end()); + + polygon.clear(); + std::copy(points.begin(), points.end(), std::back_inserter(polygon)); +} diff --git a/boustrophedon_server/src/boustrophedon_server/striping_planner.cpp b/boustrophedon_server/src/boustrophedon_server/striping_planner.cpp new file mode 100644 index 0000000..7051eaf --- /dev/null +++ b/boustrophedon_server/src/boustrophedon_server/striping_planner.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +void StripingPlanner::setParameters(StripingPlanner::Parameters parameters) +{ + params_ = parameters; +} + +void StripingPlanner::addToPath(const Polygon& polygon, const Point& robot_position, std::vector& path) +{ + std::vector new_path_section; + + const auto& last_point = path.empty() ? robot_position : path.back().point; + + double left_vertex_dist = std::abs(last_point.x() - polygon.left_vertex()->x()); + double right_vertex_dist = std::abs(last_point.x() - polygon.right_vertex()->x()); + bool left_closer = left_vertex_dist < right_vertex_dist; + + if (!left_closer) + { + AffineTransform flip_transform(-1, 0, 0, 1); + transformPoints(polygon.vertices_begin(), polygon.vertices_end(), flip_transform); + } + + fillPolygon(polygon, new_path_section); + + if (!left_closer) + { + AffineTransform flip_transform(-1, 0, 0, 1); + transformNavPoints(new_path_section.begin(), new_path_section.end(), flip_transform); + } + + path.insert(path.end(), new_path_section.begin(), new_path_section.end()); +} + +void StripingPlanner::fillPolygon(const Polygon& polygon, std::vector& path) +{ + auto left_vertex = *CGAL::left_vertex_2(polygon.container().begin(), polygon.container().end()); + auto min_x = left_vertex.x(); + auto max_x = CGAL::right_vertex_2(polygon.container().begin(), polygon.container().end())->x(); + + auto stripe_count = static_cast(std::round(CGAL::to_double(max_x - min_x) / params_.stripe_separation)) + 1; + + auto current_point = left_vertex; + + for (auto stripe_num = 0; stripe_num < stripe_count; stripe_num++) + { + auto x = (stripe_num * params_.stripe_separation) + min_x; + + auto intersections = getIntersectionPoints(polygon, Line(Point(x, 0.0), Point(x, 1.0))); + + if (intersections.size() > 1) + { + auto comp_dist_to_curr_point = [current_point](const auto& a, const auto& b) { + auto comp = CGAL::compare_distance_to_point(current_point, a, b); + return (comp == CGAL::SMALLER); + }; + + std::sort(intersections.begin(), intersections.end(), comp_dist_to_curr_point); + + intersections.erase(std::unique(intersections.begin(), intersections.end()), intersections.end()); + + path.emplace_back(PointType::StripeStart, intersections.front()); + path.emplace_back(PointType::StripeEnd, intersections.back()); + current_point = intersections.back(); + } + } +} + +std::vector StripingPlanner::getIntersectionPoints(const Polygon& polygon, const Line& line) +{ + std::vector intersections; + + for (auto edge_iterator = polygon.edges_begin(); edge_iterator != polygon.edges_end(); edge_iterator++) + { + Segment edge = *edge_iterator; + + auto result = CGAL::intersection(edge, line); + + if (result) + { + if (auto seg = boost::get(&*result)) + { + intersections.push_back(seg->source()); + intersections.push_back(seg->target()); + } + else + { + auto pt = boost::get(&*result); + intersections.push_back(*pt); + } + } + } + return intersections; +}